use std::{convert::TryInto, error::Error, fs::File, io::Read, sync::mpsc::channel};
use clap::Clap;
use log::info;
use crate::{
context::PivotByRawLineContext,
decoder::Decoder,
method::complex::{eluv::ELUVMethodBuilder, extended_line::ExtendedLineMethodBuilder},
};
use super::{encoder::{ELUVCharacterSet, get_character_set_type, validate_pivot_smaller_than_text}, progress::{new_progress_bar, spawn_progress_thread, ProgressStatus}};
#[derive(Clap)]
pub struct DecodeSubCommand {
#[clap(short, long)]
text: String,
#[clap(short, long)]
pivot: usize,
#[clap(long, group = "method_args")]
eluv: bool,
#[clap(long, arg_enum, requires = "eluv")]
set: Option<ELUVCharacterSet>,
#[clap(long, default_value = "1")]
variant: u8,
#[clap(long = "eline", group = "method_args")]
#[allow(dead_code)]
extended_line: bool,
}
impl DecodeSubCommand {
pub fn run(&self) -> Result<Vec<u8>, Box<dyn Error>> {
let stego_text_file_input = File::open(&self.text)?;
self.do_decode(stego_text_file_input)
}
pub(crate) fn get_method(
&self,
) -> Result<Box<dyn Decoder<PivotByRawLineContext>>, Box<dyn Error>> {
Ok(if self.eluv {
Box::new(
ELUVMethodBuilder::new()
.character_set(get_character_set_type(&self.set))
.variant(self.variant.try_into()?)
.build(),
)
} else {
Box::new(
ExtendedLineMethodBuilder::new()
.variant(self.variant.try_into()?)
.build(),
)
})
}
pub fn do_decode(&self, mut stego_input: impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
let mut stego_text = String::new();
stego_input.read_to_string(&mut stego_text)?;
validate_pivot_smaller_than_text(self.pivot, &stego_text)?;
let decoder = self.get_method()?;
info!("Using method variant {}", self.variant);
let mut context = PivotByRawLineContext::new(stego_text.as_str(), self.pivot);
let progress_bar = new_progress_bar(stego_text.len() as u64);
let (tx, rx) = channel::<ProgressStatus>();
progress_bar.set_message("Decoding cover text...");
spawn_progress_thread(progress_bar.clone(), rx);
let result = decoder.decode(&mut context, Some(&tx));
tx.send(ProgressStatus::Finished).ok();
progress_bar.finish_with_message("Finished decoding");
result
}
}
#[allow(unused_imports)]
mod test {
use crate::binary::Bit;
use std::{error::Error, io::Read};
use super::DecodeSubCommand;
#[test]
fn decodes_zeroes_if_not_data_encoded_extended_line() -> Result<(), Box<dyn Error>> {
let stego_input = "a b";
let command = DecodeSubCommand {
text: "stub".into(),
pivot: 3,
eluv: false,
extended_line: true,
set: None,
variant: 1,
};
let result = command.do_decode(stego_input.as_bytes());
assert_eq!(result.ok(), Some(vec![0]));
Ok(())
}
#[test]
fn decodes_zeroes_if_not_data_encoded_eluv() -> Result<(), Box<dyn Error>> {
let stego_input = "a b";
let command = DecodeSubCommand {
text: "stub".into(),
pivot: 3,
eluv: true,
extended_line: false,
set: None,
variant: 1,
};
let result = command.do_decode(stego_input.as_bytes());
assert_eq!(result.ok(), Some(vec![0]));
Ok(())
}
#[test]
fn fails_when_pivot_is_too_large() -> Result<(), Box<dyn Error>> {
let stego_input = "aaaaa";
let command = DecodeSubCommand {
text: "stub".into(),
pivot: 6,
eluv: false,
extended_line: true,
set: None,
variant: 1,
};
let result = command.do_decode(stego_input.as_bytes());
assert!(result.is_err());
Ok(())
}
}