Skip to content
34 changes: 32 additions & 2 deletions sdk/src/assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,16 @@
Uuid(String, Vec<u8>), // user defined content (uuid, data)
}

impl From<AssertionData> for Vec<u8> {
fn from(ad: AssertionData) -> Self {
match ad {
AssertionData::Json(s) => s.into_bytes(), // json encoded data
AssertionData::Binary(x) | AssertionData::Uuid(_, x) => x, // binary data
AssertionData::Cbor(x) => x,

Check warning on line 193 in sdk/src/assertion.rs

View check run for this annotation

Codecov / codecov/patch

sdk/src/assertion.rs#L189-L193

Added lines #L189 - L193 were not covered by tests
}
}

Check warning on line 195 in sdk/src/assertion.rs

View check run for this annotation

Codecov / codecov/patch

sdk/src/assertion.rs#L195

Added line #L195 was not covered by tests
}

impl fmt::Debug for AssertionData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Expand Down Expand Up @@ -239,8 +249,8 @@
}

/// return content_type for the the data enclosed in the Assertion
pub(crate) fn content_type(&self) -> String {
self.content_type.clone()
pub(crate) fn content_type(&self) -> &str {
self.content_type.as_str()
}

// pub(crate) fn set_data(mut self, data: &AssertionData) -> Self {
Expand Down Expand Up @@ -388,6 +398,26 @@
)
}

/// Deconstruct a binary assertion, moving the Vec<u8> out without copying
pub(crate) fn binary_deconstruct(
assertion: Assertion,
) -> Result<(String, Option<usize>, String, Vec<u8>)> {
match assertion.data {
AssertionData::Binary(data) => Ok((
assertion.label,
assertion.version,
assertion.content_type,
data,
)),
_ => Err(AssertionDecodeError::from_assertion_unexpected_data_type(
&assertion,
assertion.decode_data(),
"binary",
)
.into()),
}
}

