c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

manifest.rs (100665B)


      1 // Copyright 2022 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 use std::{borrow::Cow, collections::HashMap, io::Cursor};
     15 #[cfg(feature = "file_io")]
     16 use std::{fs::create_dir_all, path::Path};
     17 
     18 use async_generic::async_generic;
     19 #[cfg(feature = "json_schema")]
     20 use schemars::JsonSchema;
     21 use serde::{de::DeserializeOwned, Deserialize, Serialize};
     22 use serde_json::Value;
     23 use tracing::{debug, error};
     24 use uuid::Uuid;
     25 
     26 use crate::{
     27     assertion::{AssertionBase, AssertionData},
     28     assertions::{
     29         labels, Actions, CreativeWork, DataHash, Exif, SoftwareAgent, Thumbnail, User, UserCbor,
     30     },
     31     asset_io::{CAIRead, CAIReadWrite},
     32     claim::{Claim, RemoteManifest},
     33     error::{Error, Result},
     34     ingredient::Ingredient,
     35     jumbf,
     36     manifest_assertion::ManifestAssertion,
     37     resource_store::{mime_from_uri, skip_serializing_resources, ResourceRef, ResourceStore},
     38     salt::DefaultSalt,
     39     store::Store,
     40     AsyncSigner, ClaimGeneratorInfo, HashRange, ManifestAssertionKind, RemoteSigner, Signer,
     41     SigningAlg,
     42 };
     43 
     44 /// A Manifest represents all the information in a c2pa manifest
     45 #[derive(Debug, Default, Deserialize, Serialize)]
     46 #[cfg_attr(feature = "json_schema", derive(JsonSchema))]
     47 pub struct Manifest {
     48     /// Optional prefix added to the generated Manifest Label
     49     /// This is typically Internet domain name for the vendor (i.e. `adobe`)
     50     #[serde(skip_serializing_if = "Option::is_none")]
     51     vendor: Option<String>,
     52 
     53     /// A User Agent formatted string identifying the software/hardware/system produced this claim
     54     /// Spaces are not allowed in names, versions can be specified with product/1.0 syntax
     55     #[serde(default = "default_claim_generator")]
     56     pub claim_generator: String,
     57 
     58     /// A list of claim generator info data identifying the software/hardware/system produced this claim
     59     #[serde(skip_serializing_if = "Option::is_none")]
     60     pub claim_generator_info: Option<Vec<ClaimGeneratorInfo>>,
     61 
     62     /// A human-readable title, generally source filename.
     63     #[serde(skip_serializing_if = "Option::is_none")]
     64     title: Option<String>,
     65 
     66     /// The format of the source file as a MIME type.
     67     #[serde(default = "default_format")]
     68     format: String,
     69 
     70     /// Instance ID from `xmpMM:InstanceID` in XMP metadata.
     71     #[serde(default = "default_instance_id")]
     72     instance_id: String,
     73 
     74     #[serde(skip_serializing_if = "Option::is_none")]
     75     claim_generator_hints: Option<HashMap<String, Value>>,
     76 
     77     #[serde(skip_serializing_if = "Option::is_none")]
     78     thumbnail: Option<ResourceRef>,
     79 
     80     /// A List of ingredients
     81     #[serde(default = "default_vec::<Ingredient>")]
     82     ingredients: Vec<Ingredient>,
     83 
     84     /// A List of verified credentials
     85     #[serde(skip_serializing_if = "Option::is_none")]
     86     credentials: Option<Vec<Value>>,
     87 
     88     /// A list of assertions
     89     #[serde(default = "default_vec::<ManifestAssertion>")]
     90     assertions: Vec<ManifestAssertion>,
     91 
     92     /// A list of redactions - URIs to a redacted assertions
     93     #[serde(skip_serializing_if = "Option::is_none")]
     94     redactions: Option<Vec<String>>,
     95 
     96     /// Signature data (only used for reporting)
     97     #[serde(skip_serializing_if = "Option::is_none")]
     98     signature_info: Option<SignatureInfo>,
     99 
    100     #[serde(skip_serializing_if = "Option::is_none")]
    101     label: Option<String>,
    102 
    103     /// Indicates where a generated manifest goes
    104     #[serde(skip)]
    105     remote_manifest: Option<RemoteManifest>,
    106 
    107     /// container for binary assets (like thumbnails)
    108     #[serde(skip_deserializing)]
    109     #[serde(skip_serializing_if = "skip_serializing_resources")]
    110     resources: ResourceStore,
    111 }
    112 
    113 fn default_claim_generator() -> String {
    114     format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))
    115 }
    116 
    117 fn default_instance_id() -> String {
    118     format!("xmp:iid:{}", Uuid::new_v4())
    119 }
    120 
    121 fn default_format() -> String {
    122     "application/octet-stream".to_owned()
    123 }
    124 
    125 const fn default_vec<T>() -> Vec<T> {
    126     Vec::new()
    127 }
    128 
    129 impl Manifest {
    130     /// Create a new Manifest
    131     /// requires a claim_generator string (User Agent))
    132     pub fn new<S: Into<String>>(claim_generator: S) -> Self {
    133         Self {
    134             claim_generator: claim_generator.into(),
    135             format: default_format(),
    136             instance_id: default_instance_id(),
    137             ..Default::default()
    138         }
    139     }
    140 
    141     /// Returns a User Agent formatted string identifying the software/hardware/system produced this claim
    142     pub fn claim_generator(&self) -> &str {
    143         self.claim_generator.as_str()
    144     }
    145 
    146     /// returns the manifest label for this Manifest, as referenced in a ManifestStore
    147     pub fn label(&self) -> Option<&str> {
    148         self.label.as_deref()
    149     }
    150 
    151     /// Returns a MIME content_type for the asset associated with this manifest.
    152     pub fn format(&self) -> &str {
    153         &self.format
    154     }
    155 
    156     /// Returns the instance identifier.
    157     pub fn instance_id(&self) -> &str {
    158         &self.instance_id
    159     }
    160 
    161     /// Returns a user-displayable title for this manifest
    162     pub fn title(&self) -> Option<&str> {
    163         self.title.as_deref()
    164     }
    165 
    166     /// Returns thumbnail tuple with Some((format, bytes)) or None
    167     pub fn thumbnail(&self) -> Option<(&str, Cow<Vec<u8>>)> {
    168         self.thumbnail
    169             .as_ref()
    170             .and_then(|t| Some(t.format.as_str()).zip(self.resources.get(&t.identifier).ok()))
    171     }
    172 
    173     /// Returns a thumbnail ResourceRef or `None`.
    174     pub const fn thumbnail_ref(&self) -> Option<&ResourceRef> {
    175         self.thumbnail.as_ref()
    176     }
    177 
    178     /// Returns immutable [Ingredient]s used by this Manifest
    179     /// This can include a parent as well as any placed assets
    180     pub fn ingredients(&self) -> &[Ingredient] {
    181         &self.ingredients
    182     }
    183 
    184     /// Returns mutable [Ingredient]s used by this Manifest
    185     /// This can include a parent as well as any placed assets
    186     pub fn ingredients_mut(&mut self) -> &mut [Ingredient] {
    187         &mut self.ingredients
    188     }
    189 
    190     /// Returns Assertions for this Manifest
    191     pub fn assertions(&self) -> &[ManifestAssertion] {
    192         &self.assertions
    193     }
    194 
    195     /// Returns Verifiable Credentials
    196     pub fn credentials(&self) -> Option<&[Value]> {
    197         self.credentials.as_deref()
    198     }
    199 
    200     /// Returns the remote_manifest Url if there is one
    201     /// This is only used when creating a manifest, it will always be None when reading
    202     pub fn remote_manifest_url(&self) -> Option<&str> {
    203         match self.remote_manifest.as_ref() {
    204             Some(RemoteManifest::Remote(url)) => Some(url.as_str()),
    205             Some(RemoteManifest::EmbedWithRemote(url)) => Some(url.as_str()),
    206             _ => None,
    207         }
    208     }
    209 
    210     /// Sets the vendor prefix to be used when generating manifest labels
    211     /// Optional prefix added to the generated Manifest Label
    212     /// This is typically a lower case Internet domain name for the vendor (i.e. `adobe`)
    213     pub fn set_vendor<S: Into<String>>(&mut self, vendor: S) -> &mut Self {
    214         self.vendor = Some(vendor.into());
    215         self
    216     }
    217 
    218     /// Sets the label for this manifest
    219     /// A label will be generated if this is not called
    220     /// This is needed if embedding a URL that references the manifest label
    221     pub fn set_label<S: Into<String>>(&mut self, label: S) -> &mut Self {
    222         self.label = Some(label.into());
    223         self
    224     }
    225 
    226     /// Sets a human readable name for the product that created this manifest
    227     pub fn set_claim_generator<S: Into<String>>(&mut self, generator: S) -> &mut Self {
    228         self.claim_generator = generator.into();
    229         self
    230     }
    231 
    232     /// Sets a human-readable title for this ingredient.
    233     pub fn set_format<S: Into<String>>(&mut self, format: S) -> &mut Self {
    234         self.format = format.into();
    235         self
    236     }
    237 
    238     /// Sets a human-readable title for this ingredient.
    239     pub fn set_instance_id<S: Into<String>>(&mut self, instance_id: S) -> &mut Self {
    240         self.instance_id = instance_id.into();
    241         self
    242     }
    243 
    244     /// Sets a human-readable title for this ingredient.
    245     pub fn set_title<S: Into<String>>(&mut self, title: S) -> &mut Self {
    246         self.title = Some(title.into());
    247         self
    248     }
    249 
    250     /// Sets the thumbnail from a ResourceRef.
    251     pub fn set_thumbnail_ref(&mut self, thumbnail: ResourceRef) -> Result<&mut Self> {
    252         // verify the resource referenced exists
    253         if thumbnail.format != "none" && !self.resources.exists(&thumbnail.identifier) {
    254             return Err(Error::NotFound);
    255         };
    256         self.thumbnail = Some(thumbnail);
    257         Ok(self)
    258     }
    259 
    260     /// Sets the thumbnail format and image data.
    261     pub fn set_thumbnail<S: Into<String>, B: Into<Vec<u8>>>(
    262         &mut self,
    263         format: S,
    264         thumbnail: B,
    265     ) -> Result<&mut Self> {
    266         let base_id = self
    267             .label()
    268             .unwrap_or_else(|| self.instance_id())
    269             .to_string();
    270         self.thumbnail = Some(
    271             self.resources
    272                 .add_with(&base_id, &format.into(), thumbnail)?,
    273         );
    274         Ok(self)
    275     }
    276 
    277     /// If set, the embed calls will create a sidecar .c2pa manifest file next to the output file
    278     /// No change will be made to the output file
    279     pub fn set_sidecar_manifest(&mut self) -> &mut Self {
    280         self.remote_manifest = Some(RemoteManifest::SideCar);
    281         self
    282     }
    283 
    284     /// If set, the embed calls will put the remote url into the output file xmp provenance
    285     /// and create a c2pa manifest file next to the output file
    286     pub fn set_remote_manifest<S: Into<String>>(&mut self, remote_url: S) -> &mut Self {
    287         self.remote_manifest = Some(RemoteManifest::Remote(remote_url.into()));
    288         self
    289     }
    290 
    291     /// If set, the embed calls will put the remote url into the output file xmp provenance
    292     /// and will embed the manifest into the output file
    293     pub fn set_embedded_manifest_with_remote_ref<S: Into<String>>(
    294         &mut self,
    295         remote_url: S,
    296     ) -> &mut Self {
    297         self.remote_manifest = Some(RemoteManifest::EmbedWithRemote(remote_url.into()));
    298         self
    299     }
    300 
    301     pub const fn signature_info(&self) -> Option<&SignatureInfo> {
    302         self.signature_info.as_ref()
    303     }
    304 
    305     /// Returns the parent ingredient if it exists
    306     pub fn parent(&self) -> Option<&Ingredient> {
    307         self.ingredients.iter().find(|i| i.is_parent())
    308     }
    309 
    310     /// Sets the parent ingredient, assuring it is first and setting the is_parent flag
    311     pub fn set_parent(&mut self, mut ingredient: Ingredient) -> Result<&mut Self> {
    312         // there should only be one parent so return an error if we already have one
    313         if self.parent().is_some() {
    314             error!("parent already added");
    315             return Err(Error::BadParam("Parent parent already added".to_owned()));
    316         }
    317         ingredient.set_is_parent();
    318         self.ingredients.insert(0, ingredient);
    319 
    320         Ok(self)
    321     }
    322 
    323     /// Add an ingredient removing duplicates (consumes the asset)
    324     pub fn add_ingredient(&mut self, ingredient: Ingredient) -> &mut Self {
    325         self.ingredients.push(ingredient);
    326         self
    327     }
    328 
    329     /// Adds assertion using given label and any serde serializable
    330     /// The data for predefined assertions must be in correct format
    331     ///
    332     /// # Example: Creating a custom assertion from a serde_json object.
    333     ///```
    334     /// # use c2pa::Result;
    335     /// use c2pa::Manifest;
    336     /// use serde_json::json;
    337     /// # fn main() -> Result<()> {
    338     /// let mut manifest = Manifest::new("my_app");
    339     /// let value = json!({"my_tag": "Anything I want"});
    340     /// manifest.add_labeled_assertion("org.contentauth.foo", &value)?;
    341     /// # Ok(())
    342     /// # }
    343     /// ```
    344     pub fn add_labeled_assertion<S: Into<String>, T: Serialize>(
    345         &mut self,
    346         label: S,
    347         data: &T,
    348     ) -> Result<&mut Self> {
    349         self.assertions
    350             .push(ManifestAssertion::from_labeled_assertion(label, data)?);
    351         Ok(self)
    352     }
    353 
    354     /// Adds ManifestAssertions from existing assertions
    355     /// The data for standard assertions must be in correct format
    356     ///
    357     /// # Example: Creating a from an Actions object.
    358     ///```
    359     /// # use c2pa::Result;
    360     /// use c2pa::{
    361     ///     assertions::{c2pa_action, Action, Actions},
    362     ///     Manifest,
    363     /// };
    364     /// # fn main() -> Result<()> {
    365     /// let mut manifest = Manifest::new("my_app");
    366     /// let actions = Actions::new().add_action(Action::new(c2pa_action::EDITED));
    367     /// manifest.add_assertion(&actions)?;
    368     /// # Ok(())
    369     /// # }
    370     /// ```
    371     pub fn add_assertion<T: Serialize + AssertionBase>(&mut self, data: &T) -> Result<&mut Self> {
    372         self.assertions
    373             .push(ManifestAssertion::from_assertion(data)?);
    374         Ok(self)
    375     }
    376 
    377     /// Retrieves an assertion by label if it exists or Error::NotFound
    378     ///
    379     /// Example: Find an Actions Assertion
    380     /// ```
    381     /// # use c2pa::Result;
    382     /// use c2pa::{
    383     ///     assertions::{c2pa_action, Action, Actions},
    384     ///     Manifest,
    385     /// };
    386     /// # fn main() -> Result<()> {
    387     /// let mut manifest = Manifest::new("my_app");
    388     /// let actions = Actions::new().add_action(Action::new(c2pa_action::EDITED));
    389     /// manifest.add_assertion(&actions)?;
    390     ///
    391     /// let actions: Actions = manifest.find_assertion(Actions::LABEL)?;
    392     /// for action in actions.actions {
    393     ///     println!("{}", action.action());
    394     /// }
    395     /// # Ok(())
    396     /// # }
    397     /// ```
    398     pub fn find_assertion<T: DeserializeOwned>(&self, label: &str) -> Result<T> {
    399         if let Some(manifest_assertion) = self.assertions.iter().find(|a| a.label() == label) {
    400             manifest_assertion.to_assertion()
    401         } else {
    402             Err(Error::NotFound)
    403         }
    404     }
    405 
    406     /// Retrieves an assertion by label and instance if it exists or Error::NotFound
    407     pub fn find_assertion_with_instance<T: DeserializeOwned>(
    408         &self,
    409         label: &str,
    410         instance: usize,
    411     ) -> Result<T> {
    412         if let Some(manifest_assertion) = self
    413             .assertions
    414             .iter()
    415             .find(|a| a.label() == label && a.instance() == instance)
    416         {
    417             manifest_assertion.to_assertion()
    418         } else {
    419             Err(Error::NotFound)
    420         }
    421     }
    422 
    423     /// Redacts an assertion from the parent [Ingredient] of this manifest using the provided
    424     /// assertion label.
    425     pub fn add_redaction<S: Into<String>>(&mut self, label: S) -> Result<&mut Self> {
    426         // todo: any way to verify if this assertion exists in the parent claim here?
    427         match self.redactions.as_mut() {
    428             Some(redactions) => redactions.push(label.into()),
    429             None => self.redactions = Some([label.into()].to_vec()),
    430         }
    431         Ok(self)
    432     }
    433 
    434     /// Add verifiable credentials
    435     pub fn add_verifiable_credential<T: Serialize>(&mut self, data: &T) -> Result<&mut Self> {
    436         let value = serde_json::to_value(data).map_err(|_err| Error::AssertionEncoding)?;
    437         match self.credentials.as_mut() {
    438             Some(credentials) => credentials.push(value),
    439             None => self.credentials = Some([value].to_vec()),
    440         }
    441         Ok(self)
    442     }
    443 
    444     /// Returns the name of the signature issuer
    445     pub fn issuer(&self) -> Option<String> {
    446         self.signature_info.to_owned().and_then(|sig| sig.issuer)
    447     }
    448 
    449     /// Returns the time that the manifest was signed
    450     pub fn time(&self) -> Option<String> {
    451         self.signature_info.to_owned().and_then(|sig| sig.time)
    452     }
    453 
    454     /// Returns an iterator over [`ResourceRef`][ResourceRef]s.
    455     pub fn iter_resources(&self) -> impl Iterator<Item = ResourceRef> + '_ {
    456         self.resources
    457             .resources()
    458             .keys()
    459             .map(|uri| ResourceRef::new(mime_from_uri(uri), uri.to_owned()))
    460     }
    461 
    462     /// Return an immutable reference to the manifest resources
    463     pub const fn resources(&self) -> &ResourceStore {
    464         &self.resources
    465     }
    466 
    467     /// Return a mutable reference to the manifest resources
    468     pub fn resources_mut(&mut self) -> &mut ResourceStore {
    469         &mut self.resources
    470     }
    471 
    472     /// Creates a Manifest from a JSON string formatted as a Manifest
    473     pub fn from_json(json: &str) -> Result<Self> {
    474         serde_json::from_slice(json.as_bytes()).map_err(Error::JsonError)
    475     }
    476 
    477     /// Setting a base path will make the manifest use resource files instead of memory buffers
    478     ///
    479     /// The files will be relative to the given base path
    480     /// Ingredients resources will also be relative to this path
    481     #[cfg(feature = "file_io")]
    482     pub fn with_base_path<P: AsRef<Path>>(&mut self, base_path: P) -> Result<&Self> {
    483         create_dir_all(&base_path)?;
    484         self.resources.set_base_path(base_path.as_ref());
    485         for i in 0..self.ingredients.len() {
    486             // todo: create different subpath for each ingredient?
    487             self.ingredients[i].with_base_path(base_path.as_ref())?;
    488         }
    489         Ok(self)
    490     }
    491 
    492     // Generates a Manifest given a store and a manifest label
    493     pub(crate) fn from_store(
    494         store: &Store,
    495         manifest_label: &str,
    496         #[cfg(feature = "file_io")] resource_path: Option<&Path>,
    497     ) -> Result<Self> {
    498         let claim = store
    499             .get_claim(manifest_label)
    500             .ok_or_else(|| Error::ClaimMissing {
    501                 label: manifest_label.to_owned(),
    502             })?;
    503 
    504         // extract vendor from claim label
    505         let claim_generator = claim.claim_generator().to_owned();
    506 
    507         let mut manifest = Manifest::new(claim_generator);
    508 
    509         #[cfg(feature = "file_io")]
    510         if let Some(base_path) = resource_path {
    511             manifest.with_base_path(base_path)?;
    512         }
    513 
    514         if let Some(info_vec) = claim.claim_generator_info() {
    515             let mut generators = Vec::new();
    516             for claim_info in info_vec {
    517                 let mut info = claim_info.to_owned();
    518                 if let Some(icon) = claim_info.icon.as_ref() {
    519                     info.set_icon(icon.to_resource_ref(manifest.resources_mut(), claim)?);
    520                 }
    521                 generators.push(info);
    522             }
    523             manifest.claim_generator_info = Some(generators);
    524         }
    525 
    526         manifest.set_label(claim.label());
    527         manifest.resources.set_label(claim.label()); // default manifest for relative urls
    528         manifest.claim_generator_hints = claim.get_claim_generator_hint_map().cloned();
    529 
    530         // get credentials converting from AssertionData to Value
    531         let credentials: Vec<Value> = claim
    532             .get_verifiable_credentials()
    533             .iter()
    534             .filter_map(|d| match d {
    535                 AssertionData::Json(s) => serde_json::from_str(s).ok(),
    536                 _ => None,
    537             })
    538             .collect();
    539 
    540         if !credentials.is_empty() {
    541             manifest.credentials = Some(credentials);
    542         }
    543 
    544         manifest.redactions = claim.redactions().map(|rs| {
    545             rs.iter()
    546                 .filter_map(|r| jumbf::labels::assertion_label_from_uri(r))
    547                 .collect()
    548         });
    549 
    550         if let Some(title) = claim.title() {
    551             manifest.set_title(title);
    552         }
    553         manifest.set_format(claim.format());
    554         manifest.set_instance_id(claim.instance_id());
    555 
    556         for assertion in claim.assertions() {
    557             let claim_assertion = store.get_claim_assertion_from_uri(
    558                 &jumbf::labels::to_absolute_uri(claim.label(), &assertion.url()),
    559             )?;
    560             let assertion = claim_assertion.assertion();
    561             let label = claim_assertion.label();
    562             let base_label = assertion.label();
    563             debug!("assertion = {}", &label);
    564             match base_label.as_ref() {
    565                 base if base.starts_with(labels::ACTIONS) => {
    566                     let mut actions = Actions::from_assertion(assertion)?;
    567 
    568                     for action in actions.actions_mut() {
    569                         if let Some(SoftwareAgent::ClaimGeneratorInfo(info)) =
    570                             action.software_agent_mut()
    571                         {
    572                             if let Some(icon) = info.icon.as_mut() {
    573                                 let icon = icon.to_resource_ref(manifest.resources_mut(), claim)?;
    574                                 info.set_icon(icon);
    575                             }
    576                         }
    577                     }
    578 
    579                     // convert icons in templates to resource refs
    580                     if let Some(templates) = actions.templates.as_mut() {
    581                         for template in templates {
    582                             // replace icon with resource ref
    583                             template.icon = match template.icon.take() {
    584                                 Some(icon) => {
    585                                     Some(icon.to_resource_ref(manifest.resources_mut(), claim)?)
    586                                 }
    587                                 None => None,
    588                             };
    589 
    590                             // replace software agent with resource ref
    591                             template.software_agent = match template.software_agent.take() {
    592                                 Some(SoftwareAgent::ClaimGeneratorInfo(mut info)) => {
    593                                     if let Some(icon) = info.icon.as_mut() {
    594                                         let icon =
    595                                             icon.to_resource_ref(manifest.resources_mut(), claim)?;
    596                                         info.set_icon(icon);
    597                                     }
    598                                     Some(SoftwareAgent::ClaimGeneratorInfo(info))
    599                                 }
    600                                 agent => agent,
    601                             };
    602                         }
    603                     }
    604                     let manifest_assertion = ManifestAssertion::from_assertion(&actions)?
    605                         .set_instance(claim_assertion.instance());
    606                     manifest.assertions.push(manifest_assertion);
    607                 }
    608                 base if base.starts_with(labels::INGREDIENT) => {
    609                     // note that we use the original label here, not the base label
    610                     let assertion_uri = jumbf::labels::to_assertion_uri(claim.label(), &label);
    611                     let ingredient = Ingredient::from_ingredient_uri(
    612                         store,
    613                         manifest_label,
    614                         &assertion_uri,
    615                         #[cfg(feature = "file_io")]
    616                         resource_path,
    617                     )?;
    618                     manifest.add_ingredient(ingredient);
    619                 }
    620                 labels::DATA_HASH | labels::BMFF_HASH | labels::BOX_HASH => {
    621                     // do not include data hash when reading manifests
    622                 }
    623                 label if label.starts_with(labels::CLAIM_THUMBNAIL) => {
    624                     let thumbnail = Thumbnail::from_assertion(assertion)?;
    625                     let id = jumbf::labels::to_assertion_uri(claim.label(), label);
    626                     let id = jumbf::labels::to_relative_uri(&id);
    627                     manifest.thumbnail = Some(manifest.resources.add_uri(
    628                         &id,
    629                         &thumbnail.content_type,
    630                         thumbnail.data,
    631                     )?);
    632                 }
    633                 _ => {
    634                     // inject assertions for all other assertions
    635                     match assertion.decode_data() {
    636                         AssertionData::Cbor(_) => {
    637                             let value = assertion.as_json_object()?;
    638                             let ma = ManifestAssertion::new(base_label, value)
    639                                 .set_instance(claim_assertion.instance());
    640                             manifest.assertions.push(ma);
    641                         }
    642                         AssertionData::Json(_) => {
    643                             let value = assertion.as_json_object()?;
    644                             let ma = ManifestAssertion::new(base_label, value)
    645                                 .set_instance(claim_assertion.instance())
    646                                 .set_kind(ManifestAssertionKind::Json);
    647 
    648                             manifest.assertions.push(ma);
    649                         }
    650 
    651                         // todo: support binary forms
    652                         AssertionData::Binary(_x) => {}
    653                         AssertionData::Uuid(_, _) => {}
    654                     }
    655                 }
    656             }
    657         }
    658 
    659         manifest.signature_info = match claim.signature_info() {
    660             Some(signature_info) => Some(SignatureInfo {
    661                 alg: signature_info.alg,
    662                 issuer: signature_info.issuer_org,
    663                 time: signature_info.date.map(|d| d.to_rfc3339()),
    664                 cert_serial_number: signature_info.cert_serial_number.map(|s| s.to_string()),
    665                 cert_chain: String::from_utf8(signature_info.cert_chain)
    666                     .map_err(|_e| Error::CoseInvalidCert)?,
    667                 revocation_status: signature_info.revocation_status,
    668             }),
    669             None => None,
    670         };
    671 
    672         Ok(manifest)
    673     }
    674 
    675     /// Sets the asset field from data in a file
    676     /// the information in the claim should reflect the state of the asset it is embedded in
    677     /// this method can be used to ensure that data is correct
    678     /// it will extract filename,format and xmp info and generate a thumbnail
    679     #[cfg(feature = "file_io")]
    680     pub fn set_asset_from_path<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
    681         // Gather the information we need from the target path
    682         let ingredient = Ingredient::from_file_info(path.as_ref());
    683 
    684         self.set_format(ingredient.format());
    685         self.set_instance_id(ingredient.instance_id());
    686 
    687         // if there is already an asset title preserve it
    688         if self.title().is_none() {
    689             self.set_title(ingredient.title());
    690         }
    691 
    692         // if a thumbnail is not already defined, create one here
    693         if self.thumbnail_ref().is_none() {
    694             #[cfg(feature = "add_thumbnails")]
    695             if let Ok((format, image)) = crate::utils::thumbnail::make_thumbnail(path.as_ref()) {
    696                 // Do not write this as a file when reading from files
    697                 let base_path = self.resources_mut().take_base_path();
    698                 self.set_thumbnail(format, image)?;
    699                 if let Some(path) = base_path {
    700                     self.resources_mut().set_base_path(path)
    701                 }
    702             }
    703         }
    704         Ok(())
    705     }
    706 
    707     // Convert a Manifest into a Claim
    708     pub(crate) fn to_claim(&self) -> Result<Claim> {
    709         // add library identifier to claim_generator
    710         let generator = format!(
    711             "{} {}/{}",
    712             &self.claim_generator,
    713             crate::NAME,
    714             crate::VERSION
    715         );
    716 
    717         let mut claim = match self.label() {
    718             Some(label) => Claim::new_with_user_guid(&generator, &label.to_string()),
    719             None => Claim::new(&generator, self.vendor.as_deref()),
    720         };
    721 
    722         if let Some(info_vec) = self.claim_generator_info.as_ref() {
    723             for info in info_vec {
    724                 let mut claim_info = info.to_owned();
    725                 if let Some(icon) = claim_info.icon.as_ref() {
    726                     claim_info.icon = Some(icon.to_hashed_uri(self.resources(), &mut claim)?);
    727                 }
    728                 claim.add_claim_generator_info(claim_info);
    729             }
    730         }
    731 
    732         if let Some(remote_op) = &self.remote_manifest {
    733             match remote_op {
    734                 RemoteManifest::NoRemote => (),
    735                 RemoteManifest::SideCar => claim.set_external_manifest(),
    736                 RemoteManifest::Remote(r) => claim.set_remote_manifest(r)?,
    737                 RemoteManifest::EmbedWithRemote(r) => claim.set_embed_remote_manifest(r)?,
    738             };
    739         }
    740 
    741         if let Some(title) = self.title() {
    742             claim.set_title(Some(title.to_owned()));
    743         }
    744         self.format().clone_into(&mut claim.format);
    745         self.instance_id().clone_into(&mut claim.instance_id);
    746 
    747         if let Some(thumb_ref) = self.thumbnail_ref() {
    748             // Setting the format to "none" will ensure that no claim thumbnail is added
    749             if thumb_ref.format != "none" {
    750                 let data = self.resources.get(&thumb_ref.identifier)?;
    751                 claim.add_assertion(&Thumbnail::new(
    752                     &labels::add_thumbnail_format(labels::CLAIM_THUMBNAIL, &thumb_ref.format),
    753                     data.into_owned(),
    754                 ))?;
    755             }
    756         }
    757 
    758         // add any verified credentials - needs to happen early so we can reference them
    759         let mut vc_table = HashMap::new();
    760         if let Some(verified_credentials) = self.credentials.as_ref() {
    761             for vc in verified_credentials {
    762                 let vc_str = &vc.to_string();
    763                 let id = Claim::vc_id(vc_str)?;
    764                 vc_table.insert(id, claim.add_verifiable_credential(vc_str)?);
    765             }
    766         }
    767 
    768         let mut ingredient_map = HashMap::new();
    769         // add all ingredients to the claim
    770         for ingredient in &self.ingredients {
    771             let uri = ingredient.add_to_claim(&mut claim, self.redactions.clone(), None)?;
    772             ingredient_map.insert(ingredient.instance_id(), uri);
    773         }
    774 
    775         let salt = DefaultSalt::default();
    776 
    777         // add any additional assertions
    778         for manifest_assertion in &self.assertions {
    779             match manifest_assertion.label() {
    780                 l if l.starts_with(Actions::LABEL) => {
    781                     let version = labels::version(l);
    782 
    783                     let mut actions: Actions = manifest_assertion.to_assertion()?;
    784 
    785                     let ingredients_key = match version {
    786                         None | Some(1) => "ingredient",
    787                         Some(2) => "ingredients",
    788                         _ => return Err(Error::AssertionUnsupportedVersion),
    789                     };
    790 
    791                     // fixup parameters field from instance_id to ingredient uri
    792                     let needs_ingredient: Vec<(usize, crate::assertions::Action)> = actions
    793                         .actions()
    794                         .iter()
    795                         .enumerate()
    796                         .filter_map(|(i, a)| {
    797                             if a.instance_id().is_some()
    798                                 && a.get_parameter(ingredients_key).is_none()
    799                             {
    800                                 Some((i, a.clone()))
    801                             } else {
    802                                 None
    803                             }
    804                         })
    805                         .collect();
    806 
    807                     for (index, action) in needs_ingredient {
    808                         if let Some(id) = action.instance_id() {
    809                             if let Some(hash_url) = ingredient_map.get(id) {
    810                                 let update = match ingredients_key {
    811                                     "ingredient" => {
    812                                         action.set_parameter(ingredients_key, hash_url.clone())
    813                                     }
    814                                     _ => {
    815                                         // we only support on instanceId for actions, so only one ingredient on writing
    816                                         action.set_parameter(ingredients_key, [hash_url.clone()])
    817                                     }
    818                                 }?;
    819                                 actions = actions.update_action(index, update);
    820                             }
    821                         }
    822                     }
    823 
    824                     if let Some(templates) = actions.templates.as_mut() {
    825                         for template in templates {
    826                             // replace icon with hashed_uri
    827                             template.icon = match template.icon.take() {
    828                                 Some(icon) => {
    829                                     Some(icon.to_hashed_uri(self.resources(), &mut claim)?)
    830                                 }
    831                                 None => None,
    832                             };
    833 
    834                             // replace software agent with hashed_uri
    835                             template.software_agent = match template.software_agent.take() {
    836                                 Some(SoftwareAgent::ClaimGeneratorInfo(mut info)) => {
    837                                     if let Some(icon) = info.icon.as_mut() {
    838                                         let icon =
    839                                             icon.to_hashed_uri(self.resources(), &mut claim)?;
    840                                         info.set_icon(icon);
    841                                     }
    842                                     Some(SoftwareAgent::ClaimGeneratorInfo(info))
    843                                 }
    844                                 agent => agent,
    845                             };
    846                         }
    847                     }
    848 
    849                     // convert icons in software agents to hashed uris
    850                     let actions_mut = actions.actions_mut();
    851                     #[allow(clippy::needless_range_loop)]
    852                     // clippy is wrong here, we reference index twice
    853                     for index in 0..actions_mut.len() {
    854                         let action = &actions_mut[index];
    855                         if let Some(SoftwareAgent::ClaimGeneratorInfo(info)) =
    856                             action.software_agent()
    857                         {
    858                             if let Some(icon) = info.icon.as_ref() {
    859                                 let mut info = info.to_owned();
    860                                 let icon_uri = icon.to_hashed_uri(self.resources(), &mut claim)?;
    861                                 let update = info.set_icon(icon_uri);
    862                                 let mut action = action.to_owned();
    863                                 action = action.set_software_agent(update.to_owned());
    864                                 actions_mut[index] = action;
    865                             }
    866                         }
    867                     }
    868 
    869                     claim.add_assertion(&actions)
    870                 }
    871                 CreativeWork::LABEL => {
    872                     let mut cw: CreativeWork = manifest_assertion.to_assertion()?;
    873                     // insert a credentials field if we have a vc that matches the identifier
    874                     // todo: this should apply to any person, not just author
    875                     if let Some(cw_authors) = cw.author() {
    876                         let mut authors = Vec::new();
    877                         for a in cw_authors {
    878                             authors.push(
    879                                 a.identifier()
    880                                     .and_then(|i| {
    881                                         vc_table
    882                                             .get(&i)
    883                                             .map(|uri| a.clone().add_credential(uri.clone()))
    884                                     })
    885                                     .unwrap_or_else(|| Ok(a.clone()))?,
    886                             );
    887                         }
    888                         cw = cw.set_author(&authors)?;
    889                     }
    890                     claim.add_assertion_with_salt(&cw, &salt)
    891                 }
    892                 Exif::LABEL => {
    893                     let exif: Exif = manifest_assertion.to_assertion()?;
    894                     claim.add_assertion_with_salt(&exif, &salt)
    895                 }
    896                 _ => match manifest_assertion.kind() {
    897                     ManifestAssertionKind::Cbor => claim.add_assertion_with_salt(
    898                         &UserCbor::new(
    899                             manifest_assertion.label(),
    900                             serde_cbor::to_vec(&manifest_assertion.value()?)?,
    901                         ),
    902                         &salt,
    903                     ),
    904                     ManifestAssertionKind::Json => claim.add_assertion_with_salt(
    905                         &User::new(
    906                             manifest_assertion.label(),
    907                             &serde_json::to_string(&manifest_assertion.value()?)?,
    908                         ),
    909                         &salt,
    910                     ),
    911                     ManifestAssertionKind::Binary => {
    912                         // todo: Support binary kinds
    913                         return Err(Error::AssertionEncoding);
    914                     }
    915                     ManifestAssertionKind::Uri => {
    916                         // todo: Support binary kinds
    917                         return Err(Error::AssertionEncoding);
    918                     }
    919                 },
    920             }?;
    921         }
    922 
    923         Ok(claim)
    924     }
    925 
    926     // Convert a Manifest into a Store
    927     pub(crate) fn to_store(&self) -> Result<Store> {
    928         let claim = self.to_claim()?;
    929         // commit the claim
    930         let mut store = Store::new();
    931         let _provenance = store.commit_claim(claim)?;
    932         Ok(store)
    933     }
    934 
    935     // factor out this code to set up the destination path with a file
    936     // so we can use set_asset_from_path to initialize the right fields in Manifest
    937     #[cfg(feature = "file_io")]
    938     fn embed_prep<P: AsRef<Path>>(&mut self, source_path: P, dest_path: P) -> Result<P> {
    939         let mut copied = false;
    940 
    941         if !source_path.as_ref().exists() {
    942             let path = source_path.as_ref().to_string_lossy().into_owned();
    943             return Err(Error::FileNotFound(path));
    944         }
    945         // we need to copy the source to target before setting the asset info
    946         if !dest_path.as_ref().exists() {
    947             // ensure the path to the file exists
    948             if let Some(output_dir) = dest_path.as_ref().parent() {
    949                 create_dir_all(output_dir)?;
    950             }
    951             std::fs::copy(&source_path, &dest_path)?;
    952             copied = true;
    953         }
    954         // first add the information about the target file
    955         self.set_asset_from_path(dest_path.as_ref())?;
    956 
    957         if copied {
    958             Ok(dest_path)
    959         } else {
    960             Ok(source_path)
    961         }
    962     }
    963 
    964     /// Embed a signed manifest into the target file using a supplied signer.
    965     ///
    966     /// # Example: Embed a manifest in a file
    967     ///
    968     /// ```
    969     /// # use c2pa::Result;
    970     /// use c2pa::{create_signer, Manifest, SigningAlg};
    971     /// use serde::Serialize;
    972     ///
    973     /// #[derive(Serialize)]
    974     /// struct Test {
    975     ///     my_tag: usize,
    976     /// }
    977     ///
    978     /// # fn main() -> Result<()> {
    979     /// let mut manifest = Manifest::new("my_app".to_owned());
    980     /// manifest.add_labeled_assertion("org.contentauth.test", &Test { my_tag: 42 })?;
    981     ///
    982     /// // Create a PS256 signer using certs and public key files.
    983     /// let signcert_path = "tests/fixtures/certs/ps256.pub";
    984     /// let pkey_path = "tests/fixtures/certs/ps256.pem";
    985     /// let signer = create_signer::from_files(signcert_path, pkey_path, SigningAlg::Ps256, None)?;
    986     ///
    987     /// // Embed a manifest using the signer.
    988     /// manifest.embed("tests/fixtures/C.jpg", "../target/test_file.jpg", &*signer)?;
    989     /// # Ok(())
    990     /// # }
    991     /// ```
    992     #[cfg(feature = "file_io")]
    993     pub fn embed<P: AsRef<Path>>(
    994         &mut self,
    995         source_path: P,
    996         dest_path: P,
    997         signer: &dyn Signer,
    998     ) -> Result<()> {
    999         // Add manifest info for this target file
   1000         let source_path = self.embed_prep(source_path.as_ref(), dest_path.as_ref())?;
   1001 
   1002         // convert the manifest to a store
   1003         let mut store = self.to_store()?;
   1004 
   1005         // sign and write our store to the output image file
   1006         store.save_to_asset(source_path.as_ref(), signer, dest_path.as_ref())?;
   1007         Ok(())
   1008     }
   1009 
   1010     /// Embed a signed manifest into a stream using a supplied signer.
   1011     /// returns the bytes of the  manifest that was embedded
   1012     #[async_generic(async_signature(
   1013         &mut self,
   1014         format: &str,
   1015         asset: &[u8],
   1016         signer: &dyn AsyncSigner,
   1017     ))]
   1018     pub fn embed_from_memory(
   1019         &mut self,
   1020         format: &str,
   1021         asset: &[u8],
   1022         signer: &dyn Signer,
   1023     ) -> Result<Vec<u8>> {
   1024         // first make a copy of the asset that will contain our modified result
   1025         // todo:: see if we can pass a trait with to_vec support like we to for Strings
   1026         let asset = asset.to_vec();
   1027         let mut stream = std::io::Cursor::new(asset);
   1028         let mut output_stream = Cursor::new(Vec::new());
   1029         if _sync {
   1030             self.embed_to_stream(format, &mut stream, &mut output_stream, signer)?;
   1031         } else {
   1032             self.embed_to_stream_async(format, &mut stream, &mut output_stream, signer)
   1033                 .await?;
   1034         }
   1035         Ok(output_stream.into_inner())
   1036     }
   1037 
   1038     /// Embed a signed manifest into a stream using a supplied signer.
   1039     ///
   1040     /// Returns the bytes of the new asset
   1041     #[deprecated(since = "0.27.2", note = "use embed_to_stream instead")]
   1042     pub fn embed_stream(
   1043         &mut self,
   1044         format: &str,
   1045         stream: &mut dyn CAIRead,
   1046         signer: &dyn Signer,
   1047     ) -> Result<Vec<u8>> {
   1048         // sign and write our store to to the output image file
   1049         let output_vec: Vec<u8> = Vec::new();
   1050         let mut output_stream = Cursor::new(output_vec);
   1051 
   1052         self.embed_to_stream(format, stream, &mut output_stream, signer)?;
   1053 
   1054         Ok(output_stream.into_inner())
   1055     }
   1056 
   1057     /// Embed a signed manifest into a stream using a supplied signer.
   1058     ///
   1059     /// Returns the bytes of c2pa_manifest that was embedded.
   1060     #[async_generic(async_signature(
   1061         &mut self,
   1062         format: &str,
   1063         source: &mut dyn CAIRead,
   1064         dest: &mut dyn CAIReadWrite,
   1065         signer: &dyn AsyncSigner,
   1066     ))]
   1067     pub fn embed_to_stream(
   1068         &mut self,
   1069         format: &str,
   1070         source: &mut dyn CAIRead,
   1071         dest: &mut dyn CAIReadWrite,
   1072         signer: &dyn Signer,
   1073     ) -> Result<Vec<u8>> {
   1074         self.set_format(format);
   1075         // todo:: read instance_id from xmp from stream
   1076         self.set_instance_id(format!("xmp:iid:{}", Uuid::new_v4()));
   1077 
   1078         // generate thumbnail if we don't already have one
   1079         #[cfg(feature = "add_thumbnails")]
   1080         {
   1081             if self.thumbnail_ref().is_none() {
   1082                 if let Ok((format, image)) =
   1083                     crate::utils::thumbnail::make_thumbnail_from_stream(format, source)
   1084                 {
   1085                     self.set_thumbnail(format, image)?;
   1086                 }
   1087             }
   1088         }
   1089 
   1090         // convert the manifest to a store
   1091         let mut store = self.to_store()?;
   1092 
   1093         // sign and write our store to to the output image file
   1094         if _sync {
   1095             store.save_to_stream(format, source, dest, signer)
   1096         } else {
   1097             store
   1098                 .save_to_stream_async(format, source, dest, signer)
   1099                 .await
   1100         }
   1101     }
   1102 
   1103     /// Embed a signed manifest into a stream using a supplied signer.
   1104     /// returns the  asset generated and bytes of the manifest that was embedded
   1105     //#[cfg(feature = "remote_wasm_sign")]
   1106     pub async fn embed_from_memory_remote_signed(
   1107         &mut self,
   1108         format: &str,
   1109         asset: &[u8],
   1110         signer: &dyn RemoteSigner,
   1111     ) -> Result<(Vec<u8>, Vec<u8>)> {
   1112         self.set_format(format);
   1113         // todo:: read instance_id from xmp from stream
   1114         self.set_instance_id(format!("xmp:iid:{}", Uuid::new_v4()));
   1115 
   1116         // generate thumbnail if we don't already have one
   1117         #[allow(unused_mut)] // so that this builds with WASM
   1118         let mut stream = std::io::Cursor::new(asset);
   1119         #[cfg(feature = "add_thumbnails")]
   1120         {
   1121             if self.thumbnail_ref().is_none() {
   1122                 if let Ok((format, image)) =
   1123                     crate::utils::thumbnail::make_thumbnail_from_stream(format, &mut stream)
   1124                 {
   1125                     self.set_thumbnail(format, image)?;
   1126                 }
   1127             }
   1128         }
   1129         let asset = stream.into_inner();
   1130 
   1131         // convert the manifest to a store
   1132         let mut store = self.to_store()?;
   1133 
   1134         // sign and write our store to to the output image file
   1135         let (output_asset, output_manifest) = store
   1136             .save_to_memory_remote_signed(format, asset, signer)
   1137             .await?;
   1138 
   1139         Ok((output_asset, output_manifest))
   1140     }
   1141 
   1142     /// Embed a signed manifest into the target file using a supplied [`AsyncSigner`].
   1143     #[cfg(feature = "file_io")]
   1144     pub async fn embed_async_signed<P: AsRef<Path>>(
   1145         &mut self,
   1146         source_path: P,
   1147         dest_path: P,
   1148         signer: &dyn AsyncSigner,
   1149     ) -> Result<Vec<u8>> {
   1150         // Add manifest info for this target file
   1151         let source_path = self.embed_prep(source_path.as_ref(), dest_path.as_ref())?;
   1152         // convert the manifest to a store
   1153         let mut store = self.to_store()?;
   1154         // sign and write our store to to the output image file
   1155         store
   1156             .save_to_asset_async(source_path.as_ref(), signer, dest_path.as_ref())
   1157             .await
   1158     }
   1159 
   1160     /// Embed a signed manifest into the target file using a supplied [`RemoteSigner`].
   1161     #[cfg(feature = "file_io")]
   1162     pub async fn embed_remote_signed<P: AsRef<Path>>(
   1163         &mut self,
   1164         source_path: P,
   1165         dest_path: P,
   1166         signer: &dyn RemoteSigner,
   1167     ) -> Result<Vec<u8>> {
   1168         // Add manifest info for this target file
   1169         let source_path = self.embed_prep(source_path.as_ref(), dest_path.as_ref())?;
   1170         // convert the manifest to a store
   1171         let mut store = self.to_store()?;
   1172         // sign and write our store to to the output image file
   1173         store
   1174             .save_to_asset_remote_signed(source_path.as_ref(), signer, dest_path.as_ref())
   1175             .await
   1176     }
   1177 
   1178     /// Removes any existing manifest from a file
   1179     ///
   1180     /// This should only be used for special cases, such as converting an embedded manifest
   1181     /// to a cloud manifest
   1182     #[cfg(feature = "file_io")]
   1183     pub fn remove_manifest<P: AsRef<Path>>(asset_path: P) -> Result<()> {
   1184         use crate::jumbf_io::remove_jumbf_from_file;
   1185         remove_jumbf_from_file(asset_path.as_ref())
   1186     }
   1187 
   1188     /// Generates a data hashed placeholder manifest for a file
   1189     ///
   1190     /// The return value is pre-formatted for insertion into a file of the given format
   1191     /// For JPEG it is a series of App11 JPEG segments containing space for a manifest
   1192     /// This is used to create a properly formatted file ready for signing.
   1193     /// The reserve_size is the amount of space to reserve for the signature box.  This
   1194     /// value is fixed once set and must be sufficient to hold the completed signature
   1195     pub fn data_hash_placeholder(&mut self, reserve_size: usize, format: &str) -> Result<Vec<u8>> {
   1196         let dh: Result<DataHash> = self.find_assertion(DataHash::LABEL);
   1197         if dh.is_err() {
   1198             let mut ph = DataHash::new("jumbf manifest", "sha256");
   1199             for _ in 0..10 {
   1200                 ph.add_exclusion(HashRange::new(0, 2));
   1201             }
   1202             self.add_assertion(&ph)?;
   1203         }
   1204 
   1205         let mut store = self.to_store()?;
   1206         let placeholder = store.get_data_hashed_manifest_placeholder(reserve_size, format)?;
   1207         Ok(placeholder)
   1208     }
   1209 
   1210     /// Generates an data hashed embeddable manifest for a file
   1211     ///
   1212     /// The return value is pre-formatted for insertion into a file of the given format
   1213     /// For JPEG it is a series of App11 JPEG segments containing a signed manifest
   1214     /// This can directly replace a placeholder manifest to create a properly signed asset
   1215     /// The data hash must contain exclusions and may contain pre-calculated hashes
   1216     /// if an asset reader is provided, it will be used to calculate the data hash
   1217     #[async_generic(async_signature(
   1218         &mut self,
   1219         dh: &DataHash,
   1220         signer: &dyn AsyncSigner,
   1221         format: &str,
   1222         mut asset_reader: Option<&mut dyn CAIRead>,
   1223     ))]
   1224     pub fn data_hash_embeddable_manifest(
   1225         &mut self,
   1226         dh: &DataHash,
   1227         signer: &dyn Signer,
   1228         format: &str,
   1229         mut asset_reader: Option<&mut dyn CAIRead>,
   1230     ) -> Result<Vec<u8>> {
   1231         let mut store = self.to_store()?;
   1232         if let Some(asset_reader) = asset_reader.as_deref_mut() {
   1233             asset_reader.rewind()?;
   1234         }
   1235         if _sync {
   1236             store.get_data_hashed_embeddable_manifest(dh, signer, format, asset_reader)
   1237         } else {
   1238             store
   1239                 .get_data_hashed_embeddable_manifest_async(dh, signer, format, asset_reader)
   1240                 .await
   1241         }
   1242     }
   1243 
   1244     /// Generates an data hashed embeddable manifest for a file
   1245     ///
   1246     /// The return value is pre-formatted for insertion into a file of the given format
   1247     /// For JPEG it is a series of App11 JPEG segments containing a signed manifest
   1248     /// This can directly replace a placeholder manifest to create a properly signed asset
   1249     /// The data hash must contain exclusions and may contain pre-calculated hashes
   1250     /// if an asset reader is provided, it will be used to calculate the data hash
   1251     pub async fn data_hash_embeddable_manifest_remote(
   1252         &mut self,
   1253         dh: &DataHash,
   1254         signer: &dyn RemoteSigner,
   1255         format: &str,
   1256         mut asset_reader: Option<&mut dyn CAIRead>,
   1257     ) -> Result<Vec<u8>> {
   1258         let mut store = self.to_store()?;
   1259         if let Some(asset_reader) = asset_reader.as_deref_mut() {
   1260             asset_reader.rewind()?;
   1261         }
   1262         store
   1263             .get_data_hashed_embeddable_manifest_remote(dh, signer, format, asset_reader)
   1264             .await
   1265     }
   1266 
   1267     /// Generates a signed box hashed manifest, optionally preformatted for embedding
   1268     ///
   1269     /// The manifest must include a box hash assertion with correct hashes
   1270     #[async_generic(async_signature(
   1271         &mut self,
   1272         signer: &dyn AsyncSigner,
   1273         format: Option<&str>,
   1274     ))]
   1275     pub fn box_hash_embeddable_manifest(
   1276         &mut self,
   1277         signer: &dyn Signer,
   1278         format: Option<&str>,
   1279     ) -> Result<Vec<u8>> {
   1280         let mut store = self.to_store()?;
   1281         let mut cm = if _sync {
   1282             store.get_box_hashed_embeddable_manifest(signer)
   1283         } else {
   1284             store.get_box_hashed_embeddable_manifest_async(signer).await
   1285         }?;
   1286         if let Some(format) = format {
   1287             cm = Store::get_composed_manifest(&cm, format)?;
   1288         }
   1289         Ok(cm)
   1290     }
   1291 
   1292     /// Formats a signed manifest for embedding in the given format
   1293     ///
   1294     /// For instance, this would return one or JPEG App11 segments containing the manifest
   1295     pub fn composed_manifest(manifest_bytes: &[u8], format: &str) -> Result<Vec<u8>> {
   1296         Store::get_composed_manifest(manifest_bytes, format)
   1297     }
   1298 }
   1299 
   1300 impl std::fmt::Display for Manifest {
   1301     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
   1302         let json = serde_json::to_string_pretty(self).unwrap_or_default();
   1303         f.write_str(&json)
   1304     }
   1305 }
   1306 #[derive(Clone, Debug, Deserialize, Serialize)]
   1307 #[cfg_attr(feature = "json_schema", derive(JsonSchema))]
   1308 /// Holds information about a signature
   1309 pub struct SignatureInfo {
   1310     /// human readable issuing authority for this signature
   1311     #[serde(skip_serializing_if = "Option::is_none")]
   1312     alg: Option<SigningAlg>,
   1313     /// human readable issuing authority for this signature
   1314     #[serde(skip_serializing_if = "Option::is_none")]
   1315     issuer: Option<String>,
   1316 
   1317     /// The serial number of the certificate
   1318     #[serde(skip_serializing_if = "Option::is_none")]
   1319     cert_serial_number: Option<String>,
   1320 
   1321     /// the time the signature was created
   1322     #[serde(skip_serializing_if = "Option::is_none")]
   1323     time: Option<String>,
   1324 
   1325     /// the cert chain for this claim
   1326     #[serde(skip)] // don't serialize this, let someone ask for it
   1327     cert_chain: String,
   1328 
   1329     /// revocation status of the certificate
   1330     #[serde(skip_serializing_if = "Option::is_none")]
   1331     revocation_status: Option<bool>,
   1332 }
   1333 
   1334 impl SignatureInfo {
   1335     // returns the cert chain for this signature
   1336     pub fn cert_chain(&self) -> &str {
   1337         &self.cert_chain
   1338     }
   1339 }
   1340 
   1341 #[cfg(test)]
   1342 pub(crate) mod tests {
   1343     #![allow(clippy::expect_used)]
   1344     #![allow(clippy::unwrap_used)]
   1345 
   1346     use std::io::Cursor;
   1347 
   1348     #[cfg(feature = "file_io")]
   1349     use tempfile::tempdir;
   1350     #[cfg(target_arch = "wasm32")]
   1351     use wasm_bindgen_test::*;
   1352 
   1353     #[cfg(target_arch = "wasm32")]
   1354     wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
   1355 
   1356     use crate::{
   1357         assertions::{c2pa_action, Action, Actions},
   1358         ingredient::Ingredient,
   1359         reader::Reader,
   1360         utils::test::{temp_remote_signer, temp_signer, TEST_VC},
   1361         Manifest, Result,
   1362     };
   1363     #[cfg(feature = "file_io")]
   1364     use crate::{
   1365         assertions::{labels::ACTIONS, DataHash},
   1366         error::Error,
   1367         hash_utils::HashRange,
   1368         resource_store::ResourceRef,
   1369         status_tracker::{DetailedStatusTracker, StatusTracker},
   1370         store::Store,
   1371         utils::test::{
   1372             fixture_path, temp_dir_path, temp_fixture_path, write_jpeg_placeholder_file,
   1373             TEST_SMALL_JPEG,
   1374         },
   1375         validation_status,
   1376     };
   1377 
   1378     // example of random data structure as an assertion
   1379     #[derive(serde::Serialize)]
   1380     #[allow(dead_code)] // this here for wasm builds to pass clippy  (todo: remove)
   1381     struct MyStruct {
   1382         l1: String,
   1383         l2: u32,
   1384     }
   1385 
   1386     fn test_manifest() -> Manifest {
   1387         Manifest::new("test".to_owned())
   1388     }
   1389 
   1390     #[test]
   1391     #[cfg(feature = "file_io")]
   1392     fn from_file() {
   1393         let mut manifest = test_manifest();
   1394         let source_path = fixture_path(TEST_SMALL_JPEG);
   1395         manifest
   1396             .set_vendor("vendor".to_owned())
   1397             .set_parent(Ingredient::from_file(&source_path).expect("from_file"))
   1398             .expect("set_parent");
   1399 
   1400         let vc: serde_json::Value = serde_json::from_str(TEST_VC).unwrap();
   1401         manifest
   1402             .add_verifiable_credential(&vc)
   1403             .expect("verifiable_credential");
   1404 
   1405         manifest
   1406             .add_labeled_assertion(
   1407                 "my.assertion",
   1408                 &MyStruct {
   1409                     l1: "some data".to_owned(),
   1410                     l2: 5,
   1411                 },
   1412             )
   1413             .expect("add_assertion");
   1414 
   1415         let actions = Actions::new().add_action(
   1416             Action::new(c2pa_action::EDITED)
   1417                 .set_parameter("name".to_owned(), "gaussian_blur")
   1418                 .unwrap(),
   1419         );
   1420 
   1421         manifest.add_assertion(&actions).expect("add_assertion");
   1422 
   1423         manifest.add_ingredient(Ingredient::from_file(&source_path).expect("from_file"));
   1424 
   1425         // generate json and omit binary thumbnails for printout
   1426         let mut json = serde_json::to_string_pretty(&manifest).expect("error to json");
   1427         while let Some(index) = json.find("\"thumbnail\": [") {
   1428             if let Some(idx2) = json[index..].find(']') {
   1429                 json = format!(
   1430                     "{}\"thumbnail\": \"<omitted>\"{}",
   1431                     &json[..index],
   1432                     &json[index + idx2 + 1..]
   1433                 );
   1434             }
   1435         }
   1436 
   1437         // copy an image to use as our target
   1438         let dir = tempdir().expect("temp dir");
   1439         let test_output = dir.path().join("wc_embed_test.jpg");
   1440 
   1441         //embed a claim generated from this manifest
   1442         let signer = temp_signer();
   1443 
   1444         let _store = manifest
   1445             .embed(&source_path, &test_output, signer.as_ref())
   1446             .expect("embed");
   1447 
   1448         assert_eq!(manifest.format(), "image/jpeg");
   1449         assert_eq!(manifest.title(), Some("wc_embed_test.jpg"));
   1450         if cfg!(feature = "add_thumbnails") {
   1451             assert!(manifest.thumbnail().is_some());
   1452         } else {
   1453             assert!(manifest.thumbnail().is_none());
   1454         }
   1455         let ingredient = Ingredient::from_file(&test_output).expect("load_from_asset");
   1456         assert!(ingredient.active_manifest().is_some());
   1457     }
   1458 
   1459     #[test]
   1460     #[cfg(feature = "file_io")]
   1461     /// test assertion validation on actions, should generate an error
   1462     fn ws_bad_assertion() {
   1463         // copy an image to use as our target for embedding
   1464         let ap = fixture_path(TEST_SMALL_JPEG);
   1465         let temp_dir = tempdir().expect("temp dir");
   1466         let test_output = temp_dir_path(&temp_dir, "ws_bad_assertion.jpg");
   1467         std::fs::copy(ap, test_output).expect("copy");
   1468 
   1469         let mut manifest = test_manifest();
   1470 
   1471         manifest
   1472             .add_labeled_assertion(
   1473                 "c2pa.actions",
   1474                 &MyStruct {
   1475                     // add something that isn't an actions struct
   1476                     l1: "some data".to_owned(),
   1477                     l2: 5,
   1478                 },
   1479             )
   1480             .expect("add_assertion");
   1481 
   1482         // convert to store
   1483         let result = manifest.to_store();
   1484 
   1485         println!("{result:?}");
   1486         assert!(result.is_err())
   1487     }
   1488 
   1489     #[test]
   1490     #[cfg(feature = "file_io")]
   1491     /// test assertion validation on actions, should generate an error
   1492     fn ws_valid_labeled_assertion() {
   1493         // copy an image to use as our target for embedding
   1494         let ap = fixture_path(TEST_SMALL_JPEG);
   1495         let temp_dir = tempdir().expect("temp dir");
   1496         let test_output = temp_dir_path(&temp_dir, "ws_bad_assertion.jpg");
   1497         std::fs::copy(ap, test_output).expect("copy");
   1498 
   1499         let mut manifest = test_manifest();
   1500 
   1501         manifest
   1502             .add_labeled_assertion(
   1503                 "c2pa.actions",
   1504                 &serde_json::json!({
   1505                     "actions": [
   1506                         {
   1507                             "action": "c2pa.edited",
   1508                             "parameters": {
   1509                                 "description": "gradient",
   1510                                 "name": "any value"
   1511                             },
   1512                             "softwareAgent": "TestApp"
   1513                         },
   1514                         {
   1515                             "action": "c2pa.dubbed",
   1516                             "changes": [
   1517                                 {
   1518                                     "description": "translated to klingon",
   1519                                     "region": [
   1520                                         {
   1521                                             "type": "temporal",
   1522                                             "time": {}
   1523                                         },
   1524                                         {
   1525                                             "type": "identified",
   1526                                             "item": {
   1527                                                 "identifier": "https://bioportal.bioontology.org/ontologies/FMA",
   1528                                                 "value": "lips"
   1529                                             }
   1530                                         }
   1531                                     ]
   1532                                 }
   1533                             ]
   1534                         }
   1535                     ]
   1536                 }),
   1537             )
   1538             .expect("add_assertion");
   1539 
   1540         // convert to store
   1541         let store = manifest.to_store().expect("valid action to_store");
   1542         let m2 = Manifest::from_store(&store, &store.provenance_label().unwrap(), None)
   1543             .expect("from_store");
   1544         let actions: Actions = m2
   1545             .find_assertion("c2pa.actions.v2")
   1546             .expect("find_assertion");
   1547         assert_eq!(actions.actions()[0].action(), "c2pa.edited");
   1548         assert_eq!(actions.actions()[1].action(), "c2pa.dubbed");
   1549     }
   1550 
   1551     #[test]
   1552     fn test_verifiable_credential() {
   1553         let mut manifest = test_manifest();
   1554         let vc: serde_json::Value = serde_json::from_str(TEST_VC).unwrap();
   1555         manifest
   1556             .add_verifiable_credential(&vc)
   1557             .expect("verifiable_credential");
   1558         let store = manifest.to_store().expect("to_store");
   1559         let claim = store.provenance_claim().unwrap();
   1560         assert!(!claim.get_verifiable_credentials().is_empty());
   1561     }
   1562 
   1563     #[test]
   1564     fn test_assertion_user_cbor() {
   1565         use crate::{assertions::UserCbor, Manifest};
   1566 
   1567         const LABEL: &str = "org.cai.test";
   1568         const DATA: &str = r#"{ "l1":"some data", "l2":"some other data" }"#;
   1569         let json: serde_json::Value = serde_json::from_str(DATA).unwrap();
   1570         let data = serde_cbor::to_vec(&json).unwrap();
   1571         let cbor = UserCbor::new(LABEL, data);
   1572         let mut manifest = test_manifest();
   1573         manifest.add_assertion(&cbor).expect("add_assertion");
   1574         manifest.add_assertion(&cbor).expect("add_assertion");
   1575         let store = manifest.to_store().expect("to_store");
   1576 
   1577         let _manifest2 = Manifest::from_store(
   1578             &store,
   1579             &store.provenance_label().unwrap(),
   1580             #[cfg(feature = "file_io")]
   1581             None,
   1582         )
   1583         .expect("from_store");
   1584         println!("{store}");
   1585         println!("{_manifest2:?}");
   1586         let cbor2: UserCbor = manifest.find_assertion(LABEL).expect("get_assertion");
   1587         assert_eq!(cbor, cbor2);
   1588     }
   1589 
   1590     #[test]
   1591     #[cfg(feature = "file_io")]
   1592     fn test_redaction() {
   1593         const ASSERTION_LABEL: &str = "stds.schema-org.CreativeWork";
   1594 
   1595         let temp_dir = tempdir().expect("temp dir");
   1596         let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   1597         let output2 = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   1598 
   1599         let mut manifest = test_manifest();
   1600 
   1601         manifest
   1602             .add_labeled_assertion(
   1603                 ASSERTION_LABEL,
   1604                 &serde_json::json! (
   1605                 {
   1606                     "@context": "https://schema.org",
   1607                     "@type": "CreativeWork",
   1608                     "author": [
   1609                       {
   1610                         "@type": "Person",
   1611                         "name": "Joe Bloggs"
   1612                       },
   1613 
   1614                     ]
   1615                   }),
   1616             )
   1617             .expect("add_assertion");
   1618 
   1619         let signer = temp_signer();
   1620 
   1621         let c2pa_data = manifest
   1622             .embed(&output, &output, signer.as_ref())
   1623             .expect("embed");
   1624         let mut validation_log = DetailedStatusTracker::new();
   1625 
   1626         let store1 = Store::load_from_memory("c2pa", &c2pa_data, true, &mut validation_log)
   1627             .expect("load from memory");
   1628         let claim1_label = store1.provenance_label().unwrap();
   1629         let claim = store1.provenance_claim().unwrap();
   1630         assert!(claim.get_claim_assertion(ASSERTION_LABEL, 0).is_some()); // verify the assertion is there
   1631 
   1632         // create a new claim and make the previous file a parent
   1633         let mut manifest2 = test_manifest();
   1634         manifest2
   1635             .set_parent(Ingredient::from_file(&output).expect("from_file"))
   1636             .expect("set_parent");
   1637 
   1638         // redact the assertion
   1639         manifest2
   1640             .add_redaction(ASSERTION_LABEL)
   1641             .expect("add_redaction");
   1642 
   1643         //embed a claim in output2
   1644         let signer = temp_signer();
   1645         let _store2 = manifest2
   1646             .embed(&output2, &output2, signer.as_ref())
   1647             .expect("embed");
   1648 
   1649         let mut report = DetailedStatusTracker::new();
   1650         let store3 = Store::load_from_asset(&output2, true, &mut report).unwrap();
   1651         let claim2 = store3.provenance_claim().unwrap();
   1652 
   1653         // assert!(!claim2.get_verifiable_credentials().is_empty());
   1654 
   1655         // test that the redaction is in the new claim and the assertion is removed from the first one
   1656 
   1657         assert!(claim2.redactions().is_some());
   1658         assert!(!claim2.redactions().unwrap().is_empty());
   1659         assert!(!report.get_log().is_empty());
   1660         let redacted_uri = &claim2.redactions().unwrap()[0];
   1661 
   1662         let claim1 = store3.get_claim(&claim1_label).unwrap();
   1663         assert!(claim1.get_claim_assertion(redacted_uri, 0).is_none());
   1664     }
   1665 
   1666     #[test]
   1667     #[cfg(feature = "file_io")]
   1668     fn test_action_assertion_redaction_error() {
   1669         let temp_dir = tempdir().expect("temp dir");
   1670         let parent_output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   1671 
   1672         // Create parent with a c2pa_action type assertion.
   1673         let mut parent_manifest = test_manifest();
   1674         let actions = Actions::new().add_action(
   1675             Action::new(c2pa_action::FILTERED)
   1676                 .set_parameter("name".to_owned(), "gaussian blur")
   1677                 .unwrap()
   1678                 .set_when("2015-06-26T16:43:23+0200"),
   1679         );
   1680         parent_manifest
   1681             .add_assertion(&actions)
   1682             .expect("add_assertion");
   1683 
   1684         let signer = temp_signer();
   1685         parent_manifest
   1686             .embed(&parent_output, &parent_output, signer.as_ref())
   1687             .expect("embed");
   1688 
   1689         // Add parent_manifest as an ingredient of the new manifest and redact the assertion `c2pa.actions`.
   1690         let mut manifest = test_manifest();
   1691         manifest
   1692             .set_parent(Ingredient::from_file(&parent_output).expect("from_file"))
   1693             .expect("set_parent");
   1694         assert!(manifest.add_redaction(ACTIONS).is_ok());
   1695 
   1696         // Attempt embedding the manifest with the invalid redaction.
   1697         let redact_output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   1698         let embed_result = manifest.embed(&redact_output, &redact_output, signer.as_ref());
   1699         assert!(matches!(
   1700             embed_result.err().unwrap(),
   1701             Error::AssertionInvalidRedaction
   1702         ));
   1703     }
   1704 
   1705     #[test]
   1706     fn manifest_assertion_instances() {
   1707         let mut manifest = Manifest::new("test".to_owned());
   1708         let actions = Actions::new().add_action(Action::new(c2pa_action::EDITED));
   1709         // add three assertions with the same label
   1710         manifest.add_assertion(&actions).expect("add_assertion");
   1711         manifest.add_assertion(&actions).expect("add_assertion");
   1712         manifest.add_assertion(&actions).expect("add_assertion");
   1713 
   1714         // convert to a store and read back again
   1715         let store = manifest.to_store().expect("to_store");
   1716         println!("{store}");
   1717         let active_label = store.provenance_label().unwrap();
   1718 
   1719         let manifest2 = Manifest::from_store(
   1720             &store,
   1721             &active_label,
   1722             #[cfg(feature = "file_io")]
   1723             None,
   1724         )
   1725         .expect("from_store");
   1726         println!("{manifest2}");
   1727 
   1728         // now check to see if we have three separate assertions with different instances
   1729         let action2: Result<Actions> = manifest2.find_assertion_with_instance(Actions::LABEL, 2);
   1730         assert!(action2.is_ok());
   1731         assert_eq!(action2.unwrap().actions()[0].action(), c2pa_action::EDITED);
   1732     }
   1733 
   1734     #[cfg(all(feature = "file_io", feature = "openssl_sign"))]
   1735     #[actix::test]
   1736     async fn test_embed_async_sign() {
   1737         let temp_dir = tempdir().expect("temp dir");
   1738         let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   1739 
   1740         let async_signer =
   1741             crate::openssl::temp_signer_async::AsyncSignerAdapter::new(crate::SigningAlg::Ps256);
   1742 
   1743         let mut manifest = test_manifest();
   1744         manifest
   1745             .embed_async_signed(&output, &output, &async_signer)
   1746             .await
   1747             .expect("embed");
   1748         let reader = Reader::from_file(&output).expect("from_file");
   1749         assert_eq!(
   1750             reader.active_manifest().unwrap().title().unwrap(),
   1751             TEST_SMALL_JPEG
   1752         );
   1753     }
   1754 
   1755     #[cfg(all(feature = "file_io", feature = "openssl_sign"))]
   1756     #[actix::test]
   1757     async fn test_embed_remote_sign() {
   1758         let temp_dir = tempdir().expect("temp dir");
   1759         let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   1760 
   1761         let remote_signer = temp_remote_signer();
   1762 
   1763         let mut manifest = test_manifest();
   1764         manifest
   1765             .embed_remote_signed(&output, &output, remote_signer.as_ref())
   1766             .await
   1767             .expect("embed");
   1768         let manifest_store = Reader::from_file(&output).expect("from_file");
   1769         assert_eq!(
   1770             manifest_store.active_manifest().unwrap().title().unwrap(),
   1771             TEST_SMALL_JPEG
   1772         );
   1773     }
   1774 
   1775     #[cfg(feature = "file_io")]
   1776     #[test]
   1777     fn test_embed_user_label() {
   1778         let temp_dir = tempdir().expect("temp dir");
   1779         let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   1780 
   1781         let signer = temp_signer();
   1782 
   1783         let mut manifest = test_manifest();
   1784         manifest.set_label("MyLabel");
   1785         manifest
   1786             .embed(&output, &output, signer.as_ref())
   1787             .expect("embed");
   1788 
   1789         let reader = Reader::from_file(&output).expect("from_file");
   1790         assert_eq!(
   1791             reader.active_manifest().unwrap().title().unwrap(),
   1792             TEST_SMALL_JPEG
   1793         );
   1794     }
   1795 
   1796     #[cfg(feature = "file_io")]
   1797     #[test]
   1798     fn test_embed_sidecar_user_label() {
   1799         let temp_dir = tempdir().expect("temp dir");
   1800         let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   1801         let sidecar = output.with_extension("c2pa");
   1802         let fp = format!("file:/{}", sidecar.to_str().unwrap());
   1803         let url = url::Url::parse(&fp).unwrap();
   1804 
   1805         let signer = temp_signer();
   1806 
   1807         let mut manifest = test_manifest();
   1808         manifest.set_label("MyLabel");
   1809         manifest.set_remote_manifest(url);
   1810         let c2pa_data = manifest
   1811             .embed(&output, &output, signer.as_ref())
   1812             .expect("embed");
   1813 
   1814         let manifest_store =
   1815             Reader::from_stream("application/c2pa", Cursor::new(c2pa_data)).expect("from_bytes");
   1816         assert_eq!(
   1817             manifest_store.active_manifest().unwrap().title().unwrap(),
   1818             TEST_SMALL_JPEG
   1819         );
   1820     }
   1821 
   1822     #[cfg_attr(not(target_arch = "wasm32"), actix::test)]
   1823     #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
   1824     async fn test_embed_jpeg_stream_wasm() {
   1825         use crate::assertions::User;
   1826         let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg");
   1827         // convert buffer to cursor with Read/Write/Seek capability
   1828 
   1829         let mut manifest = Manifest::new("my_app".to_owned());
   1830         manifest.set_title("EmbedStream");
   1831         manifest
   1832             .add_assertion(&User::new(
   1833                 "org.contentauth.mylabel",
   1834                 r#"{"my_tag":"Anything I want"}"#,
   1835             ))
   1836             .unwrap();
   1837 
   1838         // add a parent ingredient
   1839         let mut ingredient = Ingredient::from_memory_async("jpeg", image)
   1840             .await
   1841             .expect("from_stream_async");
   1842         ingredient.set_title("parent.jpg");
   1843         manifest.set_parent(ingredient).expect("set_parent");
   1844 
   1845         let signer = temp_remote_signer();
   1846 
   1847         // Embed a manifest using the signer.
   1848         let (out_vec, _out_manifest) = manifest
   1849             .embed_from_memory_remote_signed("jpeg", image, signer.as_ref())
   1850             .await
   1851             .expect("embed_stream");
   1852 
   1853         // try to load the image
   1854         let manifest_store = Reader::from_stream("image/jpeg", Cursor::new(out_vec)).unwrap();
   1855 
   1856         /* to be enabled later
   1857                 // try to load the manifest
   1858                 let mut validation_log = DetailedStatusTracker::new();
   1859                 Store::from_jumbf(&out_manifest, &mut validation_log).expect("manifest_load_error");
   1860         */
   1861         println!("It worked: {manifest_store}\n");
   1862     }
   1863 
   1864     #[cfg_attr(not(target_arch = "wasm32"), actix::test)]
   1865     #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
   1866     async fn test_embed_png_stream_wasm() {
   1867         use crate::assertions::User;
   1868         let image = include_bytes!("../tests/fixtures/libpng-test.png");
   1869         // convert buffer to cursor with Read/Write/Seek capability
   1870 
   1871         let mut manifest = Manifest::new("my_app".to_owned());
   1872         manifest.set_title("EmbedStream");
   1873         manifest
   1874             .add_assertion(&User::new(
   1875                 "org.contentauth.mylabel",
   1876                 r#"{"my_tag":"Anything I want"}"#,
   1877             ))
   1878             .unwrap();
   1879 
   1880         let signer = temp_remote_signer();
   1881 
   1882         // Embed a manifest using the signer.
   1883         let (out_vec, _out_manifest) = manifest
   1884             .embed_from_memory_remote_signed("png", image, signer.as_ref())
   1885             .await
   1886             .expect("embed_stream");
   1887 
   1888         // try to load the image
   1889         let manifest_store = Reader::from_stream("image/png", Cursor::new(out_vec)).unwrap();
   1890 
   1891         /* to be enabled later
   1892                 // try to load the manifest
   1893                 let mut validation_log = DetailedStatusTracker::new();
   1894                 Store::from_jumbf(&out_manifest, &mut validation_log).expect("manifest_load_error");
   1895         */
   1896 
   1897         println!("It worked: {manifest_store}\n");
   1898     }
   1899 
   1900     #[cfg_attr(not(target_arch = "wasm32"), actix::test)]
   1901     #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
   1902     async fn test_embed_webp_stream_wasm() {
   1903         use crate::assertions::User;
   1904         let image = include_bytes!("../tests/fixtures/mars.webp");
   1905         // convert buffer to cursor with Read/Write/Seek capability
   1906 
   1907         let mut manifest = Manifest::new("my_app".to_owned());
   1908         manifest.set_title("EmbedStream");
   1909         manifest
   1910             .add_assertion(&User::new(
   1911                 "org.contentauth.mylabel",
   1912                 r#"{"my_tag":"Anything I want"}"#,
   1913             ))
   1914             .unwrap();
   1915 
   1916         let signer = temp_remote_signer();
   1917 
   1918         // Embed a manifest using the signer.
   1919         let (out_vec, _out_manifest) = manifest
   1920             .embed_from_memory_remote_signed("image/webp", image, signer.as_ref())
   1921             .await
   1922             .expect("embed_stream");
   1923 
   1924         // try to load the image
   1925         let manifest_store = Reader::from_stream("image/webp", Cursor::new(out_vec)).unwrap();
   1926 
   1927         /* to be enabled later
   1928                 // try to load the manifest
   1929                 let mut validation_log = DetailedStatusTracker::new();
   1930                 Store::from_jumbf(&out_manifest, &mut validation_log).expect("manifest_load_error");
   1931         */
   1932 
   1933         println!("It worked: {manifest_store}\n");
   1934     }
   1935 
   1936     #[test]
   1937     fn test_embed_stream() {
   1938         use crate::assertions::User;
   1939         let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg");
   1940         // convert buffer to cursor with Read/Write/Seek capability
   1941         let mut stream = std::io::Cursor::new(image.to_vec());
   1942         // let mut image = image.to_vec();
   1943         // let mut stream = std::io::Cursor::new(image.as_mut_slice());
   1944 
   1945         let mut manifest = Manifest::new("my_app".to_owned());
   1946         manifest.set_title("EmbedStream");
   1947         manifest
   1948             .add_assertion(&User::new(
   1949                 "org.contentauth.mylabel",
   1950                 r#"{"my_tag":"Anything I want"}"#,
   1951             ))
   1952             .unwrap();
   1953 
   1954         let signer = temp_signer();
   1955         let mut output = Cursor::new(Vec::new());
   1956         // Embed a manifest using the signer.
   1957         manifest
   1958             .embed_to_stream("jpeg", &mut stream, &mut output, signer.as_ref())
   1959             .expect("embed_stream");
   1960 
   1961         stream.set_position(0);
   1962         let reader = Reader::from_stream("jpeg", &mut output).expect("from_bytes");
   1963         assert_eq!(
   1964             reader.active_manifest().unwrap().title().unwrap(),
   1965             "EmbedStream"
   1966         );
   1967         #[cfg(feature = "add_thumbnails")]
   1968         assert!(reader.active_manifest().unwrap().thumbnail().is_some());
   1969         //println!("{manifest_store}");main
   1970     }
   1971 
   1972     #[cfg(any(target_arch = "wasm32", feature = "openssl_sign"))]
   1973     #[cfg_attr(feature = "openssl_sign", actix::test)]
   1974     #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
   1975     async fn test_embed_from_memory_async() {
   1976         use crate::{assertions::User, utils::test::temp_async_signer};
   1977         let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg");
   1978         // convert buffer to cursor with Read/Write/Seek capability
   1979         let mut stream = std::io::Cursor::new(image.to_vec());
   1980         // let mut image = image.to_vec();
   1981         // let mut stream = std::io::Cursor::new(image.as_mut_slice());
   1982 
   1983         let mut manifest = Manifest::new("my_app".to_owned());
   1984         manifest.set_title("EmbedStream");
   1985         manifest
   1986             .add_assertion(&User::new(
   1987                 "org.contentauth.mylabel",
   1988                 r#"{"my_tag":"Anything I want"}"#,
   1989             ))
   1990             .unwrap();
   1991 
   1992         let signer = temp_async_signer();
   1993         let mut output = Cursor::new(Vec::new());
   1994         // Embed a manifest using the signer.
   1995         manifest
   1996             .embed_to_stream_async("jpeg", &mut stream, &mut output, signer.as_ref())
   1997             .await
   1998             .expect("embed_stream");
   1999 
   2000         let manifest_store = crate::ManifestStore::from_bytes("jpeg", &output.into_inner(), true)
   2001             .expect("from_bytes");
   2002         assert_eq!(
   2003             manifest_store.get_active().unwrap().title().unwrap(),
   2004             "EmbedStream"
   2005         );
   2006         #[cfg(feature = "add_thumbnails")]
   2007         assert!(manifest_store.get_active().unwrap().thumbnail().is_some());
   2008         //println!("{manifest_store}");main
   2009     }
   2010 
   2011     #[cfg(feature = "file_io")]
   2012     #[actix::test]
   2013     /// Verify that an ingredient with error is reported on the ingredient and not on the manifest_store
   2014     async fn test_embed_with_ingredient_error() {
   2015         let temp_dir = tempdir().expect("temp dir");
   2016         let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   2017 
   2018         let signer = temp_signer();
   2019 
   2020         let mut manifest = test_manifest();
   2021         let ingredient =
   2022             Ingredient::from_file(fixture_path("XCA.jpg")).expect("getting ingredient");
   2023         assert!(ingredient.validation_status().is_some());
   2024         assert_eq!(
   2025             ingredient.validation_status().unwrap()[0].code(),
   2026             validation_status::ASSERTION_DATAHASH_MISMATCH
   2027         );
   2028         manifest.add_ingredient(ingredient);
   2029         manifest
   2030             .embed(&output, &output, signer.as_ref())
   2031             .expect("embed");
   2032         let manifest_store = Reader::from_file(&output).expect("from_file");
   2033         println!("{manifest_store}");
   2034         let manifest = manifest_store.active_manifest().unwrap();
   2035         let ingredient_status = manifest.ingredients()[0].validation_status();
   2036         assert_eq!(
   2037             ingredient_status.unwrap()[0].code(),
   2038             validation_status::ASSERTION_DATAHASH_MISMATCH
   2039         );
   2040         assert_eq!(manifest.title().unwrap(), TEST_SMALL_JPEG);
   2041         assert!(manifest_store.validation_status().is_none())
   2042     }
   2043 
   2044     #[cfg(feature = "file_io")]
   2045     #[test]
   2046     fn test_embed_sidecar_with_parent_manifest() {
   2047         let temp_dir = tempdir().expect("temp dir");
   2048         let source = fixture_path("XCA.jpg");
   2049         let output = temp_dir.path().join("XCAplus.jpg");
   2050         let sidecar = output.with_extension("c2pa");
   2051         let fp = format!("file:/{}", sidecar.to_str().unwrap());
   2052         let url = url::Url::parse(&fp).unwrap();
   2053 
   2054         let signer = temp_signer();
   2055 
   2056         let parent = Ingredient::from_file(fixture_path("XCA.jpg")).expect("getting parent");
   2057         let mut manifest = test_manifest();
   2058         manifest.set_parent(parent).expect("setting parent");
   2059         manifest.set_remote_manifest(url.clone());
   2060         let _c2pa_data = manifest
   2061             .embed(&source, &output, signer.as_ref())
   2062             .expect("embed");
   2063 
   2064         assert_eq!(manifest.remote_manifest_url().unwrap(), url.to_string());
   2065 
   2066         //let manifest_store = crate::ManifestStore::from_file(&sidecar).expect("from_file");
   2067         let manifest_store = Reader::from_file(&output).expect("from_file");
   2068         assert_eq!(
   2069             manifest_store.active_manifest().unwrap().title().unwrap(),
   2070             "XCAplus.jpg"
   2071         );
   2072     }
   2073 
   2074     #[cfg(feature = "file_io")]
   2075     #[test]
   2076     fn test_embed_user_thumbnail() {
   2077         let temp_dir = tempdir().expect("temp dir");
   2078         let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   2079 
   2080         let signer = temp_signer();
   2081 
   2082         let mut manifest = test_manifest();
   2083         let thumb_data = vec![1, 2, 3];
   2084         manifest
   2085             .set_thumbnail("image/jpeg", thumb_data.clone())
   2086             .expect("set_thumbnail");
   2087         manifest
   2088             .embed(&output, &output, signer.as_ref())
   2089             .expect("embed");
   2090         let manifest_store = Reader::from_file(&output).expect("from_file");
   2091         let active_manifest = manifest_store.active_manifest().unwrap();
   2092         let (format, image) = active_manifest.thumbnail().unwrap();
   2093         assert_eq!(format, "image/jpeg");
   2094         assert_eq!(image.into_owned(), thumb_data);
   2095     }
   2096 
   2097     #[cfg(feature = "file_io")]
   2098     const MANIFEST_JSON: &str = r#"{
   2099         "claim_generator": "test",
   2100         "claim_generator_info": [
   2101             {
   2102                 "name": "test",
   2103                 "version": "1.0",
   2104                 "icon": {
   2105                     "format": "image/svg+xml",
   2106                     "identifier": "sample1.svg"
   2107                 }
   2108             }
   2109         ],
   2110         "format" : "image/jpeg",
   2111         "thumbnail": {
   2112             "format": "image/jpeg",
   2113             "identifier": "IMG_0003.jpg"
   2114         },
   2115         "assertions": [
   2116             {
   2117                 "label": "c2pa.actions.v2",
   2118                 "data": {
   2119                     "actions": [
   2120                         {
   2121                             "action": "c2pa.opened",
   2122                             "instanceId": "xmp.iid:7b57930e-2f23-47fc-affe-0400d70b738d",
   2123                             "parameters": {
   2124                                 "description": "import"
   2125                             },
   2126                             "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/algorithmicMedia",
   2127                             "softwareAgent": {
   2128                                 "name": "TestApp",
   2129                                 "version": "1.0",
   2130                                 "icon": {
   2131                                     "format": "image/svg+xml",
   2132                                     "identifier": "sample1.svg"
   2133                                 },
   2134                                 "something": "else"
   2135                             },
   2136                             "changes": [
   2137                                 {
   2138                                     "region" : [
   2139                                         {
   2140                                             "type" : "temporal",
   2141                                             "time" : {}
   2142                                         },
   2143                                         {
   2144                                             "type" : "identified",
   2145                                             "item" : {
   2146                                               "identifier" : "https://bioportal.bioontology.org/ontologies/FMA",
   2147                                               "value" : "lips"
   2148                                             }
   2149                                         }
   2150                                     ],
   2151                                     "description": "lip synced area"
   2152                                 }
   2153                             ]
   2154                         }
   2155                     ],
   2156                     "templates": [
   2157                         {
   2158                             "action": "c2pa.opened",
   2159                             "softwareAgent": {
   2160                                 "name": "TestApp",
   2161                                 "version": "1.0",
   2162                                 "icon": {
   2163                                     "format": "image/svg+xml",
   2164                                     "identifier": "sample1.svg"
   2165                                 },
   2166                                 "something": "else"
   2167                             },
   2168                             "icon": {
   2169                                 "format": "image/svg+xml",
   2170                                 "identifier": "sample1.svg"
   2171                             }
   2172                         }
   2173                     ]
   2174                 }
   2175             }
   2176         ],
   2177         "ingredients": [{
   2178             "title": "A.jpg",
   2179             "format": "image/jpeg",
   2180             "document_id": "xmp.did:813ee422-9736-4cdc-9be6-4e35ed8e41cb",
   2181             "relationship": "parentOf",
   2182             "thumbnail": {
   2183                 "format": "image/png",
   2184                 "identifier": "exp-test1.png"
   2185             }
   2186         },
   2187         {
   2188             "title": "prompt",
   2189             "format": "text/plain",
   2190             "relationship": "inputTo",
   2191             "data": {
   2192               "format": "text/plain",
   2193               "identifier": "prompt.txt",
   2194               "data_types": [
   2195                 {
   2196                   "type": "c2pa.types.generator.prompt"
   2197                 }
   2198               ]
   2199             }
   2200           }
   2201         ]
   2202     }"#;
   2203 
   2204     #[test]
   2205     #[cfg(feature = "openssl_sign")]
   2206     /// tests and illustrates how to add assets to a non-file based manifest by using a stream
   2207     fn from_json_with_stream() {
   2208         use crate::assertions::Relationship;
   2209 
   2210         let mut manifest = Manifest::from_json(MANIFEST_JSON).unwrap();
   2211         // add binary resources to manifest and ingredients giving matching the identifiers given in JSON
   2212         manifest
   2213             .resources_mut()
   2214             .add("IMG_0003.jpg", *b"my value")
   2215             .unwrap()
   2216             .add("sample1.svg", *b"my value")
   2217             .expect("add resource");
   2218         manifest.ingredients_mut()[0]
   2219             .resources_mut()
   2220             .add("exp-test1.png", *b"my value")
   2221             .expect("add_resource");
   2222         manifest.ingredients_mut()[1]
   2223             .resources_mut()
   2224             .add("prompt.txt", *b"pirate with bird on shoulder")
   2225             .expect("add_resource");
   2226 
   2227         println!("{manifest}");
   2228 
   2229         let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg");
   2230         // convert buffer to cursor with Read/Write/Seek capability
   2231         let mut input = std::io::Cursor::new(image.to_vec());
   2232 
   2233         let signer = temp_signer();
   2234         // Embed a manifest using the signer.
   2235         let mut output = Cursor::new(Vec::new());
   2236         manifest
   2237             .embed_to_stream("jpeg", &mut input, &mut output, signer.as_ref())
   2238             .expect("embed_stream");
   2239 
   2240         output.set_position(0);
   2241         let reader = Reader::from_stream("jpeg", &mut output).expect("from_bytes");
   2242         println!("manifest_store = {reader}");
   2243         let m = reader.active_manifest().unwrap();
   2244 
   2245         //println!("after = {m}");
   2246 
   2247         assert!(m.thumbnail().is_some());
   2248         let (format, image) = m.thumbnail().unwrap();
   2249         assert_eq!(format, "image/jpeg");
   2250         assert_eq!(image.to_vec(), b"my value");
   2251         assert_eq!(m.ingredients().len(), 2);
   2252         assert_eq!(m.ingredients()[1].relationship(), &Relationship::InputTo);
   2253         assert!(m.ingredients()[1].data_ref().is_some());
   2254         assert_eq!(m.ingredients()[1].data_ref().unwrap().format, "text/plain");
   2255         let id = m.ingredients()[1].data_ref().unwrap().identifier.as_str();
   2256         assert_eq!(
   2257             m.ingredients()[1].resources().get(id).unwrap().into_owned(),
   2258             b"pirate with bird on shoulder"
   2259         );
   2260         // println!("{manifest_store}");
   2261     }
   2262 
   2263     #[test]
   2264     #[cfg(feature = "openssl_sign")]
   2265     /// tests and illustrates how to add assets to a non-file based manifest by using a memory buffer
   2266     fn from_json_with_memory() {
   2267         use crate::assertions::Relationship;
   2268 
   2269         let mut manifest = Manifest::from_json(MANIFEST_JSON).unwrap();
   2270         // add binary resources to manifest and ingredients giving matching the identifiers given in JSON
   2271         manifest
   2272             .resources_mut()
   2273             .add("IMG_0003.jpg", *b"my value")
   2274             .unwrap()
   2275             .add("sample1.svg", *b"my value")
   2276             .expect("add resource");
   2277         manifest.ingredients_mut()[0]
   2278             .resources_mut()
   2279             .add("exp-test1.png", *b"my value")
   2280             .expect("add_resource");
   2281         manifest.ingredients_mut()[1]
   2282             .resources_mut()
   2283             .add("prompt.txt", *b"pirate with bird on shoulder")
   2284             .expect("add_resource");
   2285 
   2286         println!("{manifest}");
   2287 
   2288         let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg");
   2289 
   2290         let signer = temp_signer();
   2291         // Embed a manifest using the signer.
   2292         let output_image = manifest
   2293             .embed_from_memory("jpeg", image, signer.as_ref())
   2294             .expect("embed_stream");
   2295 
   2296         let reader = Reader::from_stream("jpeg", Cursor::new(output_image)).expect("from_bytes");
   2297         println!("manifest_store = {reader}");
   2298         let m = reader.active_manifest().unwrap();
   2299 
   2300         assert!(m.thumbnail().is_some());
   2301         let (format, image) = m.thumbnail().unwrap();
   2302         assert_eq!(format, "image/jpeg");
   2303         assert_eq!(image.to_vec(), b"my value");
   2304         assert_eq!(m.ingredients().len(), 2);
   2305         assert_eq!(m.ingredients()[1].relationship(), &Relationship::InputTo);
   2306         assert!(m.ingredients()[1].data_ref().is_some());
   2307         assert_eq!(m.ingredients()[1].data_ref().unwrap().format, "text/plain");
   2308         let id = m.ingredients()[1].data_ref().unwrap().identifier.as_str();
   2309         assert_eq!(
   2310             m.ingredients()[1].resources().get(id).unwrap().into_owned(),
   2311             b"pirate with bird on shoulder"
   2312         );
   2313         // println!("{manifest_store}");
   2314     }
   2315 
   2316     #[test]
   2317     #[cfg(feature = "file_io")]
   2318     fn from_json_with_files() {
   2319         let mut manifest = Manifest::from_json(MANIFEST_JSON).unwrap();
   2320         let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
   2321         path.push("tests/fixtures"); // the path we want to read files from
   2322         manifest.with_base_path(path).expect("with_files");
   2323         // convert the manifest to a store
   2324         let store = manifest.to_store().expect("to store");
   2325         let mut resource_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
   2326         resource_path.push("../target/tmp/manifest");
   2327         let m2 = Manifest::from_store(
   2328             &store,
   2329             &store.provenance_label().unwrap(),
   2330             Some(&resource_path),
   2331         )
   2332         .expect("from store");
   2333         println!("{m2}");
   2334         assert!(m2.thumbnail().is_some());
   2335         assert!(m2.ingredients()[0].thumbnail().is_some());
   2336     }
   2337 
   2338     #[cfg(feature = "file_io")]
   2339     #[test]
   2340     fn test_embed_from_json() {
   2341         let mut fixtures = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
   2342         fixtures.push("tests/fixtures"); // the path we want to read files from
   2343 
   2344         let temp_dir = tempdir().expect("temp dir");
   2345         let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   2346 
   2347         let signer = temp_signer();
   2348 
   2349         let mut manifest = Manifest::from_json(MANIFEST_JSON).expect("from_json");
   2350         manifest.with_base_path(fixtures).expect("with_base");
   2351         manifest
   2352             .embed(&output, &output, signer.as_ref())
   2353             .expect("embed");
   2354 
   2355         let reader = Reader::from_file(&output).expect("from_file");
   2356         println!("{reader}");
   2357         let active_manifest = reader.active_manifest().unwrap();
   2358         let (format, _) = active_manifest.thumbnail().unwrap();
   2359         assert_eq!(format, "image/jpeg");
   2360     }
   2361 
   2362     #[cfg(feature = "file_io")]
   2363     #[test]
   2364     fn test_embed_webp_from_json() {
   2365         use crate::utils::test::TEST_WEBP;
   2366 
   2367         let mut fixtures = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
   2368         fixtures.push("tests/fixtures"); // the path we want to read files from
   2369 
   2370         let temp_dir = tempdir().expect("temp dir");
   2371         let output = temp_fixture_path(&temp_dir, TEST_WEBP);
   2372 
   2373         let signer = temp_signer();
   2374 
   2375         let mut manifest = Manifest::from_json(MANIFEST_JSON).expect("from_json");
   2376         manifest.with_base_path(fixtures).expect("with_base");
   2377         manifest
   2378             .embed(&output, &output, signer.as_ref())
   2379             .expect("embed");
   2380 
   2381         let manifest_store = Reader::from_file(&output).expect("from_file");
   2382         println!("{manifest_store}");
   2383         let active_manifest = manifest_store.active_manifest().unwrap();
   2384         let (format, _) = active_manifest.thumbnail().unwrap();
   2385         assert_eq!(format, "image/jpeg");
   2386     }
   2387 
   2388     #[test]
   2389     #[cfg(feature = "file_io")]
   2390     fn test_create_file_based_ingredient() {
   2391         let mut fixtures = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
   2392         fixtures.push("tests/fixtures");
   2393 
   2394         let temp_dir = tempdir().expect("temp dir");
   2395         let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   2396 
   2397         let mut manifest = Manifest::new("claim_generator");
   2398         manifest.with_base_path(fixtures).expect("with_base");
   2399         // verify we can't set a references that don't exist
   2400         assert!(manifest
   2401             .set_thumbnail_ref(ResourceRef::new("image/jpg", "foo"))
   2402             .is_err());
   2403         assert!(manifest.thumbnail_ref().is_none());
   2404         // verify we can set a references that do exist
   2405         assert!(manifest
   2406             .set_thumbnail_ref(ResourceRef::new("image/jpeg", "C.jpg"))
   2407             .is_ok());
   2408         assert!(manifest.thumbnail_ref().is_some());
   2409 
   2410         let signer = temp_signer();
   2411         manifest
   2412             .embed(&output, &output, signer.as_ref())
   2413             .expect("embed");
   2414     }
   2415 
   2416     #[test]
   2417     #[cfg(all(feature = "file_io", feature = "add_thumbnails"))]
   2418     fn test_create_no_claim_thumbnail() {
   2419         let mut fixtures = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
   2420         fixtures.push("tests/fixtures");
   2421 
   2422         let temp_dir = tempdir().expect("temp dir");
   2423         let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG);
   2424 
   2425         let mut manifest = Manifest::new("claim_generator");
   2426 
   2427         // Set format to none to force no claim thumbnail generated
   2428         assert!(manifest
   2429             .set_thumbnail_ref(ResourceRef::new("none", "none"))
   2430             .is_ok());
   2431         // verify there is a thumbnail ref
   2432         assert!(manifest.thumbnail_ref().is_some());
   2433         // verify there is no thumbnail
   2434         assert!(manifest.thumbnail().is_none());
   2435 
   2436         let signer = temp_signer();
   2437         manifest
   2438             .embed(&output, &output, signer.as_ref())
   2439             .expect("embed");
   2440 
   2441         let manifest_store = Reader::from_file(&output).expect("from_file");
   2442         println!("{manifest_store}");
   2443         let active_manifest = manifest_store.active_manifest().unwrap();
   2444         assert!(active_manifest.thumbnail_ref().is_none());
   2445         assert!(active_manifest.thumbnail().is_none());
   2446     }
   2447 
   2448     #[test]
   2449     fn test_missing_thumbnail() {
   2450         const MANIFEST_JSON: &str = r#"
   2451             {
   2452                 "claim_generator": "test",
   2453                 "format" : "image/jpeg",
   2454                 "thumbnail": {
   2455                     "format": "image/jpeg",
   2456                     "identifier": "does_not_exist.jpg"
   2457                 }
   2458             }
   2459         "#;
   2460 
   2461         let mut manifest = Manifest::from_json(MANIFEST_JSON).expect("from_json");
   2462 
   2463         let mut source = std::io::Cursor::new(vec![1, 2, 3]);
   2464         let mut dest = std::io::Cursor::new(Vec::new());
   2465         let signer = temp_signer();
   2466         let result =
   2467             manifest.embed_to_stream("image/jpeg", &mut source, &mut dest, signer.as_ref());
   2468         assert!(result.is_err());
   2469         assert!(result
   2470             .unwrap_err()
   2471             .to_string()
   2472             .contains("resource not found: does_not_exist.jpg"));
   2473     }
   2474 
   2475     #[test]
   2476     #[cfg(feature = "file_io")]
   2477     fn test_data_hash_embeddable_manifest() {
   2478         let ap = fixture_path("cloud.jpg");
   2479 
   2480         let signer = temp_signer();
   2481 
   2482         let mut manifest = Manifest::new("claim_generator");
   2483 
   2484         // get a placeholder the manifest
   2485         let placeholder = manifest
   2486             .data_hash_placeholder(signer.reserve_size(), "jpeg")
   2487             .unwrap();
   2488 
   2489         let temp_dir = tempfile::tempdir().unwrap();
   2490         let output = temp_dir_path(&temp_dir, "boxhash-out.jpg");
   2491         let mut output_file = std::fs::OpenOptions::new()
   2492             .read(true)
   2493             .write(true)
   2494             .create(true)
   2495             .truncate(true)
   2496             .open(&output)
   2497             .unwrap();
   2498 
   2499         // write a jpeg file with a placeholder for the manifest (returns offset of the placeholder)
   2500         let offset =
   2501             write_jpeg_placeholder_file(&placeholder, &ap, &mut output_file, None).unwrap();
   2502 
   2503         // build manifest to insert in the hole
   2504 
   2505         // create an hash exclusion for the manifest
   2506         let exclusion = HashRange::new(offset, placeholder.len());
   2507         let exclusions = vec![exclusion];
   2508 
   2509         let mut dh = DataHash::new("source_hash", "sha256");
   2510         dh.exclusions = Some(exclusions);
   2511 
   2512         let signed_manifest = manifest
   2513             .data_hash_embeddable_manifest(
   2514                 &dh,
   2515                 signer.as_ref(),
   2516                 "image/jpeg",
   2517                 Some(&mut output_file),
   2518             )
   2519             .unwrap();
   2520 
   2521         use std::io::{Seek, SeekFrom, Write};
   2522 
   2523         // path in new composed manifest
   2524         output_file.seek(SeekFrom::Start(offset as u64)).unwrap();
   2525         output_file.write_all(&signed_manifest).unwrap();
   2526 
   2527         let manifest_store = Reader::from_file(&output).expect("from_file");
   2528         println!("{manifest_store}");
   2529         assert!(manifest_store.validation_status().is_none());
   2530     }
   2531 
   2532     #[cfg(all(feature = "file_io", feature = "openssl_sign"))]
   2533     #[actix::test]
   2534     async fn test_data_hash_embeddable_manifest_remote_signed() {
   2535         let ap = fixture_path("cloud.jpg");
   2536 
   2537         let signer = temp_remote_signer();
   2538 
   2539         let mut manifest = Manifest::new("claim_generator");
   2540 
   2541         // get a placeholder the manifest
   2542         let placeholder = manifest
   2543             .data_hash_placeholder(signer.reserve_size(), "jpeg")
   2544             .unwrap();
   2545 
   2546         let temp_dir = tempfile::tempdir().unwrap();
   2547         let output = temp_dir_path(&temp_dir, "boxhash-out.jpg");
   2548         let mut output_file = std::fs::OpenOptions::new()
   2549             .read(true)
   2550             .write(true)
   2551             .create(true)
   2552             .truncate(true)
   2553             .open(&output)
   2554             .unwrap();
   2555 
   2556         // write a jpeg file with a placeholder for the manifest (returns offset of the placeholder)
   2557         let offset =
   2558             write_jpeg_placeholder_file(&placeholder, &ap, &mut output_file, None).unwrap();
   2559 
   2560         // build manifest to insert in the hole
   2561 
   2562         // create an hash exclusion for the manifest
   2563         let exclusion = HashRange::new(offset, placeholder.len());
   2564         let exclusions = vec![exclusion];
   2565 
   2566         let mut dh = DataHash::new("source_hash", "sha256");
   2567         dh.exclusions = Some(exclusions);
   2568 
   2569         let signed_manifest = manifest
   2570             .data_hash_embeddable_manifest_remote(
   2571                 &dh,
   2572                 signer.as_ref(),
   2573                 "c2pa", // force an uncomposed manifest - you could send this to the cloud
   2574                 Some(&mut output_file),
   2575             )
   2576             .await
   2577             .unwrap();
   2578 
   2579         // test composed manifest here to ensure it works
   2580         let signed_manifest =
   2581             Manifest::composed_manifest(&signed_manifest, "image/jpeg").expect("composed_manifest");
   2582         use std::io::{Seek, SeekFrom, Write};
   2583 
   2584         // path in new composed manifest
   2585         output_file.seek(SeekFrom::Start(offset as u64)).unwrap();
   2586         output_file.write_all(&signed_manifest).unwrap();
   2587 
   2588         let manifest_store = Reader::from_file(&output).expect("from_file");
   2589         println!("{manifest_store}");
   2590         assert!(manifest_store.validation_status().is_none());
   2591     }
   2592 
   2593     #[test]
   2594     #[cfg(feature = "file_io")]
   2595     fn test_box_hash_embeddable_manifest() {
   2596         let asset_bytes = include_bytes!("../tests/fixtures/boxhash.jpg");
   2597         let box_hash_data = include_bytes!("../tests/fixtures/boxhash.json");
   2598         let box_hash: crate::assertions::BoxHash = serde_json::from_slice(box_hash_data).unwrap();
   2599 
   2600         let mut manifest = Manifest::new("test_app".to_owned());
   2601         manifest.set_title("BoxHashTest").set_format("image/jpeg");
   2602 
   2603         manifest
   2604             .add_labeled_assertion(crate::assertions::labels::BOX_HASH, &box_hash)
   2605             .unwrap();
   2606 
   2607         let signer = temp_signer();
   2608 
   2609         let embeddable = manifest
   2610             .box_hash_embeddable_manifest(signer.as_ref(), None)
   2611             .expect("embeddable_manifest");
   2612 
   2613         // Validate the embeddable manifest against the asset bytes
   2614         let reader = Reader::from_manifest_data_and_stream(
   2615             &embeddable,
   2616             "image/jpeg",
   2617             Cursor::new(asset_bytes),
   2618         )
   2619         .unwrap();
   2620         println!("{reader}");
   2621         assert!(reader.active_manifest().is_some());
   2622         assert!(reader.validation_status().is_none());
   2623     }
   2624 }