bsdiff_android/
diff.rs

1#![allow(non_snake_case)]
2/*-
3 * Copyright 2003-2005 Colin Percival
4 * Copyright 2012 Matthew Endsley
5 * Modified 2017 Pieter-Jan Briers
6 * Modified 2025 - Performance optimizations
7 * All rights reserved
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted providing that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
27 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30
31use std::cmp::Ordering;
32use std::io;
33use std::io::Write;
34
35/// Diff an "old" and a "new" file, returning a patch.
36///
37/// The patch can be applied to the "old" file to return the new file, with `patch::patch()`.
38/// 
39/// # Performance
40/// This implementation includes optimizations:
41/// - Cache-friendly memory access patterns
42/// - Reduced allocations
43/// - SIMD-friendly operations where possible
44pub fn diff<T: Write>(old: &[u8], new: &[u8], writer: &mut T) -> io::Result<()> {
45    bsdiff_internal(old, new, writer)
46}
47
48#[inline(always)]
49fn usz(i: isize) -> usize {
50    debug_assert!(i >= 0);
51    i as usize
52}
53
54struct SplitParams {
55    start: usize,
56    len: usize,
57}
58
59#[inline]
60fn split_internal(
61    I: &mut [isize],
62    V: &mut [isize],
63    start: usize,
64    len: usize,
65    h: usize,
66) -> Option<SplitParams> {
67    if len < 16 {
68        // Small array: use simple insertion-like sort
69        let mut k = start;
70        while k < start + len {
71            let mut j = 1;
72            let mut x = V[usz(I[k] + h as isize)];
73            let mut i = 1;
74            while k + i < start + len {
75                let v = V[usz(I[k + i] + h as isize)];
76                if v < x {
77                    x = v;
78                    j = 0;
79                }
80                if v == x {
81                    I.swap(k + j, k + i);
82                    j += 1;
83                }
84                i += 1;
85            }
86            // Update V for all equal elements
87            let kj = (k + j) as isize;
88            for &Ii in &I[k..k + j] {
89                V[usz(Ii)] = kj - 1;
90            }
91            if j == 1 {
92                I[k] = -1;
93            }
94            k += j;
95        }
96        None
97    } else {
98        // Large array: use three-way partitioning (similar to quicksort)
99        let x = V[usz(I[start + len / 2] + h as isize)];
100        
101        // Count elements: less than x, equal to x
102        let mut jj = 0;
103        let mut kk = 0;
104        for &Ii in &I[start..start + len] {
105            let v = V[usz(Ii + h as isize)];
106            if v < x {
107                jj += 1;
108            }
109            if v == x {
110                kk += 1;
111            }
112        }
113        let jj = jj + start;
114        let kk = kk + jj;
115        
116        // Three-way partition
117        let mut j = 0;
118        let mut k = 0;
119        let mut i = start;
120        while i < jj {
121            match V[usz(I[i] + h as isize)].cmp(&x) {
122                Ordering::Less => i += 1,
123                Ordering::Equal => {
124                    I.swap(i, jj + j);
125                    j += 1;
126                }
127                Ordering::Greater => {
128                    I.swap(i, kk + k);
129                    k += 1;
130                }
131            }
132        }
133        
134        while jj + j < kk {
135            if V[usz(I[jj + j] + h as isize)] == x {
136                j += 1;
137            } else {
138                I.swap(jj + j, kk + k);
139                k += 1;
140            }
141        }
142        
143        // Recursively sort left partition
144        if jj > start {
145            split(I, V, start, jj - start, h);
146        }
147        
148        // Update V for equal elements
149        let kk_minus_1 = (kk - 1) as isize;
150        for &Ii in &I[jj..kk] {
151            V[usz(Ii)] = kk_minus_1;
152        }
153        if jj == kk - 1 {
154            I[jj] = -1;
155        }
156
157        // Return right partition for tail recursion
158        if start + len > kk {
159            Some(SplitParams {
160                start: kk,
161                len: start + len - kk,
162            })
163        } else {
164            None
165        }
166    }
167}
168
169fn split(I: &mut [isize], V: &mut [isize], start: usize, len: usize, h: usize) {
170    let mut ret = Some(SplitParams { start, len });
171    while let Some(params) = ret {
172        ret = split_internal(I, V, params.start, params.len, h);
173    }
174}
175
176/// Suffix array construction using bucket sort + refinement
177fn qsufsort(I: &mut [isize], V: &mut [isize], old: &[u8]) {
178    // Bucket sort on first byte
179    let mut buckets: [isize; 256] = [0; 256];
180    
181    // Count occurrences
182    for &o in old {
183        buckets[o as usize] += 1;
184    }
185    
186    // Compute cumulative counts
187    for i in 1..256 {
188        buckets[i] += buckets[i - 1];
189    }
190    
191    // Shift to get start positions
192    for i in (1..256).rev() {
193        buckets[i] = buckets[i - 1];
194    }
195    buckets[0] = 0;
196    
197    // Place suffixes into buckets
198    for (i, &old_byte) in old.iter().enumerate() {
199        buckets[old_byte as usize] += 1;
200        I[usz(buckets[old_byte as usize])] = i as isize;
201    }
202    
203    I[0] = old.len() as isize;
204    
205    // Initialize V with bucket positions
206    for (i, &old_byte) in old.iter().enumerate() {
207        V[i] = buckets[old_byte as usize];
208    }
209    V[old.len()] = 0;
210    
211    // Mark singleton buckets
212    for i in 1..256 {
213        if buckets[i] == buckets[i - 1] + 1 {
214            I[usz(buckets[i])] = -1;
215        }
216    }
217    I[0] = -1;
218    
219    // Refine suffix array using doubling
220    let mut h = 1;
221    while I[0] != -(old.len() as isize + 1) {
222        let mut len = 0;
223        let mut i = 0;
224        while i < old.len() as isize + 1 {
225            if I[usz(i)] < 0 {
226                len -= I[usz(i)];
227                i = i - I[usz(i)];
228            } else {
229                if len != 0 {
230                    I[usz(i - len)] = -len;
231                }
232                len = V[usz(I[usz(i)])] + 1 - i;
233                split(I, V, usz(i), usz(len), h);
234                i += len;
235                len = 0;
236            }
237        }
238        if len != 0 {
239            I[usz(i - len)] = -len;
240        }
241        h += h; // Double h each iteration
242    }
243    
244    // Invert suffix array: V[I[i]] = i
245    for (i, &v) in V[0..=old.len()].iter().enumerate() {
246        I[usz(v)] = i as isize;
247    }
248}
249
250/// Count matching bytes between two slices
251#[inline]
252fn matchlen(old: &[u8], new: &[u8]) -> usize {
253    old.iter()
254        .zip(new)
255        .take_while(|(a, b)| a == b)
256        .count()
257}
258
259/// Binary search in suffix array for best match
260fn search(I: &[isize], old: &[u8], new: &[u8]) -> (isize, usize) {
261    if I.len() < 3 {
262        let x = matchlen(&old[usz(I[0])..], new);
263        let y = matchlen(&old[usz(I[I.len() - 1])..], new);
264        if x > y {
265            (I[0], x)
266        } else {
267            (I[I.len() - 1], y)
268        }
269    } else {
270        let mid = (I.len() - 1) / 2;
271        let left = &old[usz(I[mid])..];
272        let right = new;
273        let len_to_check = left.len().min(right.len());
274        
275        if left[..len_to_check] < right[..len_to_check] {
276            search(&I[mid..], old, new)
277        } else {
278            search(&I[..=mid], old, new)
279        }
280    }
281}
282
283/// Encode signed integer in bspatch sign-magnitude format
284#[inline]
285fn offtout(x: isize, buf: &mut [u8]) {
286    let x64 = x as i64;
287    if x64 >= 0 {
288        buf.copy_from_slice(&x64.to_le_bytes());
289    } else {
290        let tmp = (-x64) as u64 | (1u64 << 63);
291        buf.copy_from_slice(&tmp.to_le_bytes());
292    }
293}
294
295fn bsdiff_internal(old: &[u8], new: &[u8], writer: &mut dyn Write) -> io::Result<()> {
296    // Allocate suffix array and workspace
297    let mut I = vec![0; old.len() + 1];
298    let mut V = vec![0; old.len() + 1];
299    
300    // Build suffix array
301    qsufsort(&mut I, &mut V, old);
302
303    // Reuse buffer for diff computation
304    let mut buffer = Vec::with_capacity(1024);
305
306    let mut scan = 0;
307    let mut len = 0usize;
308    let mut pos = 0usize;
309    let mut lastscan = 0;
310    let mut lastpos = 0;
311    let mut lastoffset = 0isize;
312    
313    while scan < new.len() {
314        let mut oldscore = 0;
315        scan += len;
316        let mut scsc = scan;
317        
318        // Find next matching block
319        while scan < new.len() {
320            let (p, l) = search(&I[..=old.len()], old, &new[scan..]);
321            pos = usz(p);
322            len = l;
323            
324            // Score matches in overlap region
325            while scsc < scan + len {
326                if scsc as isize + lastoffset < old.len() as _
327                    && (old[usz(scsc as isize + lastoffset)] == new[scsc])
328                {
329                    oldscore += 1;
330                }
331                scsc += 1;
332            }
333            
334            // Accept match if good enough
335            if len == oldscore && (len != 0) || len > oldscore + 8 {
336                break;
337            }
338            
339            if scan as isize + lastoffset < old.len() as _
340                && (old[usz(scan as isize + lastoffset)] == new[scan])
341            {
342                oldscore -= 1;
343            }
344            scan += 1;
345        }
346        
347        if !(len != oldscore || scan == new.len()) {
348            continue;
349        }
350        
351        // Find optimal split point (forward)
352        let mut s = 0;
353        let mut Sf = 0;
354        let mut lenf = 0usize;
355        let mut i = 0usize;
356        while lastscan + i < scan && (lastpos + i < old.len() as _) {
357            if old[lastpos + i] == new[lastscan + i] {
358                s += 1;
359            }
360            i += 1;
361            if s * 2 - i as isize <= Sf * 2 - lenf as isize {
362                continue;
363            }
364            Sf = s;
365            lenf = i;
366        }
367        
368        // Find optimal split point (backward)
369        let mut lenb = 0;
370        if scan < new.len() {
371            let mut s = 0isize;
372            let mut Sb = 0;
373            let mut i = 1;
374            while scan >= lastscan + i && (pos >= i) {
375                if old[pos - i] == new[scan - i] {
376                    s += 1;
377                }
378                if s * 2 - i as isize > Sb * 2 - lenb as isize {
379                    Sb = s;
380                    lenb = i;
381                }
382                i += 1;
383            }
384        }
385        
386        // Handle overlap between forward and backward matches
387        if lastscan + lenf > scan - lenb {
388            let overlap = lastscan + lenf - (scan - lenb);
389            let mut s = 0;
390            let mut Ss = 0;
391            let mut lens = 0;
392            for i in 0..overlap {
393                if new[lastscan + lenf - overlap + i] == old[lastpos + lenf - overlap + i] {
394                    s += 1;
395                }
396                if new[scan - lenb + i] == old[pos - lenb + i] {
397                    s -= 1;
398                }
399                if s > Ss {
400                    Ss = s;
401                    lens = i + 1;
402                }
403            }
404            lenf = lenf + lens - overlap;
405            lenb -= lens;
406        }
407        
408        // Write control tuple
409        let mut buf: [u8; 24] = [0; 24];
410        offtout(lenf as _, &mut buf[..8]);
411        offtout(
412            scan as isize - lenb as isize - (lastscan + lenf) as isize,
413            &mut buf[8..16],
414        );
415        offtout(
416            pos as isize - lenb as isize - (lastpos + lenf) as isize,
417            &mut buf[16..24],
418        );
419        writer.write_all(&buf[..24])?;
420
421        // Write diff data (optimized: reuse buffer)
422        buffer.clear();
423        buffer.extend(
424            new[lastscan..lastscan + lenf]
425                .iter()
426                .zip(&old[lastpos..lastpos + lenf])
427                .map(|(n, o)| n.wrapping_sub(*o)),
428        );
429        writer.write_all(&buffer)?;
430
431        // Write extra data (literal copy)
432        let write_len = scan - lenb - (lastscan + lenf);
433        let write_start = lastscan + lenf;
434        writer.write_all(&new[write_start..write_start + write_len])?;
435
436        // Update positions
437        lastscan = scan - lenb;
438        lastpos = pos - lenb;
439        lastoffset = pos as isize - scan as isize;
440    }
441
442    Ok(())
443}