use crate::cli::{DecryptCmd, EncryptCmd, ShowMetaCmd};
use crate::utils::{u64_to_u8_array, xor_encode_decode};
use std::fs::File;
use std::io::{copy, Error, ErrorKind, Read, Seek, SeekFrom, Write};
use std::mem;
#[derive(Debug, Clone)]
pub struct Header {
pub header: u64,
}
#[derive(Debug, Clone)]
pub struct Chunk {
pub size: u32,
pub r#type: u32,
pub data: Vec<u8>,
pub crc: u32,
}
#[derive(Debug, Clone)]
pub struct MetaChunk {
pub header: Header,
pub chk: Chunk,
pub offset: u64,
}
impl MetaChunk {
pub fn new(file: &mut File, suppress: bool) -> Result<MetaChunk, Error> {
let mut header = Header { header: 0 };
file.read_exact(unsafe { mem::transmute::<_, &mut [u8; 8]>(&mut header.header) })?;
let b_arr = u64_to_u8_array(header.header);
if &b_arr[1..4] != b"PNG" {
panic!("Not a valid PNG format");
} else if !suppress {
println!("It is a valid PNG file. Let's process it!");
}
let offset = file.stream_position()?;
Ok(MetaChunk {
header,
chk: Chunk {
size: 0,
r#type: 0,
data: Vec::new(),
crc: 0,
},
offset,
})
}
pub fn process_image(&mut self, file: &mut File, c: &ShowMetaCmd) {
let mut _chunk_type = String::new();
let end_chunk_type = "IEND";
for (i, j) in (c.start_chunk..c.end_chunk).enumerate() {
let offset = self.get_offset(file);
self.read_chunk(file);
if !c.suppress {
println!("\x1b[92m---- Chunk #{} ----\x1b[0m", j);
println!("Chunk offset: {:?}", offset);
println!("Chunk size: {:?}", self.chk.size);
println!("Chunk crc: {:x}", self.chk.crc);
}
_chunk_type = self.chunk_type_to_string();
if i + 1 >= c.nb_chunks || _chunk_type == end_chunk_type {
break;
}
}
}
fn get_offset<T: Read + Seek>(&mut self, file: &mut T) -> u64 {
let offset = file.seek(SeekFrom::Current(5)).unwrap();
self.offset = offset;
offset
}
fn read_chunk<T: Read + Seek>(&mut self, file: &mut T) {
self.read_chunk_size(file);
self.read_chunk_type(file);
self.read_chunk_bytes(file, self.chk.size);
self.read_chunk_crc(file);
}
fn read_chunk_size<R: Read>(&mut self, file: &mut R) {
let mut size_bytes = [0; 4];
match file.read_exact(&mut size_bytes) {
Ok(_) => {
self.chk.size = size_bytes[3] as u32;
}
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
eprintln!("Warning: Reached end of file prematurely while reading chunk size");
}
Err(err) => {
eprintln!("Error reading chunk size bytes: {}", err);
}
}
}
fn read_chunk_type<R: Read>(&mut self, file: &mut R) {
let mut type_bytes = [0; 4];
match file.read_exact(&mut type_bytes) {
Ok(_) => {
self.chk.r#type = u32::from_be_bytes(type_bytes);
}
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
eprintln!("Warning: Reached end of file prematurely while reading chunk type");
}
Err(err) => {
eprintln!("Error reading chunk type bytes: {}", err);
}
}
}
fn read_chunk_bytes<T: Read + Seek>(&mut self, file: &mut T, len: u32) {
self.chk.data = vec![0; len as usize];
match file.read_exact(&mut self.chk.data) {
Ok(_) => {
}
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
eprintln!("Error reading chunk bytes: Reached end of file prematurely");
self.chk
.data
.truncate(file.stream_position().unwrap() as usize);
}
Err(err) => {
eprintln!("Error reading chunk bytes: {}", err);
}
}
}
fn read_chunk_crc<R: Read>(&mut self, file: &mut R) {
let mut crc_bytes = [0; 4];
match file.read_exact(&mut crc_bytes) {
Ok(_) => {
self.chk.crc = u32::from_be_bytes(crc_bytes);
}
Err(err) if err.kind() == ErrorKind::UnexpectedEof => {
eprintln!("Warning: Reached end of file prematurely while reading CRC");
}
Err(err) => {
eprintln!("Error reading CRC bytes: {}", err);
}
}
}
fn chunk_type_to_string(&self) -> String {
String::from_utf8_lossy(&self.chk.r#type.to_be_bytes()).to_string()
}
fn marshal_data(&self) -> Vec<u8> {
let mut bytes_msb = Vec::new();
bytes_msb.push(self.chk.data.len() as u8);
bytes_msb.write_all(&self.chk.r#type.to_be_bytes()).unwrap();
bytes_msb.write_all(&self.chk.data).unwrap();
bytes_msb.write_all(&self.chk.crc.to_be_bytes()).unwrap();
bytes_msb
}
pub fn write_encrypted_data<R: Read + Seek, W: Write>(
&mut self,
r: &mut R,
c: &EncryptCmd,
mut w: W,
) {
let b_arr = u64_to_u8_array(self.header.header);
w.write_all(&b_arr).unwrap();
let offset = &c.offset;
let mut buff = vec![0; offset - 8];
buff.resize(offset - 8, 0);
r.read_exact(&mut buff).unwrap();
w.write_all(&buff).unwrap();
let data: Vec<u8> = self.marshal_data();
w.write_all(&data).unwrap();
copy(r, &mut w).unwrap();
}
pub fn write_decrypted_data<R: Read + Seek, W: Write>(
&mut self,
r: &mut R,
c: &DecryptCmd,
mut w: W,
) {
let b_arr = u64_to_u8_array(self.header.header);
w.write_all(&b_arr).unwrap();
let offset = &c.offset;
let mut buff = vec![0; offset - 8];
buff.resize(offset - 16, 0);
r.read_exact(&mut buff).unwrap();
w.write_all(&buff).unwrap();
let _offset = self.get_offset(r);
self.read_chunk(r);
let decoded_data = xor_encode_decode(&self.chk.data, &c.key);
let decoded_string = String::from_utf8_lossy(&decoded_data);
println!(
"\x1b[38;5;7mYour decoded secret is:\x1b[0m \x1b[38;5;214m{:?}\x1b[0m",
decoded_string
);
r.seek(SeekFrom::Current(self.chk.data.len().try_into().unwrap()))
.expect("Error seeking to offset");
copy(r, &mut w).unwrap();
}
}