From ed31c4201834719b2b75dca7cad1637abf1a80bb Mon Sep 17 00:00:00 2001 From: Silas Bartha Date: Sun, 2 Jun 2024 18:38:25 -0400 Subject: Made codecs object-safe and deal with bytes only --- Cargo.lock | 7 ++++++ Cargo.toml | 4 ++-- src/codec.rs | 40 +++++++++++++++++---------------- src/jpeg/segment.rs | 42 +++++++++------------------------- src/lossless/lsb.rs | 65 ++++++++++++++++++++--------------------------------- 5 files changed, 64 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a46c2b7..a826f40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + [[package]] name = "autocfg" version = "1.3.0" @@ -252,6 +258,7 @@ dependencies = [ name = "occule" version = "0.1.0" dependencies = [ + "anyhow", "image", "img-parts", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 10ab85a..2dc41f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "occule" -version = "0.1.0" +version = "0.2.0" edition = "2021" [features] @@ -9,7 +9,7 @@ jpeg = ["dep:img-parts"] lossless = ["dep:image"] [dependencies] -thiserror = "1.0.61" +thiserror = "^1.0" [dependencies.img-parts] version = "0.3.0" diff --git a/src/codec.rs b/src/codec.rs index 8b38a9b..11fa76e 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -1,26 +1,28 @@ +use thiserror::Error; + /// Codecs enable the concealment of payload data inside the data of a carrier. pub trait Codec { - /// Data type representing the carrier. - type Carrier; - - /// Data type representing the payload. - type Payload; - - /// Data type representing encoder output/decoder input (usually the same as the carrier). - type Output; - - /// Type of errors produced by this codec. - type Error; - /// Embeds payload data inside carrier, returning the result. - fn encode(&self, carrier: C, payload: P) -> Result - where - C: Into, - P: Into; + fn encode(&self, carrier: &[u8], payload: &[u8]) -> Result, CodecError>; /// Extracts payload data from an encoded carrier, returning the carrier with data removed and the /// payload data. - fn decode(&self, encoded: E) -> Result<(Self::Carrier, Self::Payload), Self::Error> - where - E: Into; + fn decode(&self, encoded: &[u8]) -> Result<(Vec, Vec), CodecError>; +} + +/// Errors produced by a codec +#[derive(Debug, Error)] +pub enum CodecError { + /// Variant used when data is determined not to be encoded. Note that a codec may have no way + /// of knowing this, so this may not be returned even if the data was not encoded + #[error("Data was not encoded with this codec")] + DataNotEncoded, + + /// Variant used when data is invalid in some way. Allows a message string for further context + #[error("Provided data invalid: {0}")] + DataInvalid(String), + + /// Variant used when some dependency, such as a file load, fails + #[error("Error occured in dependency: {0}")] + DependencyError(String), } diff --git a/src/jpeg/segment.rs b/src/jpeg/segment.rs index b20ba3b..cd1a651 100644 --- a/src/jpeg/segment.rs +++ b/src/jpeg/segment.rs @@ -1,9 +1,8 @@ use std::{mem::size_of, usize}; use img_parts::jpeg::{markers, Jpeg, JpegSegment}; -use thiserror::Error; -use crate::codec::Codec; +use crate::{codec::Codec, CodecError}; /// Codec for storing payload data in JPEG comment (COM) segments. Can store an arbitrary amount of /// data, as long as the number of comment segments does not exceed u64::MAX. @@ -14,21 +13,13 @@ pub struct JpegSegmentCodec { } impl Codec for JpegSegmentCodec { - type Carrier = Vec; - type Payload = Vec; - type Output = Self::Carrier; - type Error = JpegSegmentError; - - fn encode(&self, carrier: C, payload: P) -> Result - where - C: Into, - P: Into, + fn encode(&self, carrier: &[u8], payload: &[u8]) -> Result, CodecError> { - let mut jpeg = match Jpeg::from_bytes(carrier.into().into()) { - Ok(image) => image, - Err(err) => return Err(JpegSegmentError::ParseFailed { inner: err }) + let mut jpeg = match Jpeg::from_bytes(carrier.to_vec().into()) { + Ok(v) => v, + Err(e) => return Err(CodecError::DependencyError(e.to_string())) }; - let mut payload_bytes: Self::Carrier = payload.into(); + let mut payload_bytes = payload.to_vec(); let segment_count = ((payload_bytes.len() + size_of::()) as u64).div_ceil((u16::MAX as usize - size_of::()) as u64); payload_bytes.splice(0..0, segment_count.to_le_bytes()); for (index, payload_chunk) in payload_bytes.chunks(u16::MAX as usize - size_of::()).enumerate() { @@ -38,13 +29,11 @@ impl Codec for JpegSegmentCodec { Ok(jpeg.encoder().bytes().to_vec()) } - fn decode(&self, encoded: E) -> Result<(Self::Carrier, Self::Payload), Self::Error> - where - E: Into, + fn decode(&self, encoded: &[u8]) -> Result<(Vec, Vec), CodecError> { - let mut jpeg = match Jpeg::from_bytes(encoded.into().into()) { - Ok(image) => image, - Err(err) => return Err(JpegSegmentError::ParseFailed { inner: err }) + let mut jpeg = match Jpeg::from_bytes(encoded.to_vec().into()) { + Ok(v) => v, + Err(e) => return Err(CodecError::DependencyError(e.to_string())) }; let segment = jpeg.segments_mut().remove(self.start_index); let segment_bytes = segment.contents(); @@ -68,14 +57,3 @@ impl Default for JpegSegmentCodec { } } } - -/// Errors thrown by the JPEG segment codec. -#[derive(Error, Debug)] -pub enum JpegSegmentError { - /// Parsing JPEG data failed. - #[error("Failed to parse JPEG data: {inner:?}")] - ParseFailed { - /// Error thrown by parser. - inner: img_parts::Error, - } -} diff --git a/src/lossless/lsb.rs b/src/lossless/lsb.rs index 59dcc0b..f8f1113 100644 --- a/src/lossless/lsb.rs +++ b/src/lossless/lsb.rs @@ -1,33 +1,25 @@ -use std::cmp::Ordering; +use std::{cmp::Ordering, io::{BufWriter, Cursor}}; -use image::{ColorType, DynamicImage, GenericImageView, Pixel}; -use thiserror::Error; +use image::{DynamicImage, GenericImageView, Pixel}; -use crate::codec::Codec; +use crate::{codec::Codec, CodecError}; /// Least-significant bit (LSB) steganography encodes data in the least-significant bits of colors /// in an image. This implementation reduces the colors in the carrier (irreversibly) in order to /// allow a byte of data to fit in each pixel of the image. 3 bits of data are encoded per pixel, /// and the 9th bit is used to signal the end of data. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct LsbCodec; impl Codec for LsbCodec { - type Carrier = DynamicImage; - type Payload = Vec; - type Output = Self::Carrier; - type Error = LsbError; - - fn encode(&self, carrier: C, payload: P) -> Result - where - C: Into, - P: Into, + fn encode(&self, carrier: &[u8], payload: &[u8]) -> Result, CodecError> { - let mut image: DynamicImage = carrier.into(); - let payload: Vec = payload.into(); + let image_format = image::guess_format(carrier.into()).unwrap(); + let mut image: DynamicImage = image::load_from_memory(carrier.into()).unwrap(); + let payload: &[u8] = payload.into(); if image.pixels().count() < payload.len() { - return Err(LsbError::PayloadTooBig); + return Err(CodecError::DataInvalid("Payload Too Big for Carrier".into())); } let mut payload_iter = payload.iter(); @@ -51,17 +43,20 @@ impl Codec for LsbCodec { } } }, - _ => return Err(LsbError::UnsupportedFormat { format: image.color() }) + _ => return Err(CodecError::DataInvalid("Unsupported Image Color Format".into())) } - Ok(image) + let mut buf = BufWriter::new(Cursor::new(Vec::::new())); + if let Err(e) = image.write_to(&mut buf, image_format) { + return Err(CodecError::DependencyError(e.to_string())) + } + Ok(buf.into_inner().unwrap().into_inner()) } - fn decode(&self, carrier: E) -> Result<(Self::Carrier, Self::Payload), LsbError> - where - E: Into, + fn decode(&self, carrier: &[u8]) -> Result<(Vec, Vec), CodecError> { - let mut image: DynamicImage = carrier.into(); + let image_format = image::guess_format(carrier.into()).unwrap(); + let mut image: DynamicImage = image::load_from_memory(carrier.into()).unwrap(); let mut payload: Vec = Vec::new(); match image { @@ -83,10 +78,14 @@ impl Codec for LsbCodec { } } }, - _ => return Err(LsbError::UnsupportedFormat { format: image.color() }) + _ => return Err(CodecError::DataInvalid("Unsupported Image Color Format".into())) } - Ok((image, payload)) + let mut buf = BufWriter::new(Cursor::new(Vec::::new())); + if let Err(e) = image.write_to(&mut buf, image_format) { + return Err(CodecError::DependencyError(e.to_string())) + } + Ok((buf.into_inner().unwrap().into_inner(), payload)) } } @@ -138,19 +137,3 @@ fn decode_pixel>(pixel: &mut P) -> Option { } Some(payload_byte) } - -/// Errors thrown by the LSB Codec. -#[derive(Error, Debug)] -pub enum LsbError { - - /// Error thrown when payload is too big for the carrier. - #[error("Payload is too big for the carrier. Choose a smaller payload or an image with greater pixel dimensions.")] - PayloadTooBig, - - /// Error thrown when pixel format is unsupported. - #[error("Specified image format ({format:?}) is unsupported.")] - UnsupportedFormat { - /// Provided (invalid) format. - format: ColorType - }, -} -- cgit v1.2.3