Implement xor-files

This commit is contained in:
Lukas Fürderer 2019-02-12 16:13:11 +01:00
commit 5dfd671fef
8 changed files with 533 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

4
Cargo.lock generated Normal file
View File

@ -0,0 +1,4 @@
[[package]]
name = "xor-files"
version = "0.1.0-dev"

7
Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "xor-files"
version = "0.1.0-dev"
authors = ["Lukas Fürderer <l.fuerderer@gmail.com>"]
edition = "2018"
[dependencies]

81
src/main.rs Normal file
View File

@ -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);
}

96
src/paramtest.rs Normal file
View File

@ -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()
));
}

1
src/version.rs Normal file
View File

@ -0,0 +1 @@
pub const VERSION: &'static str = "0.1.0-dev";

160
src/worker/mod.rs Normal file
View File

@ -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)
}
}

182
src/worker/operationtest.rs Normal file
View File

@ -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(()));
}