From f9728d490dfeb8d4ca6ff67d8a1bdec03105760a Mon Sep 17 00:00:00 2001 From: ef3d0c3e Date: Thu, 15 Aug 2024 20:12:42 +0200 Subject: [PATCH] First commit --- Cargo.lock | 476 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 13 ++ src/block.rs | 58 ++++++ src/embed.rs | 93 ++++++++++ src/header.rs | 33 ++++ src/image.rs | 5 + src/main.rs | 213 ++++++++++++++++++++++ 7 files changed, 891 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/block.rs create mode 100644 src/embed.rs create mode 100644 src/header.rs create mode 100644 src/image.rs create mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..879fb49 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,476 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cpufeatures" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png_data2" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "argon2", + "bitvec", + "getopts", + "png", + "rand", + "rand_chacha", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e3a5561 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "png_data2" +version = "0.1.0" +edition = "2021" + +[dependencies] +aes-gcm = "0.10.3" +argon2 = "0.5.3" +bitvec = "1.0.1" +getopts = "0.2.21" +png = "0.17.13" +rand = "0.8.5" +rand_chacha = "0.3.1" diff --git a/src/block.rs b/src/block.rs new file mode 100644 index 0000000..2f72dee --- /dev/null +++ b/src/block.rs @@ -0,0 +1,58 @@ +use std::{fmt::Formatter, intrinsics::ctlz, str::FromStr}; + +/// Block mode for embedded data +#[derive(Debug)] +pub struct BlockMode { + pub len: usize, + pub crc_len: usize, +} + +impl BlockMode { + /// Gets the best [`BlockMode`] and the remainder + pub fn from_length(len: usize, crc: bool) -> Self { + let mut best_remainder = len; + let mut best_p = 0; + for p in 4..16 { + let remainder = len % (1 << p); + if remainder <= best_remainder { + best_remainder = remainder; + best_p = p; + } + } + + Self { + len: 1 << best_p, + crc_len: (best_p/4) * crc as usize, + } + } + pub fn to_data(&self) -> u8 { + ((self.crc_len != 0) as u8) | (ctlz(self.len) << 1) as u8 + } +} + +impl core::fmt::Display for BlockMode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "(len: {}, crc_len: {})", self.len, self.crc_len) + } +} + +impl FromStr for BlockMode { + type Err = String; + + fn from_str(s: &str) -> Result { + let size = s + .parse::() + .map_err(|err| format!("Failed to parse `{}` as block size: {err}", s))?; + + if size < 6 || size > 16 { + Err(format!( + "Invalid block size specified: `{size}` expected value within [6; 16]" + ))?; + } + + Ok(BlockMode { + len: 1 << size, + crc_len: size, + }) + } +} diff --git a/src/embed.rs b/src/embed.rs new file mode 100644 index 0000000..5768e4d --- /dev/null +++ b/src/embed.rs @@ -0,0 +1,93 @@ +use std::fmt::Formatter; +use std::str::FromStr; + +use bitvec::prelude::*; +use bitvec::vec::BitVec; + +use crate::block::BlockMode; +use crate::image::ImageInfo; + +pub enum EmbedAlgorithm { + Lo(u8), +} + +impl EmbedAlgorithm { + pub fn max_size(&self, blockmode: &BlockMode, info: &Box) -> usize { + let blocks_num = info.size() / blockmode.len; + + match self { + EmbedAlgorithm::Lo(bits) => { + (((blockmode.len - blockmode.crc_len)*blocks_num) as f64 * (*bits as f64) / 8f64).floor() as usize + } + } + } + + pub fn next_block(&self, original_data: &[u8], embed_data: &BitVec, mut embed_offset: usize, blockmode: &BlockMode) -> (Vec, usize) { + let mut result = Vec::::with_capacity(blockmode.len); + + match self { + EmbedAlgorithm::Lo(bits) => { + let mask = (1<, bits: u8) -> u8 + { + let mut result : u8 = 0; + for i in 0..bits + { + result |= (slice[i as usize] as u8) << i; + } + result + } + + for i in 0..(blockmode.len-blockmode.crc_len) + { + let embed = bits_to_byte(embed_data.get(embed_offset..embed_offset+*bits as usize).unwrap(), *bits); + let b = original_data[i]; + + result.push((b & !mask) | embed); + + embed_offset += *bits as usize; + } + } + } + + + (result, embed_offset) + } +} + +impl core::fmt::Display for EmbedAlgorithm { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + EmbedAlgorithm::Lo(bits) => write!(f, "Lo({bits})"), + } + } +} + +impl FromStr for EmbedAlgorithm { + type Err = String; + + fn from_str(s: &str) -> Result { + let (dig_pos, _) = s + .char_indices() + .find(|(_, c)| c.is_ascii_digit()) + .ok_or(format!("Unknown algorithm: {s}"))?; + + let (first, second) = s.split_at(dig_pos); + match first { + "lo" => { + let value = second.parse::().map_err(|err| { + format!("Failed to convert `{second}` to a number of bits: {err}") + })?; + if value > 8 || value == 0 { + Err(format!( + "Cannot specify {value} bits for `lo` method, must be within [1, 8]" + )) + } else { + Ok(EmbedAlgorithm::Lo(value)) + } + } + _ => Err(format!("Unknown algorithm: {s}")), + } + } +} diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 0000000..4d0ab60 --- /dev/null +++ b/src/header.rs @@ -0,0 +1,33 @@ +use crate::block::BlockMode; + +pub struct Header { + pub blockmode: BlockMode, + pub comment: Option, +} + +impl Header { + pub fn to_data(&self, version: u16, embed_len: u32) -> Vec { + let mut header = vec![]; + + // Version + header.extend_from_slice(version.to_le_bytes().as_slice()); + + // TODO: IV+Cipherinfo + // Blockmode + header.push(self.blockmode.to_data().to_le()); + + // Data len + header.extend_from_slice(embed_len.to_le_bytes().as_slice()); + + // Comment len + let comment_len = self.comment.as_ref().map(|c| c.len()).unwrap_or(0); + header.extend_from_slice(comment_len.to_le_bytes().as_slice()); + + // Comment + if let Some(comment) = &self.comment { + header.extend_from_slice(comment.as_bytes()); + } + + header + } +} diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000..e4caaee --- /dev/null +++ b/src/image.rs @@ -0,0 +1,5 @@ +pub trait ImageInfo { + fn width(&self) -> u32; + fn height(&self) -> u32; + fn size(&self) -> usize; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0d3c200 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,213 @@ +#![feature(core_intrinsics)] +pub mod block; +pub mod embed; +pub mod header; +pub mod image; + +use std::env; +use std::fs::File; +use std::process::ExitCode; +use std::str::FromStr; + +use bitvec::slice::BitSlice; +use bitvec::vec::BitVec; +use block::BlockMode; +use embed::EmbedAlgorithm; +use getopts::Matches; +use getopts::Options; +use header::Header; +use image::ImageInfo; +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; +use rand::Rng; +use rand::prelude::SliceRandom; + +fn print_usage(program: &str, opts: Options) { + let brief = format!( + "Usage: {0} -(e|d|i) FILE [opts] + Encode: {0} -e file.tar -l rgba8 -c \"(.tar) my archive\" > out.png + Decode: {0} -d out.png > file.tar + Info: {0} -i out.png # (.tar) my archive", + program + ); + print!("{}", opts.usage(&brief)); +} + +fn print_version() { + print!( + "png_data -- Embed data into PNG\n +Public domain\n" + ); +} + +impl ImageInfo for png::OutputInfo { + fn width(&self) -> u32 { self.width } + + fn height(&self) -> u32 { self.height } + + fn size(&self) -> usize { self.buffer_size() } +} + +fn get_algorithm(s: Option) -> Result { + if let Some(s) = &s { + EmbedAlgorithm::from_str(s.as_str()) + } else { + Err("Missing required algorithm parameter".into()) + } +} + +fn get_blockmode(s: Option) -> Result { + if let Some(s) = &s { + BlockMode::from_str(s) + } else { + Err("Missing requires blockmode parameter".into()) + } +} + +fn decode_image(image: String) -> Result<(Vec, Box), String> { + match image.split_at(image.find('.').unwrap_or(0)).1 { + ".png" => { + let decoder = png::Decoder::new( + File::open(&image).map_err(|err| format!("Failed to read `{image}`: {err}"))?, + ); + let mut reader = decoder + .read_info() + .map_err(|err| format!("Failed to read png info for `{image}`: {err}"))?; + let mut result = Vec::with_capacity(reader.output_buffer_size()); + result.resize(reader.output_buffer_size(), 0); + let info = reader + .next_frame(result.as_mut_slice()) + .map_err(|err| format!("Failed to read png info for `{image}`: {err}"))?; + result.resize(info.buffer_size(), 0); + + Ok((result, Box::new(info))) + } + _ => Err(format!("Unable get image type for {image}")), + } +} + +fn derive_seed(seed: &str) -> Result<[u8; 32], String> { + let mut result = [0u8; 32]; + argon2::Argon2::default().hash_password_into(seed.as_bytes(), b"SEED SALT", &mut result) + .map_err(|err| format!("Failed to derive seed `{seed}`: {err}"))?; + Ok(result) +} + +fn encode(image: String, matches: Matches) -> Result, String> { + let algorithm = get_algorithm(matches.opt_str("l"))?; + let crc = false; + let embed_file = matches + .opt_str("i") + .ok_or(format!("Embed file is required"))?; + + let (data, info) = decode_image(image)?; + let blockmode = BlockMode::from_length(info.size(), crc); + let seed = derive_seed( + matches + .opt_str("s") + .unwrap_or(format!("{}x{}", info.width(), info.height())) + .as_str(), + )?; + let max_size = algorithm.max_size(&blockmode, &info); + + let embed_data = std::fs::read(&embed_file) + .map_err(|err| format!("Failed to read embed file `{embed_file}`: {err}"))?; + + // Get header + let header = Header { + blockmode, + comment: matches.opt_str("c"), + }; + let header_data = header.to_data(1, embed_data.len() as u32); + + // Check length + if embed_data.len() + header_data.len() > max_size { + Err(format!( + "Cannot embed {}bytes into {}bytes using the {algorithm} algorithm with blockmode {}. Max embeddable size: {max_size}bytes", + embed_data.len()+header_data.len(), + data.len(), + header.blockmode, + ))?; + } + + // Shuffle the blocks + let mut rand = ChaCha8Rng::from_seed(seed); + let blocks_num = info.size() / (header.blockmode.len-header.blockmode.crc_len); + + let mut blocks_pos = (0..blocks_num).collect::>(); + blocks_pos.shuffle(&mut rand); + + + let mut bv = BitVec::::from_vec(header_data); + bv.extend_from_raw_slice(embed_data.as_slice()); + + let mut embed_offset = 0; + for i in 0 .. blocks_num + { + println!("{:#?}", bv.len()); + let (block, mut new_offset) = algorithm.next_block( + &data.as_slice()[i*header.blockmode.len..], + &bv, + embed_offset, + &header.blockmode); + new_offset += header.blockmode.crc_len*8; + + embed_offset = new_offset; + } + algorithm.next_block(data.as_slice(), &bv, 48, &header.blockmode); + // TODO: WRITE CRC + + println!("Ok"); + + Ok(vec![]) +} + +fn main() -> ExitCode { + let args: Vec = env::args().collect(); + let program = args[0].clone(); + + let mut opts = Options::new(); + opts.optopt("i", "input", "Input file", "PATH"); + opts.optflag("e", "encode", "Encode file"); + opts.optflag("d", "decode", "Decode mode"); + opts.optopt("c", "comment", "Header comment", "TXT"); + opts.optopt("s", "seed", "Force seed", "TXT"); + opts.optflag("", "no-crc", "Disables CRC"); + opts.optflag("z", "info", "Read information"); + opts.optopt("l", "algorithm", "Embed algorithm", "lo3"); + opts.optflag("h", "help", "Print this help menu"); + opts.optflag("v", "version", "Print program version and licenses"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => { + panic!("{}", f.to_string()) + } + }; + if matches.opt_present("v") { + print_version(); + return ExitCode::SUCCESS; + } + if matches.opt_present("h") { + print_usage(&program, opts); + return ExitCode::SUCCESS; + } + if matches.free.is_empty() { + print_usage(&program, opts); + return ExitCode::FAILURE; + } + + let input = matches.free[0].clone(); + + if matches.opt_present("e") { + match encode(input, matches) { + Ok(_) => todo!(""), + Err(e) => { + eprintln!("{e}"); + return ExitCode::FAILURE; + } + } + } + + ExitCode::SUCCESS +}