Browse Source

Implement xor-files

master
Lukas Fürderer 3 years ago
commit
5dfd671fef
  1. 2
      .gitignore
  2. 4
      Cargo.lock
  3. 7
      Cargo.toml
  4. 81
      src/main.rs
  5. 96
      src/paramtest.rs
  6. 1
      src/version.rs
  7. 160
      src/worker/mod.rs
  8. 182
      src/worker/operationtest.rs

2
.gitignore vendored

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

4
Cargo.lock generated

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

7
Cargo.toml

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

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

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

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

160
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<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

@ -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…
Cancel
Save

Du besuchst diese Seite mit einem veralteten IPv4-Internetzugang. Möglicherweise treten in Zukunft Probleme mit der Erreichbarkeit und Performance auf. Bitte frage deinen Internetanbieter oder Netzwerkadministrator nach IPv6-Unterstützung.
You are visiting this site with an outdated IPv4 internet access. You may experience problems with accessibility and performance in the future. Please ask your ISP or network administrator for IPv6 support.
Weitere Infos | More Information
Klicke zum schließen | Click to close