use std::io;
use std::io::Read;
use std::ops::DerefMut;
pub fn patch<T, W>(old: &[u8], patch: &mut T, new: &mut W) -> io::Result<()>
where
T: Read,
W: io::Write + DerefMut<Target = [u8]>,
{
let mut oldpos: usize = 0;
loop {
let mut buf = [0; 24];
if read_or_eof(patch, &mut buf)? {
return Ok(());
}
let mix_len_raw = offtin(buf[0..8].try_into().unwrap());
let copy_len_raw = offtin(buf[8..16].try_into().unwrap());
let seek_len = offtin(buf[16..24].try_into().unwrap());
if mix_len_raw < 0 || copy_len_raw < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Negative length: mix={}, copy={}", mix_len_raw, copy_len_raw),
));
}
let mix_len = mix_len_raw as usize;
let copy_len = copy_len_raw as usize;
let to_read = mix_len
.checked_add(copy_len)
.ok_or(io::ErrorKind::InvalidData)?;
let mix_start = new.len();
let mut read_from = patch.take(to_read as u64);
let has_read = io::copy(&mut read_from, new)?;
if has_read != to_read as u64 {
return Err(io::ErrorKind::UnexpectedEof.into());
}
let mix_end = mix_start
.checked_add(mix_len)
.ok_or(io::ErrorKind::InvalidData)?;
let mix_slice = new
.get_mut(mix_start..mix_end)
.ok_or(io::ErrorKind::UnexpectedEof)?;
let oldpos_end = oldpos
.checked_add(mix_len)
.ok_or(io::ErrorKind::InvalidData)?;
let old_slice = old
.get(oldpos..oldpos_end)
.ok_or(io::ErrorKind::UnexpectedEof)?;
for (n, o) in mix_slice.iter_mut().zip(old_slice.iter().copied()) {
*n = n.wrapping_add(o);
}
oldpos += mix_len;
let new_oldpos = (oldpos as i64)
.checked_add(seek_len)
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Seek overflow: oldpos={}, seek={}", oldpos, seek_len),
)
})?;
if new_oldpos < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Seek underflow: oldpos={}, seek={}", oldpos, seek_len),
));
}
oldpos = new_oldpos as usize;
}
}
#[inline]
fn read_or_eof<T: Read>(reader: &mut T, buf: &mut [u8; 24]) -> io::Result<bool> {
let mut tmp = &mut buf[..];
loop {
match reader.read(tmp) {
Ok(0) => {
return if tmp.len() == 24 {
Ok(true) } else {
Err(io::ErrorKind::UnexpectedEof.into())
}
}
Ok(n) => {
if n >= tmp.len() {
return Ok(false);
}
tmp = &mut tmp[n..];
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
}
#[inline]
fn offtin(buf: [u8; 8]) -> i64 {
let y = i64::from_le_bytes(buf);
if 0 == y & (1 << 63) {
y
} else {
-(y & !(1 << 63))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_offtin_zero() {
let buf = [0u8; 8];
assert_eq!(offtin(buf), 0);
}
#[test]
fn test_offtin_positive() {
let buf = [42, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(offtin(buf), 42);
}
#[test]
fn test_offtin_negative() {
let buf = [42, 0, 0, 0, 0, 0, 0, 0x80];
assert_eq!(offtin(buf), -42);
}
#[test]
fn test_offtin_max_positive() {
let buf = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F];
assert_eq!(offtin(buf), i64::MAX);
}
#[test]
fn test_offtin_max_negative() {
let buf = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
assert_eq!(offtin(buf), -i64::MAX);
}
}