Skip to main content

gix_pack/data/
delta.rs

1///
2pub mod apply {
3    /// Returned when failing to apply deltas.
4    #[derive(thiserror::Error, Debug)]
5    #[allow(missing_docs)]
6    pub enum Error {
7        #[error("Corrupt delta data: {message}")]
8        Corrupt { message: &'static str },
9        #[error("Encountered unsupported command code: 0")]
10        UnsupportedCommandCode,
11        #[error("Delta copy from base: byte slices must match")]
12        DeltaCopyBaseSliceMismatch,
13        #[error("Delta copy data: byte slices must match")]
14        DeltaCopyDataSliceMismatch,
15    }
16}
17
18/// Given the decompressed pack delta `d`, decode a size in bytes (either the base object size or the result object size)
19/// Equivalent to [this canonical git function](https://github.com/git/git/blob/311531c9de557d25ac087c1637818bd2aad6eb3a/delta.h#L89)
20pub(crate) fn decode_header_size(d: &[u8]) -> Result<(u64, usize), apply::Error> {
21    let mut shift = 0;
22    let mut size = 0u64;
23    let mut consumed = 0;
24    for cmd in d.iter() {
25        if shift >= u64::BITS {
26            return Err(apply::Error::Corrupt {
27                message: "delta header size uses more bits than fit into u64",
28            });
29        }
30        consumed += 1;
31        size |= (u64::from(*cmd) & 0x7f) << shift;
32        shift += 7;
33        if *cmd & 0x80 == 0 {
34            return Ok((size, consumed));
35        }
36    }
37    Err(apply::Error::Corrupt {
38        message: "delta header size is truncated",
39    })
40}
41
42pub(crate) fn apply(base: &[u8], mut target: &mut [u8], data: &[u8]) -> Result<(), apply::Error> {
43    fn next_byte(data: &[u8], i: &mut usize) -> Result<u8, apply::Error> {
44        let byte = *data.get(*i).ok_or(apply::Error::Corrupt {
45            message: "delta copy instruction is truncated",
46        })?;
47        *i += 1;
48        Ok(byte)
49    }
50
51    let mut i = 0;
52    while let Some(cmd) = data.get(i) {
53        i += 1;
54        match cmd {
55            cmd if cmd & 0b1000_0000 != 0 => {
56                let (mut ofs, mut size): (u32, u32) = (0, 0);
57                if cmd & 0b0000_0001 != 0 {
58                    ofs = u32::from(next_byte(data, &mut i)?);
59                }
60                if cmd & 0b0000_0010 != 0 {
61                    ofs |= u32::from(next_byte(data, &mut i)?) << 8;
62                }
63                if cmd & 0b0000_0100 != 0 {
64                    ofs |= u32::from(next_byte(data, &mut i)?) << 16;
65                }
66                if cmd & 0b0000_1000 != 0 {
67                    ofs |= u32::from(next_byte(data, &mut i)?) << 24;
68                }
69                if cmd & 0b0001_0000 != 0 {
70                    size = u32::from(next_byte(data, &mut i)?);
71                }
72                if cmd & 0b0010_0000 != 0 {
73                    size |= u32::from(next_byte(data, &mut i)?) << 8;
74                }
75                if cmd & 0b0100_0000 != 0 {
76                    size |= u32::from(next_byte(data, &mut i)?) << 16;
77                }
78                if size == 0 {
79                    size = 0x10000; // 65536
80                }
81                let ofs = ofs as usize;
82                let end = ofs.checked_add(size as usize).ok_or(apply::Error::Corrupt {
83                    message: "delta copy range overflows",
84                })?;
85                std::io::Write::write(
86                    &mut target,
87                    base.get(ofs..end).ok_or(apply::Error::Corrupt {
88                        message: "delta copy range exceeds base object size",
89                    })?,
90                )
91                .map_err(|_e| apply::Error::DeltaCopyBaseSliceMismatch)?;
92            }
93            0 => {
94                return Err(apply::Error::Corrupt {
95                    message: "delta command 0 is reserved and invalid",
96                });
97            }
98            size => {
99                let end = i.checked_add(*size as usize).ok_or(apply::Error::Corrupt {
100                    message: "delta insert range overflows",
101                })?;
102                std::io::Write::write(
103                    &mut target,
104                    data.get(i..end).ok_or(apply::Error::Corrupt {
105                        message: "delta insert data is truncated",
106                    })?,
107                )
108                .map_err(|_e| apply::Error::DeltaCopyDataSliceMismatch)?;
109                i = end;
110            }
111        }
112    }
113    debug_assert_eq!(
114        i,
115        data.len(),
116        "delta instructions were not consumed completely, should be impossible"
117    );
118    if !target.is_empty() {
119        return Err(apply::Error::Corrupt {
120            message: "delta instructions produced fewer bytes than promised",
121        });
122    }
123
124    Ok(())
125}