assertion.rs (23640B)
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::fmt; 15 16 use serde::{de::DeserializeOwned, Deserialize, Serialize}; 17 use serde_bytes::ByteBuf; 18 use serde_json::Value; 19 use thiserror::Error; 20 21 use crate::{ 22 assertions::labels, 23 error::{Error, Result}, 24 }; 25 26 /// Check to see if this a label whose string can vary, if so return the root of the label and version if available 27 fn 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 // is it a type of thumbnail 32 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 // check for a valid version number 39 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 59 pub 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 69 pub fn get_thumbnail_image_type(thumbnail_label: &str) -> 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(); // strip and other label adornments 74 image_type[0].to_ascii_lowercase() 75 } else { 76 "none".to_string() 77 } 78 } 79 80 pub fn get_thumbnail_instance(label: &str) -> Option<usize> { 81 let label_type = get_thumbnail_type(label); 82 // only ingredients thumbs store ids in the label, so use placeholder ids for the others 83 match label_type.as_ref() { 84 labels::INGREDIENT_THUMBNAIL => { 85 // extract id from underscore separated part of the full label 86 let components: Vec<&str> = label.split("__").collect(); 87 if components.len() == 2 { 88 let subparts: Vec<&str> = components[1].split('.').collect(); 89 match subparts[0].parse::<usize>() { 90 Ok(i) => Some(i), 91 Err(_e) => None, 92 } 93 } else { 94 Some(0) 95 } 96 } 97 _ => None, 98 } 99 } 100 101 /// The core required trait for all assertions. 102 /// 103 /// This defines the label and version for the assertion 104 /// and supplies the to/from converters for C2PA assertion format. 105 pub trait AssertionBase 106 where 107 Self: Sized, 108 { 109 const LABEL: &'static str = "unknown"; 110 111 const VERSION: Option<usize> = None; 112 113 /// Returns a label for this assertion. 114 fn label(&self) -> &str { 115 Self::LABEL 116 } 117 118 /// Returns a version for this assertion. 119 fn version(&self) -> Option<usize> { 120 Self::VERSION 121 } 122 123 /// Returns an Assertion upon success or Error otherwise. 124 fn to_assertion(&self) -> Result<Assertion>; 125 126 /// Returns Self or AssertionDecode Result from an assertion 127 fn from_assertion(assertion: &Assertion) -> Result<Self>; 128 } 129 130 /// Trait to handle default Cbor encoding/decoding of Assertions 131 pub trait AssertionCbor: Serialize + DeserializeOwned + AssertionBase { 132 fn to_cbor_assertion(&self) -> Result<Assertion> { 133 let data = 134 AssertionData::Cbor(serde_cbor::to_vec(self).map_err(|_err| Error::AssertionEncoding)?); 135 Ok(Assertion::new(self.label(), self.version(), data)) 136 } 137 138 fn from_cbor_assertion(assertion: &Assertion) -> Result<Self> { 139 assertion.check_max_version(Self::VERSION)?; 140 141 match assertion.decode_data() { 142 AssertionData::Cbor(data) => Ok(serde_cbor::from_slice(data).map_err(|e| { 143 Error::AssertionDecoding(AssertionDecodeError::from_assertion_and_cbor_err( 144 assertion, e, 145 )) 146 })?), 147 148 data => Err(AssertionDecodeError::from_assertion_unexpected_data_type( 149 assertion, data, "cbor", 150 ) 151 .into()), 152 } 153 } 154 } 155 156 /// Trait to handle default Json encoding/decoding of Assertions 157 pub trait AssertionJson: Serialize + DeserializeOwned + AssertionBase { 158 fn to_json_assertion(&self) -> Result<Assertion> { 159 let data = AssertionData::Json( 160 serde_json::to_string(self).map_err(|_err| Error::AssertionEncoding)?, 161 ); 162 Ok(Assertion::new(self.label(), self.version(), data).set_content_type("application/json")) 163 } 164 165 fn from_json_assertion(assertion: &Assertion) -> Result<Self> { 166 assertion.check_max_version(Self::VERSION)?; 167 168 match assertion.decode_data() { 169 AssertionData::Json(data) => Ok(serde_json::from_str(data) 170 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(assertion, e))?), 171 data => Err(Error::AssertionDecoding( 172 AssertionDecodeError::from_assertion_unexpected_data_type(assertion, data, "json"), 173 )), 174 } 175 } 176 } 177 178 /// Assertion data as binary CBOR or JSON depending upon 179 /// the Assertion type (see spec). 180 /// For JSON assertions the data is a JSON string and a Vec of u8 values for 181 /// binary data and JSON data to be CBOR encoded. 182 #[derive(Deserialize, Serialize, PartialEq, Eq, Clone)] 183 pub enum AssertionData { 184 Json(String), // json encoded data 185 Binary(Vec<u8>), // binary data 186 Cbor(Vec<u8>), // binary cbor encoded data 187 Uuid(String, Vec<u8>), // user defined content (uuid, data) 188 } 189 190 impl fmt::Debug for AssertionData { 191 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 192 match self { 193 Self::Json(s) => write!(f, "{s:?}"), // json encoded data 194 Self::Binary(_) => write!(f, "<omitted>"), 195 Self::Uuid(uuid, _) => { 196 write!(f, "uuid: {uuid}, <omitted>") 197 } 198 Self::Cbor(s) => { 199 let buf: Vec<u8> = Vec::new(); 200 let mut from = serde_cbor::Deserializer::from_slice(s); 201 let mut to = serde_json::Serializer::pretty(buf); 202 203 serde_transcode::transcode(&mut from, &mut to).map_err(|_err| fmt::Error)?; 204 let buf2 = to.into_inner(); 205 206 let decoded: Value = serde_json::from_slice(&buf2).map_err(|_err| fmt::Error)?; 207 208 write!(f, "{:?}", decoded.to_string()) 209 } 210 } 211 } 212 } 213 214 /// Internal Assertion structure 215 /// 216 /// Each assertion type will 217 /// contain its AssertionData. For the User Assertion type we 218 /// allow a String to set the label. The AssertionData contains 219 /// the data payload for the assertion and the version number for its schema (if supported). 220 #[derive(Clone, Debug, PartialEq, Eq)] 221 pub struct Assertion { 222 label: String, 223 version: Option<usize>, 224 data: AssertionData, 225 content_type: String, 226 } 227 228 impl Assertion { 229 pub(crate) fn new(label: &str, version: Option<usize>, data: AssertionData) -> Self { 230 Self { 231 label: label.to_owned(), 232 version, 233 content_type: "application/cbor".to_owned(), 234 data, 235 } 236 } 237 238 pub(crate) fn set_content_type(mut self, content_type: &str) -> Self { 239 content_type.clone_into(&mut self.content_type); 240 self 241 } 242 243 /// return content_type for the the data enclosed in the Assertion 244 pub(crate) fn content_type(&self) -> String { 245 self.content_type.clone() 246 } 247 248 // pub(crate) fn set_data(mut self, data: &AssertionData) -> Self { 249 // self.data = data.to_owned(); 250 // self 251 // } 252 253 // Return version string of known assertion if available 254 pub(crate) const fn get_ver(&self) -> Option<usize> { 255 self.version 256 } 257 258 // pub fn check_version(&self, max_version: usize) -> AssertionDecodeResult<()> { 259 // match self.version { 260 // Some(version) if version > max_version => Err(AssertionDecodeError { 261 // label: self.label.clone(), 262 // version: self.version, 263 // content_type: self.content_type.clone(), 264 // source: AssertionDecodeErrorCause::AssertionTooNew { 265 // max: max_version, 266 // found: version, 267 // }, 268 // }), 269 // _ => Ok(()), 270 // } 271 // } 272 273 /// Return a reference to the AssertionData bound to this Assertion 274 pub(crate) const fn decode_data(&self) -> &AssertionData { 275 &self.data 276 } 277 278 /// return mimetype for the data enclosed in the Assertion 279 pub(crate) fn mime_type(&self) -> String { 280 self.content_type.clone() 281 } 282 283 /// Test to see if the Assertions are of the same variant 284 pub(crate) fn assertions_eq(a: &Assertion, b: &Assertion) -> bool { 285 a.label_root() == b.label_root() 286 } 287 288 /// Return the CAI label for this Assertion (no version) 289 pub(crate) fn label_root(&self) -> String { 290 let label = get_mutable_label(&self.label).0; 291 // thumbnails need the image_type added 292 match get_thumbnail_image_type(&self.label).as_str() { 293 "none" => label, 294 image_type => format!("{label}.{image_type}"), 295 } 296 } 297 298 /// Return the CAI label for this Assertion with version string if available 299 pub(crate) fn label(&self) -> String { 300 let base_label = self.label_root(); 301 match self.get_ver() { 302 Some(v) => { 303 if v > 1 { 304 // c2pa does not include v1 labels 305 format!("{base_label}.v{v}") 306 } else { 307 base_label 308 } 309 } 310 None => base_label, 311 } 312 } 313 314 /// Return a reference to the data as a byte array 315 pub(crate) fn data(&self) -> &[u8] { 316 // return bytes of the assertion data 317 match self.decode_data() { 318 AssertionData::Json(x) => x.as_bytes(), // json encoded data 319 AssertionData::Binary(x) | AssertionData::Uuid(_, x) => x, // binary data 320 AssertionData::Cbor(x) => x, 321 } 322 } 323 324 /// Return assertion as serde_json Object 325 /// this may have loss of cbor structure if unsupported in conversion to json 326 /// It should always do the correct thing when using the correct tagged CBOR types 327 pub(crate) fn as_json_object(&self) -> AssertionDecodeResult<Value> { 328 match self.decode_data() { 329 AssertionData::Json(x) => serde_json::from_str(x) 330 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e)), 331 332 AssertionData::Cbor(x) => { 333 let buf: Vec<u8> = Vec::new(); 334 let mut from = serde_cbor::Deserializer::from_slice(x); 335 let mut to = serde_json::Serializer::new(buf); 336 337 serde_transcode::transcode(&mut from, &mut to) 338 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e))?; 339 340 let buf2 = to.into_inner(); 341 serde_json::from_slice(&buf2) 342 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e)) 343 } 344 345 AssertionData::Binary(x) => { 346 let binary_bytes = ByteBuf::from(x.clone()); 347 let binary_str = serde_json::to_string(&binary_bytes) 348 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e))?; 349 350 serde_json::from_str(&binary_str) 351 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e)) 352 } 353 AssertionData::Uuid(uuid, x) => { 354 #[derive(Serialize)] 355 struct TmpObj<'a> { 356 uuid: &'a str, 357 data: ByteBuf, 358 } 359 360 let v = TmpObj { 361 uuid, 362 data: ByteBuf::from(x.clone()), 363 }; 364 365 let binary_str = serde_json::to_string(&v) 366 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e))?; 367 368 serde_json::from_str(&binary_str) 369 .map_err(|e| AssertionDecodeError::from_assertion_and_json_err(self, e)) 370 } 371 } 372 } 373 374 fn from_assertion_data(label: &str, content_type: &str, data: AssertionData) -> Assertion { 375 use crate::claim::Claim; 376 let version = labels::version(label); 377 let (label, instance) = Claim::assertion_label_from_link(label); 378 let label = Claim::label_with_instance(&label, instance); 379 380 Self { 381 label, 382 version, 383 data, 384 content_type: content_type.to_owned(), 385 } 386 } 387 388 /// create an assertion from binary data 389 pub(crate) fn from_data_binary(label: &str, mime_type: &str, binary_data: &[u8]) -> Assertion { 390 Self::from_assertion_data( 391 label, 392 mime_type, 393 AssertionData::Binary(binary_data.to_vec()), 394 ) 395 } 396 397 /// create an assertion from user binary data 398 pub(crate) fn from_data_uuid(label: &str, uuid_str: &str, binary_data: &[u8]) -> Assertion { 399 Self::from_assertion_data( 400 label, 401 "application/octet-stream", 402 AssertionData::Uuid(uuid_str.to_owned(), binary_data.to_vec()), 403 ) 404 } 405 406 pub(crate) fn from_data_cbor(label: &str, binary_data: &[u8]) -> Assertion { 407 Self::from_assertion_data( 408 label, 409 "application/cbor", 410 AssertionData::Cbor(binary_data.to_vec()), 411 ) 412 } 413 414 pub(crate) fn from_data_json( 415 label: &str, 416 binary_data: &[u8], 417 ) -> AssertionDecodeResult<Assertion> { 418 let json = String::from_utf8(binary_data.to_vec()).map_err(|_| AssertionDecodeError { 419 label: label.to_string(), 420 version: None, // TODO: Can we get this info? 421 content_type: "json".to_string(), 422 source: AssertionDecodeErrorCause::BinaryDataNotUtf8, 423 })?; 424 425 Ok(Self::from_assertion_data( 426 label, 427 "application/json", 428 AssertionData::Json(json), 429 )) 430 } 431 432 // Check assertion label against a target label. 433 pub(crate) fn check_version_from_label( 434 &self, 435 desired_version: usize, 436 ) -> AssertionDecodeResult<()> { 437 if let Some(base_version) = labels::version(&self.label) { 438 if desired_version > base_version { 439 return Err(AssertionDecodeError { 440 label: self.label.clone(), 441 version: self.version, 442 content_type: self.content_type.clone(), 443 source: AssertionDecodeErrorCause::AssertionTooNew { 444 max: desired_version, 445 found: base_version, 446 }, 447 }); 448 } 449 } 450 451 Ok(()) 452 } 453 454 fn check_max_version(&self, max_version: Option<usize>) -> AssertionDecodeResult<()> { 455 if let Some(data_version) = self.version { 456 if let Some(max_version) = max_version { 457 if data_version > max_version { 458 return Err(AssertionDecodeError { 459 label: self.label.clone(), 460 version: self.version, 461 content_type: self.content_type.clone(), 462 source: AssertionDecodeErrorCause::AssertionTooNew { 463 max: max_version, 464 found: data_version, 465 }, 466 }); 467 } 468 } 469 } 470 Ok(()) 471 } 472 } 473 474 #[derive(Serialize, Deserialize, Debug)] 475 pub(crate) struct JsonAssertionData { 476 label: String, 477 data: Value, 478 is_cbor: bool, 479 } 480 481 /// This error type is returned when an assertion can not be decoded. 482 #[non_exhaustive] 483 pub struct AssertionDecodeError { 484 pub label: String, 485 pub version: Option<usize>, 486 pub content_type: String, 487 pub source: AssertionDecodeErrorCause, 488 } 489 490 impl AssertionDecodeError { 491 fn fmt_internal(&self, f: &mut fmt::Formatter) -> fmt::Result { 492 write!( 493 f, 494 "could not decode assertion {} (version {}, content type {}): {}", 495 self.label, 496 self.version 497 .map_or("(no version)".to_string(), |v| v.to_string()), 498 self.content_type, 499 self.source 500 ) 501 } 502 503 pub(crate) fn from_assertion_and_cbor_err( 504 assertion: &Assertion, 505 source: serde_cbor::error::Error, 506 ) -> Self { 507 Self { 508 label: assertion.label.clone(), 509 version: assertion.version, 510 content_type: assertion.content_type.clone(), 511 source: source.into(), 512 } 513 } 514 515 pub(crate) fn from_assertion_and_json_err( 516 assertion: &Assertion, 517 source: serde_json::error::Error, 518 ) -> Self { 519 Self { 520 label: assertion.label.clone(), 521 version: assertion.version, 522 content_type: assertion.content_type.clone(), 523 source: source.into(), 524 } 525 } 526 527 pub(crate) fn from_assertion_unexpected_data_type( 528 assertion: &Assertion, 529 assertion_data: &AssertionData, 530 expected: &str, 531 ) -> Self { 532 Self { 533 label: assertion.label.clone(), 534 version: assertion.version, 535 content_type: assertion.content_type.clone(), 536 source: AssertionDecodeErrorCause::UnexpectedDataType { 537 expected: expected.to_string(), 538 found: Self::data_type_from_assertion_data(assertion_data), 539 }, 540 } 541 } 542 543 fn data_type_from_assertion_data(assertion_data: &AssertionData) -> String { 544 match assertion_data { 545 AssertionData::Json(_) => "json".to_string(), 546 AssertionData::Binary(_) => "binary".to_string(), 547 AssertionData::Cbor(_) => "cbor".to_string(), 548 AssertionData::Uuid(_, _) => "uuid".to_string(), 549 } 550 } 551 552 pub(crate) fn from_json_err( 553 label: String, 554 version: Option<usize>, 555 content_type: String, 556 source: serde_json::error::Error, 557 ) -> Self { 558 Self { 559 label, 560 version, 561 content_type, 562 source: source.into(), 563 } 564 } 565 566 #[cfg(feature = "unstable_api")] 567 pub(crate) fn from_err<S: Into<AssertionDecodeErrorCause>>( 568 label: String, 569 version: Option<usize>, 570 content_type: String, 571 source: S, 572 ) -> Self { 573 Self { 574 label, 575 version, 576 content_type, 577 source: source.into(), 578 } 579 } 580 } 581 582 impl std::fmt::Debug for AssertionDecodeError { 583 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 584 self.fmt_internal(f) 585 } 586 } 587 588 impl std::fmt::Display for AssertionDecodeError { 589 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 590 self.fmt_internal(f) 591 } 592 } 593 594 impl std::error::Error for AssertionDecodeError { 595 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 596 Some(&self.source) 597 } 598 } 599 600 /// This error type is used inside `AssertionDecodeError` to describe the 601 /// root cause for the decoding error. 602 #[derive(Debug, Error)] 603 #[non_exhaustive] 604 pub enum AssertionDecodeErrorCause { 605 /// The assertion had an unexpected data type. 606 #[error("the assertion had an unexpected data type: expected {expected}, found {found}")] 607 UnexpectedDataType { expected: String, found: String }, 608 609 /// The assertion has a version that is newer that this toolkit can understand. 610 #[error("the assertion version is too new: expected no later than {max}, found {found}")] 611 AssertionTooNew { max: usize, found: usize }, 612 613 /// Binary data could not be interpreted as UTF-8. 614 #[error("binary data could not be interpreted as UTF-8")] 615 BinaryDataNotUtf8, 616 617 /// Assertion data did not match hash link. 618 #[error("the assertion data did not match the hash embedded in the link")] 619 AssertionDataIncorrect, 620 621 #[error(transparent)] 622 JsonError(#[from] serde_json::Error), 623 624 #[error(transparent)] 625 CborError(#[from] serde_cbor::Error), 626 } 627 628 pub(crate) type AssertionDecodeResult<T> = std::result::Result<T, AssertionDecodeError>; 629 630 #[cfg(test)] 631 pub mod tests { 632 #![allow(clippy::unwrap_used)] 633 634 use super::*; 635 use crate::assertions::{Action, Actions}; 636 637 #[test] 638 fn test_version_label() { 639 let test_json = r#"{ 640 "left": 0, 641 "right": 2000, 642 "top": 1000, 643 "bottom": 4000 644 }"#; 645 let json = AssertionData::Json(test_json.to_string()); 646 let json2 = AssertionData::Json(test_json.to_string()); 647 648 let a = Assertion::new(Actions::LABEL, Some(2), json); 649 let a_no_ver = Assertion::new(Actions::LABEL, None, json2); 650 651 assert_eq!(a.get_ver().unwrap(), 2); 652 assert_eq!(a_no_ver.get_ver(), None); 653 assert_eq!(a.label(), format!("{}.{}", Actions::LABEL, "v2")); 654 assert_eq!(a.label_root(), Actions::LABEL); 655 assert_eq!(a_no_ver.label(), Actions::LABEL); 656 } 657 658 #[test] 659 fn test_cbor_conversion() { 660 let action = Actions::new() 661 .add_action( 662 Action::new("c2pa.cropped") 663 .set_parameter( 664 "coordinate".to_owned(), 665 serde_json::json!({"left": 0,"right": 2000,"top": 1000,"bottom": 4000}), 666 ) 667 .unwrap(), 668 ) 669 .add_action( 670 Action::new("c2pa.filtered") 671 .set_parameter("name".to_owned(), "gaussian blur") 672 .unwrap() 673 .set_software_agent("Photoshop") 674 .set_when("2015-06-26T16:43:23+0200"), 675 ) 676 .to_assertion() 677 .unwrap(); 678 679 let action_cbor = action.data(); 680 681 let action_restored = Assertion::from_data_cbor(&action.label(), action_cbor); 682 683 assert!(Assertion::assertions_eq(&action, &action_restored)); 684 685 let action_obj = action.as_json_object().unwrap(); 686 let action_restored_obj = action_restored.as_json_object().unwrap(); 687 688 assert_eq!(action_obj, action_restored_obj); 689 } 690 }