use std::{convert::TryInto, error::Error, fs::File, io::Read, sync::mpsc::channel};
use clap::Clap;
use log::{info, trace};
use crate::{
binary::BitIterator,
context::PivotByLineContext,
encoder::Encoder,
method::{
complex::{eluv::ELUVMethodBuilder, extended_line::ExtendedLineMethodBuilder},
trailing_unicode::character_sets::CharacterSetType,
},
};
use super::{
progress::{new_progress_bar, spawn_progress_thread, ProgressStatus},
writer::Writer,
};
#[derive(Clap, Debug, PartialEq)]
pub enum ELUVCharacterSet {
FourBit,
ThreeBit,
TwoBit,
Full,
Twitter,
}
#[derive(Clap)]
pub struct EncodeSubCommand {
#[clap(short, long)]
cover: String,
#[clap(short, long)]
data: String,
#[clap(short, long)]
pivot: Option<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,
}
pub fn validate_pivot_smaller_than_text(
pivot: usize,
cover_text: &str,
) -> Result<(), Box<dyn Error>> {
let text_length = cover_text.len();
if pivot > text_length {
return Err("Pivot is greater than the cover text length.".into());
}
Ok(())
}
impl EncodeSubCommand {
pub fn run(&self) -> Result<Vec<u8>, Box<dyn Error>> {
let cover_file_input = File::open(&self.cover)?;
let data_file_input = File::open(&self.data)?;
self.do_encode(cover_file_input, data_file_input)
}
pub(crate) fn do_encode(
&self,
mut cover_input: impl Read,
mut data_input: impl Read,
) -> Result<Vec<u8>, Box<dyn Error>> {
let mut cover_text = String::new();
let mut data = vec![];
cover_input.read_to_string(&mut cover_text)?;
data_input.read_to_end(&mut data)?;
trace!("text: {:?}", data);
let pivot = pick_pivot_from(
self.pivot,
determine_pivot_size(cover_text.split_whitespace()),
)?;
validate_pivot_smaller_than_text(pivot, &cover_text)?;
let capacity_msg = format!(
"Required cover text capacity: {}",
BitIterator::new(&data).count()
);
Writer::warn(&capacity_msg);
info!("Encoding secret data");
let mut data_iterator = BitIterator::new(&data);
let progress_bar = new_progress_bar(data_iterator.count() as u64);
let (tx, rx) = channel::<ProgressStatus>();
progress_bar.set_message("Encoding..");
spawn_progress_thread(progress_bar.clone(), rx);
let method = self.get_method()?;
info!("Using method variant {}", self.variant);
let mut context = PivotByLineContext::new(&cover_text, pivot);
let stego_result = method.encode(&mut context, &mut data_iterator, Some(&tx));
tx.send(ProgressStatus::Finished).ok();
progress_bar.finish_with_message("Finished encoding");
Ok(stego_result?.as_bytes().into())
}
pub(crate) fn get_method(
&self,
) -> Result<Box<dyn Encoder<PivotByLineContext>>, 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 get_character_set_type(set_option: &Option<ELUVCharacterSet>) -> CharacterSetType {
if let Some(char_set) = set_option {
match char_set {
ELUVCharacterSet::FourBit => CharacterSetType::FourBitUnicodeSet,
ELUVCharacterSet::ThreeBit => CharacterSetType::ThreeBitUnicodeSet,
ELUVCharacterSet::TwoBit => CharacterSetType::TwoBitUnicodeSet,
ELUVCharacterSet::Full => CharacterSetType::FullUnicodeSet,
ELUVCharacterSet::Twitter => CharacterSetType::TwitterUnicodeSet,
}
} else {
CharacterSetType::FullUnicodeSet
}
}
pub(crate) fn determine_pivot_size<'a>(words: impl Iterator<Item = &'a str>) -> usize {
words
.into_iter()
.map(|string| string.chars().count() + 1)
.max()
.unwrap_or(0)
}
pub(crate) fn pick_pivot_from(
user_pivot: Option<usize>,
calculated_pivot: usize,
) -> Result<usize, Box<dyn Error>> {
Ok(if let Some(value) = user_pivot {
if value < calculated_pivot {
return Err("Provided pivot is smaller than the largest word in text! Cannot guarantee encoding will succeed.".into());
}
Writer::info(&format!("Using user provided pivot: {}", value));
value
} else {
Writer::info(&format!(
"Using pivot based on the cover text: {}",
calculated_pivot
));
calculated_pivot
})
}
#[allow(unused_imports)]
mod test {
use std::{error::Error, io::Read};
use crate::method::trailing_unicode::character_sets::CharacterSetType;
use super::{get_character_set_type, ELUVCharacterSet, EncodeSubCommand};
#[test]
fn fails_when_there_is_not_enough_cover_text() -> Result<(), Box<dyn Error>> {
let cover_input = "a b c ".repeat(2);
let data_input: Vec<u8> = vec![0b11111111];
let command = EncodeSubCommand {
cover: "stub".into(),
data: "stub".into(),
pivot: Some(3),
eluv: false,
extended_line: true,
set: None,
variant: 1,
};
let result = command.do_encode(cover_input.as_bytes(), data_input.as_slice());
assert!(result.is_err());
Ok(())
}
#[test]
fn fails_when_pivot_is_too_small() -> Result<(), Box<dyn Error>> {
let cover_input = "aaaaa ".repeat(2);
let data_input: Vec<u8> = vec![0b11111111];
let command = EncodeSubCommand {
cover: "stub".into(),
data: "stub".into(),
pivot: Some(3),
eluv: false,
extended_line: true,
set: None,
variant: 1,
};
let result = command.do_encode(cover_input.as_bytes(), data_input.as_slice());
assert!(result.is_err());
Ok(())
}
#[test]
fn fails_when_pivot_is_too_large() -> Result<(), Box<dyn Error>> {
let cover_input = "aaaaa";
let data_input: Vec<u8> = vec![0b11111111];
let command = EncodeSubCommand {
cover: "stub".into(),
data: "stub".into(),
pivot: Some(6),
eluv: false,
extended_line: true,
set: None,
variant: 1,
};
let result = command.do_encode(cover_input.as_bytes(), data_input.as_slice());
assert!(result.is_err());
Ok(())
}
#[test]
fn get_character_set_type_returns_default_when_none_is_provided() -> Result<(), Box<dyn Error>>
{
assert_eq!(
get_character_set_type(&None),
CharacterSetType::FullUnicodeSet
);
Ok(())
}
#[test]
fn get_character_set_type_maps_sets_correctly() -> Result<(), Box<dyn Error>> {
assert_eq!(
get_character_set_type(&Some(ELUVCharacterSet::Full)),
CharacterSetType::FullUnicodeSet
);
assert_eq!(
get_character_set_type(&Some(ELUVCharacterSet::FourBit)),
CharacterSetType::FourBitUnicodeSet
);
assert_eq!(
get_character_set_type(&Some(ELUVCharacterSet::ThreeBit)),
CharacterSetType::ThreeBitUnicodeSet
);
assert_eq!(
get_character_set_type(&Some(ELUVCharacterSet::TwoBit)),
CharacterSetType::TwoBitUnicodeSet
);
Ok(())
}
}