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
|
||||
|
||||
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`
|
||||
|
|
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) {
|
||||
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\"
|
||||
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
|
||||
);
|
||||
print!("{}", opts.usage(&brief));
|
||||
|
@ -40,7 +40,7 @@ fn print_version() {
|
|||
print!(
|
||||
r#"png_embed (c) ef3d0c3e -- Embed data into PNGs
|
||||
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>.
|
||||
|
||||
This program is free software; you may modify and redistribute it.
|
||||
|
@ -288,7 +288,7 @@ fn main() -> ExitCode {
|
|||
+ matches.opt_present("z") as usize
|
||||
> 1
|
||||
{
|
||||
eprintln!("Specify either `-e(--embed)`, -z(--info) or `-d(--decode)`");
|
||||
eprintln!("Specify either `-e(--embed)`, `-z(--info)` or `-d(--decode)`");
|
||||
return ExitCode::FAILURE;
|
||||
} else if !matches.opt_present("l") {
|
||||
eprintln!("Missing algorithm name");
|
||||
|
|
Loading…
Reference in a new issue