metadata.rs (10748B)
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::collections::HashMap; 15 16 use chrono::{SecondsFormat, Utc}; 17 #[cfg(feature = "json_schema")] 18 use schemars::JsonSchema; 19 use serde::{Deserialize, Serialize}; 20 use serde_json::Value; 21 22 use crate::{ 23 assertion::{Assertion, AssertionBase, AssertionCbor}, 24 assertions::labels, 25 error::Result, 26 hashed_uri::HashedUri, 27 utils::cbor_types::DateT, 28 }; 29 30 const ASSERTION_CREATION_VERSION: usize = 1; 31 32 /// The Metadata structure can be used as part of other assertions or on its own to reference others 33 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] 34 #[cfg_attr(feature = "json_schema", derive(JsonSchema))] 35 pub struct Metadata { 36 #[serde(rename = "reviewRatings", skip_serializing_if = "Option::is_none")] 37 reviews: Option<Vec<ReviewRating>>, 38 #[serde(rename = "dateTime", skip_serializing_if = "Option::is_none")] 39 date_time: Option<DateT>, 40 #[serde(skip_serializing_if = "Option::is_none")] 41 reference: Option<HashedUri>, 42 #[serde(skip_serializing_if = "Option::is_none")] 43 data_source: Option<DataSource>, 44 #[serde(flatten)] 45 other: HashMap<String, Value>, 46 } 47 48 impl Metadata { 49 /// Label prefix for an assertion metadata assertion. 50 /// 51 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_metadata_about_assertions>. 52 pub const LABEL: &'static str = labels::ASSERTION_METADATA; 53 54 pub fn new() -> Self { 55 Self { 56 reviews: None, 57 date_time: Some(DateT( 58 Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true), 59 )), 60 reference: None, 61 data_source: None, 62 other: HashMap::new(), 63 } 64 } 65 66 /// Returns the list of [`ReviewRating`] for this assertion if it exists. 67 pub fn reviews(&self) -> Option<&[ReviewRating]> { 68 self.reviews.as_deref() 69 } 70 71 /// Returns the ISO 8601 date-time string when the assertion was created/generated. 72 pub fn date_time(&self) -> Option<&str> { 73 self.date_time.as_deref() 74 } 75 76 /// Returns the [`DataSource`] for this assertion if it exists. 77 pub const fn data_source(&self) -> Option<&DataSource> { 78 self.data_source.as_ref() 79 } 80 81 /// Adds a [`ReviewRating`] associated with the assertion. 82 pub fn add_review(mut self, review: ReviewRating) -> Self { 83 match &mut self.reviews { 84 None => self.reviews = Some(vec![review]), 85 Some(reviews) => reviews.push(review), 86 } 87 self 88 } 89 90 /// Sets the list of [`ReviewRating`]s associated with the assertion. 91 /// 92 /// This replaces any previous list. 93 pub fn set_reviews(mut self, reviews: Vec<ReviewRating>) -> Self { 94 self.reviews = Some(reviews); 95 self 96 } 97 98 /// Sets the ISO 8601 date-time string when the assertion was created/generated. 99 pub fn set_date_time(&mut self, date_time: String) -> &mut Self { 100 self.date_time = Some(DateT(date_time)); 101 self 102 } 103 104 /// Sets a [`HashedUri`] reference to another assertion to which this metadata applies. 105 #[cfg(test)] // only referenced from test code 106 pub(crate) fn set_reference(mut self, reference: HashedUri) -> Self { 107 self.reference = Some(reference); 108 self 109 } 110 111 /// Sets a description of the source of the assertion data, selected from a predefined list. 112 pub fn set_data_source(mut self, data_source: DataSource) -> Self { 113 self.data_source = Some(data_source); 114 self 115 } 116 117 /// Adds an additional key / value pair. 118 pub fn insert(&mut self, key: &str, value: Value) -> &mut Self { 119 self.other.insert(key.to_string(), value); 120 self 121 } 122 123 /// Gets additional values by key. 124 pub fn get(&self, key: &str) -> Option<&Value> { 125 self.other.get(key) 126 } 127 } 128 129 impl Default for Metadata { 130 fn default() -> Self { 131 Self::new() 132 } 133 } 134 135 impl AssertionCbor for Metadata {} 136 137 impl AssertionBase for Metadata { 138 const LABEL: &'static str = Self::LABEL; 139 const VERSION: Option<usize> = Some(ASSERTION_CREATION_VERSION); 140 141 fn to_assertion(&self) -> Result<Assertion> { 142 Self::to_cbor_assertion(self) 143 } 144 145 fn from_assertion(assertion: &Assertion) -> Result<Self> { 146 Self::from_cbor_assertion(assertion) 147 } 148 } 149 150 /// DATA_SOURCE Type values 151 pub mod c2pa_source { 152 pub const SIGNER: &str = "signer"; 153 pub const GENERATOR_REE: &str = "claimGenerator.REE"; 154 pub const GENERATOR_TEE: &str = "claimGenerator.TEE"; 155 pub const LOCAL_REE: &str = "localProvider.REE"; 156 pub const LOCAL_TEE: &str = "localProvider.TEE"; 157 pub const REMOTE_REE: &str = "remoteProvider.1stParty"; 158 pub const REMOTE_TEE: &str = "remoteProvider.3rdParty"; 159 pub const HUMAN_ANONYMOUS: &str = "humanEntry.anonymous"; 160 pub const HUMAN_IDENTIFIED: &str = "humanEntry.identified"; 161 } 162 163 /// A description of the source for assertion data 164 #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq, Eq)] 165 #[cfg_attr(feature = "json_schema", derive(JsonSchema))] 166 #[non_exhaustive] 167 pub struct DataSource { 168 /// A value from among the enumerated list indicating the source of the assertion. 169 #[serde(rename = "type")] 170 pub source_type: String, 171 172 /// A human-readable string giving details about the source of the assertion data. 173 #[serde(skip_serializing_if = "Option::is_none")] 174 pub details: Option<String>, 175 176 /// A list of [`Actor`]s associated with this source. 177 #[serde(skip_serializing_if = "Option::is_none")] 178 pub actors: Option<Vec<Actor>>, 179 } 180 181 impl DataSource { 182 pub fn new(source_type: &str) -> Self { 183 Self { 184 source_type: source_type.to_owned(), 185 details: None, 186 actors: None, 187 } 188 } 189 190 /// Sets a human-readable string giving details about the source of the assertion data. 191 pub fn set_details(mut self, details: String) -> Self { 192 self.details = Some(details); 193 self 194 } 195 196 /// Sets a list of [`Actor`]s associated with this source. 197 pub fn set_actors(mut self, actors: Option<Vec<Actor>>) -> Self { 198 self.actors = actors; 199 self 200 } 201 } 202 203 /// Identifies a person responsible for an action. 204 #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq, Eq)] 205 #[cfg_attr(feature = "json_schema", derive(JsonSchema))] 206 #[non_exhaustive] 207 pub struct Actor { 208 /// An identifier for a human actor, used when the "type" is `humanEntry.identified`. 209 #[serde(skip_serializing_if = "Option::is_none")] 210 pub identifier: Option<String>, 211 212 /// List of references to W3C Verifiable Credentials. 213 #[serde(skip_serializing_if = "Option::is_none")] 214 pub credentials: Option<Vec<HashedUri>>, 215 } 216 217 impl Actor { 218 pub fn new(identifier: Option<&str>, credentials: Option<&Vec<HashedUri>>) -> Self { 219 Self { 220 identifier: identifier.map(|id| id.to_owned()), 221 credentials: credentials.cloned(), 222 } 223 } 224 } 225 226 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] 227 pub enum ReviewCode { 228 #[serde(rename(serialize = "actions.unknownActionsPerformed"))] 229 ActionsUnknown, 230 #[serde(rename(serialize = "actions.missing"))] 231 ActionsMissing, 232 #[serde(rename(serialize = "actions.possiblyMissing"))] 233 ActionsPossiblyMissing, 234 #[serde(rename(serialize = "depthMap.sceneMismatch"))] 235 DepthMapSceneMismatch, 236 #[serde(rename(serialize = "ingredient.modified"))] 237 IngredientModified, 238 #[serde(rename(serialize = "ingredient.possiblyModified"))] 239 IngredientPossiblyModified, 240 #[serde(rename(serialize = "thumbnail.primaryMismatch"))] 241 ThumbnailPrimaryMismatch, 242 #[serde(rename(serialize = "stds.iptc.location.inaccurate"))] 243 IptcLocationInaccurate, 244 #[serde(rename(serialize = "stds.schema-org.CreativeWork.misattributed"))] 245 CreativeWorkMisAttributed, 246 #[serde(rename(serialize = "stds.schema-org.CreativeWork.missingAttribution"))] 247 CreativeWorkMissingAttribution, 248 Other(String), 249 } 250 251 /// A rating on an Assertion. 252 /// 253 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_claim_review>. 254 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] 255 #[cfg_attr(feature = "json_schema", derive(JsonSchema))] 256 pub struct ReviewRating { 257 pub explanation: String, 258 #[serde(skip_serializing_if = "Option::is_none")] 259 pub code: Option<String>, 260 pub value: u8, 261 } 262 263 impl ReviewRating { 264 pub fn new(explanation: &str, code: Option<String>, value: u8) -> Self { 265 Self { 266 explanation: explanation.to_owned(), 267 value, // should be in range 1 to 5 268 code, 269 } 270 } 271 } 272 273 #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] 274 #[cfg_attr(feature = "json_schema", derive(JsonSchema))] 275 pub struct AssetType { 276 #[serde(rename = "type")] 277 pub asset_type: String, 278 #[serde(skip_serializing_if = "Option::is_none")] 279 pub version: Option<String>, 280 } 281 282 #[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] 283 pub struct DataBox { 284 #[serde(rename = "dc:format")] 285 pub format: String, 286 #[serde(with = "serde_bytes")] 287 pub data: Vec<u8>, 288 pub data_types: Option<Vec<AssetType>>, 289 } 290 291 #[cfg(test)] 292 pub mod tests { 293 #![allow(clippy::expect_used)] 294 #![allow(clippy::unwrap_used)] 295 296 use super::*; 297 298 #[test] 299 fn assertion_metadata() { 300 let review = ReviewRating::new("foo", Some("bar".to_owned()), 3); 301 let test_value = Value::from("test"); 302 let mut original = Metadata::new().add_review(review); 303 original.insert("foo", test_value); 304 println!("{:?}", &original); 305 let assertion = original.to_assertion().expect("build_assertion"); 306 assert_eq!(assertion.mime_type(), "application/cbor"); 307 assert_eq!(assertion.label(), Metadata::LABEL); 308 let result = Metadata::from_assertion(&assertion).expect("extract_assertion"); 309 println!("{:?}", serde_json::to_string(&result)); 310 assert_eq!(original.date_time, result.date_time); 311 assert_eq!(original.reviews, result.reviews); 312 assert_eq!(original.get("foo").unwrap(), "test"); 313 //assert_eq!(original.reviews.unwrap().len(), 1); 314 } 315 }