/// create an assertion from user binary data
pub(crate) fn from_data_uuid(label: &str, uuid_str: &str, binary_data: &[u8]) -> Assertion {
Self::from_assertion_data(
Expand Down
146 changes: 146 additions & 0 deletions sdk/src/assertions/embedded_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright 2022 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

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

use serde::Serialize;

use crate::{
assertion::{Assertion, AssertionBase, AssertionData},
assertions::labels,
error::Result,
};

/// A EmbeddedData assertion
#[derive(Serialize)]
pub struct EmbeddedData {
pub label: String,
pub content_type: String,
pub data: Vec<u8>,
}

impl EmbeddedData {
/// Label prefix for a embedded data assertion.
/// Note that this is often overridden for thumbnails or icons
pub const LABEL: &'static str = labels::EMBEDDED_DATA;

/// Create a new EmbeddedData with a specific content type
pub fn new<L, C, D>(label: L, content_type: C, data: D) -> Self
where
L: Into<String>,
C: Into<String>,
D: Into<Vec<u8>>,
{
Self {
data: data.into(),
label: label.into(),
content_type: content_type.into(),
}
}
}

impl AssertionBase for EmbeddedData {
fn label(&self) -> &str {
self.label.as_str()
}

Check warning on line 53 in sdk/src/assertions/embedded_data.rs

View check run for this annotation

Codecov / codecov/patch

sdk/src/assertions/embedded_data.rs#L51-L53

Added lines #L51 - L53 were not covered by tests

fn to_assertion(&self) -> Result<Assertion> {
let data = AssertionData::Binary(self.data.to_owned());
Ok(Assertion::new(&self.label, None, data).set_content_type(&self.content_type))
}

fn from_assertion(assertion: &Assertion) -> Result<Self> {
// Clone the assertion to use the TryFrom implementation
// This avoids code duplication while maintaining the interface
assertion.try_into()
}
}

impl TryFrom<Assertion> for EmbeddedData {
type Error = crate::Error;

fn try_from(assertion: Assertion) -> Result<Self> {
match crate::assertion::Assertion::binary_deconstruct(assertion) {
Ok((label, _version, content_type, data)) => Ok(Self {
data,
label,
content_type,
}),
Err(err) => Err(err),
}
}
}

impl TryFrom<&Assertion> for EmbeddedData {
type Error = crate::Error;

fn try_from(assertion: &Assertion) -> Result<Self> {
assertion.clone().try_into()
}
}

#[cfg(test)]
pub mod tests {
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]

use super::*;

// a binary assertion ('deadbeefadbeadbe')
fn some_binary_data() -> Vec<u8> {
vec![
0x0d, 0x0e, 0x0a, 0x0d, 0x0b, 0x0e, 0x0e, 0x0f, 0x0a, 0x0d, 0x0b, 0x0e, 0x0a, 0x0d,
0x0b, 0x0e,
]
}

fn embedded_data_test(label: &str, content_type: &str) {
let original = EmbeddedData::new(label, content_type, some_binary_data());
let assertion = original.to_assertion().expect("build_assertion");
assert_eq!(assertion.content_type(), content_type);
assert_eq!(assertion.label(), label);
let result = EmbeddedData::from_assertion(&assertion).expect("from_assertion");
assert_eq!(original.label, result.label);
assert_eq!(original.content_type, result.content_type);
assert_eq!(original.data, result.data);
}

#[test]
fn assertion_embedded_data_valid() {
embedded_data_test(labels::JPEG_CLAIM_THUMBNAIL, "image/jpeg");
embedded_data_test(labels::PNG_CLAIM_THUMBNAIL, "image/png");
embedded_data_test(labels::JPEG_INGREDIENT_THUMBNAIL, "image/jpeg");
embedded_data_test(labels::PNG_INGREDIENT_THUMBNAIL, "image/png");
// unrecognized labels will be formatted as octet_streams
embedded_data_test("foo", "application/octet-stream");
}

#[test]
fn assertion_embedded_data_invalid_from() {
// only current error is if the assertion data is the wrong type, so use JSON
let data = AssertionData::Json("foo".to_owned());
let assertion = Assertion::new(labels::JPEG_CLAIM_THUMBNAIL, None, data);
let result = EmbeddedData::from_assertion(&assertion);
assert!(result.is_err())
}

#[test]
fn assertion_embedded_data_with_format() {
let original = EmbeddedData::new(EmbeddedData::LABEL, "image/png", some_binary_data());
let assertion = original.to_assertion().expect("build_assertion");
assert_eq!(assertion.content_type(), "image/png");
assert_eq!(assertion.label(), EmbeddedData::LABEL);
let result = EmbeddedData::from_assertion(&assertion).expect("from_assertion");
assert_eq!(original.label, result.label);
assert_eq!(original.content_type, result.content_type);
assert_eq!(original.data, result.data);
}
}
11 changes: 11 additions & 0 deletions sdk/src/assertions/labels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ pub const DEPTHMAP: &str = "c2pa.depthmap";
/// See <https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_asset_type>.
pub const ASSET_TYPE: &str = "c2pa.asset-type";

/// Label prefix for a embedded data assertion.
///
/// See <https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_embedded_data>.
pub const EMBEDDED_DATA: &str = "c2pa.embedded-data";

/// Label prefix for a Icon assertion.
///
/// See <https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_generator_info_map>.
pub const ICON: &str = "c2pa.icon";

/// Label prefix for a GDepth assertion.
/// Label prefix for a GDepth depthmap assertion.
///
/// See <https://c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_gdepth_depthmap>.
Expand Down
3 changes: 3 additions & 0 deletions sdk/src/assertions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,7 @@ mod uuid_assertion;
#[allow(unused_imports)]
pub(crate) use uuid_assertion::Uuid;

mod embedded_data;
pub use embedded_data::EmbeddedData;

pub mod region_of_interest;
80 changes: 53 additions & 27 deletions sdk/src/assertions/thumbnail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,27 @@
// specific language governing permissions and limitations under
// each license.

use std::ops::{Deref, DerefMut};

use serde::Serialize;

use crate::{
assertion::{
get_thumbnail_image_type, Assertion, AssertionBase, AssertionData, AssertionDecodeError,
},
assertions::labels,
assertion::{get_thumbnail_image_type, Assertion, AssertionBase, AssertionData},
assertions::{labels, EmbeddedData},
error::Result,
};

/// A Thumbnail assertion
#[derive(Serialize)]
pub struct Thumbnail {
pub data: Vec<u8>,
pub label: String,
pub content_type: String,
}
/// We no longer need a specific Thumbnail Assertion type, so this is deprecated.
/// Please use EmbeddedData instead.
/// This exists to maintain compatibility with existing assertions that use the old v1 label format.
pub struct Thumbnail(EmbeddedData);

impl Thumbnail {
pub fn new(label: &str, data: Vec<u8>) -> Self {
/// This creates thumbnails with the old v1 label format and is deprecated.
/// Use `EmbeddedData::new` instead to specify the content type.
pub(crate) fn new(label: &str, data: Vec<u8>) -> Self {
let image_type = get_thumbnail_image_type(label);
let content_type = match &image_type {
Some(it) => match it.as_str() {
Expand All @@ -46,12 +47,21 @@
None => "application/octet-stream",
}
.to_string();
Self(EmbeddedData::new(label, content_type, data))
}
}

Thumbnail {
data,
label: label.to_owned(),
content_type,
}
impl Deref for Thumbnail {
type Target = EmbeddedData;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for Thumbnail {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0

Check warning on line 64 in sdk/src/assertions/thumbnail.rs

View check run for this annotation

Codecov / codecov/patch

sdk/src/assertions/thumbnail.rs#L63-L64

Added lines #L63 - L64 were not covered by tests
}
}

Expand All @@ -66,22 +76,26 @@
}

fn to_assertion(&self) -> Result<Assertion> {
let data = AssertionData::Binary(self.data.to_owned());
let data = AssertionData::Binary(self.data.clone());
Ok(Assertion::new(&self.label, None, data).set_content_type(&self.content_type))
}

fn from_assertion(assertion: &Assertion) -> Result<Thumbnail> {
match assertion.decode_data() {
AssertionData::Binary(data) => Ok(Self {
data: data.to_owned(),
label: assertion.label(),
content_type: assertion.content_type(),
}),
ad => Err(AssertionDecodeError::from_assertion_unexpected_data_type(
assertion, ad, "binary",
)
.into()),
}
let embedded_data = EmbeddedData::from_assertion(assertion)?;
Ok(Self(embedded_data))
}
}

// make it easy to convert from EmbeddedData to Thumbnail and vice versa
impl From<EmbeddedData> for Thumbnail {
fn from(embedded_data: EmbeddedData) -> Self {
Self(embedded_data)
}

Check warning on line 93 in sdk/src/assertions/thumbnail.rs

View check run for this annotation

Codecov / codecov/patch

sdk/src/assertions/thumbnail.rs#L91-L93

Added lines #L91 - L93 were not covered by tests
}

impl From<Thumbnail> for EmbeddedData {
fn from(val: Thumbnail) -> Self {
val.0
}
}

Expand Down Expand Up @@ -129,4 +143,16 @@
let result = Thumbnail::from_assertion(&assertion);
assert!(result.is_err())
}

