diff --git a/sdk/src/assertions/bmff_hash.rs b/sdk/src/assertions/bmff_hash.rs index 46d751d20..e29716823 100644 --- a/sdk/src/assertions/bmff_hash.rs +++ b/sdk/src/assertions/bmff_hash.rs @@ -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, + fragment_paths: &mut [Box], alg: Option<&str>, ) -> crate::Result<()> { self.verify_self()?; @@ -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() { diff --git a/sdk/src/claim.rs b/sdk/src/claim.rs index 576e48b2c..a823596ce 100644 --- a/sdk/src/claim.rs +++ b/sdk/src/claim.rs @@ -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, &'a str), + StreamFragments(&'a mut dyn CAIRead, &'a mut [Box], &'a str), } #[derive(PartialEq, Debug, Eq, Clone, Hash)] @@ -2721,7 +2720,6 @@ impl Claim { *fragment_data, Some(claim.alg()), ), - #[cfg(feature = "file_io")] ClaimAssetData::StreamFragments(initseg_data, fragment_paths, _) => dh .verify_stream_segments( *initseg_data, diff --git a/sdk/src/error.rs b/sdk/src/error.rs index 7f87cdd09..651567bbe 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -364,7 +364,7 @@ pub enum Error { } /// A specialized `Result` type for C2PA toolkit operations. -pub type Result = std::result::Result; +pub type Result = std::result::Result; impl From for Error { fn from(err: CoseError) -> Self { diff --git a/sdk/src/reader.rs b/sdk/src/reader.rs index 72089eaed..c9d0dc3cf 100644 --- a/sdk/src/reader.rs +++ b/sdk/src/reader.rs @@ -32,6 +32,7 @@ use serde_with::skip_serializing_none; #[cfg(feature = "file_io")] use crate::utils::io_utils::uri_to_path; use crate::{ + asset_io::CAIRead, crypto::base64, dynamic_assertion::PartialClaim, error::{Error, Result}, @@ -72,6 +73,173 @@ pub trait AsyncPostValidator { ) -> Result>; } +pub struct ReaderBuilder { + settings: Settings, + external_manifest: Option>, + fragments: Option>>, +} + +impl ReaderBuilder { + pub fn new() -> Self { + Self { + settings: Settings::default(), + external_manifest: None, + fragments: None, + } + } + + pub fn with_settings(settings: Settings) -> Self { + Self { + settings, + external_manifest: None, + fragments: None, + } + } + + pub fn external_manifest( + mut self, + external_manifest: impl Read + Seek + Send + 'static, + ) -> Self { + self.external_manifest = Some(Box::new(external_manifest)); + self + } + + pub fn add_fragment(mut self, fragment: impl Read + Seek + Send + 'static) -> Self { + let fragments = self.fragments.get_or_insert_default(); + fragments.push(Box::new(fragment)); + self + } + + // TODO: + // pub fn http_resolver(self, resolver: impl SyncHttpResolver) -> Self { + // todo!() + // } + + #[async_generic] + pub fn build(self, format: &str, mut stream: impl Read + Seek + Send) -> Result { + match self.fragments { + Some(mut fragments) => { + match self.external_manifest { + Some(mut external_manifest) => { + // TODO: internally we actually convert c2pa_data to a stream again, we should modify the function + // signatures to stream by default + let mut c2pa_data = Vec::new(); + external_manifest.read_to_end(&mut c2pa_data)?; + + let mut validation_log = StatusTracker::default(); + if _sync { + let store = Store::from_manifest_data_and_stream_and_fragments( + &c2pa_data, + format, + &mut stream, + &mut fragments, + self.settings.verify.verify_after_reading, + &mut validation_log, + &self.settings, + )?; + Reader::from_store(store, &mut validation_log, &self.settings) + } else { + let store = Store::from_manifest_data_and_stream_and_fragments_async( + &c2pa_data, + format, + &mut stream, + &mut fragments, + self.settings.verify.verify_after_reading, + &mut validation_log, + &self.settings, + ) + .await?; + Reader::from_store_async(store, &mut validation_log, &self.settings) + .await + } + } + None => { + let mut validation_log = StatusTracker::default(); + if _sync { + let store = Store::load_fragments_from_stream( + format, + &mut stream, + &mut fragments, + self.settings.verify.verify_after_reading, + &mut validation_log, + &self.settings, + )?; + Reader::from_store(store, &mut validation_log, &self.settings) + } else { + let store = Store::load_fragments_from_stream_async( + format, + &mut stream, + &mut fragments, + self.settings.verify.verify_after_reading, + &mut validation_log, + &self.settings, + ) + .await?; + Reader::from_store_async(store, &mut validation_log, &self.settings) + .await + } + } + } + } + None => match self.external_manifest { + Some(mut external_manifest) => { + // TODO: internally we actually convert c2pa_data to a stream again, we should modify the function + // signatures to stream by default + let mut c2pa_data = Vec::new(); + external_manifest.read_to_end(&mut c2pa_data)?; + + let mut validation_log = StatusTracker::default(); + if _sync { + let store = Store::from_manifest_data_and_stream( + &c2pa_data, + format, + stream, + self.settings.verify.verify_after_reading, + &mut validation_log, + &self.settings, + )?; + Reader::from_store(store, &mut validation_log, &self.settings) + } else { + let store = Store::from_manifest_data_and_stream_async( + &c2pa_data, + format, + stream, + self.settings.verify.verify_after_reading, + &mut validation_log, + &self.settings, + ) + .await?; + Reader::from_store_async(store, &mut validation_log, &self.settings).await + } + } + None => { + let mut validation_log = StatusTracker::default(); + if _sync { + let store = Store::from_stream( + format, + &mut stream, + self.settings.verify.verify_after_reading, + &mut validation_log, + &self.settings, + )?; + Reader::from_store(store, &mut validation_log, &self.settings) + } else { + let store = Store::from_stream_async( + format, + &mut stream, + self.settings.verify.verify_after_reading, + &mut validation_log, + &self.settings, + ) + .await?; + Reader::from_store_async(store, &mut validation_log, &self.settings).await + } + } + }, + } + } +} + /// Use a Reader to read and validate a manifest store. #[skip_serializing_none] #[derive(Serialize, Deserialize)] @@ -359,10 +527,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> = fragments + .iter() + .map(|path| File::open(path).map(|file| Box::new(file) as Box)) + .collect::>()?; + + match Store::load_fragments_from_stream( &asset_type, &mut init_segment, - fragments, + &mut fragments, verify, &mut validation_log, &settings, diff --git a/sdk/src/store.rs b/sdk/src/store.rs index c4d8489ba..953fe8887 100644 --- a/sdk/src/store.rs +++ b/sdk/src/store.rs @@ -12,7 +12,7 @@ // each license. #[cfg(feature = "file_io")] -use std::path::{Path, PathBuf}; +use std::path::Path; use std::{ collections::{HashMap, HashSet}, io::{Cursor, Read, Seek}, @@ -2031,8 +2031,7 @@ impl Store { let format = typ.to_owned(); object_locations_from_stream(&format, reader) } - #[cfg(feature = "file_io")] - ClaimAssetData::StreamFragments(reader, _path_bufs, typ) => { + ClaimAssetData::StreamFragments(reader, _fragments, typ) => { let format = typ.to_owned(); object_locations_from_stream(&format, reader) } @@ -3856,17 +3855,60 @@ impl Store { Ok(store) } + #[async_generic] + pub fn from_manifest_data_and_stream_and_fragments( + c2pa_data: &[u8], + asset_type: &str, + init_segment: &mut dyn CAIRead, + fragments: &mut [Box], + verify: bool, + validation_log: &mut StatusTracker, + settings: &Settings, + ) -> Result { + init_segment.rewind()?; + + // First we convert the JUMBF into a usable store. + let store = Store::from_jumbf_with_settings(c2pa_data, validation_log, settings) + .inspect_err(|e| { + log_item!("asset", "error loading file", "load_from_asset") + .failure_no_throw(validation_log, e); + })?; + + if verify { + init_segment.rewind()?; + // verify store and claims + if _sync { + Store::verify_store( + &store, + &mut ClaimAssetData::StreamFragments(init_segment, fragments, asset_type), + validation_log, + settings, + )?; + } else { + Store::verify_store_async( + &store, + &mut ClaimAssetData::StreamFragments(init_segment, fragments, asset_type), + validation_log, + settings, + ) + .await?; + } + } + + Ok(store) + } + /// Load Store from a init and fragments /// asset_type: asset extension or mime type /// init_segment: reader for the file containing the initialization segments /// fragments: list of paths to the fragments to verify /// verify: if true will run verification checks when loading, all fragments must verify for Ok status /// validation_log: If present all found errors are logged and returned, otherwise first error causes exit and is returned - #[cfg(feature = "file_io")] - pub fn load_from_file_and_fragments( + #[async_generic] + pub fn load_fragments_from_stream( asset_type: &str, init_segment: &mut dyn CAIRead, - fragments: &Vec, + fragments: &mut [Box], verify: bool, validation_log: &mut StatusTracker, settings: &Settings, @@ -8533,15 +8575,18 @@ pub mod tests { // test verifying all at once let mut output_fragments = Vec::new(); for entry in &fragments { - output_fragments.push(new_output_path.join(entry.file_name().unwrap())); + let file = + std::fs::File::open(new_output_path.join(entry.file_name().unwrap())) + .unwrap(); + output_fragments.push(Box::new(file) as Box); } //let mut reader = Cursor::new(init_stream); let mut validation_log = StatusTracker::default(); - let _manifest = Store::load_from_file_and_fragments( + let _manifest = Store::load_fragments_from_stream( "mp4", &mut init_stream, - &output_fragments, + &mut output_fragments, false, &mut validation_log, &settings,