use crate::{Error, WithError as _, bail, error};
pub(crate) fn decode_header_size(d: &[u8]) -> Result<(usize, usize), Error> {
let mut shift = 0;
let mut size = 0u64;
let mut consumed = 0;
for cmd in d {
if shift >= u64::BITS {
bail!("delta header size uses more bits than fit into u64");
}
consumed += 1;
size |= (u64::from(*cmd) & 0x7f) << shift;
shift += 7;
if *cmd & 0x80 == 0 {
let size =
usize::try_from(size).with_err(|| error!("delta size exceeds usize: {size}"))?;
return Ok((size, consumed));
}
}
bail!("delta header size is truncated");
}
pub(crate) fn apply(base: &[u8], mut target: &mut [u8], data: &[u8]) -> Result<(), Error> {
fn next_byte(data: &[u8], i: &mut usize) -> Result<u8, Error> {
let byte = *data
.get(*i)
.with_err_msg("delta copy instruction is truncated")?;
*i += 1;
Ok(byte)
}
let mut i = 0;
while let Some(cmd) = data.get(i) {
i += 1;
let bytes = match cmd {
cmd if cmd & 0b1000_0000 != 0 => {
let (mut ofs, mut size): (u32, u32) = (0, 0);
if cmd & 0b0000_0001 != 0 {
ofs = u32::from(next_byte(data, &mut i)?);
}
if cmd & 0b0000_0010 != 0 {
ofs |= u32::from(next_byte(data, &mut i)?) << 8;
}
if cmd & 0b0000_0100 != 0 {
ofs |= u32::from(next_byte(data, &mut i)?) << 16;
}
if cmd & 0b0000_1000 != 0 {
ofs |= u32::from(next_byte(data, &mut i)?) << 24;
}
if cmd & 0b0001_0000 != 0 {
size = u32::from(next_byte(data, &mut i)?);
}
if cmd & 0b0010_0000 != 0 {
size |= u32::from(next_byte(data, &mut i)?) << 8;
}
if cmd & 0b0100_0000 != 0 {
size |= u32::from(next_byte(data, &mut i)?) << 16;
}
if size == 0 {
size = 0x10000; }
let ofs = ofs as usize;
let end = ofs
.checked_add(size as usize)
.with_err_msg("delta copy range overflows")?;
base.get(ofs..end).with_err(|| {
error!(
"delta copy range ({ofs}..{end}) exceeds base object size {}",
base.len()
)
})?
}
0 => {
bail!("delta command 0 is reserved and invalid");
}
size => {
let end = i
.checked_add(*size as usize)
.with_err_msg("delta insert range overflows")?;
let bytes = data
.get(i..end)
.with_err_msg("delta insert data is truncated")?;
i = end;
bytes
}
};
let (out, rest) = target
.split_at_mut_checked(bytes.len())
.with_err_msg("delta instructions produced more bytes than promised")?;
out.copy_from_slice(bytes);
target = rest;
}
debug_assert_eq!(
i,
data.len(),
"delta instructions were not consumed completely, should be impossible"
);
if !target.is_empty() {
bail!("delta instructions produced fewer bytes than promised");
}
Ok(())
}