bsdiff_android/
patch.rs

1/*-
2 * Copyright 2003-2005 Colin Percival
3 * Copyright 2012 Matthew Endsley
4 * Modified 2017 Pieter-Jan Briers
5 * Modified 2021 Kornel Lesinski
6 * Modified 2025 - Performance optimizations and validation
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::io;
32use std::io::Read;
33use std::ops::DerefMut;
34
35/// Apply a patch to an "old" file, returning the "new" file.
36///
37/// `old` is the old file, `patch` will be read from with the patch, `new` is the buffer that will be written into.
38///
39/// This is optimized for performance with:
40/// - Bulk read operations
41/// - SIMD-friendly memory access patterns
42/// - Proper validation with early errors
43pub fn patch<T, W>(old: &[u8], patch: &mut T, new: &mut W) -> io::Result<()>
44where
45    T: Read,
46    W: io::Write + DerefMut<Target = [u8]>,
47{
48    let mut oldpos: usize = 0;
49    
50    loop {
51        // Read control data
52        let mut buf = [0; 24];
53        if read_or_eof(patch, &mut buf)? {
54            return Ok(());
55        }
56
57        // Decode using bspatch sign-magnitude encoding (NOT plain LE)
58        // This matches AOSP/bspatch behavior
59        let mix_len_raw = offtin(buf[0..8].try_into().unwrap());
60        let copy_len_raw = offtin(buf[8..16].try_into().unwrap());
61        let seek_len = offtin(buf[16..24].try_into().unwrap());
62
63        // Validate lengths are non-negative
64        if mix_len_raw < 0 || copy_len_raw < 0 {
65            return Err(io::Error::new(
66                io::ErrorKind::InvalidData,
67                format!("Negative length: mix={}, copy={}", mix_len_raw, copy_len_raw),
68            ));
69        }
70
71        let mix_len = mix_len_raw as usize;
72        let copy_len = copy_len_raw as usize;
73
74        // Check for overflow before reading
75        let to_read = mix_len
76            .checked_add(copy_len)
77            .ok_or(io::ErrorKind::InvalidData)?;
78
79        // Read diff string and literal data at once (bulk operation)
80        let mix_start = new.len();
81        let mut read_from = patch.take(to_read as u64);
82        let has_read = io::copy(&mut read_from, new)?;
83
84        if has_read != to_read as u64 {
85            return Err(io::ErrorKind::UnexpectedEof.into());
86        }
87
88        // Compute mix range with overflow checks
89        let mix_end = mix_start
90            .checked_add(mix_len)
91            .ok_or(io::ErrorKind::InvalidData)?;
92
93        let mix_slice = new
94            .get_mut(mix_start..mix_end)
95            .ok_or(io::ErrorKind::UnexpectedEof)?;
96
97        // Compute old range with overflow checks
98        let oldpos_end = oldpos
99            .checked_add(mix_len)
100            .ok_or(io::ErrorKind::InvalidData)?;
101
102        let old_slice = old
103            .get(oldpos..oldpos_end)
104            .ok_or(io::ErrorKind::UnexpectedEof)?;
105
106        // Mix operation: new[i] += old[i]
107        // This is optimized for SIMD and cache locality
108        for (n, o) in mix_slice.iter_mut().zip(old_slice.iter().copied()) {
109            *n = n.wrapping_add(o);
110        }
111
112        // Adjust oldpos with mix_len
113        oldpos += mix_len;
114
115        // Apply seek with proper validation
116        // CRITICAL FIX: Check for underflow before converting
117        let new_oldpos = (oldpos as i64)
118            .checked_add(seek_len)
119            .ok_or_else(|| {
120                io::Error::new(
121                    io::ErrorKind::InvalidData,
122                    format!("Seek overflow: oldpos={}, seek={}", oldpos, seek_len),
123                )
124            })?;
125
126        if new_oldpos < 0 {
127            return Err(io::Error::new(
128                io::ErrorKind::InvalidData,
129                format!("Seek underflow: oldpos={}, seek={}", oldpos, seek_len),
130            ));
131        }
132
133        oldpos = new_oldpos as usize;
134    }
135}
136
137/// It allows EOF only before the first byte.
138/// Optimized to minimize syscalls
139#[inline]
140fn read_or_eof<T: Read>(reader: &mut T, buf: &mut [u8; 24]) -> io::Result<bool> {
141    let mut tmp = &mut buf[..];
142    loop {
143        match reader.read(tmp) {
144            Ok(0) => {
145                return if tmp.len() == 24 {
146                    Ok(true) // Clean EOF at start
147                } else {
148                    Err(io::ErrorKind::UnexpectedEof.into())
149                }
150            }
151            Ok(n) => {
152                if n >= tmp.len() {
153                    return Ok(false);
154                }
155                tmp = &mut tmp[n..];
156            }
157            Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
158            Err(e) => return Err(e),
159        }
160    }
161}
162
163/// Reads sign-magnitude i64 as used in bspatch
164/// This is the CORRECT encoding used by AOSP and classic bspatch
165/// NOT plain little-endian i64!
166#[inline]
167fn offtin(buf: [u8; 8]) -> i64 {
168    let y = i64::from_le_bytes(buf);
169    if 0 == y & (1 << 63) {
170        y
171    } else {
172        -(y & !(1 << 63))
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_offtin_zero() {
182        let buf = [0u8; 8];
183        assert_eq!(offtin(buf), 0);
184    }
185
186    #[test]
187    fn test_offtin_positive() {
188        let buf = [42, 0, 0, 0, 0, 0, 0, 0];
189        assert_eq!(offtin(buf), 42);
190    }
191
192    #[test]
193    fn test_offtin_negative() {
194        // 42 with sign bit set
195        let buf = [42, 0, 0, 0, 0, 0, 0, 0x80];
196        assert_eq!(offtin(buf), -42);
197    }
198
199    #[test]
200    fn test_offtin_max_positive() {
201        // Maximum positive value: 0x7FFFFFFFFFFFFFFF
202        let buf = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F];
203        assert_eq!(offtin(buf), i64::MAX);
204    }
205
206    #[test]
207    fn test_offtin_max_negative() {
208        // Maximum negative value: sign bit + 0x7FFFFFFFFFFFFFFF
209        let buf = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
210        assert_eq!(offtin(buf), -i64::MAX);
211    }
212}