From 5dfd671feffb0ff494b055eb15c21ae7ed9e8af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20F=C3=BCrderer?= Date: Tue, 12 Feb 2019 16:13:11 +0100 Subject: [PATCH] Implement xor-files --- .gitignore | 2 + Cargo.lock | 4 + Cargo.toml | 7 ++ src/main.rs | 81 ++++++++++++++++ src/paramtest.rs | 96 +++++++++++++++++++ src/version.rs | 1 + src/worker/mod.rs | 160 +++++++++++++++++++++++++++++++ src/worker/operationtest.rs | 182 ++++++++++++++++++++++++++++++++++++ 8 files changed, 533 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/paramtest.rs create mode 100644 src/version.rs create mode 100644 src/worker/mod.rs create mode 100644 src/worker/operationtest.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..0dc00be --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4 @@ +[[package]] +name = "xor-files" +version = "0.1.0-dev" + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c5dd108 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "xor-files" +version = "0.1.0-dev" +authors = ["Lukas Fürderer "] +edition = "2018" + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9e368ab --- /dev/null +++ b/src/main.rs @@ -0,0 +1,81 @@ +#[cfg(test)] +mod paramtest; + +mod version; +mod worker; + +use std::env::args; +use std::result::Result; +use std::process::exit; +use version::VERSION; +use worker::{Worker, XORWorker}; + +fn info(exe_name: &str) -> String { + format!(concat!( + "xor-files version {}\n\n", + + "Usage: {} inputfile-1 inputfile-2 outputfile\n\n", + + "Each file can be for example a regular file or a fifo pipe.\n", + "The inputfiles are read, combined with xor and written to the ", + "outputfile.\n\n", + + "If the first inputfile is larger, the second ", + "is treated as if it was padded with\n", + "nullbytes.\n", + "If the second inputfile is larger, only ", + "the size of the first inputfile is\n", + "processed and the rest will be ignored." + ), VERSION, exe_name) +} + +fn perform(mut arg_iter: I) -> Result<(), String> + where I: Iterator, + ::Item: ToString, + W: XORWorker { + // Read parameters + let exe_name: String = match arg_iter.next() { + Some(x) => x.to_string(), + None => "xor-files".to_string(), + }; + let mut arg_vec: Vec = Vec::with_capacity(3); + for i in 0..3 { + arg_vec.push(match arg_iter.next() { + Some(x) => x.to_string(), + None => return Err( + format!( + "Expected 3 arguments, but got {}\n\n{}", + i, info(&exe_name) + ) + ), + }); + } + // Search for more parameters + let mut i = 3; + loop { + match arg_iter.next() { + Some(_) => { + i += 1; + }, + None => break, + }; + } + if i > 3 { + return Err(format!( + "Expected 3 arguments, but got {}\n\n{}", + i, info(&exe_name) + )); + } + W::work([&arg_vec[0][..], &arg_vec[1][..]], &arg_vec[2][..]) +} + +fn main() { + let exit_code = match perform::<_, Worker>(args()) { + Ok(()) => 0, + Err(e) => { + eprintln!("{}", e); + 1 + } + }; + exit(exit_code); +} diff --git a/src/paramtest.rs b/src/paramtest.rs new file mode 100644 index 0000000..08b441e --- /dev/null +++ b/src/paramtest.rs @@ -0,0 +1,96 @@ +use crate::{info, perform}; +use crate::worker::XORWorker; + +// Test wrong number of arguments +#[test] +fn test_missing_exe_param() { + let params: Vec<&'static str> = vec![]; + let result = perform::<_, TestWorker>(params.iter()); + assert_eq!(result, Err( + format!( + "Expected 3 arguments, but got 0\n\n{}", + info("xor-files"), + ) + )); +} + +#[test] +fn test_zero_params() { + let params = vec!["myxor"]; + let result = perform::<_, TestWorker>(params.iter()); + assert_eq!(result, Err( + format!( + "Expected 3 arguments, but got 0\n\n{}", + info("myxor"), + ) + )); +} + +#[test] +fn test_one_param() { + let params = vec!["myxor", "a"]; + let result = perform::<_, TestWorker>(params.iter()); + assert_eq!(result, Err( + format!( + "Expected 3 arguments, but got 1\n\n{}", + info("myxor"), + ) + )); +} + +#[test] +fn test_two_params() { + let params = vec!["myxor", "a", "b"]; + let result = perform::<_, TestWorker>(params.iter()); + assert_eq!(result, Err( + format!( + "Expected 3 arguments, but got 2\n\n{}", + info("myxor"), + ) + )); +} + +#[test] +fn test_four_params() { + let params = vec!["myxor", "a", "b", "c", "d"]; + let result = perform::<_, TestWorker>(params.iter()); + assert_eq!(result, Err( + format!( + "Expected 3 arguments, but got 4\n\n{}", + info("myxor"), + ) + )); +} + +#[test] +fn test_five_params() { + let params = vec!["myxor", "a", "b", "c", "d", "e"]; + let result = perform::<_, TestWorker>(params.iter()); + assert_eq!(result, Err( + format!( + "Expected 3 arguments, but got 5\n\n{}", + info("myxor"), + ) + )); +} + +// Test right number of arguments +struct TestWorker; + +impl XORWorker for TestWorker { + fn work(input: [&str; 2], output: &str) -> Result<(), String> { + Err(format!( + "Operation:\n{} = {} xor {}", + output, input[0], input[1] + )) + } +} + +#[test] +fn test_three_params() { + let params = vec!["myxor", "file-a", "file-b", "file-c"]; + let result = perform::<_, TestWorker>(params.iter()); + assert_eq!(result, Err( + "Operation:\nfile-c = file-a xor file-b".to_string() + )); +} diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..395025f --- /dev/null +++ b/src/version.rs @@ -0,0 +1 @@ +pub const VERSION: &'static str = "0.1.0-dev"; diff --git a/src/worker/mod.rs b/src/worker/mod.rs new file mode 100644 index 0000000..e0e6b66 --- /dev/null +++ b/src/worker/mod.rs @@ -0,0 +1,160 @@ +#[cfg(test)] +mod operationtest; + +use std::fs::{File, OpenOptions}; +use std::io::{Read, Write}; + +pub trait XORWorker { + fn work(input: [&str; 2], output: &str) -> Result<(), String>; +} + +pub struct Worker; + +fn open(name: &str, writable: bool) -> Result { + let f = OpenOptions::new() + .read(!writable) + .write(writable) + .create(writable) + .open(name); + match f { + Ok(file) => Ok(file), + Err(e) => Err(format!( + "Could not open file \"{}\" for {}:\n{}", + name, + if writable {"writing"} else {"reading"}, + e + )), + } +} + +const BUF_SIZE: usize = (1 << 16); + +struct ReadWrapper<'a, T> + where T: Read { + stream: T, + name: &'a str, +} + +impl<'a, T> ReadWrapper<'a, T> + where T: Read { + fn read(&mut self, buf: &mut [u8]) -> Result { + match self.stream.read(buf) { + Ok(size) => Ok(size), + Err(e) => Err(format!( + "Error reading \"{}\":\n{}", + self.name, + e + )), + } + } +} + +struct WriteWrapper<'a, T> + where T: Write { + stream: T, + name: &'a str, +} + +impl<'a, T> WriteWrapper<'a, T> + where T: Write { + fn write_all(&mut self, buf: &[u8]) -> Result<(), String> { + match self.stream.write_all(buf) { + Ok(()) => Ok(()), + Err(e) => Err(format!( + "Error reading \"{}\":\n{}", + self.name, + e + )), + } + } +} + +fn xor_into(dest: &mut [u8], src: &[u8]) { + for i in 0..dest.len() { + dest[i] ^= src[i]; + } +} + +fn operate( + mut in1: ReadWrapper, + mut in2: ReadWrapper, + mut out: WriteWrapper + ) -> Result<(), String> + where I1: Read, + I2: Read, + O: Write +{ + let mut in1_buffer = [0u8; BUF_SIZE]; // also used for output + let mut in2_buffer = [0u8; BUF_SIZE]; + + let mut in1_remaining = &mut[][..]; // empty at begin + let mut in2_remaining = &[][..]; + + let mut in2_finished = false; + + loop { + let l1 = in1_remaining.len(); + let l2 = in2_remaining.len(); + match (in2_finished, l1, l2) { + (_, 0, _) => { + // in1 buffer is empty, read + match in1.read(&mut in1_buffer[..])? { + 0 => { + // nothing more on input1, finished + return Ok(()); + }, + size => { + // take data + in1_remaining = &mut in1_buffer[0..size]; + }, + }; + }, + (false, other, 0) => { + // in2 buffer is empty, read + let toread = if other == 0 {BUF_SIZE} else {other}; + in2_remaining = &[][..]; + match in2.read(&mut in2_buffer[0..toread])? { + 0 => { + // nothing more on input2 + in2_finished = true; + }, + size => { + in2_remaining = &in2_buffer[0..size]; + }, + }; + }, + (true, _, 0) => { + // Data from i1 must be streamed to output + out.write_all(&in1_remaining)?; + in1_remaining = &mut in1_remaining[0..0]; + }, + (_, i1size, i2size) => { + // There is data on both sides, process it + let amount = if i1size < i2size {i1size} else {i2size}; + xor_into(&mut in1_remaining[0..amount], &in2_remaining[0..amount]); + out.write_all(&in1_remaining[0..amount])?; + in1_remaining = &mut in1_remaining[amount..]; + in2_remaining = &in2_remaining[amount..]; + }, + }; + } +} + +impl XORWorker for Worker { + fn work(input: [&str; 2], output: &str) -> Result<(), String> { + let in1 = ReadWrapper { + stream: open(input[0], false)?, + name: input[0], + }; + let in2 = ReadWrapper { + stream: open(input[1], false)?, + name: input[1], + }; + let out = WriteWrapper { + stream: open(output, true)?, + name: output, + }; + + operate(in1, in2, out) + } +} diff --git a/src/worker/operationtest.rs b/src/worker/operationtest.rs new file mode 100644 index 0000000..828589a --- /dev/null +++ b/src/worker/operationtest.rs @@ -0,0 +1,182 @@ +use crate::worker::{operate, ReadWrapper, WriteWrapper}; +use std::io::{self, Read, Write}; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::thread::JoinHandle; + +enum Request { + Read(u32, usize, Sender>), + Write(Vec), +} + +struct FakeReader { + channel: Sender, + number: u32, +} + +impl Read for FakeReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let (tx, rx) = channel(); + self.channel.send(Request::Read( + self.number, + buf.len(), + tx, + )).unwrap(); + let result = rx.recv().unwrap(); + buf[0..result.len()].copy_from_slice(&result[..]); + Ok(result.len()) + } +} + +struct FakeWriter { + channel: Sender, +} + +impl Write for FakeWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.channel.send(Request::Write( + Vec::from(buf), + )).unwrap(); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +struct Test { + join_handle: JoinHandle>, + recv: Receiver, +} + +impl Test { + fn new() -> Test { + let (tx, rx) = channel(); + let rd1 = FakeReader { + channel: tx.clone(), + number: 0 + }; + let rd2 = FakeReader { + channel: tx.clone(), + number: 1, + }; + let wr = FakeWriter { + channel: tx, + }; + let join_handle = std::thread::spawn(move || -> Result<(), String> { + let in1 = ReadWrapper { + stream: rd1, + name: "reader1", + }; + let in2 = ReadWrapper { + stream: rd2, + name: "reader2", + }; + let out = WriteWrapper { + stream: wr, + name: "writer", + }; + + operate(in1, in2, out) + }); + Test { + join_handle, + recv: rx, + } + } + fn reading(&mut self, reader_nr: u32, size: usize, data: Vec) { + let req = self.recv.recv().unwrap(); + match req { + Request::Read(req_nr, req_size, back) => { + assert_eq!(reader_nr, req_nr); + assert_eq!(size, req_size); + back.send(data).unwrap(); + }, + _ => panic!("Unexpected behaviour"), + } + } + fn writing(&mut self, data: Vec) { + let req = self.recv.recv().unwrap(); + match req { + Request::Write(req_data) => { + assert_eq!(req_data, data); + }, + _ => panic!("Unexpected behaviour"), + } + } + fn terminate(self, return_val: Result<(), String>) { + let result = self.join_handle.join().unwrap(); + assert_eq!(return_val, result); + } +} + +#[test] +fn parallel_reading() { + let mut t = Test::new(); + t.reading(0, 65536, vec![0x01, 0x02, 0x03]); + t.reading(1, 3, vec![0x50, 0x60, 0x70]); + t.writing(vec![0x51, 0x62, 0x73]); + t.reading(0, 65536, vec![]); + t.terminate(Ok(())); +} + +#[test] +#[should_panic] +fn panic_on_wrong_reader() { + let mut t = Test::new(); + t.reading(0, 65536, vec![0x01, 0x02, 0x03]); + t.reading(1, 3, vec![0x50, 0x60, 0x70]); + t.writing(vec![0x51, 0x62, 0x73]); + t.reading(1, 65536, vec![]); + t.terminate(Ok(())); +} + +#[test] +#[should_panic] +fn panic_on_wrong_reading_size() { + let mut t = Test::new(); + t.reading(0, 65536, vec![0x01, 0x02, 0x03]); + t.reading(1, 4, vec![0x50, 0x60, 0x70]); + t.writing(vec![0x51, 0x62, 0x73]); + t.reading(0, 65536, vec![]); + t.terminate(Ok(())); +} + +#[test] +#[should_panic] +fn panic_on_wrong_output_data() { + let mut t = Test::new(); + t.reading(0, 65536, vec![0x01, 0x02, 0x03]); + t.reading(1, 3, vec![0x50, 0x60, 0x70]); + t.writing(vec![0x51, 0x62, 0x74]); + t.reading(0, 65536, vec![]); + t.terminate(Ok(())); +} + +#[test] +fn left_reads_more() { + let mut t = Test::new(); + t.reading(0, 65536, vec![0x01, 0x02, 0x03]); + t.reading(1, 3, vec![0x50]); + t.writing(vec![0x51]); + t.reading(1, 2, vec![0x60]); + t.writing(vec![0x62]); + t.reading(1, 1, vec![0x70]); + t.writing(vec![0x73]); + t.reading(0, 65536, vec![]); + t.terminate(Ok(())); +} + +#[test] +fn left_is_larger() { + let mut t = Test::new(); + t.reading(0, 65536, vec![0x01, 0x02, 0x03]); + t.reading(1, 3, vec![0x50, 0x60, 0x70]); + t.writing(vec![0x51, 0x62, 0x73]); + t.reading(0, 65536, vec![0x04, 0x05, 0x06]); + t.reading(1, 3, vec![]); + t.writing(vec![0x04, 0x05, 0x06]); + t.reading(0, 65536, vec![0x07, 0x08, 0x09]); + t.writing(vec![0x07, 0x08, 0x09]); + t.reading(0, 65536, vec![]); + t.terminate(Ok(())); +}