# 20 - Delta Sync Implementation

Delta sync transfers only the modified parts of a file instead of the whole thing. Based on the rsync algorithm, it can save 90-99% bandwidth in common scenarios.

**Status**: pending implementation
**Priority**: medium
**Estimated savings**: 10-100x less data transfer

## Contents

1. [Problem Statement](#problem-statement)
2. [How It Works](#how-it-works)
3. [Key Algorithms](#key-algorithms)
4. [Proposed Architecture](#proposed-architecture)
5. [Data Structures](#data-structures)
6. [API Endpoints](#api-endpoints)
7. [Step-by-Step Implementation](#step-by-step-implementation)
8. [Integration with Existing System](#integration-with-existing-system)
9. [Use Cases and Effectiveness](#use-cases-and-effectiveness)
10. [Performance Considerations](#performance-considerations)
11. [Testing](#testing)
12. [Required Dependencies](#required-dependencies)

---

## Key Benefits

| Metric | Without Delta Sync | With Delta Sync |
|---------|----------------|----------------|
| Edit 1 line in 100MB | 100MB transferred | ~1KB transferred |
| Sync time (slow connection) | 4+ minutes | <1 second |
| Bandwidth consumption | 100% | 0.1-10% |

---

## Problem Statement

### Current scenario (no delta sync)

```
User has document.docx (50MB) on OxiCloud
    │
    ▼
Downloads full file (50MB) ──────────────────────► 50MB ↓
    │
    ▼
Edits one word
    │
    ▼
Uploads full file again (50MB) ──────────────────► 50MB ↑
    │
    ▼
TOTAL: 100MB transferred to change one word
```

### Target scenario (with delta sync)

```
User has document.docx (50MB) on OxiCloud
    │
    ▼
Downloads full file (50MB) ──────────────────────► 50MB ↓ (first time)
    │
    ▼
Edits one word
    │
    ▼
Uploads ONLY modified blocks ───────────────────► ~50KB ↑
    │
    ▼
TOTAL: 50.05MB (99.9% savings on upload)
```

---

## How It Works

### Block (chunk) concept

The file gets divided into fixed-size blocks (typically 4KB-64KB):

```
Original file (server):
┌────────┬────────┬────────┬────────┬────────┐
│ Bloque │ Bloque │ Bloque │ Bloque │ Bloque │
│   0    │   1    │   2    │   3    │   4    │
│ 4KB    │ 4KB    │ 4KB    │ 4KB    │ 4KB    │
│        │        │        │        │        │
│ weak:A │ weak:B │ weak:C │ weak:D │ weak:E │
│ sha:X1 │ sha:X2 │ sha:X3 │ sha:X4 │ sha:X5 │
└────────┴────────┴────────┴────────┴────────┘

Modified file (client):
┌────────┬────────┬────────┬────────┬────────┐
│ Bloque │ Bloque │ Bloque │ Bloque │ Bloque │
│   0    │   1    │   2    │   3    │   4    │
│ 4KB    │ 4KB    │ 4KB    │ 4KB    │ 4KB    │
│        │        │        │        │        │
│ weak:A │ weak:B │ weak:F │ weak:D │ weak:E │  ← Block 2 changed
│ sha:X1 │ sha:X2 │ sha:Y3 │ sha:X4 │ sha:X5 │
└────────┴────────┴───▲────┴────────┴────────┘
                      │
              ONLY THIS ONE GETS TRANSFERRED
```

### Sync flow

```
┌─────────────────────────────────────────────────────────────────────┐
│                     DELTA SYNC FLOW                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  CLIENT                               SERVER                        │
│                                                                      │
│  1. Has modified                      1. Has original file           │
│     file                                 + block index               │
│                                                                      │
│  2. Requests signatures ─────────────►                              │
│     GET /files/{id}/signatures                                       │
│                                                                      │
│                       ◄────────────── 3. Returns signature list      │
│                                          [(weak, strong), ...]       │
│                                                                      │
│  4. Compares local blocks                                            │
│     against server                                                   │
│     signatures                                                       │
│                                                                      │
│  5. Generates delta ──────────────────►                              │
│     POST /files/{id}/delta                                           │
│     [References + New data]                                          │
│                                                                      │
│                                       6. Reconstructs file           │
│                                          by applying delta           │
│                                                                      │
│                       ◄────────────── 7. Confirms update             │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘
```

---

## Key Algorithms

### 1. Rolling Checksum (modified Adler-32)

The rolling checksum computes the hash of a sliding window in O(1):

```rust
/// Rolling checksum para búsqueda rápida de bloques coincidentes
/// Similar al usado por rsync (Adler-32 modificado)
pub struct RollingChecksum {
    a: u32,  // Suma simple de bytes
    b: u32,  // Suma ponderada
    window_size: usize,
    buffer: VecDeque<u8>,
}

impl RollingChecksum {
    pub fn new(window_size: usize) -> Self {
        Self {
            a: 0,
            b: 0,
            window_size,
            buffer: VecDeque::with_capacity(window_size),
        }
    }
    
    /// Añadir un byte y calcular nuevo checksum
    /// Complejidad: O(1)
    pub fn roll(&mut self, new_byte: u8) -> u32 {
        if self.buffer.len() >= self.window_size {
            // Remover byte antiguo
            let old_byte = self.buffer.pop_front().unwrap() as u32;
            self.a = self.a.wrapping_sub(old_byte).wrapping_add(new_byte as u32);
            self.b = self.b.wrapping_sub(old_byte * self.window_size as u32)
                          .wrapping_add(self.a);
        } else {
            // Ventana no llena todavía
            self.a = self.a.wrapping_add(new_byte as u32);
            self.b = self.b.wrapping_add(self.a);
        }
        
        self.buffer.push_back(new_byte);
        self.checksum()
    }
    
    /// Calcular checksum actual
    pub fn checksum(&self) -> u32 {
        (self.b << 16) | (self.a & 0xFFFF)
    }
    
    /// Reset para nuevo archivo
    pub fn reset(&mut self) {
        self.a = 0;
        self.b = 0;
        self.buffer.clear();
    }
}
```

### 2. Block Signature

Each block carries two signatures:
- **Weak checksum** (32-bit) -- fast O(1) lookup
- **Strong hash** (SHA-256) -- definitive verification

```rust
/// Firma de un bloque para identificación
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockSignature {
    /// Índice del bloque en el archivo
    pub index: u32,
    /// Offset en bytes desde el inicio del archivo
    pub offset: u64,
    /// Tamaño del bloque (puede ser menor para el último)
    pub size: u32,
    /// Rolling checksum (32-bit) - búsqueda rápida
    pub weak_checksum: u32,
    /// SHA-256 hash (256-bit) - verificación definitiva
    pub strong_hash: [u8; 32],
}

/// Genera firmas para todos los bloques de un archivo
pub fn generate_signatures(data: &[u8], block_size: usize) -> Vec<BlockSignature> {
    let mut signatures = Vec::new();
    let mut offset = 0u64;
    let mut index = 0u32;
    
    for chunk in data.chunks(block_size) {
        // Weak checksum (rolling)
        let weak = adler32_checksum(chunk);
        
        // Strong hash (SHA-256)
        let mut hasher = Sha256::new();
        hasher.update(chunk);
        let strong: [u8; 32] = hasher.finalize().into();
        
        signatures.push(BlockSignature {
            index,
            offset,
            size: chunk.len() as u32,
            weak_checksum: weak,
            strong_hash: strong,
        });
        
        offset += chunk.len() as u64;
        index += 1;
    }
    
    signatures
}

fn adler32_checksum(data: &[u8]) -> u32 {
    let mut a: u32 = 1;
    let mut b: u32 = 0;
    
    for &byte in data {
        a = (a + byte as u32) % 65521;
        b = (b + a) % 65521;
    }
    
    (b << 16) | a
}
```

### 3. Delta Generation

```rust
/// Instrucción de delta
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeltaInstruction {
    /// Copiar bloque existente del archivo original
    Copy {
        /// Índice del bloque en el archivo original
        block_index: u32,
    },
    /// Insertar datos literales nuevos
    Literal {
        /// Datos nuevos a insertar
        data: Vec<u8>,
    },
}

/// Delta completo para actualizar un archivo
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileDelta {
    /// ID del archivo base
    pub base_file_id: String,
    /// Hash del archivo base (para verificación)
    pub base_file_hash: String,
    /// Nuevo tamaño del archivo
    pub new_size: u64,
    /// Instrucciones de delta
    pub instructions: Vec<DeltaInstruction>,
    /// Hash del archivo resultante (para verificación)
    pub result_hash: String,
}

/// Genera delta comparando archivo local con firmas remotas
pub fn generate_delta(
    local_data: &[u8],
    remote_signatures: &[BlockSignature],
    block_size: usize,
) -> FileDelta {
    // Crear índice de weak checksums para búsqueda O(1)
    let mut weak_index: HashMap<u32, Vec<&BlockSignature>> = HashMap::new();
    for sig in remote_signatures {
        weak_index.entry(sig.weak_checksum)
            .or_default()
            .push(sig);
    }
    
    let mut instructions = Vec::new();
    let mut rolling = RollingChecksum::new(block_size);
    let mut pos = 0;
    let mut literal_buffer = Vec::new();
    
    while pos < local_data.len() {
        // Calcular rolling checksum de la ventana actual
        let end = (pos + block_size).min(local_data.len());
        let window = &local_data[pos..end];
        
        let weak = if window.len() == block_size {
            rolling.reset();
            for &b in window {
                rolling.roll(b);
            }
            rolling.checksum()
        } else {
            adler32_checksum(window)
        };
        
        // Buscar coincidencia
        let mut found_match = false;
        
        if let Some(candidates) = weak_index.get(&weak) {
            // Verificar con strong hash
            let mut hasher = Sha256::new();
            hasher.update(window);
            let strong: [u8; 32] = hasher.finalize().into();
            
            for sig in candidates {
                if sig.strong_hash == strong && sig.size as usize == window.len() {
                    // ¡Coincidencia encontrada!
                    
                    // Flush literal buffer si hay datos pendientes
                    if !literal_buffer.is_empty() {
                        instructions.push(DeltaInstruction::Literal {
                            data: std::mem::take(&mut literal_buffer),
                        });
                    }
                    
                    // Añadir instrucción de copia
                    instructions.push(DeltaInstruction::Copy {
                        block_index: sig.index,
                    });
                    
                    pos += window.len();
                    found_match = true;
                    break;
                }
            }
        }
        
        if !found_match {
            // No hay coincidencia, añadir byte a literal buffer
            literal_buffer.push(local_data[pos]);
            pos += 1;
        }
    }
    
    // Flush remaining literal buffer
    if !literal_buffer.is_empty() {
        instructions.push(DeltaInstruction::Literal {
            data: literal_buffer,
        });
    }
    
    // Calcular hash del resultado
    let mut hasher = Sha256::new();
    hasher.update(local_data);
    let result_hash = hex::encode(hasher.finalize());
    
    FileDelta {
        base_file_id: String::new(), // Se llena al enviar
        base_file_hash: String::new(), // Se llena al enviar
        new_size: local_data.len() as u64,
        instructions,
        result_hash,
    }
}
```

### 4. Delta Application

```rust
/// Aplica delta a un archivo base para obtener el nuevo archivo
pub fn apply_delta(
    base_data: &[u8],
    signatures: &[BlockSignature],
    delta: &FileDelta,
    block_size: usize,
) -> Result<Vec<u8>, DeltaSyncError> {
    let mut result = Vec::with_capacity(delta.new_size as usize);
    
    for instruction in &delta.instructions {
        match instruction {
            DeltaInstruction::Copy { block_index } => {
                // Copiar bloque del archivo base
                let sig = signatures.get(*block_index as usize)
                    .ok_or(DeltaSyncError::InvalidBlockIndex(*block_index))?;
                
                let start = sig.offset as usize;
                let end = start + sig.size as usize;
                
                if end > base_data.len() {
                    return Err(DeltaSyncError::InvalidBlockRange);
                }
                
                result.extend_from_slice(&base_data[start..end]);
            }
            DeltaInstruction::Literal { data } => {
                // Insertar datos literales
                result.extend_from_slice(data);
            }
        }
    }
    
    // Verificar hash del resultado
    let mut hasher = Sha256::new();
    hasher.update(&result);
    let actual_hash = hex::encode(hasher.finalize());
    
    if actual_hash != delta.result_hash {
        return Err(DeltaSyncError::HashMismatch {
            expected: delta.result_hash.clone(),
            actual: actual_hash,
        });
    }
    
    Ok(result)
}
```

---

## Proposed Architecture

### File structure

```
src/
├── infrastructure/
│   └── services/
│       ├── mod.rs                      # Añadir: pub mod delta_sync_service;
│       └── delta_sync_service.rs       # NUEVO: Servicio principal
│
├── interfaces/
│   └── api/
│       └── handlers/
│           ├── mod.rs                  # Añadir: pub mod delta_sync_handler;
│           └── delta_sync_handler.rs   # NUEVO: Endpoints API
│
└── common/
    └── di.rs                           # Añadir: delta_sync_service a AppState
```

### Main service (delta_sync_service.rs)

```rust
//! Delta Sync Service - Sincronización eficiente por diferencias
//!
//! Implementa algoritmo similar a rsync para transferir solo
//! las partes modificadas de los archivos.

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::fs;
use tokio::sync::RwLock;
use sha2::{Sha256, Digest};
use serde::{Deserialize, Serialize};

/// Tamaño de bloque por defecto (16KB - buen balance)
pub const DEFAULT_BLOCK_SIZE: usize = 16 * 1024;

/// Tamaño mínimo de archivo para usar delta sync
pub const MIN_DELTA_SYNC_SIZE: u64 = 64 * 1024; // 64KB

/// Errores del servicio Delta Sync
#[derive(Debug, thiserror::Error)]
pub enum DeltaSyncError {
    #[error("Archivo no encontrado: {0}")]
    FileNotFound(String),
    
    #[error("Firmas no encontradas para archivo: {0}")]
    SignaturesNotFound(String),
    
    #[error("Índice de bloque inválido: {0}")]
    InvalidBlockIndex(u32),
    
    #[error("Rango de bloque inválido")]
    InvalidBlockRange,
    
    #[error("Hash no coincide: esperado {expected}, actual {actual}")]
    HashMismatch { expected: String, actual: String },
    
    #[error("Error de I/O: {0}")]
    IoError(#[from] std::io::Error),
    
    #[error("Error de serialización: {0}")]
    SerializationError(String),
}

/// Servicio de Delta Sync
pub struct DeltaSyncService {
    /// Directorio para almacenar índices de firmas
    signatures_dir: PathBuf,
    /// Cache en memoria de firmas recientes
    signature_cache: Arc<RwLock<HashMap<String, Vec<BlockSignature>>>>,
    /// Tamaño de bloque configurado
    block_size: usize,
    /// Máximo de entradas en cache
    max_cache_entries: usize,
}

impl DeltaSyncService {
    pub fn new(storage_root: &Path) -> Self {
        Self {
            signatures_dir: storage_root.join(".delta_signatures"),
            signature_cache: Arc::new(RwLock::new(HashMap::new())),
            block_size: DEFAULT_BLOCK_SIZE,
            max_cache_entries: 1000,
        }
    }
    
    pub fn with_block_size(mut self, block_size: usize) -> Self {
        self.block_size = block_size;
        self
    }
    
    /// Inicializar servicio (crear directorios)
    pub async fn initialize(&self) -> std::io::Result<()> {
        fs::create_dir_all(&self.signatures_dir).await?;
        tracing::info!("Delta Sync service initialized with block size: {}KB", 
                       self.block_size / 1024);
        Ok(())
    }
    
    /// Generar y almacenar firmas para un archivo
    pub async fn index_file(
        &self, 
        file_id: &str, 
        file_path: &Path
    ) -> Result<Vec<BlockSignature>, DeltaSyncError> {
        let data = fs::read(file_path).await?;
        
        // No indexar archivos pequeños
        if data.len() < MIN_DELTA_SYNC_SIZE as usize {
            return Ok(Vec::new());
        }
        
        let signatures = generate_signatures(&data, self.block_size);
        
        // Guardar en disco
        let sig_path = self.signature_path(file_id);
        let sig_json = serde_json::to_vec(&signatures)
            .map_err(|e| DeltaSyncError::SerializationError(e.to_string()))?;
        fs::write(&sig_path, sig_json).await?;
        
        // Actualizar cache
        {
            let mut cache = self.signature_cache.write().await;
            if cache.len() >= self.max_cache_entries {
                // LRU simple: eliminar primera entrada
                if let Some(key) = cache.keys().next().cloned() {
                    cache.remove(&key);
                }
            }
            cache.insert(file_id.to_string(), signatures.clone());
        }
        
        tracing::debug!("Indexed file {} with {} blocks", file_id, signatures.len());
        Ok(signatures)
    }
    
    /// Obtener firmas de un archivo
    pub async fn get_signatures(
        &self, 
        file_id: &str
    ) -> Result<Vec<BlockSignature>, DeltaSyncError> {
        // Buscar en cache primero
        {
            let cache = self.signature_cache.read().await;
            if let Some(sigs) = cache.get(file_id) {
                return Ok(sigs.clone());
            }
        }
        
        // Cargar de disco
        let sig_path = self.signature_path(file_id);
        if !sig_path.exists() {
            return Err(DeltaSyncError::SignaturesNotFound(file_id.to_string()));
        }
        
        let sig_json = fs::read(&sig_path).await?;
        let signatures: Vec<BlockSignature> = serde_json::from_slice(&sig_json)
            .map_err(|e| DeltaSyncError::SerializationError(e.to_string()))?;
        
        // Actualizar cache
        {
            let mut cache = self.signature_cache.write().await;
            cache.insert(file_id.to_string(), signatures.clone());
        }
        
        Ok(signatures)
    }
    
    /// Aplicar delta a un archivo
    pub async fn apply_delta(
        &self,
        file_id: &str,
        base_path: &Path,
        delta: &FileDelta,
    ) -> Result<Vec<u8>, DeltaSyncError> {
        let base_data = fs::read(base_path).await?;
        let signatures = self.get_signatures(file_id).await?;
        
        apply_delta(&base_data, &signatures, delta, self.block_size)
    }
    
    /// Eliminar firmas de un archivo (cuando se borra)
    pub async fn remove_signatures(&self, file_id: &str) -> Result<(), DeltaSyncError> {
        // Eliminar de cache
        {
            let mut cache = self.signature_cache.write().await;
            cache.remove(file_id);
        }
        
        // Eliminar de disco
        let sig_path = self.signature_path(file_id);
        if sig_path.exists() {
            fs::remove_file(&sig_path).await?;
        }
        
        Ok(())
    }
    
    /// Estadísticas del servicio
    pub async fn get_stats(&self) -> DeltaSyncStats {
        let cache = self.signature_cache.read().await;
        DeltaSyncStats {
            cached_files: cache.len() as u64,
            block_size: self.block_size,
        }
    }
    
    fn signature_path(&self, file_id: &str) -> PathBuf {
        // Usar primeros 2 chars del ID para subdirectorio
        let prefix = &file_id[..2.min(file_id.len())];
        self.signatures_dir.join(prefix).join(format!("{}.sig", file_id))
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct DeltaSyncStats {
    pub cached_files: u64,
    pub block_size: usize,
}
```

---

## API Endpoints

### Handler (delta_sync_handler.rs)

```rust
use axum::{
    extract::{Path, State, Json},
    http::StatusCode,
    response::IntoResponse,
};
use crate::common::di::AppState;
use crate::infrastructure::services::delta_sync_service::*;

pub struct DeltaSyncHandler;

impl DeltaSyncHandler {
    /// GET /api/files/{id}/signatures
    /// 
    /// Obtiene las firmas de bloques de un archivo para calcular delta
    pub async fn get_signatures(
        State(state): State<AppState>,
        Path(file_id): Path<String>,
    ) -> impl IntoResponse {
        let delta_service = &state.core.delta_sync_service;
        
        match delta_service.get_signatures(&file_id).await {
            Ok(signatures) => {
                Json(SignaturesResponse {
                    file_id,
                    block_size: delta_service.block_size,
                    block_count: signatures.len() as u32,
                    signatures,
                }).into_response()
            }
            Err(DeltaSyncError::SignaturesNotFound(_)) => {
                // Archivo no indexado - cliente debe hacer upload completo
                (StatusCode::NOT_FOUND, Json(serde_json::json!({
                    "error": "Signatures not found",
                    "hint": "File not indexed for delta sync, use full upload"
                }))).into_response()
            }
            Err(e) => {
                (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                    "error": e.to_string()
                }))).into_response()
            }
        }
    }
    
    /// POST /api/files/{id}/delta
    /// 
    /// Aplica un delta para actualizar un archivo
    pub async fn apply_delta(
        State(state): State<AppState>,
        Path(file_id): Path<String>,
        Json(delta): Json<FileDelta>,
    ) -> impl IntoResponse {
        let delta_service = &state.core.delta_sync_service;
        let file_service = &state.applications.file_service;
        
        // Obtener path del archivo actual
        let file = match file_service.get_file(&file_id).await {
            Ok(f) => f,
            Err(_) => {
                return (StatusCode::NOT_FOUND, Json(serde_json::json!({
                    "error": "File not found"
                }))).into_response();
            }
        };
        
        // Aplicar delta
        let file_path = state.core.path_service.resolve_path(file.path());
        match delta_service.apply_delta(&file_id, &file_path, &delta).await {
            Ok(new_data) => {
                // Guardar nuevo contenido
                if let Err(e) = tokio::fs::write(&file_path, &new_data).await {
                    return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                        "error": format!("Failed to write file: {}", e)
                    }))).into_response();
                }
                
                // Re-indexar archivo
                if let Err(e) = delta_service.index_file(&file_id, &file_path).await {
                    tracing::warn!("Failed to re-index file after delta: {}", e);
                }
                
                // Calcular estadísticas
                let delta_size: usize = delta.instructions.iter()
                    .filter_map(|i| match i {
                        DeltaInstruction::Literal { data } => Some(data.len()),
                        _ => None,
                    })
                    .sum();
                
                Json(DeltaApplyResponse {
                    success: true,
                    new_size: new_data.len() as u64,
                    delta_size: delta_size as u64,
                    savings_percent: if new_data.len() > 0 {
                        ((1.0 - (delta_size as f64 / new_data.len() as f64)) * 100.0) as u32
                    } else { 0 },
                }).into_response()
            }
            Err(e) => {
                (StatusCode::BAD_REQUEST, Json(serde_json::json!({
                    "error": e.to_string()
                }))).into_response()
            }
        }
    }
    
    /// POST /api/files/{id}/index
    /// 
    /// Fuerza la indexación de un archivo para delta sync
    pub async fn index_file(
        State(state): State<AppState>,
        Path(file_id): Path<String>,
    ) -> impl IntoResponse {
        let delta_service = &state.core.delta_sync_service;
        let file_service = &state.applications.file_service;
        
        // Obtener path del archivo
        let file = match file_service.get_file(&file_id).await {
            Ok(f) => f,
            Err(_) => {
                return (StatusCode::NOT_FOUND, Json(serde_json::json!({
                    "error": "File not found"
                }))).into_response();
            }
        };
        
        let file_path = state.core.path_service.resolve_path(file.path());
        match delta_service.index_file(&file_id, &file_path).await {
            Ok(signatures) => {
                Json(serde_json::json!({
                    "success": true,
                    "blocks_indexed": signatures.len()
                })).into_response()
            }
            Err(e) => {
                (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                    "error": e.to_string()
                }))).into_response()
            }
        }
    }
    
    /// GET /api/delta/stats
    /// 
    /// Estadísticas del servicio delta sync
    pub async fn get_stats(
        State(state): State<AppState>,
    ) -> impl IntoResponse {
        let delta_service = &state.core.delta_sync_service;
        Json(delta_service.get_stats().await)
    }
}

#[derive(Serialize)]
struct SignaturesResponse {
    file_id: String,
    block_size: usize,
    block_count: u32,
    signatures: Vec<BlockSignature>,
}

#[derive(Serialize)]
struct DeltaApplyResponse {
    success: bool,
    new_size: u64,
    delta_size: u64,
    savings_percent: u32,
}
```

### Routes to add in routes.rs

```rust
// Delta Sync routes
let delta_sync_router = Router::new()
    .route("/files/:id/signatures", get(DeltaSyncHandler::get_signatures))
    .route("/files/:id/delta", post(DeltaSyncHandler::apply_delta))
    .route("/files/:id/index", post(DeltaSyncHandler::index_file))
    .route("/delta/stats", get(DeltaSyncHandler::get_stats))
    .with_state(app_state.clone());

// Añadir a router principal
router = router.nest("/api", delta_sync_router);
```

---

## Integration with Existing System

### 1. Auto-index on upload

In `file_handler.rs`, after a successful upload:

```rust
// Después de guardar el archivo...

// Indexar para delta sync (archivos >64KB)
if total_size >= 64 * 1024 {
    let delta_service = &state.core.delta_sync_service;
    if let Err(e) = delta_service.index_file(&file.id, &file_path).await {
        tracing::warn!("Failed to index file for delta sync: {}", e);
        // No es error fatal, el archivo se subió correctamente
    }
}
```

### 2. Clean up signatures on delete

In `file_handler.rs`, when deleting a file:

```rust
// Limpiar firmas de delta sync
let delta_service = &state.core.delta_sync_service;
if let Err(e) = delta_service.remove_signatures(&id).await {
    tracing::warn!("Failed to remove delta signatures: {}", e);
}
```

### 3. Integration with Dedup Service

Delta sync and dedup are complementary:

```
┌──────────────────────────────────────────────────────────────┐
│                    UPLOAD WITH DELTA + DEDUP                  │
├──────────────────────────────────────────────────────────────┤
│                                                               │
│  1. Client has modified file.txt                              │
│                                                               │
│  2. GET /files/{id}/signatures                                │
│     → Server returns block signatures                         │
│                                                               │
│  3. Client computes delta locally                             │
│     → Only 3 of 100 blocks changed                            │
│                                                               │
│  4. POST /files/{id}/delta                                    │
│     → Sends only the 3 new blocks                             │
│                                                               │
│  5. Server applies delta                                      │
│     → Reconstructs complete file                              │
│                                                               │
│  6. Server runs dedup on resulting file                        │
│     → If another user has same content, it deduplicates        │
│                                                               │
│  RESULT:                                                      │
│  ├── Delta sync: 97% less transfer                            │
│  └── Dedup: 30-50% less storage                               │
│                                                               │
└──────────────────────────────────────────────────────────────┘
```

---

## Use Cases and Effectiveness

| File type | Scenario | Without Delta | With Delta | Savings |
|-----------------|-----------|-----------|-----------|--------|
| `.txt` / `.md` | Edit paragraph | 1MB | ~4KB | **99.6%** |
| `.json` / `.xml` | Change value | 500KB | ~1KB | **99.8%** |
| `.rs` / `.js` | Modify function | 100KB | ~2KB | **98%** |
| `.docx` | Edit page | 5MB | ~100KB | **98%** |
| `.xlsx` | Change cells | 2MB | ~50KB | **97.5%** |
| `.pdf` | Edit text | 10MB | ~2MB | **80%** |
| `.psd` | Edit layer | 100MB | ~5MB | **95%** |
| `.zip` | Add file | 50MB | ~5MB | **90%** |
| `.mp4` | Re-encode | 500MB | 450MB | **10%** |
| `.jpg` | Edit image | 5MB | 4MB | **20%** |

For highly compressed or re-encoded files, delta sync is less effective.

---

## Performance Considerations

### Optimal block size

| Size | Pros | Cons | Best for |
|--------|------|------|------------|
| 4KB | More granular, better savings | More signature overhead | Small files |
| 16KB | Good balance | - | **General use** |
| 64KB | Less overhead | Less granular | Large files |
| 256KB | Minimal overhead | Little savings | Very large files |

### Memory

```rust
// Estimación de memoria por archivo indexado
// 
// BlockSignature size ≈ 48 bytes (4 + 8 + 4 + 4 + 32 - con padding)
// 
// Archivo 100MB con bloques de 16KB:
// - 100MB / 16KB = 6,400 bloques
// - 6,400 × 48 bytes = ~300KB de firmas
// 
// Cache de 1000 archivos ≈ 300MB máximo
```

### CPU

```rust
// Operaciones costosas:
// 
// 1. generate_signatures():  O(n) donde n = tamaño archivo
//    - SHA-256: ~500MB/s en CPU moderna
//    - Adler32: ~2GB/s
//    
// 2. generate_delta(): O(n × m) peor caso, O(n) típico
//    - n = tamaño archivo nuevo
//    - m = número de bloques originales
//    - HashMap lookup: O(1) promedio
//
// 3. apply_delta(): O(n) donde n = tamaño resultado
//    - Mayormente copias de memoria
```

---

## Testing

### Unit tests

```rust
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_rolling_checksum() {
        let mut rc = RollingChecksum::new(4);
        
        // Alimentar bytes
        for b in b"test" {
            rc.roll(*b);
        }
        let checksum1 = rc.checksum();
        
        // Rolling: quitar 't', añadir 'X'
        rc.roll(b'X');
        let checksum2 = rc.checksum();
        
        // Checksums deben ser diferentes
        assert_ne!(checksum1, checksum2);
    }
    
    #[test]
    fn test_generate_signatures() {
        let data = b"Hello, World! This is a test file for delta sync.";
        let sigs = generate_signatures(data, 16);
        
        assert_eq!(sigs.len(), 4); // 50 bytes / 16 = 3.125 → 4 bloques
        assert_eq!(sigs[0].offset, 0);
        assert_eq!(sigs[1].offset, 16);
    }
    
    #[test]
    fn test_delta_identical_files() {
        let data = b"Hello, World!";
        let sigs = generate_signatures(data, 8);
        let delta = generate_delta(data, &sigs, 8);
        
        // Solo instrucciones Copy, sin Literal
        for instr in &delta.instructions {
            assert!(matches!(instr, DeltaInstruction::Copy { .. }));
        }
    }
    
    #[test]
    fn test_delta_small_change() {
        let original = b"Hello, World! This is original.";
        let modified = b"Hello, World! This is MODIFIED.";
        
        let sigs = generate_signatures(original, 8);
        let delta = generate_delta(modified, &sigs, 8);
        
        // Debería haber algunas instrucciones Copy y algunas Literal
        let copies = delta.instructions.iter()
            .filter(|i| matches!(i, DeltaInstruction::Copy { .. }))
            .count();
        let literals = delta.instructions.iter()
            .filter(|i| matches!(i, DeltaInstruction::Literal { .. }))
            .count();
        
        assert!(copies > 0, "Should reuse some blocks");
        assert!(literals > 0, "Should have some new data");
    }
    
    #[test]
    fn test_apply_delta_roundtrip() {
        let original = b"The quick brown fox jumps over the lazy dog.";
        let modified = b"The quick brown cat jumps over the lazy dog.";
        
        let sigs = generate_signatures(original, 8);
        let delta = generate_delta(modified, &sigs, 8);
        let reconstructed = apply_delta(original, &sigs, &delta, 8).unwrap();
        
        assert_eq!(reconstructed, modified);
    }
}
```

### Integration tests

```rust
#[tokio::test]
async fn test_delta_sync_service_workflow() {
    let temp_dir = tempfile::tempdir().unwrap();
    let service = DeltaSyncService::new(temp_dir.path());
    service.initialize().await.unwrap();
    
    // Crear archivo original
    let file_path = temp_dir.path().join("test.txt");
    tokio::fs::write(&file_path, b"Original content here").await.unwrap();
    
    // Indexar
    let sigs = service.index_file("file123", &file_path).await.unwrap();
    assert!(!sigs.is_empty());
    
    // Recuperar firmas
    let retrieved = service.get_signatures("file123").await.unwrap();
    assert_eq!(sigs.len(), retrieved.len());
    
    // Simular modificación y delta
    let modified = b"Modified content here!";
    let delta = generate_delta(modified, &sigs, service.block_size);
    
    // Aplicar delta
    let result = service.apply_delta("file123", &file_path, &delta).await.unwrap();
    assert_eq!(result, modified);
}
```

---

## Required Dependencies

Add to `Cargo.toml`:

```toml
[dependencies]
# Ya existentes - verificar versiones
sha2 = "0.10"
hex = "0.4"

# Nuevas dependencias para delta sync
thiserror = "1.0"     # Para errores tipados (probablemente ya existe)
```

---

## Implementation Checklist

- [ ] Create `delta_sync_service.rs` with basic structures
- [ ] Implement **RollingChecksum**
- [ ] Implement **generate_signatures()**
- [ ] Implement **generate_delta()**
- [ ] Implement **apply_delta()**
- [ ] Create handler and API endpoints
- [ ] Integrate into DI (**AppState**)
- [ ] Add routes in `routes.rs`
- [ ] Integrate with upload (automatic indexing)
- [ ] Integrate with delete (signature cleanup)
- [ ] Unit tests
- [ ] Integration tests
- [ ] Document API endpoints
- [ ] Metrics and logging

---

## References

- [rsync algorithm](https://rsync.samba.org/tech_report/)
- [Rolling hash - Wikipedia](https://en.wikipedia.org/wiki/Rolling_hash)
- [Adler-32 checksum](https://en.wikipedia.org/wiki/Adler-32)
- [librsync](https://github.com/librsync/librsync)
