png_data WIP
This commit is contained in:
parent
a415d0d7c6
commit
2601e7dbb1
4 changed files with 283 additions and 5 deletions
|
@ -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
173
src/png_data/header.rs
Normal 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
105
src/png_data/main.rs
Normal 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
|
||||||
|
}
|
|
@ -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");
|
||||||
|
|
Loading…
Reference in a new issue