png_data WIP

This commit is contained in:
ef3d0c3e 2024-08-19 13:48:27 +02:00
parent a415d0d7c6
commit 2601e7dbb1
4 changed files with 283 additions and 5 deletions

View file

@ -27,5 +27,5 @@ Where:
# License # License
NML is licensed under the GNU AGPL version 3 or later. See [LICENSE.md](LICENSE.md) for more information. png_data is licensed under the GNU AGPL version 3 or later. See [LICENSE.md](LICENSE.md) for more information.
License for third-party dependencies can be accessed via `cargo license` License for third-party dependencies can be accessed via `cargo license`

173
src/png_data/header.rs Normal file
View file

@ -0,0 +1,173 @@
use crc::Crc;
pub trait Encode {
/// Encode the data into a vector
fn encode(&self, vec: &mut Vec<u8>);
}
pub trait Decode {
type Type;
/// Decode the data from an iterator
fn decode<I>(it: &mut I) -> Result<Self::Type, String>
where I: Iterator<Item = u8>;
}
/// The program's version.
/// Used for compatibility reasons.
#[repr(u16)]
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy)]
pub enum Version {
VERSION_1,
}
impl TryFrom<u16> for Version {
type Error = String;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0 => Ok(Version::VERSION_1),
ver => Err(format!("Unknown version: {ver}")),
}
}
}
#[derive(Debug)]
pub struct Header {
pub version: Version,
pub data_len: u32,
pub data_crc: u32,
pub comment: Option<String>,
}
impl Header {
/// Construct a new header from the embedded data
pub fn new(version: Version, data: &[u8], comment: Option<String>) -> Result<Self, String> {
if data.len() > u32::MAX as usize {
return Err(format!(
"Embedded data length: {} is greater than maximum {}",
data.len(),
u32::MAX
));
} else if let Some(len) = comment.as_ref().map(|c| c.len()) {
if len > u16::MAX as usize {
return Err(format!(
"Embedded comment is too long, maximum length: {}, got {len}",
u16::MAX
));
}
}
Ok(Self {
version,
data_len: data.len() as u32,
data_crc: Crc::<u32>::new(&crc::CRC_32_CKSUM).checksum(data),
comment,
})
}
}
impl Encode for Header {
fn encode(&self, vec: &mut Vec<u8>) {
// Version
vec.extend_from_slice((self.version as u16).to_le_bytes().as_slice());
// Data Len
vec.extend_from_slice(self.data_len.to_le_bytes().as_slice());
// Data CRC
vec.extend_from_slice(self.data_crc.to_le_bytes().as_slice());
// Comment length
let comment_length = self.comment.as_ref().map_or(0u16, |c| c.len() as u16);
vec.extend_from_slice(comment_length.to_le_bytes().as_slice());
// Comment
if let Some(comment) = &self.comment {
vec.extend_from_slice(comment.as_bytes());
}
}
}
impl Decode for Header {
type Type = Header;
fn decode<I>(it: &mut I) -> Result<Self::Type, String>
where I: Iterator<Item = u8> {
let mut count = 0;
let mut next = || -> Result<u8, String> {
let result = it
.next()
.ok_or(format!("Failed to get byte at index: {count}"));
count += 1;
result
};
let version = u16::from_le_bytes([next()?, next()?]);
let data_len = u32::from_le_bytes([next()?, next()?, next()?, next()?]);
let data_crc = u32::from_le_bytes([next()?, next()?, next()?, next()?]);
let comment_length = u16::from_le_bytes([next()?, next()?]);
let comment = if comment_length != 0 {
let mut comment_data = Vec::with_capacity(comment_length as usize);
for _ in 0..comment_length {
comment_data.push(next()?);
}
Some(
String::from_utf8(comment_data)
.map_err(|e| format!("Failed to retrieve comment: {e}"))?,
)
} else {
None
};
Ok(Header {
version: Version::try_from(version)?,
data_len,
data_crc,
comment,
})
}
}
/*
#[derive(Debug)]
pub struct HeaderCrypt {
pub version: Version,
pub nonce: Vec<u8>,
pub data_len: u32,
pub data_crc: u32,
pub comment: Option<String>,
}
impl HeaderCrypt {
/// Construct a new header from the embedded data
pub fn new(version: Version, nonce: Vec<u8>, data: &[u8], comment: Option<String>) -> Result<Self, String> {
if data.len() > u32::MAX as usize {
return Err(format!(
"Embedded data length: {} is greater than maximum {}",
data.len(),
u32::MAX
));
} else if let Some(len) = comment.as_ref().map(|c| c.len()) {
if len > u16::MAX as usize {
return Err(format!(
"Embedded comment is too long, maximum length: {}, got {len}",
u16::MAX
));
}
}
Ok(Self {
version,
nonce,
data_len: data.len() as u32,
data_crc: Crc::<u32>::new(&crc::CRC_32_CKSUM).checksum(data),
comment,
})
}
}
*/

105
src/png_data/main.rs Normal file
View file

