Skip to main content

phasm_core/
lib.rs

1// Copyright (c) 2026 Christoph Gaffga
2// SPDX-License-Identifier: GPL-3.0-only
3// https://github.com/cgaffga/phasmcore
4
5//! # phasm-core
6//!
7//! Pure-Rust steganography engine for hiding encrypted text messages in JPEG
8//! photos. Provides two embedding modes:
9//!
10//! - **Ghost** (stealth): J-UNIWARD cost function + STC coding to resist
11//!   statistical steganalysis. Optimizes for undetectability.
12//! - **Armor** (robust): STDM embedding + Reed-Solomon ECC to survive
13//!   JPEG recompression. Optimizes for message survivability.
14//!
15//! All processing is client-side. The JPEG coefficient codec (`jpeg` module)
16//! is zero-dependency (std only). The steganography layer (`stego` module)
17//! uses AES-256-GCM-SIV encryption and Argon2id key derivation.
18//!
19//! # Quick start
20//!
21//! ```rust,ignore
22//! use phasm_core::{ghost_encode, ghost_decode};
23//!
24//! let cover_jpeg = std::fs::read("photo.jpg").unwrap();
25//! let stego = ghost_encode(&cover_jpeg, "secret message", "passphrase").unwrap();
26//! let decoded = ghost_decode(&stego, "passphrase").unwrap();
27//! assert_eq!(decoded.text, "secret message");
28//! ```
29
30pub mod codec;
31pub mod det_math;
32pub mod stego;
33
34// Backward-compatible re-exports: `phasm_core::jpeg::*` still works
35pub use codec::jpeg as jpeg;
36
37pub use codec::jpeg::error::{JpegError, Result as JpegResult};
38pub use codec::jpeg::dct::{DctGrid, QuantTable};
39pub use codec::jpeg::frame::FrameInfo;
40pub use codec::jpeg::JpegImage;
41pub use stego::{ghost_encode, ghost_decode, ghost_encode_with_files, ghost_encode_si, ghost_encode_si_with_files, ghost_capacity, ghost_capacity_si, StegoError, GHOST_DECODE_STEPS, GHOST_ENCODE_STEPS};
42pub use stego::{ghost_encode_with_quality, ghost_encode_with_files_quality, ghost_encode_si_with_quality, ghost_encode_si_with_files_quality};
43pub use stego::{ghost_encode_with_shadows, ghost_encode_si_with_shadows, ghost_shadow_decode, ShadowLayer, GHOST_ENCODE_WITH_SHADOWS_STEPS, shadow_capacity, estimate_shadow_capacity, ghost_capacity_with_shadows};
44pub use stego::{ghost_encode_with_shadows_quality, ghost_encode_si_with_shadows_quality};
45pub use stego::{armor_encode, armor_encode_with_quality, armor_decode, armor_capacity, armor_capacity_info, smart_decode, DecodeQuality, ArmorCapacityInfo};
46pub use stego::EncodeQuality;
47pub use stego::{validate_encode_dimensions, MAX_DIMENSION, MAX_PIXELS, MIN_ENCODE_DIMENSION, ARMOR_TARGET_DIMENSION};
48pub use stego::{PayloadData, FileEntry, compressed_payload_size};
49pub use stego::progress;
50pub use stego::{optimize_cover, OptimizerConfig, OptimizerMode};
51
52// H.264 (production) re-exports.
53#[cfg(feature = "video")]
54pub use stego::video::{
55    h264_ghost_encode, h264_ghost_encode_inplace,
56    h264_ghost_decode, h264_ghost_capacity, h264_ghost_capacity_max,
57    h264_ghost_encode_path, h264_ghost_decode_path,
58    h264_ghost_capacity_path, h264_ghost_capacity_max_path,
59    is_mp4,
60};
61
62// H.264 phase-6 encoder transcoder (#77) — replaces VideoToolbox /
63// MediaCodec on mobile for the input-video → Baseline-CAVLC step.
64// `StreamingEncoder` is the per-frame stateful API used by the mobile
65// bridges; `transcode_yuv_to_baseline_cavlc_h264` is the one-shot
66// convenience wrapper for tests / CLI / batch contexts.
67#[cfg(feature = "h264-encoder")]
68pub use codec::h264::encoder::baseline_transcode::{
69    BaselineTranscodeConfig, StreamingEncoder, transcode_yuv_to_baseline_cavlc_h264,
70};
71
72// Phase 6D.8 chunk 5 — encode-time CABAC stego public API. UTF-8
73// string message + passphrase + raw I420 YUV → Annex-B byte stream.
74// I-frame-only single-GOP scope until §30 MVD wiring lands.
75// Mobile bridges + CLI route through this once chunk 7 atomic-swaps
76// the legacy CAVLC pipeline gates.
77#[cfg(feature = "cabac-stego")]
78pub use codec::h264::stego::encode_pixels::{
79    h264_stego_encode_i_frames_only, h264_stego_encode_i_then_p_frames,
80    h264_stego_encode_i_then_p_frames_4domain,
81    h264_stego_encode_i_then_p_frames_4domain_multigop,
82    h264_stego_encode_yuv_string, h264_stego_encode_yuv_string_4domain,
83    h264_stego_encode_yuv_string_4domain_multigop,
84    h264_stego_encode_yuv_string_4domain_multigop_streaming,
85    h264_stego_encode_yuv_string_4domain_multigop_streaming_v2,
86    h264_stego_encode_yuv_string_4domain_multigop_with_pattern,
87    h264_stego_encode_yuv_string_i_then_p,
88    h264_stego_encode_yuv_string_with_n_shadows,
89    h264_stego_encode_yuv_string_with_n_shadows_with_pattern,
90    h264_stego_encode_yuv_string_with_shadow,
91    h264_stego_encode_yuv_string_with_shadow_with_pattern,
92    h264_stego_shadow_capacity, H264ShadowCapacityInfo,
93};
94#[cfg(feature = "cabac-stego")]
95pub use codec::h264::stego::gop_pattern::{FrameType, GopPattern};
96
97// Phase 6D.8 chunk 6G + §30D-C — decode entry points. Walks the
98// encoded Annex-B + passphrase → recovered UTF-8 string. The
99// `_4domain` variant pairs with §30D-C's 3-pass encoder + uses
100// fill-MVD-first allocation; the basic variant pairs with chunk
101// 5 / §30C residual-only encoders.
102#[cfg(feature = "cabac-stego")]
103pub use codec::h264::stego::decode_pixels::{
104    h264_stego_decode_yuv_string, h264_stego_decode_yuv_string_4domain,
105    h264_stego_shadow_decode, h264_stego_smart_decode_video,
106};
107
108// HEVC (archived) re-exports — only available with the `hevc-archive` feature.
109
110/// Detected video codec inside an MP4 container.
111///
112/// The `Hevc` variant is still reported even when the HEVC pipeline is
113/// archived — detection only needs MP4-level codec bytes, not the full
114/// HEVC parser. Callers should check for `VideoCodec::H264` as the only
115/// supported stego target.
116#[cfg(feature = "video")]
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum VideoCodec {
119    /// H.264/AVC — uses CAVLC-based encoding (Baseline/Main CAVLC).
120    H264,
121    /// HEVC/H.265. Detected but not supported for stego in current builds;
122    /// the pipeline is archived behind the `hevc-archive` feature flag.
123    Hevc,
124    /// MP4 but no recognised H.264 or HEVC track.
125    Unknown,
126}
127
128/// Detect the video codec of an MP4 byte buffer. Returns `Unknown` if the
129/// input is not an MP4 or has no recognised video track.
130#[cfg(feature = "video")]
131pub fn detect_video_codec(mp4_bytes: &[u8]) -> VideoCodec {
132    if !is_mp4(mp4_bytes) {
133        return VideoCodec::Unknown;
134    }
135    let Ok(file) = codec::mp4::demux::demux(mp4_bytes) else {
136        return VideoCodec::Unknown;
137    };
138    let Some(idx) = file.video_track_idx else {
139        return VideoCodec::Unknown;
140    };
141    let track = &file.tracks[idx];
142    if track.is_h264() {
143        VideoCodec::H264
144    } else if track.is_hevc() {
145        VideoCodec::Hevc
146    } else {
147        VideoCodec::Unknown
148    }
149}