use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
pub const DEFAULT_BLOCK_SIZE: usize = 4096;
pub const MIN_BLOCK_SIZE: usize = 512;
pub const MAX_BLOCK_SIZE: usize = 1024 * 1024;
pub const ADLER_MOD: u32 = 65521;
#[derive(Debug, Clone)]
pub struct RollingChecksum {
a: u32,
b: u32,
window: Vec<u8>,
window_size: usize,
pos: usize,
count: usize,
}
impl RollingChecksum {
pub fn new(window_size: usize) -> Self {
Self {
a: 1,
b: 0,
window: alloc::vec![0u8; window_size],
window_size,
pos: 0,
count: 0,
}
}
pub fn reset(&mut self) {
self.a = 1;
self.b = 0;
self.pos = 0;
self.count = 0;
for byte in &mut self.window {
*byte = 0;
}
}
pub fn push(&mut self, byte: u8) {
self.a = (self.a + byte as u32) % ADLER_MOD;
self.b = (self.b + self.a) % ADLER_MOD;
self.window[self.pos] = byte;
self.pos = (self.pos + 1) % self.window_size;
self.count += 1;
}
pub fn roll(&mut self, new_byte: u8) {
let old_byte = self.window[self.pos] as u32;
self.a = (self.a + ADLER_MOD - old_byte + new_byte as u32) % ADLER_MOD;
let sub = (self.window_size as u32 * old_byte) % ADLER_MOD;
self.b = (self.b + ADLER_MOD - sub + self.a) % ADLER_MOD;
self.window[self.pos] = new_byte;
self.pos = (self.pos + 1) % self.window_size;
}
pub fn checksum(&self) -> u32 {
(self.b << 16) | self.a
}
pub fn window_size(&self) -> usize {
self.window_size
}
pub fn is_full(&self) -> bool {
self.count >= self.window_size
}
pub fn compute(data: &[u8]) -> u32 {
let mut a: u32 = 1;
let mut b: u32 = 0;
for &byte in data {
a = (a + byte as u32) % ADLER_MOD;
b = (b + a) % ADLER_MOD;
}
(b << 16) | a
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockSignature {
pub index: u32,
pub offset: u64,
pub length: u32,
pub rolling: u32,
pub strong: [u8; 16],
}
impl BlockSignature {
pub fn new(index: u32, offset: u64, length: u32, rolling: u32, strong: [u8; 16]) -> Self {
Self {
index,
offset,
length,
rolling,
strong,
}
}
}
#[derive(Debug, Clone)]
pub struct SignatureSet {
pub file_checksum: [u64; 4],
pub file_size: u64,
pub block_size: u32,
pub blocks: Vec<BlockSignature>,
}
impl SignatureSet {
pub fn new(file_checksum: [u64; 4], file_size: u64, block_size: u32) -> Self {
Self {
file_checksum,
file_size,
block_size,
blocks: Vec::new(),
}
}
pub fn add_block(&mut self, sig: BlockSignature) {
self.blocks.push(sig);
}
pub fn block_count(&self) -> usize {
self.blocks.len()
}
pub fn get_block(&self, index: u32) -> Option<&BlockSignature> {
self.blocks.get(index as usize)
}
pub fn find_by_rolling(&self, rolling: u32) -> Vec<&BlockSignature> {
self.blocks
.iter()
.filter(|b| b.rolling == rolling)
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeltaOp {
Copy {
src_offset: u64,
length: u64,
},
Insert {
data: Vec<u8>,
},
}
impl DeltaOp {
pub fn copy(src_offset: u64, length: u64) -> Self {
Self::Copy { src_offset, length }
}
pub fn insert(data: Vec<u8>) -> Self {
Self::Insert { data }
}
pub fn output_size(&self) -> u64 {
match self {
Self::Copy { length, .. } => *length,
Self::Insert { data } => data.len() as u64,
}
}
pub fn transfer_size(&self) -> u64 {
match self {
Self::Copy { .. } => 16, Self::Insert { data } => data.len() as u64 + 8, }
}
pub fn is_copy(&self) -> bool {
matches!(self, Self::Copy { .. })
}
pub fn is_insert(&self) -> bool {
matches!(self, Self::Insert { .. })
}
}
#[derive(Debug, Clone)]
pub struct Delta {
pub source_checksum: [u64; 4],
pub target_checksum: [u64; 4],
pub source_size: u64,
pub target_size: u64,
pub block_size: u32,
pub ops: Vec<DeltaOp>,
}
impl Delta {
pub fn new(
source_checksum: [u64; 4],
target_checksum: [u64; 4],
source_size: u64,
target_size: u64,
block_size: u32,
) -> Self {
Self {
source_checksum,
target_checksum,
source_size,
target_size,
block_size,
ops: Vec::new(),
}
}
pub fn add_op(&mut self, op: DeltaOp) {
self.ops.push(op);
}
pub fn add_copy(&mut self, src_offset: u64, length: u64) {
if let Some(DeltaOp::Copy {
src_offset: prev_off,
length: prev_len,
}) = self.ops.last_mut()
{
if *prev_off + *prev_len == src_offset {
*prev_len += length;
return;
}
}
self.ops.push(DeltaOp::Copy { src_offset, length });
}
pub fn add_insert(&mut self, data: &[u8]) {
if let Some(DeltaOp::Insert { data: prev_data }) = self.ops.last_mut() {
prev_data.extend_from_slice(data);
return;
}
self.ops.push(DeltaOp::Insert {
data: data.to_vec(),
});
}
pub fn op_count(&self) -> usize {
self.ops.len()
}
pub fn transfer_size(&self) -> u64 {
self.ops.iter().map(|op| op.transfer_size()).sum()
}
pub fn compression_ratio(&self) -> f64 {
if self.target_size == 0 {
return 0.0;
}
self.transfer_size() as f64 / self.target_size as f64
}
pub fn copy_percentage(&self) -> f64 {
let copy_bytes: u64 = self
.ops
.iter()
.filter_map(|op| match op {
DeltaOp::Copy { length, .. } => Some(*length),
_ => None,
})
.sum();
if self.target_size == 0 {
return 0.0;
}
(copy_bytes as f64 / self.target_size as f64) * 100.0
}
pub fn is_identity(&self) -> bool {
self.source_checksum == self.target_checksum
}
pub fn is_full_replace(&self) -> bool {
self.ops.iter().all(|op| op.is_insert())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncStatus {
Unchanged,
New,
Modified,
Deleted,
Renamed,
}
impl fmt::Display for SyncStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unchanged => write!(f, "unchanged"),
Self::New => write!(f, "new"),
Self::Modified => write!(f, "modified"),
Self::Deleted => write!(f, "deleted"),
Self::Renamed => write!(f, "renamed"),
}
}
}
#[derive(Debug, Clone)]
pub struct SyncEntry {
pub path: String,
pub size: u64,
pub mtime: u64,
pub checksum: [u64; 4],
pub status: SyncStatus,
pub delta: Option<Delta>,
pub original_path: Option<String>,
}
impl SyncEntry {
pub fn new(path: &str, size: u64, mtime: u64, checksum: [u64; 4], status: SyncStatus) -> Self {
Self {
path: path.to_string(),
size,
mtime,
checksum,
status,
delta: None,
original_path: None,
}
}
pub fn with_delta(mut self, delta: Delta) -> Self {
self.delta = Some(delta);
self
}
pub fn with_original(mut self, path: &str) -> Self {
self.original_path = Some(path.to_string());
self
}
pub fn transfer_size(&self) -> u64 {
match self.status {
SyncStatus::New => self.size,
SyncStatus::Modified => self.delta.as_ref().map_or(self.size, |d| d.transfer_size()),
SyncStatus::Renamed => 0, _ => 0,
}
}
}
#[derive(Debug, Clone)]
pub struct SyncPlan {
pub source: String,
pub destination: String,
pub created: u64,
pub entries: Vec<SyncEntry>,
}
impl SyncPlan {
pub fn new(source: &str, destination: &str, created: u64) -> Self {
Self {
source: source.to_string(),
destination: destination.to_string(),
created,
entries: Vec::new(),
}
}
pub fn add_entry(&mut self, entry: SyncEntry) {
self.entries.push(entry);
}
pub fn new_files(&self) -> Vec<&SyncEntry> {
self.entries
.iter()
.filter(|e| e.status == SyncStatus::New)
.collect()
}
pub fn modified_files(&self) -> Vec<&SyncEntry> {
self.entries
.iter()
.filter(|e| e.status == SyncStatus::Modified)
.collect()
}
pub fn deleted_files(&self) -> Vec<&SyncEntry> {
self.entries
.iter()
.filter(|e| e.status == SyncStatus::Deleted)
.collect()
}
pub fn renamed_files(&self) -> Vec<&SyncEntry> {
self.entries
.iter()
.filter(|e| e.status == SyncStatus::Renamed)
.collect()
}
pub fn transfer_bytes(&self) -> u64 {
self.entries.iter().map(|e| e.transfer_size()).sum()
}
pub fn file_count(&self) -> usize {
self.entries.len()
}
pub fn count_by_status(&self, status: SyncStatus) -> usize {
self.entries.iter().filter(|e| e.status == status).count()
}
pub fn is_empty(&self) -> bool {
self.entries
.iter()
.all(|e| e.status == SyncStatus::Unchanged)
}
}
#[derive(Debug, Clone)]
pub struct SyncProgress {
pub total_files: u64,
pub files_done: u64,
pub total_bytes: u64,
pub bytes_done: u64,
pub current_file: Option<String>,
pub started: u64,
pub estimated_completion: Option<u64>,
}
impl SyncProgress {
pub fn new(total_files: u64, total_bytes: u64, started: u64) -> Self {
Self {
total_files,
files_done: 0,
total_bytes,
bytes_done: 0,
current_file: None,
started,
estimated_completion: None,
}
}
pub fn update(&mut self, file: &str, bytes: u64, current_time: u64) {
self.current_file = Some(file.to_string());
self.bytes_done += bytes;
self.files_done += 1;
if self.bytes_done > 0 {
let elapsed = current_time.saturating_sub(self.started);
let rate = self.bytes_done as f64 / elapsed.max(1) as f64;
let remaining = self.total_bytes.saturating_sub(self.bytes_done);
let eta = (remaining as f64 / rate) as u64;
self.estimated_completion = Some(current_time + eta);
}
}
pub fn percentage(&self) -> f64 {
if self.total_bytes == 0 {
return 100.0;
}
(self.bytes_done as f64 / self.total_bytes as f64) * 100.0
}
pub fn rate(&self, current_time: u64) -> f64 {
let elapsed = current_time.saturating_sub(self.started).max(1);
self.bytes_done as f64 / elapsed as f64
}
}
#[derive(Debug, Clone)]
pub enum DeltaError {
InvalidBlockSize(usize),
SourceNotFound(String),
TargetNotFound(String),
ChecksumMismatch {
expected: [u64; 4],
actual: [u64; 4],
},
ApplyFailed(String),
IoError(String),
InvalidDelta(String),
Cancelled,
}
impl fmt::Display for DeltaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidBlockSize(size) => write!(f, "invalid block size: {}", size),
Self::SourceNotFound(path) => write!(f, "source not found: {}", path),
Self::TargetNotFound(path) => write!(f, "target not found: {}", path),
Self::ChecksumMismatch { .. } => write!(f, "checksum mismatch"),
Self::ApplyFailed(msg) => write!(f, "apply failed: {}", msg),
Self::IoError(msg) => write!(f, "I/O error: {}", msg),
Self::InvalidDelta(msg) => write!(f, "invalid delta: {}", msg),
Self::Cancelled => write!(f, "sync cancelled"),
}
}
}
pub type DeltaResult<T> = Result<T, DeltaError>;
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
use alloc::vec;
#[test]
fn test_rolling_checksum_basic() {
let mut rc = RollingChecksum::new(4);
rc.push(b'a');
rc.push(b'b');
rc.push(b'c');
rc.push(b'd');
assert!(rc.is_full());
let checksum1 = rc.checksum();
rc.roll(b'e');
let checksum2 = rc.checksum();
assert_ne!(checksum1, checksum2);
}
#[test]
fn test_rolling_checksum_compute() {
let data = b"hello world";
let checksum = RollingChecksum::compute(data);
assert!(checksum > 0);
}
#[test]
fn test_rolling_checksum_consistency() {
let data = b"abcd";
let direct = RollingChecksum::compute(data);
let mut rolling = RollingChecksum::new(4);
for &b in data {
rolling.push(b);
}
assert_eq!(direct, rolling.checksum());
}
#[test]
fn test_block_signature() {
let sig = BlockSignature::new(0, 0, 4096, 0x12345678, [0u8; 16]);
assert_eq!(sig.index, 0);
assert_eq!(sig.offset, 0);
assert_eq!(sig.length, 4096);
assert_eq!(sig.rolling, 0x12345678);
}
#[test]
fn test_signature_set() {
let mut set = SignatureSet::new([0; 4], 8192, 4096);
set.add_block(BlockSignature::new(0, 0, 4096, 0x1111, [0u8; 16]));
set.add_block(BlockSignature::new(1, 4096, 4096, 0x2222, [0u8; 16]));
assert_eq!(set.block_count(), 2);
assert_eq!(set.get_block(0).unwrap().rolling, 0x1111);
}
#[test]
fn test_delta_op_copy() {
let op = DeltaOp::copy(100, 50);
assert!(op.is_copy());
assert!(!op.is_insert());
assert_eq!(op.output_size(), 50);
}
#[test]
fn test_delta_op_insert() {
let op = DeltaOp::insert(alloc::vec![1, 2, 3, 4, 5]);
assert!(op.is_insert());
assert!(!op.is_copy());
assert_eq!(op.output_size(), 5);
}
#[test]
fn test_delta_merge_copies() {
let mut delta = Delta::new([0; 4], [1; 4], 1000, 1000, 4096);
delta.add_copy(0, 100);
delta.add_copy(100, 100);
assert_eq!(delta.op_count(), 1);
if let DeltaOp::Copy { length, .. } = &delta.ops[0] {
assert_eq!(*length, 200);
}
}
#[test]
fn test_delta_merge_inserts() {
let mut delta = Delta::new([0; 4], [1; 4], 1000, 1000, 4096);
delta.add_insert(b"hello");
delta.add_insert(b" world");
assert_eq!(delta.op_count(), 1);
if let DeltaOp::Insert { data } = &delta.ops[0] {
assert_eq!(data, b"hello world");
}
}
#[test]
fn test_delta_compression_ratio() {
let mut delta = Delta::new([0; 4], [1; 4], 1000, 1000, 4096);
delta.add_copy(0, 900);
delta.add_insert(b"1234567890");
let ratio = delta.compression_ratio();
assert!(ratio < 0.1);
}
#[test]
fn test_delta_copy_percentage() {
let mut delta = Delta::new([0; 4], [1; 4], 1000, 1000, 4096);
delta.add_copy(0, 800);
delta.add_insert(&[0u8; 200]);
let pct = delta.copy_percentage();
assert!((pct - 80.0).abs() < 0.1);
}
#[test]
fn test_sync_status_display() {
assert_eq!(format!("{}", SyncStatus::New), "new");
assert_eq!(format!("{}", SyncStatus::Modified), "modified");
assert_eq!(format!("{}", SyncStatus::Deleted), "deleted");
}
#[test]
fn test_sync_entry() {
let entry = SyncEntry::new("/file.txt", 1000, 12345, [0; 4], SyncStatus::New);
assert_eq!(entry.path, "/file.txt");
assert_eq!(entry.transfer_size(), 1000);
}
#[test]
fn test_sync_plan() {
let mut plan = SyncPlan::new("/source", "/dest", 12345);
plan.add_entry(SyncEntry::new("/a.txt", 100, 1, [0; 4], SyncStatus::New));
plan.add_entry(SyncEntry::new(
"/b.txt",
200,
2,
[0; 4],
SyncStatus::Modified,
));
plan.add_entry(SyncEntry::new(
"/c.txt",
300,
3,
[0; 4],
SyncStatus::Deleted,
));
assert_eq!(plan.file_count(), 3);
assert_eq!(plan.count_by_status(SyncStatus::New), 1);
assert_eq!(plan.new_files().len(), 1);
assert_eq!(plan.modified_files().len(), 1);
assert_eq!(plan.deleted_files().len(), 1);
}
#[test]
fn test_sync_progress() {
let mut progress = SyncProgress::new(10, 10000, 1000);
progress.update("/file1.txt", 1000, 1100);
assert_eq!(progress.files_done, 1);
assert_eq!(progress.bytes_done, 1000);
assert_eq!(progress.percentage(), 10.0);
}
#[test]
fn test_error_display() {
let e = DeltaError::InvalidBlockSize(100);
assert!(format!("{}", e).contains("100"));
let e = DeltaError::SourceNotFound("/file.txt".to_string());
assert!(format!("{}", e).contains("/file.txt"));
}
}