commit
5dfd671fef
8 changed files with 533 additions and 0 deletions
@ -0,0 +1,4 @@
|
||||
[[package]] |
||||
name = "xor-files" |
||||
version = "0.1.0-dev" |
||||
|
@ -0,0 +1,7 @@
|
||||
[package] |
||||
name = "xor-files" |
||||
version = "0.1.0-dev" |
||||
authors = ["Lukas Fürderer <l.fuerderer@gmail.com>"] |
||||
edition = "2018" |
||||
|
||||
[dependencies] |
@ -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<I, W>(mut arg_iter: I) -> Result<(), String> |
||||
where I: Iterator, |
||||
<I as 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<String> = 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); |
||||
} |
@ -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() |
||||
)); |
||||
} |
@ -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<File, String> { |
||||
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<usize, String> { |
||||
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<I1, I2, O>( |
||||
mut in1: ReadWrapper<I1>, |
||||
mut in2: ReadWrapper<I2>, |
||||
mut out: WriteWrapper<O> |
||||
) -> 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) |
||||
} |
||||
} |
@ -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<Vec<u8>>), |
||||
Write(Vec<u8>), |
||||
} |
||||
|
||||
struct FakeReader { |
||||
channel: Sender<Request>, |
||||
number: u32, |
||||
} |
||||
|
||||
impl Read for FakeReader { |
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { |
||||
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<Request>, |
||||
} |
||||
|
||||
impl Write for FakeWriter { |
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
||||
self.channel.send(Request::Write( |
||||
Vec::from(buf), |
||||
)).unwrap(); |
||||
Ok(buf.len()) |
||||
} |
||||
fn flush(&mut self) -> io::Result<()> { |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
struct Test { |
||||
join_handle: JoinHandle<Result<(), String>>, |
||||
recv: Receiver<Request>, |
||||
} |
||||
|
||||
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<u8>) { |
||||
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<u8>) { |
||||
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(())); |
||||
} |
Loading…
Reference in new issue