@ -0,0 +1,105 @@
mod header;
use std::env;
use std::process::ExitCode;
use getopts::Matches;
use getopts::Options;
fn print_usage(program: &str, opts: Options) {
let brief = format!(
"Usage: {0} -(e|z|d) [FILE [-o OUTPUT]] [opts]
Encode: {0} -e file.tar -l rgb8 -o out.png -c \"(.tar)\"
Info: {0} -z out.png # (.tar)
Decode: {0} -d out.png -o file.tar",
program
);
print!("{}", opts.usage(&brief));
}
fn print_version() {
print!(
r#"png_data (c) ef3d0c3e -- Pass data as PNGs
Copyright (c) 2024
png_data is licensed under the GNU Affero General Public License version 3 (AGPLv3),
under the terms of the Free Software Foundation <https://www.gnu.org/licenses/agpl-3.0.en.html>.
This program is free software; you may modify and redistribute it.
There is NO WARRANTY, to the extent permitted by law."#
);
}
fn best_layout(size: u32, bits_per_pixel: u8) -> (u32, u32) {
let sz : f64 = size as f64 / bits_per_pixel as f64;
let width = sz.sqrt().floor();
(width as u32, (sz / width as f64).ceil() as u32)
}
fn encode(input: String, output: String, layout: String, matches: Matches) -> Result<(), String> {
Ok(())
}
fn main() -> ExitCode {
let args: Vec<String> = env::args().collect();
let program = args[0].clone();
let mut opts = Options::new();
opts.optopt("e", "encode", "Embed file", "PATH");
opts.optopt("d", "decode", "Decode mode", "PATH");
opts.optflag("z", "info", "Read header");
opts.optopt("l", "layout", "Png image layout", "TXT");
opts.optopt("p", "password", "Data password", "TXT");
opts.optopt("o", "output", "Output file", "PATH");
opts.optopt("c", "comment", "Header comment", "TXT");
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;
}
// Check options
if matches.opt_present("e") as usize
+ matches.opt_present("d") as usize
+ matches.opt_present("z") as usize
> 1
{
eprintln!("Specify either `-e(--encode)`, `-z(--info)` or `-d(--decode)`");
return ExitCode::FAILURE;
}
if let Some(input_file) = matches.opt_str("e") {
let layout = match matches.opt_str("l") {
None => {
eprintln!("Missing required png layout (-l|--layout) option");
return ExitCode::FAILURE;
}
Some(layout) => layout,
};
let output_file = match matches.opt_str("o") {
None => {
eprintln!("Missing required output (-o|--output) option");
return ExitCode::FAILURE;
}
Some(output_file) => output_file,
};
if let Err(e) = encode(input_file, output_file, layout, matches) {
eprintln!("{e}");
return ExitCode::FAILURE;
}
}
ExitCode::SUCCESS
}

View file

@ -27,10 +27,10 @@ use rand_chacha::ChaCha8Rng;
fn print_usage(program: &str, opts: Options) { fn print_usage(program: &str, opts: Options) {
let brief = format!( let brief = format!(
"Usage: {0} -l ALGORITHM -(e|d|z) [EMBED] FILE -o OUTPUT [opts] "Usage: {0} -l ALGORITHM -(e|z|d) [EMBED] FILE -o OUTPUT [opts]
Encode: {0} -l lo3 -e embed.jpg input.png -o out.png -c \"Embedded JPEG file\" Encode: {0} -l lo3 -e embed.jpg input.png -o out.png -c \"Embedded JPEG file\"
Info: {0} -l lo3 out.png # Embedded JPEG file Info: {0} -l lo3 out.png # Embedded JPEG file
Decode: {0} -l lo3 out.png > decoded.jpg", Decode: {0} -l lo3 -d out.png -o decoded.jpg",
program program
); );
print!("{}", opts.usage(&brief)); print!("{}", opts.usage(&brief));
@ -40,7 +40,7 @@ fn print_version() {
print!( print!(
r#"png_embed (c) ef3d0c3e -- Embed data into PNGs r#"png_embed (c) ef3d0c3e -- Embed data into PNGs
Copyright (c) 2024 Copyright (c) 2024
NML is licensed under the GNU Affero General Public License version 3 (AGPLv3), png_embed is licensed under the GNU Affero General Public License version 3 (AGPLv3),
under the terms of the Free Software Foundation <https://www.gnu.org/licenses/agpl-3.0.en.html>. under the terms of the Free Software Foundation <https://www.gnu.org/licenses/agpl-3.0.en.html>.
This program is free software; you may modify and redistribute it. This program is free software; you may modify and redistribute it.
@ -288,7 +288,7 @@ fn main() -> ExitCode {
+ matches.opt_present("z") as usize + matches.opt_present("z") as usize
> 1 > 1
{ {
eprintln!("Specify either `-e(--embed)`, -z(--info) or `-d(--decode)`"); eprintln!("Specify either `-e(--embed)`, `-z(--info)` or `-d(--decode)`");
return ExitCode::FAILURE; return ExitCode::FAILURE;
} else if !matches.opt_present("l") { } else if !matches.opt_present("l") {
eprintln!("Missing algorithm name"); eprintln!("Missing algorithm name");