Skip to content
Closed
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
2 changes: 2 additions & 0 deletions sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ pub use manifest_store::ManifestStore;
#[cfg(feature = "v1_api")]
pub use manifest_store_report::ManifestStoreReport;
pub use reader::Reader;
pub use reader_config::{AssertionReader, ReaderConfig};
pub use resource_store::{ResourceRef, ResourceStore};
pub use signer::{AsyncSigner, RemoteSigner, Signer};
pub use utils::mime::format_from_path;
Expand Down Expand Up @@ -155,6 +156,7 @@ pub(crate) mod manifest_store_report;
#[allow(dead_code)]
// TODO: Remove this when the feature is released (used in tests only for some builds now)
pub(crate) mod reader;
pub(crate) mod reader_config;
pub(crate) mod resource_store;
pub(crate) mod salt;
pub(crate) mod signer;
Expand Down
39 changes: 37 additions & 2 deletions sdk/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use crate::{
store::Store,
validation_results::{ValidationResults, ValidationState},
validation_status::ValidationStatus,
Manifest,
Manifest, ReaderConfig,
};

/// A reader for the manifest store.
Expand Down Expand Up @@ -86,9 +86,44 @@ impl Reader {
/// println!("{}", reader.json());
/// ```
#[async_generic()]
pub fn from_stream(format: &str, mut stream: impl Read + Seek + Send) -> Result<Reader> {
pub fn from_stream(format: &str, stream: impl Read + Seek + Send) -> Result<Reader> {
let config = ReaderConfig::default();

if _sync {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only did it for this one, but I would anticipate doing this same default vs configurable approach for the other entry points.

Self::from_stream_with_config(format, stream, &config)
} else {
Self::from_stream_with_config_async(format, stream, &config).await
}
}

/// Create a manifest store [`Reader`] from a stream. A Reader is used to validate C2PA data from an asset.
/// # Arguments
/// * `format` - The format of the stream. MIME type or extension that maps to a MIME type.
/// * `stream` - The stream to read from. Must implement the Read and Seek traits. (NOTE: Explain Send trait, required for both sync & async?).
/// # 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. That is, even if there are no errors, the data still might not be valid.
/// # Example
/// This example reads from a memory buffer and prints out the JSON manifest data.
/// ```no_run
/// use std::io::Cursor;
///
/// use c2pa::Reader;
/// let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg"));
/// let reader = Reader::from_stream("image/jpeg", stream).unwrap();
/// println!("{}", reader.json());
/// ```
#[async_generic()]
pub fn from_stream_with_config(
format: &str,
mut stream: impl Read + Seek + Send,
config: &ReaderConfig,
) -> Result<Reader> {
let manifest_bytes = Store::load_jumbf_from_stream(format, &mut stream)?;

let _ = config; // TO DO: Actually use the config :-)

if _sync {
Self::from_manifest_data_and_stream(&manifest_bytes, format, &mut stream)
} else {
Expand Down
88 changes: 88 additions & 0 deletions sdk/src/reader_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2025 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

use std::slice::Iter;

use c2pa_status_tracker::StatusTracker;
use serde::Serialize;

use crate::{HashedUri, Manifest, ManifestAssertion, Result};

/// A `ReaderConfig` allows a caller to provide additional information
/// to a [`Reader`] about how the `Reader` should handle specific edge
/// cases.
///
/// [`Reader`]: crate::Reader
#[derive(Default)]
pub struct ReaderConfig {
assertion_readers: Vec<Box<dyn AssertionReader + 'static>>,
}

impl ReaderConfig {
/// Adds an [`AssertionReader`] to this configuration.
///
/// This configures the reader to process one or more C2PA assertion
/// types that are not automatically handled by this crate.
pub fn add_assertion_reader<T: AssertionReader + 'static>(&mut self, assertion_reader: T) {
self.assertion_readers.push(Box::new(assertion_reader));
}
}

/// An implementation of `AssertionReader` extends this crate to add
/// knowledge of one or more C2PA assertion types that are not automatically
/// handled by this crate.
pub trait AssertionReader {
// TO DO: Add an Async variant of this trait once we agree
// on basics.

/// Return `true` if this implementation can process an assertion of the given type.
fn can_process_assertion_with_label(&self, label: &str) -> bool;

/// Check the validity of a [`ManifestAssertion`] and return a
/// [`Serialize`]-able summary of what was found.
///
/// ## Errors vs Validation Status
///
/// The implementation should, to the maximum extent possible,
/// report validation errors (including parsing and network errors)
/// by adding entries to the provided [`StatusTracker`] instance.
///
/// Returning an `Err` result will cause the overall validation process
/// to fail and should be used only as a last resort.
///
/// In the event of more typical error conditions, the implementation
/// should return as much of the data as is available and may omit data
/// that was inaccessible or unavailable.
fn validate(
&self,
manifest: &ReaderManifest,
assertion: &ManifestAssertion,
log: Box<&'static mut dyn StatusTracker>,
// ^^ PROBLEM: StatusTracker can't be made into an object
) -> Result<Box<dyn Serialize>>;
// ^^ PROBLEM: Serialize can't be made into an object
}

/// A `ReaderManifest` contains a preliminary description of a C2PA Manifest which can be inspected by the [`AssertionReader`] in its [`validate`] method.
///
/// [`validate`]: AssertionReader::validate
pub struct ReaderManifest<'a> {
manifest: &'a Manifest, // private: could be replaced by anything
}

impl<'a> ReaderManifest<'a> {
/// Return an iterator over the assertions in this manifest.
pub fn assertions(&'a self) -> Iter<'a, HashedUri> {
todo!();
}
}
Loading