#[test]
fn assertion_thumbnail_with_format() {
let original = EmbeddedData::new("foo", "image/png", some_binary_data());
let assertion = original.to_assertion().expect("build_assertion");
assert_eq!(assertion.content_type(), "image/png");
assert_eq!(assertion.label(), "foo");
let result = Thumbnail::from_assertion(&assertion).expect("from_assertion");
assert_eq!(original.label, result.label);
assert_eq!(original.content_type, result.content_type);
assert_eq!(original.data, result.data);
}
}
21 changes: 14 additions & 7 deletions sdk/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
use crate::{
assertion::AssertionDecodeError,
assertions::{
labels, Actions, BmffHash, BoxHash, CreativeWork, DataHash, Exif, Metadata, SoftwareAgent,
Thumbnail, User, UserCbor,
labels, Actions, BmffHash, BoxHash, CreativeWork, DataHash, EmbeddedData, Exif, Metadata,
SoftwareAgent, Thumbnail, User, UserCbor,
},
claim::Claim,
error::{Error, Result},
Expand Down Expand Up @@ -700,13 +700,20 @@
let mut stream = self.resources.open(thumb_ref)?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
claim.add_assertion_with_salt(
&Thumbnail::new(
let thumbnail = if claim.version() >= 2 {
EmbeddedData::new(
labels::CLAIM_THUMBNAIL,
format_to_mime(&thumb_ref.format),
data,
)

Check warning on line 708 in sdk/src/builder.rs

View check run for this annotation

Codecov / codecov/patch

sdk/src/builder.rs#L704-L708

Added lines #L704 - L708 were not covered by tests
} else {
Thumbnail::new(
&labels::add_thumbnail_format(labels::CLAIM_THUMBNAIL, &thumb_ref.format),
data,
),
&salt,
)?;
)
.into()
};
claim.add_assertion_with_salt(&thumbnail, &salt)?;
}
}

Expand Down
Loading
Loading