commit 2d440a21313a35b5a8665e2556e33f52ba89edf7
parent 58e6094426c16565404837115e738ba792862ce9
Author: Gavin Peacock <gpeacock@adobe.com>
Date: Thu, 12 Oct 2023 17:39:13 -0700
Adds embed_to_stream (#313)
* Adds embed_to_stream with output stream and returning c2pa_data
Fixes a bug in embed_stream where missing thumbnails were not reported
* Deprecate embed_stream
* A whole lot of clippy update fixes
Update to use nightly clippy in make test
Diffstat:
15 files changed, 101 insertions(+), 60 deletions(-)
diff --git a/Makefile b/Makefile
@@ -20,7 +20,7 @@ check-docs:
cargo doc --no-deps --workspace --all-features
clippy:
- cargo clippy --all-features --all-targets -- -D warnings
+ cargo +nightly clippy --all-features --all-targets -- -D warnings
test-local:
cargo test --all-features
diff --git a/sdk/src/asn1/rfc3281.rs b/sdk/src/asn1/rfc3281.rs
@@ -69,7 +69,7 @@ pub struct AttributeCertificateInfo {
}
impl AttributeCertificateInfo {
- pub fn take_from<S: Source>(cons: &mut Constructed<S>) -> Result<Self, DecodeError<S::Error>> {
+ pub fn take_from<S: Source>(cons: &Constructed<S>) -> Result<Self, DecodeError<S::Error>> {
Err(cons.content_err("AttributeCertificateInfo parsing not implemented"))
}
}
diff --git a/sdk/src/asn1/rfc5652.rs b/sdk/src/asn1/rfc5652.rs
@@ -159,7 +159,7 @@ pub struct SignedData {
impl SignedData {
/// Attempt to decode BER encoded bytes to a parsed data structure.
pub fn decode_ber(data: &[u8]) -> Result<Self, DecodeError<std::convert::Infallible>> {
- Constructed::decode(data, bcder::Mode::Ber, |cons| Self::decode(cons))
+ Constructed::decode(data, bcder::Mode::Ber, Self::decode)
}
pub fn decode<S: Source>(cons: &mut Constructed<S>) -> Result<Self, DecodeError<S::Error>> {
@@ -1118,7 +1118,7 @@ pub type KeyDerivationAlgorithmIdentifier = AlgorithmIdentifier;
pub struct RevocationInfoChoices(Vec<RevocationInfoChoice>);
impl RevocationInfoChoices {
- pub fn take_from<S: Source>(cons: &mut Constructed<S>) -> Result<Self, DecodeError<S::Error>> {
+ pub fn take_from<S: Source>(cons: &Constructed<S>) -> Result<Self, DecodeError<S::Error>> {
Err(cons.content_err("RevocationInfoChoices parsing not implemented"))
}
}
@@ -1232,7 +1232,7 @@ pub struct OtherCertificateFormat {
}
impl OtherCertificateFormat {
- pub fn take_from<S: Source>(cons: &mut Constructed<S>) -> Result<Self, DecodeError<S::Error>> {
+ pub fn take_from<S: Source>(cons: &Constructed<S>) -> Result<Self, DecodeError<S::Error>> {
Err(cons.content_err("OtherCertificateFormat parsing not implemented"))
}
}
diff --git a/sdk/src/assertions/ingredient.rs b/sdk/src/assertions/ingredient.rs
@@ -124,7 +124,7 @@ impl Ingredient {
}
pub fn add_review(mut self, review: ReviewRating) -> Self {
- let metadata = self.metadata.unwrap_or_else(Metadata::new);
+ let metadata = self.metadata.unwrap_or_default();
self.metadata = Some(metadata.add_review(review));
self
}
diff --git a/sdk/src/asset_handlers/bmff_io.rs b/sdk/src/asset_handlers/bmff_io.rs
@@ -354,7 +354,7 @@ fn add_token_to_cache(bmff_path_map: &mut HashMap<String, Vec<Token>>, path: Str
}
}
-fn path_from_token(bmff_tree: &mut Arena<BoxInfo>, current_node_token: &Token) -> Result<String> {
+fn path_from_token(bmff_tree: &Arena<BoxInfo>, current_node_token: &Token) -> Result<String> {
let ancestors = current_node_token.ancestors(bmff_tree);
let mut path = bmff_tree[*current_node_token].data.path.clone();
diff --git a/sdk/src/asset_handlers/tiff_io.rs b/sdk/src/asset_handlers/tiff_io.rs
@@ -863,7 +863,7 @@ impl<T: Read + Write + Seek> TiffCloner<T> {
fn clone_sub_files<R: Read + Seek>(
&mut self,
- tiff_tree: &mut Arena<ImageFileDirectory>,
+ tiff_tree: &Arena<ImageFileDirectory>,
page: Token,
asset_reader: &mut R,
) -> Result<HashMap<u16, Vec<u64>>> {
diff --git a/sdk/src/ingredient.rs b/sdk/src/ingredient.rs
@@ -569,7 +569,7 @@ impl Ingredient {
&mut self,
result: Result<Store>,
manifest_bytes: Option<Vec<u8>>,
- validation_log: &mut impl StatusTracker,
+ validation_log: &impl StatusTracker,
) -> Result<()> {
match result {
Ok(store) => {
@@ -736,7 +736,7 @@ impl Ingredient {
};
// set validation status from result and log
- ingredient.update_validation_status(result, manifest_bytes, &mut validation_log)?;
+ ingredient.update_validation_status(result, manifest_bytes, &validation_log)?;
// create a thumbnail if we don't already have a manifest with a thumb we can use
if ingredient.thumbnail.is_none() {
@@ -798,7 +798,7 @@ impl Ingredient {
};
// set validation status from result and log
- ingredient.update_validation_status(result, manifest_bytes, &mut validation_log)?;
+ ingredient.update_validation_status(result, manifest_bytes, &validation_log)?;
// create a thumbnail if we don't already have a manifest with a thumb we can use
#[cfg(feature = "add_thumbnails")]
@@ -871,7 +871,7 @@ impl Ingredient {
};
// set validation status from result and log
- ingredient.update_validation_status(result, manifest_bytes, &mut validation_log)?;
+ ingredient.update_validation_status(result, manifest_bytes, &validation_log)?;
// create a thumbnail if we don't already have a manifest with a thumb we can use
#[cfg(feature = "add_thumbnails")]
@@ -1206,7 +1206,7 @@ impl Ingredient {
};
// set validation status from result and log
- ingredient.update_validation_status(result, Some(manifest_bytes), &mut validation_log)?;
+ ingredient.update_validation_status(result, Some(manifest_bytes), &validation_log)?;
// create a thumbnail if we don't already have a manifest with a thumb we can use
#[cfg(feature = "add_thumbnails")]
diff --git a/sdk/src/manifest.rs b/sdk/src/manifest.rs
@@ -29,7 +29,7 @@ use crate::{
assertions::{
labels, Actions, CreativeWork, DataHash, Exif, SoftwareAgent, Thumbnail, User, UserCbor,
},
- asset_io::CAIRead,
+ asset_io::{CAIRead, CAIReadWrite},
claim::{Claim, RemoteManifest},
error::{Error, Result},
jumbf,
@@ -1017,18 +1017,38 @@ impl Manifest {
// todo:: see if we can pass a trait with to_vec support like we to for Strings
let asset = asset.to_vec();
let mut stream = std::io::Cursor::new(asset);
-
- self.embed_stream(format, &mut stream, signer)
+ let mut output_stream = Cursor::new(Vec::new());
+ self.embed_to_stream(format, &mut stream, &mut output_stream, signer)?;
+ Ok(output_stream.into_inner())
}
/// Embed a signed manifest into a stream using a supplied signer.
/// returns the bytes of the new asset
+ #[deprecated(since = "0.27.2", note = "use embed_to_stream instead")]
pub fn embed_stream(
&mut self,
format: &str,
stream: &mut dyn CAIRead,
signer: &dyn Signer,
) -> Result<Vec<u8>> {
+ // sign and write our store to to the output image file
+ let output_vec: Vec<u8> = Vec::new();
+ let mut output_stream = Cursor::new(output_vec);
+
+ self.embed_to_stream(format, stream, &mut output_stream, signer)?;
+
+ Ok(output_stream.into_inner())
+ }
+
+ /// Embed a signed manifest into a stream using a supplied signer.
+ /// returns the bytes of c2pa_manifest that was embedded
+ pub fn embed_to_stream(
+ &mut self,
+ format: &str,
+ source: &mut dyn CAIRead,
+ dest: &mut dyn CAIReadWrite,
+ signer: &dyn Signer,
+ ) -> Result<Vec<u8>> {
self.set_format(format);
// todo:: read instance_id from xmp from stream
self.set_instance_id(format!("xmp:iid:{}", Uuid::new_v4()));
@@ -1036,9 +1056,9 @@ impl Manifest {
// generate thumbnail if we don't already have one
#[cfg(feature = "add_thumbnails")]
{
- if self.thumbnail().is_none() {
+ if self.thumbnail_ref().is_none() {
if let Ok((format, image)) =
- crate::utils::thumbnail::make_thumbnail_from_stream(format, stream)
+ crate::utils::thumbnail::make_thumbnail_from_stream(format, source)
{
self.set_thumbnail(format, image)?;
}
@@ -1049,12 +1069,7 @@ impl Manifest {
let mut store = self.to_store()?;
// sign and write our store to to the output image file
- let output_vec: Vec<u8> = Vec::new();
- let mut output_stream = Cursor::new(output_vec);
-
- store.save_to_stream(format, stream, &mut output_stream, signer)?;
-
- Ok(output_stream.into_inner())
+ store.save_to_stream(format, source, dest, signer)
}
/// Embed a signed manifest into a stream using a supplied signer.
@@ -1075,7 +1090,7 @@ impl Manifest {
let mut stream = std::io::Cursor::new(asset);
#[cfg(feature = "add_thumbnails")]
{
- if self.thumbnail().is_none() {
+ if self.thumbnail_ref().is_none() {
if let Ok((format, image)) =
crate::utils::thumbnail::make_thumbnail_from_stream(format, &mut stream)
{
@@ -1229,6 +1244,8 @@ pub(crate) mod tests {
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
+ use std::io::Cursor;
+
#[cfg(feature = "file_io")]
use tempfile::tempdir;
#[cfg(target_arch = "wasm32")]
@@ -1777,13 +1794,14 @@ pub(crate) mod tests {
.unwrap();
let signer = temp_signer();
+ let mut output = Cursor::new(Vec::new());
// Embed a manifest using the signer.
- let output_image = manifest
- .embed_stream("jpeg", &mut stream, signer.as_ref())
+ manifest
+ .embed_to_stream("jpeg", &mut stream, &mut output, signer.as_ref())
.expect("embed_stream");
- let manifest_store =
- crate::ManifestStore::from_bytes("jpeg", &output_image, true).expect("from_bytes");
+ let manifest_store = crate::ManifestStore::from_bytes("jpeg", &output.into_inner(), true)
+ .expect("from_bytes");
assert_eq!(
manifest_store.get_active().unwrap().title().unwrap(),
"EmbedStream"
@@ -1992,16 +2010,17 @@ pub(crate) mod tests {
let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg");
// convert buffer to cursor with Read/Write/Seek capability
- let mut stream = std::io::Cursor::new(image.to_vec());
+ let mut input = std::io::Cursor::new(image.to_vec());
let signer = temp_signer();
// Embed a manifest using the signer.
- let output_image = manifest
- .embed_stream("jpeg", &mut stream, signer.as_ref())
+ let mut output = Cursor::new(Vec::new());
+ manifest
+ .embed_to_stream("jpeg", &mut input, &mut output, signer.as_ref())
.expect("embed_stream");
- let manifest_store =
- crate::ManifestStore::from_bytes("jpeg", &output_image, true).expect("from_bytes");
+ let manifest_store = crate::ManifestStore::from_bytes("jpeg", &output.into_inner(), true)
+ .expect("from_bytes");
println!("manifest_store = {manifest_store}");
let m = manifest_store.get_active().unwrap();
@@ -2210,6 +2229,33 @@ pub(crate) mod tests {
}
#[test]
+ fn test_missing_thumbnail() {
+ const MANIFEST_JSON: &str = r#"
+ {
+ "claim_generator": "test",
+ "format" : "image/jpeg",
+ "thumbnail": {
+ "format": "image/jpeg",
+ "identifier": "does_not_exist.jpg"
+ }
+ }
+ "#;
+
+ let mut manifest = Manifest::from_json(MANIFEST_JSON).expect("from_json");
+
+ let mut source = std::io::Cursor::new(vec![1, 2, 3]);
+ let mut dest = std::io::Cursor::new(Vec::new());
+ let signer = temp_signer();
+ let result =
+ manifest.embed_to_stream("image/jpeg", &mut source, &mut dest, signer.as_ref());
+ assert!(result.is_err());
+ assert!(result
+ .unwrap_err()
+ .to_string()
+ .contains("resource not found: does_not_exist.jpg"));
+ }
+
+ #[test]
#[cfg(feature = "file_io")]
fn test_data_hash_embeddable_manifest() {
let ap = fixture_path("cloud.jpg");
diff --git a/sdk/src/manifest_store.rs b/sdk/src/manifest_store.rs
@@ -82,10 +82,7 @@ impl ManifestStore {
}
/// creates a ManifestStore from a Store with validation
- pub(crate) fn from_store(
- store: &Store,
- validation_log: &mut impl StatusTracker,
- ) -> ManifestStore {
+ pub(crate) fn from_store(store: &Store, validation_log: &impl StatusTracker) -> ManifestStore {
Self::from_store_impl(
store,
validation_log,
@@ -98,7 +95,7 @@ impl ManifestStore {
#[cfg(feature = "file_io")]
pub fn from_store_with_resources(
store: &Store,
- validation_log: &mut impl StatusTracker,
+ validation_log: &impl StatusTracker,
resource_path: &Path,
) -> ManifestStore {
Self::from_store_impl(store, validation_log, Some(resource_path))
@@ -107,7 +104,7 @@ impl ManifestStore {
// internal implementation of from_store
fn from_store_impl(
store: &Store,
- validation_log: &mut impl StatusTracker,
+ validation_log: &impl StatusTracker,
#[cfg(feature = "file_io")] resource_path: Option<&Path>,
) -> ManifestStore {
let mut statuses = status_for_store(store, validation_log);
@@ -146,7 +143,7 @@ impl ManifestStore {
let store = manifest.to_store()?;
Ok(Self::from_store_impl(
&store,
- &mut OneShotStatusTracker::new(),
+ &OneShotStatusTracker::new(),
#[cfg(feature = "file_io")]
manifest.resources().base_path(),
))
@@ -157,7 +154,7 @@ impl ManifestStore {
let mut validation_log = DetailedStatusTracker::new();
Store::load_from_memory(format, image_bytes, verify, &mut validation_log)
- .map(|store| Self::from_store(&store, &mut validation_log))
+ .map(|store| Self::from_store(&store, &validation_log))
}
#[cfg(feature = "file_io")]
@@ -177,7 +174,7 @@ impl ManifestStore {
let mut validation_log = DetailedStatusTracker::new();
let store = Store::load_from_asset(path.as_ref(), true, &mut validation_log)?;
- Ok(Self::from_store(&store, &mut validation_log))
+ Ok(Self::from_store(&store, &validation_log))
}
#[cfg(feature = "file_io")]
@@ -205,7 +202,7 @@ impl ManifestStore {
let store = Store::load_from_asset(path.as_ref(), true, &mut validation_log)?;
Ok(Self::from_store_with_resources(
&store,
- &mut validation_log,
+ &validation_log,
resource_path.as_ref(),
))
}
@@ -220,7 +217,7 @@ impl ManifestStore {
Store::load_from_memory_async(format, image_bytes, verify, &mut validation_log)
.await
- .map(|store| Self::from_store(&store, &mut validation_log))
+ .map(|store| Self::from_store(&store, &validation_log))
}
/// Loads a ManifestStore from an init segment and fragment. This
@@ -242,7 +239,7 @@ impl ManifestStore {
&mut validation_log,
)
.await
- .map(|store| Self::from_store(&store, &mut validation_log))
+ .map(|store| Self::from_store(&store, &validation_log))
}
/// Asynchronously loads a manifest from a buffer holding a binary manifest (.c2pa) and validates against an asset buffer
@@ -281,7 +278,7 @@ impl ManifestStore {
)
.await?;
- Ok(Self::from_store(&store, &mut validation_log))
+ Ok(Self::from_store(&store, &validation_log))
}
/// Synchronously loads a manifest from a buffer holding a binary manifest (.c2pa) and validates against an asset buffer
@@ -318,7 +315,7 @@ impl ManifestStore {
&mut validation_log,
)?;
- Ok(Self::from_store(&store, &mut validation_log))
+ Ok(Self::from_store(&store, &validation_log))
}
}
@@ -397,7 +394,7 @@ mod tests {
fn manifest_report() {
let store = create_test_store().expect("creating test store");
- let manifest_store = ManifestStore::from_store(&store, &mut OneShotStatusTracker::new());
+ let manifest_store = ManifestStore::from_store(&store, &OneShotStatusTracker::new());
assert!(manifest_store.active_manifest.is_some());
assert!(!manifest_store.manifests.is_empty());
let manifest = manifest_store.get_active().unwrap();
diff --git a/sdk/src/manifest_store_report.rs b/sdk/src/manifest_store_report.rs
@@ -124,7 +124,7 @@ impl ManifestStoreReport {
/// Creates a ManifestStoreReport from an existing Store and a validation log
pub(crate) fn from_store_with_log(
store: &Store,
- validation_log: &mut impl StatusTracker,
+ validation_log: &impl StatusTracker,
) -> Result<Self> {
let mut report = Self::from_store(store)?;
@@ -149,7 +149,7 @@ impl ManifestStoreReport {
pub fn from_bytes(format: &str, image_bytes: &[u8]) -> Result<Self> {
let mut validation_log = DetailedStatusTracker::new();
let store = Store::load_from_memory(format, image_bytes, true, &mut validation_log)?;
- Self::from_store_with_log(&store, &mut validation_log)
+ Self::from_store_with_log(&store, &validation_log)
}
/// Creates a ManifestStoreReport from a file
@@ -157,7 +157,7 @@ impl ManifestStoreReport {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut validation_log = DetailedStatusTracker::new();
let store = Store::load_from_asset(path.as_ref(), true, &mut validation_log)?;
- Self::from_store_with_log(&store, &mut validation_log)
+ Self::from_store_with_log(&store, &validation_log)
}
/// create a json string representation of this structure, omitting binaries
diff --git a/sdk/src/resource_store.rs b/sdk/src/resource_store.rs
@@ -303,13 +303,11 @@ mod tests {
println!("{manifest}");
let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg");
- // convert buffer to cursor with Read/Write/Seek capability
- let mut stream = std::io::Cursor::new(image.to_vec());
let signer = temp_signer();
// Embed a manifest using the signer.
let output_image = manifest
- .embed_stream("jpeg", &mut stream, signer.as_ref())
+ .embed_from_memory("jpeg", image, signer.as_ref())
.expect("embed_stream");
let _manifest_store =
diff --git a/sdk/src/store.rs b/sdk/src/store.rs
@@ -156,7 +156,7 @@ impl Store {
if let Some(bh) = self.manifest_box_hash_cache.get(claim.label()) {
bh.clone()
} else {
- Store::calc_manifest_box_hash(claim, None, claim.alg()).unwrap_or(Vec::new())
+ Store::calc_manifest_box_hash(claim, None, claim.alg()).unwrap_or_default()
}
}
diff --git a/sdk/src/time_stamp.rs b/sdk/src/time_stamp.rs
@@ -236,7 +236,7 @@ impl TimeStampResponse {
token
.content
.clone()
- .decode(|cons| SignedData::take_from(cons))
+ .decode(SignedData::take_from)
.map_err(|_err| Error::CoseTimeStampGeneration)?,
))
} else {
diff --git a/sdk/src/utils/hash_utils.rs b/sdk/src/utils/hash_utils.rs
@@ -139,14 +139,14 @@ impl Hasher {
pub fn hash_by_alg(alg: &str, data: &[u8], exclusions: Option<Vec<HashRange>>) -> Vec<u8> {
let mut reader = Cursor::new(data);
- hash_stream_by_alg(alg, &mut reader, exclusions, true).unwrap_or(Vec::new())
+ hash_stream_by_alg(alg, &mut reader, exclusions, true).unwrap_or_default()
}
// Return hash inclusive bytes for desired hashing algorithm.
pub fn hash_by_alg_with_inclusions(alg: &str, data: &[u8], inclusions: Vec<HashRange>) -> Vec<u8> {
let mut reader = Cursor::new(data);
- hash_stream_by_alg(alg, &mut reader, Some(inclusions), false).unwrap_or(Vec::new())
+ hash_stream_by_alg(alg, &mut reader, Some(inclusions), false).unwrap_or_default()
}
// Return hash bytes for asset using desired hashing algorithm.
diff --git a/sdk/src/validation_status.rs b/sdk/src/validation_status.rs
@@ -161,7 +161,7 @@ impl PartialEq for ValidationStatus {
/// be reported as a validation error for any ingredient.
pub fn status_for_store(
store: &Store,
- validation_log: &mut impl StatusTracker,
+ validation_log: &impl StatusTracker,
) -> Vec<ValidationStatus> {
let statuses: Vec<ValidationStatus> = validation_log
.get_log()