use crate::{
types::{ArchivedQueueDiff, QueueDiff},
Error,
};
fn find_chunk_aligned(haystack: &[u8], needle: &[u8], item_ssz_size: usize) -> Option<usize> {
if needle.len() != item_ssz_size {
return None;
}
haystack
.chunks_exact(item_ssz_size)
.position(|chunk| chunk == needle)
.map(|idx| idx * item_ssz_size)
}
pub fn diff_queue(base_ssz: &[u8], target_ssz: &[u8], item_ssz_size: usize) -> QueueDiff {
assert!(item_ssz_size > 0, "item_ssz_size must be greater than 0");
assert!(
base_ssz.len() % item_ssz_size == 0,
"base_ssz length must be a multiple of item_ssz_size"
);
assert!(
target_ssz.len() % item_ssz_size == 0,
"target_ssz length must be a multiple of item_ssz_size"
);
if target_ssz.is_empty() {
let consumed_count = base_ssz.len() / item_ssz_size;
return QueueDiff::Fifo {
consumed_count: u32::try_from(consumed_count)
.expect("queue item count exceeds u32::MAX"),
appended_items: Vec::new(),
};
}
if base_ssz.is_empty() {
return QueueDiff::Fifo {
consumed_count: 0,
appended_items: target_ssz.to_vec(),
};
}
let target_head = &target_ssz[..item_ssz_size];
match find_chunk_aligned(base_ssz, target_head, item_ssz_size) {
Some(byte_offset) => {
let remaining_base_bytes = &base_ssz[byte_offset..];
let expected_target_prefix_len = remaining_base_bytes.len();
if expected_target_prefix_len <= target_ssz.len()
&& &target_ssz[..expected_target_prefix_len] == remaining_base_bytes
{
let consumed_count = byte_offset / item_ssz_size;
let consumed_count =
u32::try_from(consumed_count).expect("queue item count exceeds u32::MAX");
let appended_items = target_ssz[expected_target_prefix_len..].to_vec();
QueueDiff::Fifo {
consumed_count,
appended_items,
}
} else {
QueueDiff::FullReplacement(target_ssz.to_vec())
}
}
None => QueueDiff::FullReplacement(target_ssz.to_vec()),
}
}
pub fn apply_queue(
base: &mut Vec<u8>,
delta: &ArchivedQueueDiff,
item_ssz_size: usize,
) -> Result<(), Error> {
if item_ssz_size == 0 {
return Err(Error::MalformedDelta(
"item_ssz_size must be greater than 0".into(),
));
}
if base.len() % item_ssz_size != 0 {
return Err(Error::MalformedDelta(format!(
"base queue length {} is not a multiple of item size {}",
base.len(),
item_ssz_size
)));
}
let delta: QueueDiff = rkyv::deserialize::<QueueDiff, rkyv::rancor::Error>(delta)
.map_err(|_| Error::MalformedDelta("failed to deserialize queue delta".into()))?;
match delta {
QueueDiff::Fifo {
consumed_count,
appended_items,
} => {
if appended_items.len() % item_ssz_size != 0 {
return Err(Error::MalformedDelta(format!(
"FIFO appended payload length {} is not a multiple of item size {}",
appended_items.len(),
item_ssz_size
)));
}
let bytes_to_drain = (consumed_count as usize)
.checked_mul(item_ssz_size)
.ok_or_else(|| {
Error::MalformedDelta("FIFO consumed byte count overflows usize".into())
})?;
if bytes_to_drain > base.len() {
return Err(Error::MalformedDelta(format!(
"FIFO consumes {} bytes from a queue containing only {} bytes",
bytes_to_drain,
base.len()
)));
}
base.drain(..bytes_to_drain);
base.extend_from_slice(&appended_items);
}
QueueDiff::FullReplacement(replacement) => {
if replacement.len() % item_ssz_size != 0 {
return Err(Error::MalformedDelta(format!(
"replacement payload length {} is not a multiple of item size {}",
replacement.len(),
item_ssz_size
)));
}
base.clear();
base.extend_from_slice(&replacement);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{ArchivedQueueDiff, QueueDiff};
fn archive(diff: &QueueDiff) -> rkyv::util::AlignedVec {
rkyv::to_bytes::<rkyv::rancor::Error>(diff).expect("test setup: failed to serialize delta")
}
fn archived(bytes: &[u8]) -> &ArchivedQueueDiff {
rkyv::access::<ArchivedQueueDiff, rkyv::rancor::Error>(bytes)
.expect("test setup: failed to access archived delta")
}
const ITEM_SIZE: usize = 4;
#[test]
fn diff_fifo_overlap() {
let base = b"AAAABBBBCCCC";
let target = b"CCCCDDDDEEEE";
let delta = diff_queue(base, target, ITEM_SIZE);
assert_eq!(
delta,
QueueDiff::Fifo {
consumed_count: 2,
appended_items: b"DDDDEEEE".to_vec(),
}
);
}
#[test]
fn diff_target_empty_all_consumed() {
let base = b"AAAABBBB";
let target = b"";
let delta = diff_queue(base, target, ITEM_SIZE);
assert_eq!(
delta,
QueueDiff::Fifo {
consumed_count: 2,
appended_items: Vec::new(),
}
);
}
#[test]
fn diff_base_empty_all_appended() {
let base = b"";
let target = b"AAAABBBB";
let delta = diff_queue(base, target, ITEM_SIZE);
assert_eq!(
delta,
QueueDiff::Fifo {
consumed_count: 0,
appended_items: b"AAAABBBB".to_vec(),
}
);
}
#[test]
fn diff_no_overlap_replacement() {
let base = b"AAAABBBB";
let target = b"CCCCDDDD";
let delta = diff_queue(base, target, ITEM_SIZE);
assert_eq!(delta, QueueDiff::FullReplacement(target.to_vec()));
}
#[test]
fn diff_false_positive_misaligned_item() {
let base = b"AABBAABB";
let target = b"BBAACCDD";
let delta = diff_queue(base, target, ITEM_SIZE);
assert_eq!(delta, QueueDiff::FullReplacement(target.to_vec()));
}
#[test]
fn diff_false_positive_prefix_mismatch() {
let base = b"AAAABBBBCCCC";
let target = b"BBBB1234CCCC";
let delta = diff_queue(base, target, ITEM_SIZE);
assert_eq!(delta, QueueDiff::FullReplacement(target.to_vec()));
}
#[test]
fn diff_false_positive_target_too_short() {
let base = b"AAAABBBBCCCC";
let target = b"BBBB";
let delta = diff_queue(base, target, ITEM_SIZE);
assert_eq!(delta, QueueDiff::FullReplacement(target.to_vec()));
}
#[test]
#[should_panic(expected = "item_ssz_size must be greater than 0")]
fn diff_panic_zero_item_size() {
diff_queue(b"AAAA", b"AAAA", 0);
}
#[test]
#[should_panic(expected = "base_ssz length must be a multiple of item_ssz_size")]
fn diff_panic_base_misaligned() {
diff_queue(b"AAA", b"AAAA", ITEM_SIZE);
}
#[test]
#[should_panic(expected = "target_ssz length must be a multiple of item_ssz_size")]
fn diff_panic_target_misaligned() {
diff_queue(b"AAAA", b"AAA", ITEM_SIZE);
}
#[test]
fn apply_fifo_transition() {
let mut base = b"AAAABBBBCCCC".to_vec();
let target = b"CCCCDDDDEEEE";
let delta = diff_queue(&base, target, ITEM_SIZE);
let bytes = archive(&delta);
apply_queue(&mut base, archived(&bytes), ITEM_SIZE).expect("test setup: apply");
assert_eq!(base, target);
}
#[test]
fn apply_full_replacement() {
let mut base = b"AAAABBBB".to_vec();
let target = b"CCCCDDDD";
let delta = diff_queue(&base, target, ITEM_SIZE);
let bytes = archive(&delta);
apply_queue(&mut base, archived(&bytes), ITEM_SIZE).expect("test setup: apply");
assert_eq!(base, target);
}
#[test]
fn apply_empty_target_all_consumed() {
let mut base = b"AAAABBBB".to_vec();
let target = b"";
let delta = diff_queue(&base, target, ITEM_SIZE);
let bytes = archive(&delta);
apply_queue(&mut base, archived(&bytes), ITEM_SIZE).expect("test setup: apply");
assert!(base.is_empty());
}
#[test]
fn apply_empty_base_all_appended() {
let mut base = Vec::new();
let target = b"AAAABBBB";
let delta = diff_queue(&base, target, ITEM_SIZE);
let bytes = archive(&delta);
apply_queue(&mut base, archived(&bytes), ITEM_SIZE).expect("test setup: apply");
assert_eq!(base, target);
}
#[test]
fn apply_error_zero_item_size() {
let mut base = b"AAAA".to_vec();
let delta = QueueDiff::FullReplacement(b"BBBB".to_vec());
let bytes = archive(&delta);
let err =
apply_queue(&mut base, archived(&bytes), 0).expect_err("test setup: expected error");
assert!(matches!(err, Error::MalformedDelta(_)));
}
#[test]
fn apply_error_base_misaligned() {
let mut base = b"AAA".to_vec();
let delta = QueueDiff::FullReplacement(b"BBBB".to_vec());
let bytes = archive(&delta);
let err = apply_queue(&mut base, archived(&bytes), ITEM_SIZE)
.expect_err("test setup: expected error");
assert!(matches!(err, Error::MalformedDelta(_)));
}
#[test]
fn apply_error_appended_misaligned() {
let mut base = b"AAAA".to_vec();
let delta = QueueDiff::Fifo {
consumed_count: 0,
appended_items: b"BBB".to_vec(),
};
let bytes = archive(&delta);
let err = apply_queue(&mut base, archived(&bytes), ITEM_SIZE)
.expect_err("test setup: expected error");
assert!(matches!(err, Error::MalformedDelta(_)));
}
#[test]
fn apply_error_consume_exceeds_base() {
let mut base = b"AAAA".to_vec(); let delta = QueueDiff::Fifo {
consumed_count: 2,
appended_items: Vec::new(),
};
let bytes = archive(&delta);
let err = apply_queue(&mut base, archived(&bytes), ITEM_SIZE)
.expect_err("test setup: expected error");
assert!(matches!(err, Error::MalformedDelta(_)));
}
#[test]
fn apply_error_replacement_misaligned() {
let mut base = b"AAAA".to_vec();
let delta = QueueDiff::FullReplacement(b"BBB".to_vec());
let bytes = archive(&delta);
let err = apply_queue(&mut base, archived(&bytes), ITEM_SIZE)
.expect_err("test setup: expected error");
assert!(matches!(err, Error::MalformedDelta(_)));
}
}