1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#![cfg_attr(feature = "clippy", feature(plugin))]
#![cfg_attr(feature = "clippy", plugin(clippy))]

extern crate data_encoding;
extern crate rayon;

#[derive(Debug, Default, PartialEq)]
pub struct Patch<'a> {
    file: Option<String>,
    pairs: Vec<PatchPair<'a>>,
    output: Option<String>,
}

#[derive(Debug, PartialEq)]
struct PatchPair<'a> {
    from: &'a [u8],
    to: &'a [u8],
}

impl<'a> Patch<'a> {
    pub fn new() -> Patch<'a> {
        Patch {
            ..Default::default()
        }
    }

    pub fn file(&mut self, s: &str) -> &mut Patch<'a> {
        self.file = Some(String::from(s));
        self
    }

    pub fn replace(&mut self, from: &'a [u8], to: &'a [u8]) -> &mut Patch<'a> {
        assert_eq!(
            from.len(),
            to.len(),
            "replase arguments, `to` and `from`, should have the same length"
        );

        self.pairs.push(PatchPair { from, to });
        self
    }

    pub fn output(&mut self, s: &str) -> &mut Patch<'a> {
        self.output = Some(String::from(s));
        self
    }

    pub fn execute(&mut self) -> Result<(), std::io::Error> {
        use std::io::prelude::*;
        use std::fs::File;

        use data_encoding::HEXUPPER;
        use rayon::prelude::*;

        if let Some(ref file) = self.file {
            let mut buffer = Vec::new();

            File::open(file)?.read_to_end(&mut buffer)?;

            for pair in &self.pairs {
                let from = HEXUPPER.decode(pair.from).unwrap();
                let to = HEXUPPER.decode(pair.to).unwrap();

                buffer
                    .par_chunks_mut(from.len())
                    .for_each(|chunk| if chunk == from.as_slice() {
                        chunk.copy_from_slice(&to);
                    });
            }

            if let Some(ref output) = self.output {
                File::create(output)?.write_all(&buffer)?;
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn create_new_patch() {
        assert_eq!(
            Patch::new(),
            Patch {
                ..Default::default()
            }
        );
    }

    #[test]
    fn set_file_to_patch() {
        assert_eq!(
            *Patch::new().file("lib.so"),
            Patch {
                file: Some(String::from("lib.so")),
                ..Default::default()
            }
        );
    }

    #[test]
    fn set_patch_output() {
        assert_eq!(
            *Patch::new().output("lib.patched.so"),
            Patch {
                output: Some(String::from("lib.patched.so")),
                ..Default::default()
            }
        );
    }

    #[test]
    fn add_patch_pair() {
        assert_eq!(
            *Patch::new()
                .file("lib.so")
                .replace(b"FF", b"C3")
                .output("lib.patched.so"),
            Patch {
                file: Some(String::from("lib.so")),
                output: Some(String::from("lib.patched.so")),
                pairs: vec![
                    PatchPair {
                        from: b"FF",
                        to: b"C3",
                    },
                ],
            }
        );
    }

    // #[test]
    // fn execute_patch() {
    //     unimplemented!();
    // }
}