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
9 changes: 3 additions & 6 deletions sdk/src/assertions/bmff_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -816,11 +816,10 @@ impl BmffHash {
Ok(())
}

#[cfg(feature = "file_io")]
pub fn verify_stream_segments(
&self,
init_stream: &mut dyn CAIRead,
fragment_paths: &Vec<std::path::PathBuf>,
fragment_paths: &mut [Box<dyn CAIRead>],
alg: Option<&str>,
) -> crate::Result<()> {
self.verify_self()?;
Expand Down Expand Up @@ -849,11 +848,9 @@ impl BmffHash {
return Err(Error::HashMismatch("No fragment specified".to_string()));
}

for fp in fragment_paths {
let mut fragment_stream = std::fs::File::open(fp)?;

for mut fragment_stream in fragment_paths {
// get merkle boxes from segment
let c2pa_boxes = read_bmff_c2pa_boxes(&mut fragment_stream)?;
let c2pa_boxes = read_bmff_c2pa_boxes(fragment_stream)?;
let bmff_merkle = c2pa_boxes.bmff_merkle;

if bmff_merkle.is_empty() {
Expand Down
140 changes: 140 additions & 0 deletions sdk/src/asset_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use std::{
io::{Cursor, Read, Seek},
path::Path,
};

use async_generic::async_generic;

use crate::{asset_io::CAIRead, settings::Settings, Reader, Result};

/// An enum representing different types of assets that can be read by the Reader
pub enum AssetData<'a> {
Stream(Box<dyn CAIRead + 'a>, &'a str),
StreamFragment(&'a mut dyn CAIRead, &'a mut dyn CAIRead, &'a str),
StreamWithManifest(Box<dyn CAIRead + 'a>, &'a str, &'a [u8]),
StreamFragments(&'a mut dyn CAIRead, &'a mut [Box<dyn CAIRead>], &'a str),
StreamWithManifestsAndFragments(
&'a mut dyn CAIRead,
&'a mut [Box<dyn CAIRead>],
&'a str,
&'a [u8],
),
}

impl<'a> AssetData<'a> {
/// Create a ReaderAsset from a file path
#[cfg(feature = "file_io")]
pub fn from_file(path: &'a Path) -> Result<Self> {
use std::fs::File;
let format = crate::format_from_path(path).ok_or(crate::Error::UnsupportedType)?;
let file = File::open(path)?;
Ok(Self::Stream(
Box::new(file),
Box::leak(format.into_boxed_str()),
))
}

/// Create a ReaderAsset from in-memory data
pub fn from_memory(data: &'a [u8], format: &'a str) -> Self {
let cursor = Cursor::new(data);
Self::Stream(Box::new(cursor), format)
}

/// Create a ReaderAsset from a stream that implements Read + Seek + Send
pub fn from_stream<T: Read + Seek + Send + 'a>(stream: T, format: &'a str) -> Self {
Self::Stream(Box::new(stream), format)
}

/// Create a ReaderAsset from a stream with fragment
pub fn from_stream_fragment(
initial_segment: &'a mut dyn CAIRead,
fragment: &'a mut dyn CAIRead,
format: &'a str,
) -> Self {
Self::StreamFragment(initial_segment, fragment, format)
}

/// Create a ReaderAsset from bytes in a cursor
pub fn from_cursor(data: Vec<u8>, format: &'a str) -> Self {
let cursor = Cursor::new(data);
Self::from_stream(cursor, format)
}

/// Create a ReaderAsset from a stream with separate manifest data
pub fn from_manifest_data_and_stream<T: Read + Seek + Send + 'a>(
manifest_data: &'a [u8],
stream: T,
format: &'a str,
) -> Self {
Self::StreamWithManifest(Box::new(stream), format, manifest_data)
}

/// Create a ReaderAsset from an initial segment and fragment streams for fragmented MP4
pub fn from_fragment_streams(
initial_segment: &'a mut dyn CAIRead,
fragments: &'a mut [Box<dyn CAIRead>],
format: &'a str,
) -> Self {
Self::StreamFragments(initial_segment, fragments, format)
}

/// Create a ReaderAsset from an initial segment and fragment streams with manifest data for fragmented MP4
pub fn from_manifest_data_and_fragment_streams(
manifest_data: &'a [u8],
initial_segment: &'a mut dyn CAIRead,
fragments: &'a mut [Box<dyn CAIRead>],
format: &'a str,
) -> Self {
Self::StreamWithManifestsAndFragments(initial_segment, fragments, format, manifest_data)
}

/// Convert to a Reader by processing the asset data with the given settings
#[async_generic]
pub fn to_reader(self, settings: &Settings) -> Result<Reader> {
if _sync {
Reader::from_asset(self, settings)
} else {
Reader::from_asset_async(self, settings).await
}
}
}

#[cfg(test)]
mod tests {
use super::*;

const IMAGE_WITH_MANIFEST: &[u8] = include_bytes!("../tests/fixtures/CA.jpg");

#[test]
fn test_from_memory() -> Result<()> {
let settings = Settings::default();
let asset = AssetData::from_memory(IMAGE_WITH_MANIFEST, "image/jpeg");
let reader = asset.to_reader(&settings)?;
assert!(reader.active_manifest().is_some());
println!("{reader}");
Ok(())
}

#[test]
fn test_from_cursor() -> Result<()> {
let settings = Settings::default();
let asset = AssetData::from_cursor(IMAGE_WITH_MANIFEST.to_vec(), "image/jpeg");
let reader = asset.to_reader(&settings)?;
assert!(reader.active_manifest().is_some());
println!("{reader}");
Ok(())
}

#[cfg(feature = "file_io")]
#[test]
fn test_from_file() -> Result<()> {
use std::path::Path;

let settings = Settings::default();
let path = Path::new("tests/fixtures/CA.jpg");
let asset = AssetData::from_file(path)?;
let reader = asset.to_reader(&settings)?;
assert!(reader.active_manifest().is_some());
Ok(())
}
}
4 changes: 1 addition & 3 deletions sdk/src/claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ pub enum ClaimAssetData<'a> {
Bytes(&'a [u8], &'a str),
Stream(&'a mut dyn CAIRead, &'a str),
StreamFragment(&'a mut dyn CAIRead, &'a mut dyn CAIRead, &'a str),
#[cfg(feature = "file_io")]
StreamFragments(&'a mut dyn CAIRead, &'a Vec<std::path::PathBuf>, &'a str),
StreamFragments(&'a mut dyn CAIRead, &'a mut [Box<dyn CAIRead>], &'a str),
}

#[derive(PartialEq, Debug, Eq, Clone, Hash)]
Expand Down Expand Up @@ -2734,7 +2733,6 @@ impl Claim {
*fragment_data,
Some(claim.alg()),
),
#[cfg(feature = "file_io")]
ClaimAssetData::StreamFragments(initseg_data, fragment_paths, _) => dh
.verify_stream_segments(
*initseg_data,
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ pub enum Error {
}

/// A specialized `Result` type for C2PA toolkit operations.
pub type Result<T> = std::result::Result<T, Error>;
pub type Result<T, E = Error> = std::result::Result<T, E>;

impl From<CoseError> for Error {
fn from(err: CoseError) -> Self {
Expand Down
3 changes: 3 additions & 0 deletions sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ pub mod identity;
/// The jumbf_io module contains the definitions for the JUMBF data in assets.
pub mod jumbf_io;

/// The reader_asset module provides a way to create Reader instances from various asset types.
pub mod asset_data;

/// The settings module provides a way to configure the C2PA SDK.
pub mod settings;

Expand Down
158 changes: 154 additions & 4 deletions sdk/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ use serde_with::skip_serializing_none;
#[cfg(feature = "file_io")]
use crate::utils::io_utils::uri_to_path;
use crate::{
asset_data::AssetData,
asset_io::CAIRead,
crypto::base64,
dynamic_assertion::PartialClaim,
error::{Error, Result},
Expand Down Expand Up @@ -107,6 +109,149 @@ type ValidationFn =
dyn Fn(&str, &crate::ManifestAssertion, &mut StatusTracker) -> Option<serde_json::Value>;

impl Reader {
/// Create a manifest store [`Reader`] from a [`AssetData`] with specified settings.
/// This is the unified constructor that replaces from_stream, from_file, from_manifest_data_and_stream,
/// from_fragment, and from_fragmented_files methods.
///
/// # Arguments
/// * `asset` - A [`AssetData`] containing the asset data.
/// * `settings` - The [`Settings`] to use for processing.
///
/// # Returns
/// A [`Reader`] for the manifest store.
///
/// # Errors
/// Returns an [`Error`] when the manifest data cannot be read. If there's no error upon reading,
/// you must still check validation status to ensure that the manifest data is validated.
///
/// # Example
/// ```no_run
/// use std::io::Cursor;
///
/// use c2pa::{AssetData, Reader, Settings};
///
/// let settings = Settings::default();
/// let data = include_bytes!("../tests/fixtures/CA.jpg");
/// let asset = AssetData::from_memory(data, "image/jpeg");
/// let reader = Reader::from_asset(asset, &settings).unwrap();
/// println!("{}", reader.json());
/// ```
#[async_generic]
pub fn from_asset(asset: AssetData<'_>, settings: &Settings) -> Result<Reader> {
let mut validation_log = StatusTracker::default();
let verify = settings.verify.verify_after_reading;

let store = match asset {
AssetData::Stream(mut stream, format) => {
if _sync {
Store::from_stream(
format,
stream.as_mut(),
verify,
&mut validation_log,
settings,
)?
} else {
Store::from_stream_async(
format,
stream.as_mut(),
verify,
&mut validation_log,
settings,
)
.await?
}
}
AssetData::StreamWithManifest(mut stream, format, manifest_data) => {
if _sync {
Store::from_manifest_data_and_stream(
manifest_data,
format,
stream.as_mut(),
verify,
&mut validation_log,
settings,
)?
} else {
Store::from_manifest_data_and_stream_async(
manifest_data,
format,
stream.as_mut(),
verify,
&mut validation_log,
settings,
)
.await?
}
}
AssetData::StreamFragment(initial_segment, fragment, format) => {
if _sync {
Store::load_fragment_from_stream(
format,
initial_segment,
fragment,
&mut validation_log,
settings,
)?
} else {
Store::load_fragment_from_stream_async(
format,
initial_segment,
fragment,
&mut validation_log,
settings,
)
.await?
}
}
AssetData::StreamFragments(initial_segment, fragments, format) => {
Store::load_fragments_from_stream(
format,
initial_segment,
fragments,
verify,
&mut validation_log,
settings,
)?
}
AssetData::StreamWithManifestsAndFragments(
initial_segment,
fragments,
format,
manifest_data,
) => {
if _sync {
Store::from_manifest_data_and_stream_and_fragments(
manifest_data,
format,
initial_segment,
fragments,
verify,
&mut validation_log,
settings,
)?
} else {
Store::from_manifest_data_and_stream_and_fragments_async(
manifest_data,
format,
initial_segment,
fragments,
verify,
&mut validation_log,
settings,
)
.await?
}
}
};

if _sync {
Self::from_store(store, &mut validation_log, settings)
} else {
Self::from_store_async(store, &mut validation_log, settings).await
}
}

/// Create a manifest store [`Reader`] from a stream. A Reader is used to validate C2PA data from an asset.
///
/// # Arguments
Expand Down Expand Up @@ -347,7 +492,7 @@ impl Reader {
/// multiple separate asset files.
pub fn from_fragmented_files<P: AsRef<std::path::Path>>(
path: P,
fragments: &Vec<std::path::PathBuf>,
fragments: &[std::path::PathBuf],
) -> Result<Reader> {
let settings = crate::settings::get_settings().unwrap_or_default();

Expand All @@ -359,10 +504,15 @@ impl Reader {

let mut init_segment = std::fs::File::open(path.as_ref())?;

match Store::load_from_file_and_fragments(
let mut fragments: Vec<Box<dyn CAIRead>> = fragments
.iter()
.map(|path| File::open(path).map(|file| Box::new(file) as Box<dyn CAIRead>))
.collect::<Result<_, std::io::Error>>()?;

match Store::load_fragments_from_stream(
&asset_type,
&mut init_segment,
fragments,
&mut fragments,
verify,
&mut validation_log,
&settings,
Expand Down Expand Up @@ -725,7 +875,7 @@ impl Reader {
}

#[async_generic()]
fn from_store(
pub(crate) fn from_store(
store: Store,
validation_log: &mut StatusTracker,
settings: &Settings,
Expand Down
Loading