s2png 1.0.0

Store any data in PNG images
Documentation
/* Test s2png's encoding and decoding, with and without encryption, for
 * corrupting data.
 */

use std::{
    convert::AsRef,
    env,
    ffi::OsStr,
    process::Command,
    {
        io::{Result, Write},
        iter::IntoIterator,
    },
};
use {
    exitcode,
    file_diff::diff,
    rand::{distributions::Uniform, Rng},
    regex::Regex,
    tempfile::NamedTempFile,
};

#[derive(Debug, Eq, PartialEq)]
struct Output {
    code: i32,
    stderr: String,
    stdout: String,
}

fn s2png<I, S>(args: I) -> Result<Output>
where
    I: IntoIterator<Item = S>,
    S: AsRef<OsStr>,
{
    let s2png_cmd = env::var("S2PNG_COMMAND").unwrap();
    let output = Command::new(s2png_cmd).args(args).output()?;

    Ok(Output {
        code: output.status.code().unwrap(),
        stderr: String::from_utf8(output.stderr).unwrap(),
        stdout: String::from_utf8(output.stdout).unwrap(),
    })
}

#[test]
#[ignore]
fn help_message() {
    let re = Regex::new(r"Store any data").unwrap();

    let output = s2png(&["-h"]).unwrap();
    assert!(re.is_match(&output.stdout));
    assert_eq!(output.code, exitcode::OK);
}

#[test]
#[ignore]
fn bad_usage() {
    let output = s2png(&["-Q"]).unwrap();
    assert_eq!(output.code, exitcode::USAGE);
}

fn roundtrip<S1, S2, I1, I2>(data: &[u8], encode_extra: I1, decode_extra: I2)
where
    S1: AsRef<str>,
    S2: AsRef<str>,
    I1: IntoIterator<Item = S1>,
    I2: IntoIterator<Item = S2>,
{
    let mut input_file = NamedTempFile::new().unwrap();
    input_file.write_all(data).unwrap();
    let png_file = NamedTempFile::new().unwrap();
    let extracted_file = NamedTempFile::new().unwrap();

    let input_fn = input_file.path().to_str().unwrap();
    let png_fn = png_file.path().to_str().unwrap();
    let extracted_fn = extracted_file.path().to_str().unwrap();

    let mut encode_args =
        vec!["-o".to_string(), png_fn.to_string(), input_fn.to_string()];
    for arg in encode_extra {
        encode_args.push(arg.as_ref().to_string());
    }

    let out1 = s2png(&encode_args).unwrap();
    eprintln!("{}", out1.stderr);
    assert_eq!(out1.code, exitcode::OK);

    let mut decode_args = vec![
        "-o".to_string(),
        extracted_fn.to_string(),
        png_fn.to_string(),
    ];
    for arg in decode_extra {
        decode_args.push(arg.as_ref().to_string());
    }

    let out2 = s2png(&decode_args).unwrap();
    eprintln!("{}", out2.stderr);
    assert_eq!(out2.code, exitcode::OK);

    assert!(diff(&input_fn, &extracted_fn));
}

#[test]
#[ignore]
fn roundtrip_basic_0xff() {
    let empty: Vec<String> = vec![];
    roundtrip(
        &vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
        &empty,
        &empty,
    );
}

#[test]
#[ignore]
fn roundtrip_square_0xff() {
    let empty: Vec<String> = vec![];
    roundtrip(
        &vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
        vec!["-s"],
        &empty,
    );
}

#[test]
#[ignore]
fn roundtrip_hex_key_0xff() {
    // printf hex_key | md5sum | cut -c 1-32
    let hex_key_extra = ["-p", "5f4dcc3b5aa765d61d8327deb882cf99"];

    roundtrip(
        &vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
        &hex_key_extra,
        &hex_key_extra,
    );
}

#[test]
#[ignore]
fn roundtrip_no_banner_0xff() {
    roundtrip(
        &vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
        vec!["-e", "-b", ""],
        vec!["-d"],
    );
}

fn random_vec(len: usize) -> Vec<u8> {
    let range = Uniform::from(0..=255);
    rand::thread_rng().sample_iter(&range).take(len).collect()
}

#[test]
#[ignore]
fn roundtrip_basic_random() {
    let empty: Vec<String> = vec![];
    roundtrip(&random_vec(0xffff), &empty, &empty);
}

#[test]
#[ignore]
fn roundtrip_square_random() {
    let empty: Vec<String> = vec![];
    roundtrip(&random_vec(0xffff), vec!["-s"], &empty);
}

#[test]
#[ignore]
fn roundtrip_hex_key_random() {
    // printf hex_key | md5sum | cut -c 1-32
    let hex_key_extra = ["-p", "5f4dcc3b5aa765d61d8327deb882cf99"];

    roundtrip(&random_vec(0xffff), &hex_key_extra, &hex_key_extra);
}

#[test]
#[ignore]
fn roundtrip_no_banner_random() {
    roundtrip(&random_vec(0xffff), vec!["-e", "-b", ""], vec!["-d"]);
}

#[test]
#[ignore]
fn empty_file() {
    roundtrip(&random_vec(0), vec!["-e"], vec!["-d"])
}

#[test]
#[ignore]
fn empty_file_no_banner() {
    let empty_file = NamedTempFile::new().unwrap();
    let input_fn = empty_file.path().to_str().unwrap();
    let output_file = NamedTempFile::new().unwrap();
    let output_fn = output_file.path().to_str().unwrap();

    let out1 = s2png(&vec!["-o", &output_fn, "-b", "", &input_fn]).unwrap();
    eprintln!("{}", out1.stderr);
    assert_eq!(out1.code, exitcode::DATAERR);
}

#[test]
#[ignore]
fn small_file_corruption_1_15() {
    for i in 1..=15 {
        roundtrip(&random_vec(i), vec!["-e", "-b", "", "-w", "10"], vec!["-d"])
    }
}

#[test]
#[ignore]
fn small_file_corruption_16_255() {
    for i in 16..=255 {
        roundtrip(&random_vec(i), vec!["-e", "-b", "", "-w", "10"], vec!["-d"])
    }
}