This repository has no description
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
83 .checked_add(size as usize)
84 .ok_or(apply::Error::Corrupt {
85 message: "delta copy range overflows",
86 })?;
87 std::io::Write::write(
88 &mut target,
89 base.get(ofs..end).ok_or(apply::Error::Corrupt {
90 message: "delta copy range exceeds base object size",
91 })?,
92 )
93 .map_err(|_e| apply::Error::DeltaCopyBaseSliceMismatch)?;
94 }
95 0 => {
96 return Err(apply::Error::Corrupt {
97 message: "delta command 0 is reserved and invalid",
98 });
99 }
100 size => {
101 let end = i.checked_add(*size as usize).ok_or(apply::Error::Corrupt {
102 message: "delta insert range overflows",
103 })?;
104 std::io::Write::write(
105 &mut target,
106 data.get(i..end).ok_or(apply::Error::Corrupt {
107 message: "delta insert data is truncated",
108 })?,
109 )
110 .map_err(|_e| apply::Error::DeltaCopyDataSliceMismatch)?;
111 i = end;
112 }
113 }
114 }
115 debug_assert_eq!(
116 i,
117 data.len(),
118 "delta instructions were not consumed completely, should be impossible"
119 );
120 if !target.is_empty() {
121 return Err(apply::Error::Corrupt {
122 message: "delta instructions produced fewer bytes than promised",
123 });
124 }
125
126 Ok(())
127}