Implement xor-files
This commit is contained in:
commit
5dfd671fef
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
4
Cargo.lock
generated
Normal file
4
Cargo.lock
generated
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[[package]]
|
||||||
|
name = "xor-files"
|
||||||
|
version = "0.1.0-dev"
|
||||||
|
|
7
Cargo.toml
Normal file
7
Cargo.toml
Normal 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
81
src/main.rs
Normal 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
96
src/paramtest.rs
Normal 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
1
src/version.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub const VERSION: &'static str = "0.1.0-dev";
|
160
src/worker/mod.rs
Normal file
160
src/worker/mod.rs
Normal 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
182
src/worker/operationtest.rs
Normal 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(()));
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user