1use std::fmt;
15
16use serde::{de::DeserializeOwned, Deserialize, Serialize};
17use serde_bytes::ByteBuf;
18use serde_json::Value;
19use thiserror::Error;
20
21use crate::{
22 assertions::labels,
23 error::{Error, Result},
24};
25
26fn get_mutable_label(var_label: &str) -> (String, Option<usize>) {
28 if var_label.starts_with(labels::SCHEMA_ORG) {
29 (var_label.to_string(), None)
30 } else {
31 let tn = get_thumbnail_type(var_label);
33
34 if tn == "none" {
35 let components: Vec<&str> = var_label.split('.').collect();
36 match components.last() {
37 Some(last) => {
38 if last.len() > 1 {
40 let (ver, ver_inst_str) = last.split_at(1);
41 if ver == "v" {
42 if let Ok(ver_inst) = ver_inst_str.parse::<usize>() {
43 let ver_trim = format!(".{last}");
44 let root_label = var_label.trim_end_matches(&ver_trim);
45 return (root_label.to_string(), Some(ver_inst));
46 }
47 }
48 }
49 (var_label.to_string(), None)
50 }
51 None => (var_label.to_string(), None),
52 }
53 } else {
54 (tn, None)
55 }
56 }
57}
58
59pub fn get_thumbnail_type(thumbnail_label: &str) -> String {
60 if thumbnail_label.starts_with(labels::CLAIM_THUMBNAIL) {
61 return labels::CLAIM_THUMBNAIL.to_string();
62 }
63 if thumbnail_label.starts_with(labels::INGREDIENT_THUMBNAIL) {
64 return labels::INGREDIENT_THUMBNAIL.to_string();
65 }
66 "none".to_string()
67}
68
69pub fn get_thumbnail_image_type(thumbnail_label: &str) -> Option<String> {
70 let components: Vec<&str> = thumbnail_label.split('.').collect();
71
72 if thumbnail_label.contains("thumbnail") && components.len() >= 4 {
73 let image_type: Vec<&str> = components[3].split('_').collect(); Some(image_type[0].to_ascii_lowercase())
75 } else {
76 None
77 }
78}
79
80pub fn get_thumbnail_instance(label: &str) -> Option<usize> {
81 let label_type = get_thumbnail_type(label);
82 match label_type.as_ref() {
84 labels::INGREDIENT_THUMBNAIL => {
85 let components: Vec<&str> = label.split("__").collect();
87 if components.len() == 2 {
88 let subparts: Vec<&str> = components[1].split('.').collect();
89 subparts[0].parse::<usize>().ok()
90 } else {
91 Some(0)
92 }
93 }
94 _ => None,
95 }
96}
97
98pub trait AssertionBase
103where
104 Self: Sized,
105{
106 const LABEL: &'static str = "unknown";
108
109 const VERSION: Option<usize> = None;
111
112 fn label(&self) -> &str {
114 Self::LABEL
115 }
116
117 fn version(&self) -> Option<usize> {
118 Self::VERSION
119 }
120
121 fn to_assertion(&self) -> Result<Assertion>;
123
124 fn from_assertion(assertion: &Assertion) -> Result<Self>;
126}
127
128pub trait AssertionCbor: Serialize + DeserializeOwned + AssertionBase {
130 fn to_cbor_assertion(&self) -> Result<Assertion> {
131 let data = AssertionData::Cbor(
132 c2pa_cbor::to_vec(self).map_err(|err| Error::AssertionEncoding(err.to_string()))?,
133 );
134 Ok(Assertion::new(self.label(), self.version(), data).set_content_type("application/cbor"))
135 }
136
137 fn from_cbor_assertion(assertion: &Assertion) -> Result<Self> {
138 assertion.check_max_version(Self::VERSION)?;
139
140 match assertion.decode_data() {
141 AssertionData::Cbor(data) => Ok(c2pa_cbor::from_slice(data).map_err(|e| {
142 Error::AssertionDecoding(AssertionDecodeError::from_assertion_and_cbor_err(
143 assertion, e,
144 ))
145 })?),
146
147 data => Err(AssertionDecodeError::from_assertion_unexpected_data_type(
148 assertion, data, "cbor",
149 )
150 .into()),
151 }
152 }
153}
154
155pub trait AssertionJson: Serialize + DeserializeOwned + AssertionBase {
157 fn to_json_assertion(&self) -> Result<Assertion> {
158 let data = AssertionData::Json(
159 serde_json::to_string(self).map_err(|err| Error::AssertionEncoding(err.to_string()))?,
160 );
161 Ok(Assertion::new(self.label(), self.version(), data).set_content_type("application/json"))
162 }
163
164 fn from_json_assertion(assertion: &Assertion) -> Result<Self> {
165 assertion.check_max_version(Self::VERSION)?;
166
167 match assertion.decode_data() {
168 AssertionData::Json(data) => Ok(serde_json::from_str(data)
169 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(assertion, e))?),
170 data => Err(Error::AssertionDecoding(
171 AssertionDecodeError::from_assertion_unexpected_data_type(assertion, data, "json"),
172 )),
173 }
174 }
175}
176
177#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Hash)]
182pub enum AssertionData {
183 Json(String), Binary(Vec<u8>), Cbor(Vec<u8>), Uuid(String, Vec<u8>), }
188
189impl From<AssertionData> for Vec<u8> {
190 fn from(ad: AssertionData) -> Self {
191 match ad {
192 AssertionData::Json(s) => s.into_bytes(), AssertionData::Binary(x) | AssertionData::Uuid(_, x) => x, AssertionData::Cbor(x) => x,
195 }
196 }
197}
198
199impl fmt::Debug for AssertionData {
200 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201 match self {
202 Self::Json(s) => write!(f, "{s:?}"), Self::Binary(_) => write!(f, "<omitted>"),
204 Self::Uuid(uuid, _) => {
205 write!(f, "uuid: {uuid}, <omitted>")
206 }
207 Self::Cbor(s) => {
208 let buf: Vec<u8> = Vec::new();
209 let mut from = c2pa_cbor::Deserializer::from_slice(s);
210 let mut to = serde_json::Serializer::pretty(buf);
211
212 serde_transcode::transcode(&mut from, &mut to).map_err(|_err| fmt::Error)?;
213 let buf2 = to.into_inner();
214
215 let decoded: Value = serde_json::from_slice(&buf2).map_err(|_err| fmt::Error)?;
216
217 write!(f, "{:?}", decoded.to_string())
218 }
219 }
220 }
221}
222
223#[derive(Clone, Debug, PartialEq, Eq, Hash)]
230pub struct Assertion {
231 label: String,
232 version: Option<usize>,
233 data: AssertionData,
234 content_type: String,
235}
236
237impl Assertion {
238 pub(crate) fn new(label: &str, version: Option<usize>, data: AssertionData) -> Self {
239 Self {
240 label: label.to_owned(),
241 version,
242 content_type: "application/cbor".to_owned(),
243 data,
244 }
245 }
246
247 pub(crate) fn set_content_type(mut self, content_type: &str) -> Self {
248 content_type.clone_into(&mut self.content_type);
249 self
250 }
251
252 pub(crate) fn content_type(&self) -> &str {
254 self.content_type.as_str()
255 }
256
257 pub(crate) fn get_ver(&self) -> usize {
264 self.version.unwrap_or(1)
265 }
266
267 pub fn version(&self) -> usize {
268 self.version.unwrap_or(1)
269 }
270
271 pub(crate) fn decode_data(&self) -> &AssertionData {
288 &self.data
289 }
290
291 pub(crate) fn mime_type(&self) -> String {
294 self.content_type.clone()
295 }
296
297 pub(crate) fn assertions_eq(a: &Assertion, b: &Assertion) -> bool {
299 a.label_root() == b.label_root()
300 }
301
302 pub(crate) fn label_root(&self) -> String {
304 let label = get_mutable_label(&self.label).0;
305 match get_thumbnail_image_type(&self.label) {
307 None => label,
308 Some(image_type) => format!("{label}.{image_type}"),
309 }
310 }
311
312 pub(crate) fn label(&self) -> String {
314 let base_label = self.label_root();
315 let v = self.get_ver();
316 if v > 1 {
317 format!("{base_label}.v{v}")
319 } else {
320 base_label
321 }
322 }
323
324 pub(crate) fn data(&self) -> &[u8] {
326 match self.decode_data() {
328 AssertionData::Json(x) => x.as_bytes(), AssertionData::Binary(x) | AssertionData::Uuid(_, x) => x, AssertionData::Cbor(x) => x,
331 }
332 }
333
334 pub(crate) fn as_json_object(&self) -> AssertionDecodeResult<Value> {
338 match self.decode_data() {
339 AssertionData::Json(x) => serde_json::from_str(x)
340 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e)),
341
342 AssertionData::Cbor(x) => {
343 let buf: Vec<u8> = Vec::new();
344 let mut from = c2pa_cbor::Deserializer::from_slice(x);
345 let mut to = serde_json::Serializer::new(buf);
346
347 serde_transcode::transcode(&mut from, &mut to)
348 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e))?;
349
350 let buf2 = to.into_inner();
351 serde_json::from_slice(&buf2)
352 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e))
353 }
354
355 AssertionData::Binary(x) => {
356 let binary_bytes = ByteBuf::from(x.clone());
357 let binary_str = serde_json::to_string(&binary_bytes)
358 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e))?;
359
360 serde_json::from_str(&binary_str)
361 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e))
362 }
363 AssertionData::Uuid(uuid, x) => {
364 #[derive(Serialize)]
365 struct TmpObj<'a> {
366 uuid: &'a str,
367 data: ByteBuf,
368 }
369
370 let v = TmpObj {
371 uuid,
372 data: ByteBuf::from(x.clone()),
373 };
374
375 let binary_str = serde_json::to_string(&v)
376 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e))?;
377
378 serde_json::from_str(&binary_str)
379 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e))
380 }
381 }
382 }
383
384 fn from_assertion_data(label: &str, content_type: &str, data: AssertionData) -> Assertion {
385 use crate::claim::Claim;
386 let version = labels::version(label);
387 let (label, instance) = Claim::assertion_label_from_link(label);
388 let label = Claim::label_with_instance(&label, instance);
389
390 Self {
391 label,
392 version: if version == 1 { None } else { Some(version) },
393 data,
394 content_type: content_type.to_owned(),
395 }
396 }
397
398 pub(crate) fn from_data_binary(label: &str, mime_type: &str, binary_data: &[u8]) -> Assertion {
400 Self::from_assertion_data(
401 label,
402 mime_type,
403 AssertionData::Binary(binary_data.to_vec()),
404 )
405 }
406
407 pub(crate) fn binary_deconstruct(
409 assertion: Assertion,
410 ) -> Result<(String, Option<usize>, String, Vec<u8>)> {
411 match assertion.data {
412 AssertionData::Binary(data) => Ok((
413 assertion.label,
414 assertion.version,
415 assertion.content_type,
416 data,
417 )),
418 _ => Err(AssertionDecodeError::from_assertion_unexpected_data_type(
419 &assertion,
420 assertion.decode_data(),
421 "binary",
422 )
423 .into()),
424 }
425 }
426
427 pub(crate) fn from_data_uuid(label: &str, uuid_str: &str, binary_data: &[u8]) -> Assertion {
429 Self::from_assertion_data(
430 label,
431 "application/octet-stream",
432 AssertionData::Uuid(uuid_str.to_owned(), binary_data.to_vec()),
433 )
434 }
435
436 pub(crate) fn from_data_cbor(label: &str, binary_data: &[u8]) -> Assertion {
437 Self::from_assertion_data(
438 label,
439 "application/cbor",
440 AssertionData::Cbor(binary_data.to_vec()),
441 )
442 }
443
444 pub(crate) fn from_data_json(
445 label: &str,
446 binary_data: &[u8],
447 ) -> AssertionDecodeResult<Assertion> {
448 let json = String::from_utf8(binary_data.to_vec()).map_err(|_| AssertionDecodeError {
449 label: label.to_string(),
450 version: None, content_type: "application/json".to_string(),
452 source: AssertionDecodeErrorCause::BinaryDataNotUtf8,
453 })?;
454
455 Ok(Self::from_assertion_data(
456 label,
457 "application/json",
458 AssertionData::Json(json),
459 ))
460 }
461
462 pub(crate) fn check_version_from_label(
464 &self,
465 desired_version: usize,
466 ) -> AssertionDecodeResult<()> {
467 let base_version = labels::version(&self.label);
468 if desired_version > base_version {
469 return Err(AssertionDecodeError {
470 label: self.label.clone(),
471 version: self.version,
472 content_type: self.content_type.clone(),
473 source: AssertionDecodeErrorCause::AssertionTooNew {
474 max: desired_version,
475 found: base_version,
476 },
477 });
478 }
479
480 Ok(())
481 }
482
483 fn check_max_version(&self, max_version: Option<usize>) -> AssertionDecodeResult<()> {
484 if let Some(data_version) = self.version {
485 if let Some(max_version) = max_version {
486 if data_version > max_version {
487 return Err(AssertionDecodeError {
488 label: self.label.clone(),
489 version: self.version,
490 content_type: self.content_type.clone(),
491 source: AssertionDecodeErrorCause::AssertionTooNew {
492 max: max_version,
493 found: data_version,
494 },
495 });
496 }
497 }
498 }
499 Ok(())
500 }
501}
502
503#[non_exhaustive]
505pub struct AssertionDecodeError {
506 pub label: String,
507 pub version: Option<usize>,
508 pub content_type: String,
509 pub source: AssertionDecodeErrorCause,
510}
511
512impl AssertionDecodeError {
513 fn fmt_internal(&self, f: &mut fmt::Formatter) -> fmt::Result {
514 write!(
515 f,
516 "could not decode assertion {} (version {}, content type {}): {}",
517 self.label,
518 self.version
519 .map_or("(no version)".to_string(), |v| v.to_string()),
520 self.content_type,
521 self.source
522 )
523 }
524
525 pub(crate) fn from_assertion_and_cbor_err(
526 assertion: &Assertion,
527 source: c2pa_cbor::error::Error,
528 ) -> Self {
529 Self {
530 label: assertion.label.clone(),
531 version: assertion.version,
532 content_type: assertion.content_type.clone(),
533 source: source.into(),
534 }
535 }
536
537 pub(crate) fn from_assertion_and_json_err(
538 assertion: &Assertion,
539 source: serde_json::error::Error,
540 ) -> Self {
541 Self {
542 label: assertion.label.clone(),
543 version: assertion.version,
544 content_type: assertion.content_type.clone(),
545 source: source.into(),
546 }
547 }
548
549 pub(crate) fn from_assertion_unexpected_data_type(
550 assertion: &Assertion,
551 assertion_data: &AssertionData,
552 expected: &str,
553 ) -> Self {
554 Self {
555 label: assertion.label.clone(),
556 version: assertion.version,
557 content_type: assertion.content_type.clone(),
558 source: AssertionDecodeErrorCause::UnexpectedDataType {
559 expected: expected.to_string(),
560 found: Self::data_type_from_assertion_data(assertion_data),
561 },
562 }
563 }
564
565 fn data_type_from_assertion_data(assertion_data: &AssertionData) -> String {
566 match assertion_data {
567 AssertionData::Json(_) => "json".to_string(),
568 AssertionData::Binary(_) => "binary".to_string(),
569 AssertionData::Cbor(_) => "cbor".to_string(),
570 AssertionData::Uuid(_, _) => "uuid".to_string(),
571 }
572 }
573
574 pub(crate) fn from_json_err(
575 label: String,
576 version: Option<usize>,
577 content_type: String,
578 source: serde_json::error::Error,
579 ) -> Self {
580 Self {
581 label,
582 version,
583 content_type,
584 source: source.into(),
585 }
586 }
587
588 pub(crate) fn from_err<S: Into<AssertionDecodeErrorCause>>(
589 label: String,
590 version: Option<usize>,
591 content_type: String,
592 source: S,
593 ) -> Self {
594 Self {
595 label,
596 version,
597 content_type,
598 source: source.into(),
599 }
600 }
601}
602
603impl std::fmt::Debug for AssertionDecodeError {
604 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
605 self.fmt_internal(f)
606 }
607}
608
609impl std::fmt::Display for AssertionDecodeError {
610 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
611 self.fmt_internal(f)
612 }
613}
614
615impl std::error::Error for AssertionDecodeError {
616 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
617 Some(&self.source)
618 }
619}
620
621#[derive(Debug, Error)]
624#[non_exhaustive]
625pub enum AssertionDecodeErrorCause {
626 #[error("the assertion had an unexpected data type: expected {expected}, found {found}")]
628 UnexpectedDataType { expected: String, found: String },
629
630 #[error("the assertion version is too new: expected no later than {max}, found {found}")]
632 AssertionTooNew { max: usize, found: usize },
633
634 #[error("binary data could not be interpreted as UTF-8")]
636 BinaryDataNotUtf8,
637
638 #[error("the assertion data did not match the hash embedded in the link")]
640 AssertionDataIncorrect,
641
642 #[error(transparent)]
643 JsonError(#[from] serde_json::Error),
644
645 #[error(transparent)]
646 CborError(#[from] c2pa_cbor::Error),
647
648 #[error("the assertion had a mandatory field: {expected} that could not be decoded")]
650 FieldDecoding { expected: String },
651}
652
653pub(crate) type AssertionDecodeResult<T> = std::result::Result<T, AssertionDecodeError>;
654
655#[cfg(test)]
656pub mod tests {
657 #![allow(clippy::unwrap_used)]
658
659 use super::*;
660 use crate::assertions::{Action, Actions};
661
662 #[test]
663 fn test_version_label() {
664 let test_json = r#"{
665 "left": 0,
666 "right": 2000,
667 "top": 1000,
668 "bottom": 4000
669 }"#;
670 let json = AssertionData::Json(test_json.to_string());
671 let json2 = AssertionData::Json(test_json.to_string());
672
673 let a = Assertion::new(Actions::LABEL, Some(2), json);
674 let a_no_ver = Assertion::new(Actions::LABEL, None, json2);
675
676 assert_eq!(a.get_ver(), 2);
677 assert_eq!(a_no_ver.get_ver(), 1);
678 assert_eq!(a.label(), format!("{}.{}", Actions::LABEL, "v2"));
679 assert_eq!(a.label_root(), Actions::LABEL);
680 assert_eq!(a_no_ver.label(), Actions::LABEL);
681 }
682
683 #[test]
684 fn test_cbor_conversion() {
685 let action = Actions::new()
686 .add_action(
687 Action::new("c2pa.cropped")
688 .set_parameter(
689 "coordinate".to_owned(),
690 serde_json::json!({"left": 0,"right": 2000,"top": 1000,"bottom": 4000}),
691 )
692 .unwrap(),
693 )
694 .add_action(
695 Action::new("c2pa.filtered")
696 .set_parameter("name".to_owned(), "gaussian blur")
697 .unwrap()
698 .set_software_agent("Photoshop")
699 .set_when("2015-06-26T16:43:23+0200"),
700 )
701 .to_assertion()
702 .unwrap();
703
704 let action_cbor = action.data();
705
706 let action_restored = Assertion::from_data_cbor(&action.label(), action_cbor);
707
708 assert!(Assertion::assertions_eq(&action, &action_restored));
709
710 let action_obj = action.as_json_object().unwrap();
711 let action_restored_obj = action_restored.as_json_object().unwrap();
712
713 assert_eq!(action_obj, action_restored_obj);
714 }
715}