Skip to content

Commit

Permalink
(MINOR) Rust API enhancements and fixes. (#575)
Browse files Browse the repository at this point in the history
* Adds Rust creation of manifest definitions.
Updates examples to use manifest definitions.

* Write json assertions correctly.

* Adds sidecar support to Reader.from_file.

* Removes optional source from signed_data_hashed_embeddable()
Use hash_stream_by_alg() instead.
Updates data_hash example to '24 API.

* Renames Builder.add_ingredient to add_ingredient_from_stream
Adds Builder.add_ingredient, accepting an Ingredient.
Exports Relationship to public SDK
Adds set_claim_generator to Builder

* Keep ManifestDefinition as part of Builder.

* Remove from_manifest_definition()
(use Builder directly to construct in Rust)

* Deprecates several Manifest methods (use Builder instead)
Updates integration tests to avoid deprecated methods.
Updates to client example.
  • Loading branch information
gpeacock authored Sep 3, 2024
1 parent 97f9b91 commit 2a14e23
Show file tree
Hide file tree
Showing 15 changed files with 349 additions and 277 deletions.
5 changes: 4 additions & 1 deletion export_schema/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{fs, path::Path};

use anyhow::Result;
use c2pa::{settings::Settings, ManifestDefinition, ManifestStore};
use c2pa::{settings::Settings, Builder, ManifestDefinition, ManifestStore};
use schemars::{schema::RootSchema, schema_for};

fn write_schema(schema: &RootSchema, name: &str) {
Expand All @@ -15,6 +15,9 @@ fn write_schema(schema: &RootSchema, name: &str) {
}

fn main() -> Result<()> {
let builder = schema_for!(Builder);
write_schema(&builder, "Builder");

let manifest_definition = schema_for!(ManifestDefinition);
write_schema(&manifest_definition, "ManifestDefinition");

Expand Down
31 changes: 16 additions & 15 deletions make_test_images/src/make_test_images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use anyhow::{Context, Result};
use c2pa::{
create_signer,
jumbf_io::{get_supported_types, load_jumbf_from_stream, save_jumbf_to_stream},
Builder, Error, Reader, Signer, SigningAlg,
Builder, Error, Ingredient, Reader, Relationship, Signer, SigningAlg,
};
use memchr::memmem;
use nom::AsBytes;
Expand Down Expand Up @@ -208,7 +208,7 @@ impl MakeTestImages {
fn add_ingredient_from_file(
builder: &mut Builder,
path: &Path,
relationship: &str,
relationship: Relationship,
) -> Result<String> {
let mut source = fs::File::open(path).context("opening ingredient")?;
let name = path
Expand All @@ -222,20 +222,18 @@ impl MakeTestImages {
.into_owned();
let format = extension_to_mime(&extension).unwrap_or("image/jpeg");

let json = json!({
"title": name,
"relationship": relationship,
})
.to_string();

let ingredient = builder.add_ingredient(&json, format, &mut source)?;
if ingredient.thumbnail_ref().is_none() {
let mut parent = Ingredient::from_stream(format, &mut source)?;
parent.set_relationship(relationship);
parent.set_title(name);
if parent.thumbnail_ref().is_none() {
source.rewind()?;
let (format, thumbnail) =
make_thumbnail_from_stream(format, &mut source).context("making thumbnail")?;
ingredient.set_thumbnail(format, thumbnail)?;
parent.set_thumbnail(format, thumbnail)?;
}

builder.add_ingredient(parent);

Ok(
builder.definition.ingredients[builder.definition.ingredients.len() - 1]
.instance_id()
Expand Down Expand Up @@ -300,7 +298,7 @@ impl MakeTestImages {
let src_path = &self.make_path(src);

let instance_id =
Self::add_ingredient_from_file(&mut builder, src_path, "parentOf")?;
Self::add_ingredient_from_file(&mut builder, src_path, Relationship::ParentOf)?;

actions.push(json!(
{
Expand Down Expand Up @@ -376,8 +374,11 @@ impl MakeTestImages {
let instance_id = match ingredient_table.get(ing.as_str()) {
Some(id) => id.to_string(),
None => {
let instance_id =
Self::add_ingredient_from_file(&mut builder, ing_path, "componentOf")?;
let instance_id = Self::add_ingredient_from_file(
&mut builder,
ing_path,
Relationship::ComponentOf,
)?;
ingredient_table.insert(ing, instance_id.clone());
instance_id
}
Expand Down Expand Up @@ -490,7 +491,7 @@ impl MakeTestImages {
let mut builder = Builder::from_json(&json)?;

let parent_name = file_name(&dst_path).ok_or(Error::BadParam("no filename".to_string()))?;
builder.add_ingredient(
builder.add_ingredient_from_stream(
json!({
"title": parent_name,
"relationship": "parentOf"
Expand Down
3 changes: 1 addition & 2 deletions sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ config = { version = "0.14.0", default-features = false, features = [
conv = "0.3.3"
coset = "0.3.1"
extfmt = "0.1.1"
ed25519-dalek = "2.1.1"
fast-xml = "0.23.1"
hex = "0.4.3"
# Version 1.13.0 doesn't compile under Rust < 1.75, pinning to 1.12.0
Expand Down Expand Up @@ -141,7 +142,6 @@ openssl = { version = "0.10.61", features = ["vendored"], optional = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
console_log = { version = "1.0.0", features = ["color"] }
ed25519-dalek = "2.1.1"
getrandom = { version = "0.2.7", features = ["js"] }
# We need to use the `inaccurate` flag here to ensure usage of the JavaScript Date API
# to handle certificate timestamp checking correctly.
Expand Down Expand Up @@ -176,5 +176,4 @@ wasm-bindgen-test = "0.3.31"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
actix = "0.13.1"
ed25519-dalek = "2.1.1"
tokio = { version = "1.36.0", features = ["full"] }
60 changes: 38 additions & 22 deletions sdk/examples/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ use std::path::PathBuf;
use anyhow::Result;
use c2pa::{
assertions::{c2pa_action, labels, Action, Actions, CreativeWork, Exif, SchemaDotOrgPerson},
create_signer, Ingredient, Manifest, Reader as ManifestStore, SigningAlg,
create_signer, Builder, ClaimGeneratorInfo, Ingredient, Reader, Relationship, SigningAlg,
};

const GENERATOR: &str = "test_app/0.1";
const INDENT_SPACE: usize = 2;

// Example for reading the contents of a manifest store, recursively showing nested manifests
fn show_manifest(manifest_store: &ManifestStore, manifest_label: &str, level: usize) -> Result<()> {
fn show_manifest(reader: &Reader, manifest_label: &str, level: usize) -> Result<()> {
let indent = " ".repeat(level * INDENT_SPACE);

println!("{indent}manifest_label: {manifest_label}");
if let Some(manifest) = manifest_store.get_manifest(manifest_label) {
if let Some(manifest) = reader.get_manifest(manifest_label) {
println!(
"{}title: {} , format: {}, instance_id: {}",
indent,
Expand Down Expand Up @@ -66,8 +66,17 @@ fn show_manifest(manifest_store: &ManifestStore, manifest_label: &str, level: us

for ingredient in manifest.ingredients().iter() {
println!("{}Ingredient title:{}", indent, ingredient.title());
if let Some(validation_status) = ingredient.validation_status() {
for status in validation_status {
println!(
"Ingredient validation status: {}: {}",
status.code(),
status.explanation().unwrap_or_default()
);
}
}
if let Some(label) = ingredient.active_manifest() {
show_manifest(manifest_store, label, level + 1)?;
show_manifest(reader, label, level + 1)?;
}
}
}
Expand All @@ -88,11 +97,11 @@ pub fn main() -> Result<()> {
let source = PathBuf::from(src);
let dest = PathBuf::from(dst);
// if a filepath was provided on the command line, read it as a parent file
let parent = Ingredient::from_file(source.as_path())?;

let mut parent = Ingredient::from_file(source.as_path())?;
parent.set_relationship(Relationship::ParentOf);
// create an action assertion stating that we imported this file
let actions = Actions::new().add_action(
Action::new(c2pa_action::PLACED)
Action::new(c2pa_action::OPENED)
.set_parameter("identifier", parent.instance_id().to_owned())?,
);

Expand All @@ -115,31 +124,38 @@ pub fn main() -> Result<()> {
)?;

// create a new Manifest
let mut manifest = Manifest::new(GENERATOR.to_owned());
// add parent and assertions
manifest
.set_parent(parent)?
.add_assertion(&actions)?
.add_assertion(&creative_work)?
.add_assertion(&exif)?;
let mut builder = Builder::new();
builder
.set_claim_generator_info(ClaimGeneratorInfo::new(GENERATOR))
.add_ingredient(parent)
.add_assertion(Actions::LABEL, &actions)?
.add_assertion(CreativeWork::LABEL, &creative_work)?
.add_assertion(Exif::LABEL, &exif)?;

// sign and embed into the target file
let signcert_path = "sdk/tests/fixtures/certs/es256.pub";
let pkey_path = "sdk/tests/fixtures/certs/es256.pem";
let signer = create_signer::from_files(signcert_path, pkey_path, SigningAlg::Es256, None)?;

manifest.embed(&source, &dest, &*signer)?;
builder.sign_file(&*signer, &source, &dest)?;

let manifest_store = ManifestStore::from_file(&dest)?;
let reader = Reader::from_file(&dest)?;

// example of how to print out the whole manifest as json
println!("{manifest_store}\n");

// walk through the manifest and access data.
println!("{reader}\n");

if let Some(manifest_label) = manifest_store.active_label() {
show_manifest(&manifest_store, manifest_label, 0)?;
// walk through the manifests and show the contents
if let Some(manifest_label) = reader.active_label() {
show_manifest(&reader, manifest_label, 0)?;
}
if let Some(validation_status) = reader.validation_status() {
for status in validation_status {
println!(
"Validation status: {}: {}",
status.code(),
status.explanation().unwrap_or_default()
);
}
}

Ok(())
}
Loading

0 comments on commit 2a14e23

Please sign in to comment.