c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

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 }