Skip to content

Commit 3a9e590

Browse files
authored
Builder Archive update (#507)
* various rust 1.78 clippy fixes * Update builder Archive format Adds version.txt Uses a manifests folder with c2pa files instead of numbered ingredients folders. Includes temporary backwards compatibility for earlier non-versioned format.
1 parent 9afd2e3 commit 3a9e590

File tree

2 files changed

+61
-26
lines changed

2 files changed

+61
-26
lines changed

sdk/src/assertion.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -471,14 +471,6 @@ impl Assertion {
471471
}
472472
}
473473

474-
#[allow(dead_code)] // TODO: temp, see #498
475-
#[derive(Serialize, Deserialize, Debug)]
476-
pub(crate) struct JsonAssertionData {
477-
label: String,
478-
data: Value,
479-
is_cbor: bool,
480-
}
481-
482474
/// This error type is returned when an assertion can not be decoded.
483475
#[non_exhaustive]
484476
pub struct AssertionDecodeError {

sdk/src/builder.rs

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ use crate::{
3737
AsyncSigner, ClaimGeneratorInfo, Signer,
3838
};
3939

40+
/// Version of the Builder Archive file
41+
const ARCHIVE_VERSION: &str = "1";
42+
4043
/// A Manifest Definition
4144
/// This is used to define a manifest and is used to build a ManifestStore
4245
/// A Manifest is a collection of ingredients and assertions
@@ -259,8 +262,7 @@ impl Builder {
259262
let mut resource = Vec::new();
260263
stream.read_to_end(&mut resource)?;
261264
// add the resource and set the resource reference
262-
self.resources
263-
.add(self.definition.instance_id.clone(), resource)?;
265+
self.resources.add(&self.definition.instance_id, resource)?;
264266
self.definition.thumbnail = Some(ResourceRef::new(
265267
format,
266268
self.definition.instance_id.clone(),
@@ -371,25 +373,36 @@ impl Builder {
371373
let mut zip = ZipWriter::new(stream);
372374
let options =
373375
FileOptions::default().compression_method(zip::CompressionMethod::Stored);
376+
// write a version file
377+
zip.start_file("version.txt", options)
378+
.map_err(|e| Error::OtherError(Box::new(e)))?;
379+
zip.write_all(ARCHIVE_VERSION.as_bytes())?;
380+
// write the manifest.json file
374381
zip.start_file("manifest.json", options)
375382
.map_err(|e| Error::OtherError(Box::new(e)))?;
376383
zip.write_all(&serde_json::to_vec(self)?)?;
377-
// add a folder to the zip file
384+
// add resource files to a resources folder
378385
zip.start_file("resources/", options)
379386
.map_err(|e| Error::OtherError(Box::new(e)))?;
380387
for (id, data) in self.resources.resources() {
381388
zip.start_file(format!("resources/{}", id), options)
382389
.map_err(|e| Error::OtherError(Box::new(e)))?;
383390
zip.write_all(data)?;
384391
}
385-
for (index, ingredient) in self.definition.ingredients.iter().enumerate() {
386-
zip.start_file(format!("ingredients/{}/", index), options)
387-
.map_err(|e| Error::OtherError(Box::new(e)))?;
388-
for (id, data) in ingredient.resources().resources() {
389-
//println!("adding ingredient {}/{}", index, id);
390-
zip.start_file(format!("ingredients/{}/{}", index, id), options)
391-
.map_err(|e| Error::OtherError(Box::new(e)))?;
392-
zip.write_all(data)?;
392+
// Write the manifest_data files
393+
// The filename is filesystem safe version of the associated manifest_label
394+
// with a .c2pa extension inside a "manifests" folder.
395+
zip.start_file("manifests/", options)
396+
.map_err(|e| Error::OtherError(Box::new(e)))?;
397+
for ingredient in self.definition.ingredients.iter() {
398+
if let Some(manifest_label) = ingredient.active_manifest() {
399+
if let Some(manifest_data) = ingredient.manifest_data() {
400+
// Convert to valid archive / file path name
401+
let manifest_name = manifest_label.replace([':'], "_") + ".c2pa";
402+
zip.start_file(format!("manifests/{manifest_name}"), options)
403+
.map_err(|e| Error::OtherError(Box::new(e)))?;
404+
zip.write_all(&manifest_data)?;
405+
}
393406
}
394407
}
395408
zip.finish()
@@ -408,14 +421,16 @@ impl Builder {
408421
/// * If the archive cannot be read.
409422
pub fn from_archive(stream: impl Read + Seek) -> Result<Self> {
410423
let mut zip = ZipArchive::new(stream).map_err(|e| Error::OtherError(Box::new(e)))?;
411-
let mut manifest = zip
424+
// First read the manifest.json file.
425+
let mut manifest_file = zip
412426
.by_name("manifest.json")
413427
.map_err(|e| Error::OtherError(Box::new(e)))?;
414-
let mut manifest_json = Vec::new();
415-
manifest.read_to_end(&mut manifest_json)?;
428+
let mut manifest_buf = Vec::new();
429+
manifest_file.read_to_end(&mut manifest_buf)?;
416430
let mut builder: Builder =
417-
serde_json::from_slice(&manifest_json).map_err(|e| Error::OtherError(Box::new(e)))?;
418-
drop(manifest);
431+
serde_json::from_slice(&manifest_buf).map_err(|e| Error::OtherError(Box::new(e)))?;
432+
drop(manifest_file);
433+
// Load all the files in the resources folder.
419434
for i in 0..zip.len() {
420435
let mut file = zip
421436
.by_index(i)
@@ -432,6 +447,29 @@ impl Builder {
432447
//println!("adding resource {}", id);
433448
builder.resources.add(id, data)?;
434449
}
450+
451+
// Load the c2pa_manifests.
452+
// We add the manifest data to any ingredient that has a matching active_manfiest label.
453+
if file.name().starts_with("manifests/") && file.name() != "manifests/" {
454+
let mut data = Vec::new();
455+
file.read_to_end(&mut data)?;
456+
let manifest_label = file
457+
.name()
458+
.split('/')
459+
.nth(1)
460+
.ok_or(Error::BadParam("Invalid manifest path".to_string()))?;
461+
let manifest_label = manifest_label.replace(['_'], ":");
462+
for ingredient in builder.definition.ingredients.iter_mut() {
463+
if let Some(active_manifest) = ingredient.active_manifest() {
464+
if manifest_label.starts_with(active_manifest) {
465+
ingredient.set_manifest_data(data.clone())?;
466+
}
467+
}
468+
}
469+
}
470+
471+
// Keep this for temporary unstable api support (un-versioned).
472+
// Earlier method used numbered library folders instead of manifests.
435473
if file.name().starts_with("ingredients/") && file.name() != "ingredients/" {
436474
let mut data = Vec::new();
437475
file.read_to_end(&mut data)?;
@@ -465,7 +503,7 @@ impl Builder {
465503
// add the default claim generator info for this library
466504
claim_generator_info.push(ClaimGeneratorInfo::default());
467505

468-
// build the claim_generator string since this is required
506+
// Build the claim_generator string since this is required
469507
let claim_generator: String = claim_generator_info
470508
.iter()
471509
.map(|s| {
@@ -493,7 +531,7 @@ impl Builder {
493531
claim.add_claim_generator_info(claim_info);
494532
}
495533

496-
// add claim metadata
534+
// Add claim metadata
497535
if let Some(metadata_vec) = metadata {
498536
for m in metadata_vec {
499537
claim.add_claim_metadata(m);
@@ -972,6 +1010,11 @@ mod tests {
9721010
.add("thumbnail1.jpg", TEST_IMAGE.to_vec())
9731011
.unwrap();
9741012

1013+
builder
1014+
.resources
1015+
.add("prompt.txt", "a random prompt")
1016+
.unwrap();
1017+
9751018
builder
9761019
.add_assertion("org.life.meaning", &TestAssertion { answer: 42 })
9771020
.unwrap();

0 commit comments

Comments
 (0)