Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ tmp
*.ts
*.mkv
*.mp4
!**/tests/**/*.mp4
shiori_*

# nix
.direnv
.direnv
node_modules/
.pnpm-debug.log
36 changes: 31 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ license = "Apache-2.0"
iori = { path = "crates/iori" }
iori-ffmpeg = { path = "crates/iori-ffmpeg" }
iori-ssa = { path = "crates/ssa" }
iori-cenc = { path = "crates/cenc" }
iori-hls = { path = "crates/iori-hls" }

iori-nicolive = { path = "platforms/nicolive" }
Expand Down
1 change: 1 addition & 0 deletions crates/cenc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tests/fixtures/bbb/*_dec.mp4
14 changes: 14 additions & 0 deletions crates/cenc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "iori-cenc"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
repository.workspace = true
license.workspace = true

[dependencies]
shiguredo_mp4 = { git = "https://github.com/shiguredo/mp4-rs", branch = "feature/fmp4" }
aes.workspace = true
hex = "0.4.3"
thiserror.workspace = true
winnow = "0.7.14"
75 changes: 75 additions & 0 deletions crates/cenc/src/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Public API for V3 streaming CENC decryption

use crate::types::KeyMap;
use crate::error::Result;
use crate::orchestrator;
use std::io::{Read, Write};

/// Decrypt fMP4 CENC-encrypted stream in a single pass
///
/// This function decrypts a fragmented MP4 (fMP4) file that uses Common Encryption (CENC).
/// It processes the input stream incrementally without loading the entire file into memory.
///
/// # Arguments
///
/// * `input` - Input stream containing encrypted fMP4 data
/// * `output` - Output stream for decrypted fMP4 data
/// * `keys` - Map of Key IDs (KID) to decryption keys (both 16 bytes)
///
/// # Supported Schemes
///
/// - `cenc` - AES-CTR mode, full sample encryption
/// - `cens` - AES-CTR mode, subsample encryption
/// - `cbc1` - AES-CBC mode, full sample encryption
/// - `cbcs` - AES-CBC mode, subsample encryption with pattern
///
/// # Memory Usage
///
/// This implementation uses O(buffer_size) memory, not O(file_size).
/// Specifically:
/// - Metadata boxes (moov, moof): Fully buffered (~1MB typically)
/// - Data boxes (mdat): Streamed sample-by-sample
/// - Maximum memory: Size of largest sample (typically <10MB)
///
/// # Example
///
/// ```rust,no_run
/// use std::fs::File;
/// use std::collections::HashMap;
/// use iori_cenc::decrypt;
///
/// let input = File::open("encrypted.mp4")?;
/// let output = File::create("decrypted.mp4")?;
///
/// let mut keys = HashMap::new();
/// keys.insert(
/// [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
/// 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff],
/// [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
/// 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef],
/// );
///
/// decrypt(input, output, keys)?;
/// # Ok::<(), iori_cenc::V3Error>(())
/// ```
///
/// # Errors
///
/// Returns error if:
/// - Input is not a valid fMP4 file
/// - Required encryption metadata is missing
/// - Decryption keys are missing for any track
/// - I/O errors occur during reading or writing
/// - Box structure is invalid or corrupted
///
/// # Limitations
///
/// - **fMP4 only**: This implementation only supports fragmented MP4 files.
/// Non-fragmented MP4 files are not supported (would require Seek trait).
/// - **Single pass**: The implementation makes a single forward pass through the file.
/// This is possible because fMP4 places metadata (moov, moof) before data (mdat).
/// - **No seeking**: The input and output streams need only implement Read/Write,
/// not Seek. This allows decryption from pipes, sockets, etc.
pub fn decrypt<R: Read, W: Write>(input: R, output: W, keys: KeyMap) -> Result<()> {
orchestrator::decrypt_stream(input, output, keys)
}
Loading
Loading