summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/codec.rs24
-rw-r--r--src/jpeg/mod.rs (renamed from src/jpg/mod.rs)0
-rw-r--r--src/jpeg/segment.rs (renamed from src/jpg/segment.rs)21
-rw-r--r--src/lib.rs14
-rw-r--r--src/lossless/lsb.rs (renamed from src/lossless/champleve.rs)35
-rw-r--r--src/lossless/mod.rs4
6 files changed, 75 insertions, 23 deletions
diff --git a/src/codec.rs b/src/codec.rs
index 03cdf15..8b38a9b 100644
--- a/src/codec.rs
+++ b/src/codec.rs
@@ -1,14 +1,26 @@
+/// 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;
- fn encode(
- &self,
- carrier: impl Into<Self::Carrier>,
- payload: impl Into<Self::Payload>,
- ) -> Result<Self::Output, Self::Error>;
+ /// Embeds payload data inside carrier, returning the result.
+ fn encode<C, P>(&self, carrier: C, payload: P) -> Result<Self::Output, Self::Error>
+ where
+ C: Into<Self::Carrier>,
+ P: Into<Self::Payload>;
- fn decode(&self, encoded: impl Into<Self::Output>) -> Result<(Self::Carrier, Self::Payload), Self::Error>;
+ /// Extracts payload data from an encoded carrier, returning the carrier with data removed and the
+ /// payload data.
+ fn decode<E>(&self, encoded: E) -> Result<(Self::Carrier, Self::Payload), Self::Error>
+ where
+ E: Into<Self::Output>;
}
diff --git a/src/jpg/mod.rs b/src/jpeg/mod.rs
index 3d6bdc3..3d6bdc3 100644
--- a/src/jpg/mod.rs
+++ b/src/jpeg/mod.rs
diff --git a/src/jpg/segment.rs b/src/jpeg/segment.rs
index f54d0e2..b20ba3b 100644
--- a/src/jpg/segment.rs
+++ b/src/jpeg/segment.rs
@@ -5,8 +5,11 @@ use thiserror::Error;
use crate::codec::Codec;
+/// 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.
#[derive(Debug, PartialEq, Eq)]
pub struct JpegSegmentCodec {
+ /// Index of segment to insert comments at.
pub start_index: usize,
}
@@ -16,7 +19,11 @@ impl Codec for JpegSegmentCodec {
type Output = Self::Carrier;
type Error = JpegSegmentError;
- fn encode(&self, carrier: impl Into<Self::Carrier>, payload: impl Into<Self::Payload>) -> Result<Self::Output, Self::Error> {
+ fn encode<C, P>(&self, carrier: C, payload: P) -> Result<Self::Output, Self::Error>
+ where
+ C: Into<Self::Carrier>,
+ P: Into<Self::Payload>,
+ {
let mut jpeg = match Jpeg::from_bytes(carrier.into().into()) {
Ok(image) => image,
Err(err) => return Err(JpegSegmentError::ParseFailed { inner: err })
@@ -31,7 +38,10 @@ impl Codec for JpegSegmentCodec {
Ok(jpeg.encoder().bytes().to_vec())
}
- fn decode(&self, encoded: impl Into<Self::Output>) -> Result<(Self::Carrier, Self::Payload), Self::Error> {
+ fn decode<E>(&self, encoded: E) -> Result<(Self::Carrier, Self::Payload), Self::Error>
+ where
+ E: Into<Self::Output>,
+ {
let mut jpeg = match Jpeg::from_bytes(encoded.into().into()) {
Ok(image) => image,
Err(err) => return Err(JpegSegmentError::ParseFailed { inner: err })
@@ -59,8 +69,13 @@ 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 { inner: img_parts::Error }
+ ParseFailed {
+ /// Error thrown by parser.
+ inner: img_parts::Error,
+ }
}
diff --git a/src/lib.rs b/src/lib.rs
index a1ed909..7cbae7c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,7 +1,15 @@
-pub mod codec;
+#![warn(missing_docs)]
-#[cfg(feature = "jpg")]
-pub mod jpg;
+//! Library providing steganography codecs for various carrier and payload types, designed to be
+//! extensible.
+mod codec;
+pub use codec::*;
+
+/// Codecs for carriers in JPEG format.
+#[cfg(feature = "jpeg")]
+pub mod jpeg;
+
+/// Codecs for carriers in lossless image formats (PNG, WebP, etc.).
#[cfg(feature = "lossless")]
pub mod lossless;
diff --git a/src/lossless/champleve.rs b/src/lossless/lsb.rs
index 948408c..59dcc0b 100644
--- a/src/lossless/champleve.rs
+++ b/src/lossless/lsb.rs
@@ -5,21 +5,29 @@ use thiserror::Error;
use crate::codec::Codec;
+/// 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)]
-pub struct ChampleveCodec;
+pub struct LsbCodec;
-impl Codec for ChampleveCodec {
+impl Codec for LsbCodec {
type Carrier = DynamicImage;
type Payload = Vec<u8>;
type Output = Self::Carrier;
- type Error = ChampleveError;
+ type Error = LsbError;
- fn encode(&self, carrier: impl Into<Self::Carrier>, payload: impl Into<Self::Payload>) -> Result<Self::Output, Self::Error> {
+ fn encode<C, P>(&self, carrier: C, payload: P) -> Result<Self::Output, Self::Error>
+ where
+ C: Into<Self::Carrier>,
+ P: Into<Self::Payload>,
+ {
let mut image: DynamicImage = carrier.into();
let payload: Vec<u8> = payload.into();
if image.pixels().count() < payload.len() {
- return Err(ChampleveError::PayloadTooBig);
+ return Err(LsbError::PayloadTooBig);
}
let mut payload_iter = payload.iter();
@@ -43,13 +51,16 @@ impl Codec for ChampleveCodec {
}
}
},
- _ => return Err(ChampleveError::UnsupportedFormat { format: image.color() })
+ _ => return Err(LsbError::UnsupportedFormat { format: image.color() })
}
Ok(image)
}
- fn decode(&self, carrier: impl Into<Self::Output>) -> Result<(Self::Carrier, Self::Payload), ChampleveError> {
+ fn decode<E>(&self, carrier: E) -> Result<(Self::Carrier, Self::Payload), LsbError>
+ where
+ E: Into<Self::Output>,
+ {
let mut image: DynamicImage = carrier.into();
let mut payload: Vec<u8> = Vec::new();
@@ -72,7 +83,7 @@ impl Codec for ChampleveCodec {
}
}
},
- _ => return Err(ChampleveError::UnsupportedFormat { format: image.color() })
+ _ => return Err(LsbError::UnsupportedFormat { format: image.color() })
}
Ok((image, payload))
@@ -128,12 +139,18 @@ fn decode_pixel<P: Pixel<Subpixel = u8>>(pixel: &mut P) -> Option<u8> {
Some(payload_byte)
}
+/// Errors thrown by the LSB Codec.
#[derive(Error, Debug)]
-pub enum ChampleveError {
+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
},
}
diff --git a/src/lossless/mod.rs b/src/lossless/mod.rs
index a9ae225..a6bda54 100644
--- a/src/lossless/mod.rs
+++ b/src/lossless/mod.rs
@@ -1,2 +1,2 @@
-mod champleve;
-pub use champleve::*;
+mod lsb;
+pub use lsb::*;