use std::io::{ErrorKind, Read};
use super::{errors::GitDeltaError, utils};
const COPY_INSTRUCTION_FLAG: u8 = 1 << 7; const COPY_OFFSET_BYTES: u8 = 4;
const COPY_SIZE_BYTES: u8 = 3;
const COPY_ZERO_SIZE: usize = 0x10000;
fn decoder_error(message: impl Into<String>) -> GitDeltaError {
GitDeltaError::DeltaDecoderError(message.into())
}
pub fn delta_decode(
mut stream: &mut impl Read,
base_info: &[u8],
) -> Result<Vec<u8>, GitDeltaError> {
let base_size =
utils::read_size_encoding(&mut stream).map_err(|err| decoder_error(err.to_string()))?;
if base_info.len() != base_size {
return Err(decoder_error("base object len is not equal"));
}
let result_size =
utils::read_size_encoding(&mut stream).map_err(|err| decoder_error(err.to_string()))?;
let mut buffer = Vec::new();
loop {
let instruction = match utils::read_bytes(stream) {
Ok([instruction]) => instruction,
Err(err) if err.kind() == ErrorKind::UnexpectedEof => break,
Err(err) => {
return Err(decoder_error(format!("Wrong instruction in delta: {err}")));
}
};
if instruction & COPY_INSTRUCTION_FLAG == 0 {
if instruction == 0 {
return Err(decoder_error("Invalid data instruction"));
}
let mut data = vec![0; instruction as usize];
stream
.read_exact(&mut data)
.map_err(|err| decoder_error(err.to_string()))?;
prepare_result_append(&mut buffer, data.len(), result_size)?;
buffer.extend_from_slice(&data);
} else {
let mut nonzero_bytes = instruction;
let offset =
utils::read_partial_int(&mut stream, COPY_OFFSET_BYTES, &mut nonzero_bytes)
.map_err(|err| decoder_error(err.to_string()))?;
let mut size =
utils::read_partial_int(&mut stream, COPY_SIZE_BYTES, &mut nonzero_bytes)
.map_err(|err| decoder_error(err.to_string()))?;
if size == 0 {
size = COPY_ZERO_SIZE;
}
let end = offset
.checked_add(size)
.ok_or_else(|| decoder_error("Invalid copy instruction"))?;
let base_data = base_info
.get(offset..end)
.ok_or_else(|| decoder_error("Invalid copy instruction"));
let base_data = base_data?;
prepare_result_append(&mut buffer, base_data.len(), result_size)?;
buffer.extend_from_slice(base_data);
}
}
if buffer.len() != result_size {
return Err(decoder_error(format!(
"result object len is not equal: expected {result_size}, got {}",
buffer.len()
)));
}
Ok(buffer)
}
fn prepare_result_append(
buffer: &mut Vec<u8>,
instruction_size: usize,
result_size: usize,
) -> Result<(), GitDeltaError> {
let new_size = buffer
.len()
.checked_add(instruction_size)
.ok_or_else(|| decoder_error("result object size overflow"))?;
if new_size > result_size {
return Err(decoder_error(format!(
"result object exceeds declared size: expected {result_size}, got at least {new_size}"
)));
}
buffer
.try_reserve_exact(instruction_size)
.map_err(|err| decoder_error(format!("cannot allocate result object: {err}")))?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::delta_decode;
use crate::delta::{encode::DeltaDiff, errors::GitDeltaError};
#[test]
fn round_trip_matches_source() {
let old = b"hello world";
let new = b"hello rust";
let delta = DeltaDiff::new(old, new).encode();
let mut cursor = Cursor::new(delta);
let decoded = delta_decode(&mut cursor, old).expect("decode");
assert_eq!(decoded, new);
}
#[test]
fn base_size_mismatch_returns_error() {
let old = b"abcde";
let new = b"abXYZ";
let delta = DeltaDiff::new(old, new).encode();
let mut cursor = Cursor::new(delta);
let err = delta_decode(&mut cursor, b"xx").unwrap_err();
assert!(matches!(err, GitDeltaError::DeltaDecoderError(_)));
}
#[test]
fn truncated_header_returns_error() {
let mut cursor = Cursor::new(Vec::<u8>::new());
let err = delta_decode(&mut cursor, b"").unwrap_err();
assert!(matches!(err, GitDeltaError::DeltaDecoderError(_)));
}
#[test]
fn zero_literal_instruction_returns_error() {
let mut cursor = Cursor::new(vec![0, 0, 0]);
let err = delta_decode(&mut cursor, b"").unwrap_err();
assert!(matches!(err, GitDeltaError::DeltaDecoderError(_)));
}
#[test]
fn truncated_literal_instruction_returns_error() {
let mut cursor = Cursor::new(vec![0, 3, 3, b'a']);
let err = delta_decode(&mut cursor, b"").unwrap_err();
assert!(matches!(err, GitDeltaError::DeltaDecoderError(_)));
}
#[test]
fn truncated_copy_instruction_returns_error() {
let mut cursor = Cursor::new(vec![3, 1, 0x81]);
let err = delta_decode(&mut cursor, b"abc").unwrap_err();
assert!(matches!(err, GitDeltaError::DeltaDecoderError(_)));
}
#[test]
fn result_size_mismatch_returns_error() {
let mut cursor = Cursor::new(vec![0, 1]);
let err = delta_decode(&mut cursor, b"").unwrap_err();
assert!(matches!(err, GitDeltaError::DeltaDecoderError(_)));
}
#[test]
fn overlong_size_varint_returns_error() {
let mut bytes = vec![0x80; 10];
bytes.push(0);
let mut cursor = Cursor::new(bytes);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
delta_decode(&mut cursor, b"")
}));
assert!(result.is_ok(), "overlong size varint should not panic");
assert!(matches!(
result.unwrap(),
Err(GitDeltaError::DeltaDecoderError(_))
));
}
#[test]
fn unallocatable_result_size_returns_error() {
let mut bytes = vec![0];
bytes.extend([0xff; 9]);
bytes.push(1);
let mut cursor = Cursor::new(bytes);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
delta_decode(&mut cursor, b"")
}));
assert!(result.is_ok(), "unallocatable result size should not panic");
assert!(matches!(
result.unwrap(),
Err(GitDeltaError::DeltaDecoderError(_))
));
}
#[test]
fn instruction_exceeding_result_size_returns_error() {
let mut cursor = Cursor::new(vec![0, 1, 2, b'a', b'b']);
let err = delta_decode(&mut cursor, b"").unwrap_err();
assert!(matches!(err, GitDeltaError::DeltaDecoderError(_)));
}
}