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    // Task #96 — combined primary + shadow capacity surface.
94    h264_stego_capacity_4domain, H264StegoCapacityInfo,
95    // Task #97 — file-attachment-aware encode entries.
96    h264_stego_encode_yuv_string_4domain_multigop_streaming_v2_with_files,
97    h264_stego_encode_yuv_string_with_n_shadows_with_pattern_and_files,
98};
99#[cfg(feature = "cabac-stego")]
100pub use codec::h264::stego::gop_pattern::{FrameType, GopPattern};
101
102// Phase 6D.8 chunk 6G + §30D-C — decode entry points. Walks the
103// encoded Annex-B + passphrase → recovered UTF-8 string. The
104// `_4domain` variant pairs with §30D-C's 3-pass encoder + uses
105// fill-MVD-first allocation; the basic variant pairs with chunk
106// 5 / §30C residual-only encoders.
107#[cfg(feature = "cabac-stego")]
108pub use codec::h264::stego::decode_pixels::{
109    h264_stego_decode_yuv_string, h264_stego_decode_yuv_string_4domain,
110    h264_stego_shadow_decode, h264_stego_smart_decode_video,
111    // Task #97 — _with_payload variant returns PayloadData (text +
112    // attached files), not just text.
113    h264_stego_smart_decode_video_with_payload,
114};
115
116// HEVC (archived) re-exports — only available with the `hevc-archive` feature.
117
118/// Detected video codec inside an MP4 container.
119///
120/// The `Hevc` variant is still reported even when the HEVC pipeline is
121/// archived — detection only needs MP4-level codec bytes, not the full
122/// HEVC parser. Callers should check for `VideoCodec::H264` as the only
123/// supported stego target.
124#[cfg(feature = "video")]
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum VideoCodec {
127    /// H.264/AVC — uses CAVLC-based encoding (Baseline/Main CAVLC).
128    H264,
129    /// HEVC/H.265. Detected but not supported for stego in current builds;
130    /// the pipeline is archived behind the `hevc-archive` feature flag.
131    Hevc,
132    /// MP4 but no recognised H.264 or HEVC track.
133    Unknown,
134}
135
136/// Detect the video codec of an MP4 byte buffer. Returns `Unknown` if the
137/// input is not an MP4 or has no recognised video track.
138#[cfg(feature = "video")]
139pub fn detect_video_codec(mp4_bytes: &[u8]) -> VideoCodec {
140    if !is_mp4(mp4_bytes) {
141        return VideoCodec::Unknown;
142    }
143    let Ok(file) = codec::mp4::demux::demux(mp4_bytes) else {
144        return VideoCodec::Unknown;
145    };
146    let Some(idx) = file.video_track_idx else {
147        return VideoCodec::Unknown;
148    };
149    let track = &file.tracks[idx];
150    if track.is_h264() {
151        VideoCodec::H264
152    } else if track.is_hevc() {
153        VideoCodec::Hevc
154    } else {
155        VideoCodec::Unknown
156    }
157}