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}