diff --git a/.changeset/stupid-bats-join.md b/.changeset/stupid-bats-join.md new file mode 100644 index 0000000..b6f2f68 --- /dev/null +++ b/.changeset/stupid-bats-join.md @@ -0,0 +1,5 @@ +--- +"@contentauth/c2pa-node": minor +--- + +Integrate postCawgValidate into Reader. diff --git a/js-src/IdentityAssertion.spec.ts b/js-src/IdentityAssertion.spec.ts index 56869e7..513634d 100644 --- a/js-src/IdentityAssertion.spec.ts +++ b/js-src/IdentityAssertion.spec.ts @@ -183,10 +183,9 @@ describe("IdentityAssertionBuilder", () => { await builder.signAsync(iaSigner, source, dest); // Verify the manifest - const reader = await Reader.fromAsset({ + await Reader.fromAsset({ buffer: dest.buffer! as Buffer, mimeType: "image/jpeg", }); - await reader.postValidateCawg(); }); }); diff --git a/js-src/Reader.spec.ts b/js-src/Reader.spec.ts index c939f5b..128363c 100644 --- a/js-src/Reader.spec.ts +++ b/js-src/Reader.spec.ts @@ -17,7 +17,6 @@ import path from "path"; import * as fs from "fs-extra"; import { Reader } from "./Reader.js"; -import { patchVerifyConfig } from "./Settings.js"; const tempDir = path.join(__dirname, "tmp"); @@ -109,7 +108,6 @@ describe("Reader", () => { }`); beforeAll(async () => { - patchVerifyConfig({ verifyTrust: false }); await fs.ensureDir(tempDir); }); @@ -206,9 +204,6 @@ describe("Reader", () => { path: "./tests/fixtures/C_with_CAWG_data.jpg", }); - // Call postValidateCawg to decode CAWG assertions - await reader.postValidateCawg(); - const activeManifest = reader.getActive(); // Find the cawg.identity assertion diff --git a/js-src/Reader.ts b/js-src/Reader.ts index 9a67d89..bf43026 100644 --- a/js-src/Reader.ts +++ b/js-src/Reader.ts @@ -72,8 +72,4 @@ export class Reader implements ReaderInterface { return manifestStore.manifests[activeManifest]; } - - async postValidateCawg(): Promise { - return getNeonBinary().readerPostValidateCawg.call(this.reader); - } } diff --git a/js-src/Settings.spec.ts b/js-src/Settings.spec.ts index 7b0e9b7..355792e 100644 --- a/js-src/Settings.spec.ts +++ b/js-src/Settings.spec.ts @@ -6,18 +6,23 @@ import type { TrustConfig, VerifyConfig } from "./types.d.ts"; import { - loadC2paSettings, - loadTrustConfig, - loadCawgTrustConfig, + getCawgTrustConfig, getSettingsJson, getTrustConfig, - getCawgTrustConfig, - loadVerifyConfig, getVerifyConfig, + loadC2paSettings, + loadCawgTrustConfig, + loadTrustConfig, + loadVerifyConfig, patchVerifyConfig, + resetSettings, } from "./Settings.js"; describe("Settings", () => { + afterAll(() => { + resetSettings(); + }); + it("loads a trustlist-shaped JSON and returns JSON via getSettingsJson", () => { // Matches c2pa-rs settings structure: trust + verify // trust.allowed_list accepts base64 lines or PEMs; provide a simple base64 line @@ -37,6 +42,7 @@ describe("Settings", () => { expect(obj.trust?.allowed_list).toBe("Zm9v\n"); expect(obj.verify?.verify_trust).toBe(true); }); + describe("loadTrustConfig", () => { it("preserves other settings when loading trust config", () => { // First, load basic settings that work with c2pa-rs @@ -128,7 +134,7 @@ describe("Settings", () => { // Verify settings are still intact const currentSettings = JSON.parse(getSettingsJson()); - expect(currentSettings.core.merkle_tree_max_proofs).toBe(5); + expect(currentSettings.core.decode_identity_assertions).toBe(true); expect(currentSettings.verify.verify_trust).toBe(true); expect(currentSettings.trust.verify_trust_list).toBe(true); }); diff --git a/js-src/Settings.ts b/js-src/Settings.ts index 08fcac8..a99b64a 100644 --- a/js-src/Settings.ts +++ b/js-src/Settings.ts @@ -209,3 +209,11 @@ export function getVerifyConfig(): VerifyConfig { strictV1Validation: parsed.strict_v1_validation, }; } + +/** + * Reset settings to their default values. + * This clears any custom settings and restores the default configuration. + */ +export function resetSettings(): void { + getNeonBinary().resetSettings(); +} diff --git a/js-src/index.node.d.ts b/js-src/index.node.d.ts index 1873f40..ccb6426 100644 --- a/js-src/index.node.d.ts +++ b/js-src/index.node.d.ts @@ -170,4 +170,7 @@ declare module "index.node" { export function loadCawgTrustConfig(trustConfigJson: string): void; export function getTrustConfig(): string; export function getCawgTrustConfig(): string; + export function loadVerifyConfig(verifyConfigJson: string): void; + export function getVerifyConfig(): string; + export function resetSettings(): void; } diff --git a/js-src/types.d.ts b/js-src/types.d.ts index 34efd50..c15260b 100644 --- a/js-src/types.d.ts +++ b/js-src/types.d.ts @@ -309,11 +309,6 @@ export interface ReaderInterface { * @param filePath The path to the file */ resourceToAsset(uri: string, output: DestinationAsset): Promise; - - /** - * Run CAWG validation - */ - postValidateCawg(): Promise; } export interface IdentityAssertionSignerInterface { diff --git a/src/lib.rs b/src/lib.rs index f100b1a..d4f33f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,10 +92,6 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { "readerResourceToAsset", neon_reader::NeonReader::resource_to_asset, )?; - cx.export_function( - "readerPostValidateCawg", - neon_reader::NeonReader::post_validate_cawg, - )?; // Signers cx.export_function("localSignerNew", neon_signer::NeonLocalSigner::new)?; @@ -186,5 +182,8 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { cx.export_function("loadVerifyConfig", settings::load_verify_config)?; cx.export_function("getVerifyConfig", settings::get_verify_config)?; + // Reset Settings + cx.export_function("resetSettings", settings::reset_settings)?; + Ok(()) } diff --git a/src/neon_builder.rs b/src/neon_builder.rs index 3512404..e842a7a 100644 --- a/src/neon_builder.rs +++ b/src/neon_builder.rs @@ -123,7 +123,6 @@ impl NeonBuilder { } pub fn add_resource(mut cx: FunctionContext) -> JsResult { - let rt = runtime(); let this = cx.this::>()?; let uri = cx.argument::(0)?.value(&mut cx); let resource = cx @@ -131,22 +130,21 @@ impl NeonBuilder { .and_then(|obj| parse_asset(&mut cx, obj))?; let builder = Arc::clone(&this.builder); - let channel = cx.channel(); - let (deferred, promise) = cx.promise(); - - rt.spawn(async move { - let mut builder = builder.lock().await; - - let result = resource.into_read_stream().and_then(|mut resource_stream| { - builder.add_resource(&uri, &mut resource_stream)?; - Ok(()) - }); + let promise = cx + .task(move || { + // Block on acquiring the async mutex lock + let rt = runtime(); + let mut builder = rt.block_on(async { builder.lock().await }); - deferred.settle_with(&channel, |mut cx| match result { + resource.into_read_stream().and_then(|mut resource_stream| { + builder.add_resource(&uri, &mut resource_stream)?; + Ok(()) + }) + }) + .promise(move |mut cx, result: Result<(), Error>| match result { Ok(_) => Ok(cx.undefined()), Err(err) => as_js_error(&mut cx, err).and_then(|err| cx.throw(err)), }); - }); Ok(promise) } @@ -167,18 +165,24 @@ impl NeonBuilder { rt.spawn(async move { let mut builder = builder.lock().await; - let result = ingredient - .mime_type() - .ok_or_else(|| Error::Signing("Ingredient asset must have a mime type".to_string())) - .and_then(|format| { - let mut ingredient_stream = ingredient.into_read_stream()?; - builder.add_ingredient_from_stream( + let result = async { + let format = ingredient + .mime_type() + .ok_or_else(|| { + Error::Signing("Ingredient asset must have a mime type".to_string()) + })? + .to_owned(); + let mut ingredient_stream = ingredient.into_read_stream()?; + builder + .add_ingredient_from_stream_async( &ingredient_json, &format, &mut ingredient_stream, - )?; - Ok(()) - }); + ) + .await?; + Ok(()) + } + .await; deferred.settle_with(&channel, move |mut cx| match result { Ok(_) => Ok(cx.undefined()), @@ -190,53 +194,47 @@ impl NeonBuilder { } pub fn to_archive(mut cx: FunctionContext) -> JsResult { - let rt = runtime(); let this = cx.this::>()?; let dest = cx .argument::(0) .and_then(|obj| parse_asset(&mut cx, obj))?; let builder = Arc::clone(&this.builder); - let channel = cx.channel(); - let (deferred, promise) = cx.promise(); - - rt.spawn(async move { - let mut builder = builder.lock().await; - let result = dest.write_stream().and_then(|dest_stream| { - builder.to_archive(dest_stream)?; - Ok(()) - }); + let promise = cx + .task(move || { + // Block on acquiring the async mutex lock + let rt = runtime(); + let mut builder = rt.block_on(async { builder.lock().await }); - deferred.settle_with(&channel, move |mut cx| match result { + dest.write_stream().and_then(|dest_stream| { + builder.to_archive(dest_stream)?; + Ok(()) + }) + }) + .promise(move |mut cx, result: Result<(), Error>| match result { Ok(_) => Ok(cx.undefined()), Err(err) => as_js_error(&mut cx, err).and_then(|err| cx.throw(err)), }); - }); Ok(promise) } pub fn from_archive(mut cx: FunctionContext) -> JsResult { - let rt = runtime(); let source = cx .argument::(0) .and_then(|obj| parse_asset(&mut cx, obj))?; - let channel = cx.channel(); - let (deferred, promise) = cx.promise(); - - rt.spawn(async move { - let result = source.into_read_stream().and_then(|source_stream| { + let promise = cx + .task(move || { + let source_stream = source.into_read_stream()?; let builder = Builder::from_archive(source_stream)?; Ok(builder) - }); - - deferred.settle_with(&channel, move |mut cx| match result { + }) + .promise(move |mut cx, result: Result| match result { Ok(builder) => Ok(cx.boxed(Self { builder: Arc::new(Mutex::new(builder)), })), Err(err) => as_js_error(&mut cx, err).and_then(|err| cx.throw(err)), }); - }); Ok(promise) } diff --git a/src/neon_reader.rs b/src/neon_reader.rs index 1023a28..cbd928b 100644 --- a/src/neon_reader.rs +++ b/src/neon_reader.rs @@ -14,7 +14,7 @@ use crate::asset::parse_asset; use crate::error::{as_js_error, Error}; use crate::runtime::runtime; -use c2pa::{identity::validator::CawgValidator, Reader}; +use c2pa::Reader; use neon::prelude::*; use neon::types::buffer::TypedArray; use std::sync::Arc; @@ -35,23 +35,29 @@ impl NeonReader { } pub fn from_stream(mut cx: FunctionContext) -> JsResult { + let rt = runtime(); + let channel = cx.channel(); let source = cx .argument::(0) .and_then(|obj| parse_asset(&mut cx, obj))?; - let promise = cx - .task(move || { + let (deferred, promise) = cx.promise(); + rt.spawn(async move { + let result = async { let format = source .mime_type() .ok_or_else(|| { Error::Signing("Ingredient asset must have a mime type".to_string()) })? .to_owned(); + let stream = source.into_read_stream()?; - let reader = Reader::from_stream(&format, stream)?; + let reader = Reader::from_stream_async(&format, stream).await?; Ok(reader) - }) - .promise(move |mut cx, result: Result| match result { + } + .await; + + deferred.settle_with(&channel, move |mut cx| match result { Ok(reader) => { let boxed_reader = cx.boxed(Self { reader: Arc::new(Mutex::new(reader)), @@ -60,30 +66,37 @@ impl NeonReader { } Err(err) => as_js_error(&mut cx, err).and_then(|err| cx.throw(err)), }); + }); Ok(promise) } pub fn from_manifest_data_and_asset(mut cx: FunctionContext) -> JsResult { + let rt = runtime(); + let channel = cx.channel(); let manifest_data = cx.argument::(0)?; let asset = cx .argument::(1) .and_then(|obj| parse_asset(&mut cx, obj))?; let c2pa_data = manifest_data.as_slice(&cx).to_vec(); - let promise = cx - .task(move || { + let (deferred, promise) = cx.promise(); + rt.spawn(async move { + let result = async { let format = asset .mime_type() .ok_or_else(|| { Error::Signing("Ingredient asset must have a mime type".to_string()) })? .to_owned(); - let mut stream = asset.into_read_stream()?; + let stream = asset.into_read_stream()?; let reader = - Reader::from_manifest_data_and_stream(&c2pa_data, &format, &mut stream)?; + Reader::from_manifest_data_and_stream_async(&c2pa_data, &format, stream) + .await?; Ok(reader) - }) - .promise(move |mut cx, result: Result| match result { + } + .await; + + deferred.settle_with(&channel, move |mut cx| match result { Ok(reader) => { let boxed_reader = cx.boxed(Self { reader: Arc::new(Mutex::new(reader)), @@ -92,7 +105,7 @@ impl NeonReader { } Err(err) => as_js_error(&mut cx, err).and_then(|err| cx.throw(err)), }); - + }); Ok(promise) } @@ -170,28 +183,4 @@ impl NeonReader { }); Ok(promise) } - - pub fn post_validate_cawg(mut cx: FunctionContext) -> JsResult { - let rt = runtime(); - let this = cx.this::>()?; - let reader = Arc::clone(&this.reader); - - let channel = cx.channel(); - let (deferred, promise) = cx.promise(); - - rt.spawn(async move { - let result = reader - .lock() - .await - .post_validate_async(&CawgValidator {}) - .await - .map_err(Error::from); - - deferred.settle_with(&channel, move |mut cx| match result { - Ok(_) => Ok(cx.undefined()), - Err(err) => as_js_error(&mut cx, err).and_then(|err| cx.throw(err)), - }); - }); - Ok(promise) - } } diff --git a/src/settings.rs b/src/settings.rs index 8a98e28..f6890d4 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -403,7 +403,10 @@ pub fn load_verify_config(mut cx: FunctionContext) -> JsResult { obj.insert("check_ingredient_trust".to_string(), val.clone()); } if let Some(val) = verify_config.get("skip_ingredient_conflict_resolution") { - obj.insert("skip_ingredient_conflict_resolution".to_string(), val.clone()); + obj.insert( + "skip_ingredient_conflict_resolution".to_string(), + val.clone(), + ); } if let Some(val) = verify_config.get("strict_v1_validation") { obj.insert("strict_v1_validation".to_string(), val.clone()); @@ -485,3 +488,23 @@ pub fn get_verify_config(mut cx: FunctionContext) -> JsResult { } } } + +/// Reset settings to their default values. +/// This replicates the private reset() function from c2pa-rs Settings. +pub fn reset_settings(mut cx: FunctionContext) -> JsResult { + // Create default settings and get their TOML representation + let default_settings = Settings::default(); + let default_toml = toml::to_string(&default_settings) + .or_else(|e| cx.throw_error(format!("Failed to serialize default settings: {}", e)))?; + + // Apply the default settings + match Settings::from_toml(&default_toml) { + Ok(_) => { + // Store the default TOML for worker threads + set_global_settings_toml(Some(default_toml)); + reload_runtime(); + Ok(cx.undefined()) + } + Err(e) => cx.throw_error(format!("Failed to apply default settings: {}", e)), + } +}