Skip to main content

socket_patch_core/patch/
diff.rs

1//! Per-file diff (bsdiff) apply support.
2//!
3//! A `diff` is a binary delta in bsdiff 4.x format that transforms the
4//! `beforeHash` bytes of a file into the `afterHash` bytes. We store diffs
5//! grouped by patch UUID — see [`crate::patch::package`] for the tar.gz
6//! archive layout.
7
8use qbsdiff::Bspatch;
9
10/// Apply a bsdiff delta to `before` and return the resulting bytes.
11///
12/// Returns an `std::io::Error` when the delta is malformed or applying it
13/// fails (for example, the delta was produced from a different source).
14pub fn apply_diff(before: &[u8], delta: &[u8]) -> Result<Vec<u8>, std::io::Error> {
15    let patcher = Bspatch::new(delta)?;
16    let mut out = Vec::with_capacity(patcher.hint_target_size() as usize);
17    patcher.apply(before, std::io::Cursor::new(&mut out))?;
18    Ok(out)
19}
20
21#[cfg(test)]
22mod tests {
23    use super::*;
24    use qbsdiff::Bsdiff;
25
26    fn make_delta(before: &[u8], after: &[u8]) -> Vec<u8> {
27        let mut delta = Vec::new();
28        Bsdiff::new(before, after)
29            .compare(std::io::Cursor::new(&mut delta))
30            .expect("compare");
31        delta
32    }
33
34    #[test]
35    fn test_apply_diff_text_round_trip() {
36        let before = b"the quick brown fox jumps over the lazy dog";
37        let after = b"the quick brown cat jumps over the lazy dog";
38        let delta = make_delta(before, after);
39        let result = apply_diff(before, &delta).unwrap();
40        assert_eq!(result, after);
41    }
42
43    #[test]
44    fn test_apply_diff_binary_round_trip() {
45        let before: Vec<u8> = (0..1024u32).map(|i| (i % 251) as u8).collect();
46        let mut after = before.clone();
47        // Mutate a handful of bytes scattered through the buffer.
48        for i in [10usize, 200, 500, 900] {
49            after[i] = after[i].wrapping_add(7);
50        }
51        let delta = make_delta(&before, &after);
52        let result = apply_diff(&before, &delta).unwrap();
53        assert_eq!(result, after);
54    }
55
56    #[test]
57    fn test_apply_diff_empty_to_nonempty() {
58        let before: &[u8] = b"";
59        let after = b"hello";
60        let delta = make_delta(before, after);
61        let result = apply_diff(before, &delta).unwrap();
62        assert_eq!(result, after);
63    }
64
65    #[test]
66    fn test_apply_diff_malformed_errors() {
67        // Random bytes are extremely unlikely to be a valid bsdiff header.
68        let bogus_delta = b"not a real bsdiff delta";
69        let result = apply_diff(b"anything", bogus_delta);
70        assert!(result.is_err(), "expected malformed-delta error");
71    }
72
73    #[test]
74    fn test_apply_diff_wrong_source_does_not_panic() {
75        // Build a delta from one source then try to apply it to a different
76        // source. qbsdiff's bspatch is content-agnostic but should still
77        // produce *some* output without panicking — the caller is
78        // responsible for verifying the result hash matches the expected
79        // `after_hash`. This test exists to lock in the
80        // never-panic-on-bad-input contract callers depend on.
81        let src_a = b"AAAAAAAAAAAAAAAAAAAA";
82        let src_b = b"BBBBBBBBBBBBBBBBBBBB";
83        let target = b"CCCCCCCCCCCCCCCCCCCC";
84        let delta = make_delta(src_a, target);
85        // Result may or may not equal target — what matters is no panic.
86        let _ = apply_diff(src_b, &delta);
87    }
88}