From fe3f72cd834a438399a5fc16da5dc262b92a3faf Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Wed, 9 Oct 2024 15:55:51 -0700 Subject: [PATCH 01/10] Add WASM tests --- .github/workflows/ci.yml | 18 ++++++++++++++++++ Cargo.toml | 14 ++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a837887..2b2db2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,6 +76,24 @@ jobs: - name: Run unit tests (cross build) run: cross test --all-targets --all-features --target ${{ matrix.target }} + tests-wasm: + name: Unit tests (WASM) + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Run Wasm tests + run: wasm-pack test --chrome --headless + working-directory: . + test-default-disabled: name: Unit tests with default features disabled runs-on: ${{ matrix.os }} diff --git a/Cargo.toml b/Cargo.toml index 02fc9e4..6a04697 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] async-trait = "0.1.78" -c2pa = { version = "0.33.0", features = ["openssl_ffi_mutex"] } chrono = "0.4.38" ciborium = "0.2.2" coset = "0.3.8" @@ -31,8 +30,19 @@ static-iref = "3.0" thiserror = "1.0.61" xsd-types = "0.9.5" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +c2pa = { version = "0.33.0", features = ["openssl_ffi_mutex"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +c2pa = { version = "0.33.0", features = [] } + [dev-dependencies] -c2pa = { version = "0.33.0", features = ["file_io", "openssl_sign", "openssl_ffi_mutex"] } serde = { version = "1.0.197", features = ["derive"] } tempfile = "3.10.1" tokio = { version = "1.40", features = ["macros"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +c2pa = { version = "0.33.0", features = ["file_io", "openssl_sign", "openssl_ffi_mutex", "v1_api"] } + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +c2pa = { version = "0.33.0", features = [] } From f2e23e535c6dcdef6dd61120c368355dede1e2ba Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Wed, 9 Oct 2024 16:03:49 -0700 Subject: [PATCH 02/10] Adjust tests for WASM --- src/tests/claim_aggregation/interop.rs | 5 +++- .../validation_error_cases.rs | 24 +++++++++++++------ src/tests/mod.rs | 1 + 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/tests/claim_aggregation/interop.rs b/src/tests/claim_aggregation/interop.rs index 82c1eb1..3af1320 100644 --- a/src/tests/claim_aggregation/interop.rs +++ b/src/tests/claim_aggregation/interop.rs @@ -12,10 +12,13 @@ // each license. use c2pa::ManifestStore; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; use crate::IdentityAssertion; -#[tokio::test] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] async fn adobe_connected_identities() { let manifest_store = ManifestStore::from_file( "src/tests/fixtures/claim_aggregation/adobe_connected_identities.jpg", diff --git a/src/tests/claim_aggregation/validation_error_cases.rs b/src/tests/claim_aggregation/validation_error_cases.rs index 9d63fcf..2196948 100644 --- a/src/tests/claim_aggregation/validation_error_cases.rs +++ b/src/tests/claim_aggregation/validation_error_cases.rs @@ -11,15 +11,20 @@ // specific language governing permissions and limitations under // each license. +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; + use super::test_issuer::TestIssuer; -#[tokio::test] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] async fn default_case() { let ti = TestIssuer::new(); ti.test_basic_case().await; } -#[tokio::test] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] #[should_panic] // TEMPORARY until error results are implemented async fn error_no_issuer() { let ti = TestIssuer::from_asset_vc( @@ -35,7 +40,8 @@ async fn error_no_issuer() { ti.test_basic_case().await; } -#[tokio::test] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] #[should_panic] // TEMPORARY until error results are implemented async fn error_no_issuance_date() { let ti = TestIssuer::from_asset_vc( @@ -52,7 +58,8 @@ async fn error_no_issuance_date() { ti.test_basic_case().await; } -#[tokio::test] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] #[should_panic] // TEMPORARY until error results are implemented async fn error_no_proof() { let ti = TestIssuer::from_asset_vc( @@ -71,7 +78,8 @@ async fn error_no_proof() { } /* TEMPORARY: Holding off on this one until SSI crate handles VC V2. :-( -#[tokio::test] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] #[should_panic] // TEMPORARY until error results are implemented async fn error_v1_vc() { let ti = TestIssuer::from_asset_vc( @@ -97,7 +105,8 @@ async fn error_v1_vc() { } */ -#[tokio::test] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] #[should_panic] // TEMPORARY until error results are implemented async fn error_missing_cawg_context() { let ti = TestIssuer::from_asset_vc( @@ -122,7 +131,8 @@ async fn error_missing_cawg_context() { ti.test_basic_case().await; } -#[tokio::test] +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] #[should_panic] // TEMPORARY until error results are implemented async fn error_missing_cawg_type() { let ti = TestIssuer::from_asset_vc( diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 7dbd68b..85fca6f 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -18,6 +18,7 @@ #![allow(clippy::panic)] #![allow(clippy::unwrap_used)] +#[cfg(not(target_arch = "wasm32"))] mod builder; mod claim_aggregation; pub(crate) mod fixtures; From b2b2bf92d647892549fe17e23dd015c307bb4454 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Wed, 9 Oct 2024 16:08:50 -0700 Subject: [PATCH 03/10] c2pa::create_signer not available on WASM --- src/tests/fixtures/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tests/fixtures/mod.rs b/src/tests/fixtures/mod.rs index 30326a4..42f89c2 100644 --- a/src/tests/fixtures/mod.rs +++ b/src/tests/fixtures/mod.rs @@ -17,7 +17,7 @@ use std::{env, path::PathBuf}; -use c2pa::{create_signer, Signer, SigningAlg}; +use c2pa::{Signer, SigningAlg}; use tempfile::TempDir; pub(crate) fn fixture_path(name: &str) -> PathBuf { @@ -38,9 +38,10 @@ pub(crate) fn temp_dir_path(temp_dir: &TempDir, file_name: &str) -> PathBuf { path } +#[cfg(not(target_arch = "wasm32"))] pub(crate) fn temp_c2pa_signer() -> Box { let sign_cert = include_bytes!("../../tests/fixtures/certs/ps256.pub").to_vec(); let pem_key = include_bytes!("../../tests/fixtures/certs/ps256.pem").to_vec(); - create_signer::from_keys(&sign_cert, &pem_key, SigningAlg::Ps256, None).unwrap() + c2pa::create_signer::from_keys(&sign_cert, &pem_key, SigningAlg::Ps256, None).unwrap() } From c42b806bf0f04c3ed862be159f4853131ea76928 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Thu, 10 Oct 2024 11:09:20 -0700 Subject: [PATCH 04/10] Clean up several WASM build issues --- Cargo.toml | 1 + src/tests/claim_aggregation/interop.rs | 7 +++---- src/tests/claim_aggregation/mod.rs | 4 ++++ src/tests/fixtures/mod.rs | 1 + 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6a04697..7ba84c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,3 +46,4 @@ c2pa = { version = "0.33.0", features = ["file_io", "openssl_sign", "openssl_ffi [target.'cfg(target_arch = "wasm32")'.dev-dependencies] c2pa = { version = "0.33.0", features = [] } +wasm-bindgen-test = "0.3.31" diff --git a/src/tests/claim_aggregation/interop.rs b/src/tests/claim_aggregation/interop.rs index 3af1320..ebebabc 100644 --- a/src/tests/claim_aggregation/interop.rs +++ b/src/tests/claim_aggregation/interop.rs @@ -20,10 +20,9 @@ use crate::IdentityAssertion; #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] async fn adobe_connected_identities() { - let manifest_store = ManifestStore::from_file( - "src/tests/fixtures/claim_aggregation/adobe_connected_identities.jpg", - ) - .unwrap(); + let test_image = include_bytes!("../fixtures/claim_aggregation/adobe_connected_identities.jpg"); + + let manifest_store = ManifestStore::from_bytes("jpg", test_image, true).unwrap(); assert!(manifest_store.validation_status().is_none()); let manifest = manifest_store.get_active().unwrap(); diff --git a/src/tests/claim_aggregation/mod.rs b/src/tests/claim_aggregation/mod.rs index fd07a77..db67a5e 100644 --- a/src/tests/claim_aggregation/mod.rs +++ b/src/tests/claim_aggregation/mod.rs @@ -12,5 +12,9 @@ // each license. mod interop; + +#[cfg(not(target_arch = "wasm32"))] mod test_issuer; + +#[cfg(not(target_arch = "wasm32"))] mod validation_error_cases; diff --git a/src/tests/fixtures/mod.rs b/src/tests/fixtures/mod.rs index 42f89c2..7c71ecc 100644 --- a/src/tests/fixtures/mod.rs +++ b/src/tests/fixtures/mod.rs @@ -17,6 +17,7 @@ use std::{env, path::PathBuf}; +#[cfg(not(target_arch = "wasm32"))] use c2pa::{Signer, SigningAlg}; use tempfile::TempDir; From bbf56fe44549fc4ca80941dc3ff5202b6f1e84b3 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Fri, 18 Oct 2024 10:05:06 -0700 Subject: [PATCH 05/10] Disable httpmock on WASM build --- Cargo.toml | 4 ++-- src/tests/claim_aggregation/w3c_vc/did_web.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 34ae318..0f97c5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,13 +34,13 @@ c2pa = { version = "0.33.0", features = ["openssl_ffi_mutex"] } c2pa = { version = "0.33.0", features = [] } [dev-dependencies] -httpmock = "0.7.0" serde = { version = "1.0.197", features = ["derive"] } tempfile = "3.10.1" -tokio = { version = "1.40", features = ["macros"] } [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] c2pa = { version = "0.33.0", features = ["file_io", "openssl_sign", "openssl_ffi_mutex", "v1_api"] } +httpmock = "0.7.0" +tokio = { version = "1.40", features = ["macros"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] c2pa = { version = "0.33.0", features = [] } diff --git a/src/tests/claim_aggregation/w3c_vc/did_web.rs b/src/tests/claim_aggregation/w3c_vc/did_web.rs index 3a5a252..d0fcbf5 100644 --- a/src/tests/claim_aggregation/w3c_vc/did_web.rs +++ b/src/tests/claim_aggregation/w3c_vc/did_web.rs @@ -44,6 +44,7 @@ async fn to_url() { ); } +#[cfg(not(target_arch = "wasm32"))] mod resolve { use httpmock::prelude::*; From 5cf0e10cf48978f9725490a774a5f8e1ac62a34b Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Fri, 18 Oct 2024 11:54:08 -0700 Subject: [PATCH 06/10] Fix a few WASM build errors --- src/tests/claim_aggregation/w3c_vc/did_web.rs | 4 ++-- src/tests/fixtures/mod.rs | 8 +++----- src/tests/fixtures/naive_credential_holder.rs | 1 + 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/tests/claim_aggregation/w3c_vc/did_web.rs b/src/tests/claim_aggregation/w3c_vc/did_web.rs index d0fcbf5..9e8e2ab 100644 --- a/src/tests/claim_aggregation/w3c_vc/did_web.rs +++ b/src/tests/claim_aggregation/w3c_vc/did_web.rs @@ -20,8 +20,8 @@ use crate::claim_aggregation::w3c_vc::{did::Did, did_web}; -#[tokio::test] -async fn to_url() { +#[test] +fn to_url() { // https://w3c-ccg.github.io/did-method-web/#example-3-creating-the-did assert_eq!( did_web::to_url(did("did:web:w3c-ccg.github.io").method_specific_id()).unwrap(), diff --git a/src/tests/fixtures/mod.rs b/src/tests/fixtures/mod.rs index b65e560..cd60f5e 100644 --- a/src/tests/fixtures/mod.rs +++ b/src/tests/fixtures/mod.rs @@ -11,10 +11,6 @@ // specific language governing permissions and limitations under // each license. -#![allow(dead_code)] -// This code should only used from unit tests. -// Silence warnings about unused code when not building tests. - use std::{env, path::PathBuf}; #[cfg(not(target_arch = "wasm32"))] @@ -22,7 +18,9 @@ use c2pa::{Signer, SigningAlg}; use tempfile::TempDir; mod naive_credential_holder; -pub(crate) use naive_credential_holder::{NaiveCredentialHolder, NaiveSignatureHandler}; +#[cfg(not(target_arch = "wasm32"))] +pub(crate) use naive_credential_holder::NaiveCredentialHolder; +pub(crate) use naive_credential_holder::NaiveSignatureHandler; pub(crate) fn fixture_path(name: &str) -> PathBuf { let root_dir = &env::var("CARGO_MANIFEST_DIR").unwrap(); diff --git a/src/tests/fixtures/naive_credential_holder.rs b/src/tests/fixtures/naive_credential_holder.rs index 6df9394..6ccb4d7 100644 --- a/src/tests/fixtures/naive_credential_holder.rs +++ b/src/tests/fixtures/naive_credential_holder.rs @@ -100,6 +100,7 @@ impl Debug for NaiveNamedActor { } } +#[allow(unused)] // .0 not necessarily referenced pub(crate) struct NaiveVerifiedIdentities<'a>(&'a NaiveNamedActor); impl<'a> Iterator for NaiveVerifiedIdentities<'a> { From f7a0b04951a99b90412ad527a73bd287c8aea159 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Mon, 21 Oct 2024 16:19:13 -0700 Subject: [PATCH 07/10] Isolate HTTP call in its own function --- src/claim_aggregation/w3c_vc/did_web.rs | 87 +++++++++++++++---------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/src/claim_aggregation/w3c_vc/did_web.rs b/src/claim_aggregation/w3c_vc/did_web.rs index f988763..e3f8153 100644 --- a/src/claim_aggregation/w3c_vc/did_web.rs +++ b/src/claim_aggregation/w3c_vc/did_web.rs @@ -18,8 +18,6 @@ // specific language governing permissions and limitations under // each license. -use reqwest::header; - use super::{did::Did, did_doc::DidDocument}; const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); @@ -32,19 +30,28 @@ thread_local! { pub(crate) static PROXY: RefCell> = const { RefCell::new(None) }; } +#[cfg(target_arch = "wasm32")] +#[derive(Debug, thiserror::Error)] +pub enum HttpError { + #[error("temporary")] + Mumble, +} +#[cfg(not(target_arch = "wasm32"))] +use reqwest::Error as HttpError; + #[derive(Debug, thiserror::Error)] pub enum DidWebError { #[error("error building HTTP client: {0}")] - Client(reqwest::Error), + Client(HttpError), #[error("error sending HTTP request ({0}): {1}")] - Request(String, reqwest::Error), + Request(String, HttpError), #[error("server error: {0}")] Server(String), #[error("error reading HTTP response: {0}")] - Response(reqwest::Error), + Response(HttpError), #[error("the document was not found: {0}")] NotFound(String), @@ -70,39 +77,53 @@ pub(crate) async fn resolve(did: &Did<'_>) -> Result { let url = to_url(method_specific_id)?; // TODO: https://w3c-ccg.github.io/did-method-web/#in-transit-security - let mut headers = reqwest::header::HeaderMap::new(); + let did_doc = get_did_doc(&url).await?; - headers.insert( - "User-Agent", - reqwest::header::HeaderValue::from_static(USER_AGENT), - ); + let json = String::from_utf8(did_doc).map_err(|_| DidWebError::InvalidData(url.clone()))?; - let client = reqwest::Client::builder() - .default_headers(headers) - .build() - .map_err(DidWebError::Client)?; - - let resp = client - .get(&url) - .header(header::ACCEPT, "application/did+json") - .send() - .await - .map_err(|e| DidWebError::Request(url.to_owned(), e))?; - - resp.error_for_status_ref().map_err(|err| { - if err.status() == Some(reqwest::StatusCode::NOT_FOUND) { - DidWebError::NotFound(url.clone()) - } else { - DidWebError::Server(err.to_string()) - } - })?; + DidDocument::from_json(&json).map_err(|_| DidWebError::InvalidData(url)) +} - let document = resp.bytes().await.map_err(DidWebError::Response)?; +async fn get_did_doc(url: &str) -> Result, DidWebError> { + #[cfg(not(target_arch = "wasm32"))] + { + use reqwest::header; + + let mut headers = reqwest::header::HeaderMap::new(); + + headers.insert( + "User-Agent", + reqwest::header::HeaderValue::from_static(USER_AGENT), + ); + + let client = reqwest::Client::builder() + .default_headers(headers) + .build() + .map_err(DidWebError::Client)?; + + let resp = client + .get(url) + .header(header::ACCEPT, "application/did+json") + .send() + .await + .map_err(|e: reqwest::Error| DidWebError::Request(url.to_owned(), e))?; + + resp.error_for_status_ref().map_err(|err| { + if err.status() == Some(reqwest::StatusCode::NOT_FOUND) { + DidWebError::NotFound(url.to_string()) + } else { + DidWebError::Server(err.to_string()) + } + })?; - let json = - String::from_utf8(document.to_vec()).map_err(|_| DidWebError::InvalidData(url.clone()))?; + let document = resp.bytes().await.map_err(DidWebError::Response)?; + Ok(document.to_vec()) + } - DidDocument::from_json(&json).map_err(|_| DidWebError::InvalidData(url)) + #[cfg(target_arch = "wasm32")] + { + unimplemented!("check back soon"); + } } pub(crate) fn to_url(did: &str) -> Result { From d8dc64be1fd0ab13d6e095e31a0d18bf21e81031 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Mon, 21 Oct 2024 19:54:28 -0700 Subject: [PATCH 08/10] WIP/FAIL: Try using web-sys --- Cargo.toml | 4 +++ src/claim_aggregation/w3c_vc/did_web.rs | 43 +++++++++++++++++++++---- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0f97c5d..8005ea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,10 @@ c2pa = { version = "0.33.0", features = ["openssl_ffi_mutex"] } [target.'cfg(target_arch = "wasm32")'.dependencies] c2pa = { version = "0.33.0", features = [] } +js-sys = "0.3.72" +wasm-bindgen = "0.2.95" +wasm-bindgen-futures = "0.4.45" +web-sys = "0.3.72" [dev-dependencies] serde = { version = "1.0.197", features = ["derive"] } diff --git a/src/claim_aggregation/w3c_vc/did_web.rs b/src/claim_aggregation/w3c_vc/did_web.rs index e3f8153..bd159be 100644 --- a/src/claim_aggregation/w3c_vc/did_web.rs +++ b/src/claim_aggregation/w3c_vc/did_web.rs @@ -30,14 +30,10 @@ thread_local! { pub(crate) static PROXY: RefCell> = const { RefCell::new(None) }; } -#[cfg(target_arch = "wasm32")] -#[derive(Debug, thiserror::Error)] -pub enum HttpError { - #[error("temporary")] - Mumble, -} #[cfg(not(target_arch = "wasm32"))] use reqwest::Error as HttpError; +#[cfg(target_arch = "wasm32")] +use String as HttpError; #[derive(Debug, thiserror::Error)] pub enum DidWebError { @@ -122,7 +118,40 @@ async fn get_did_doc(url: &str) -> Result, DidWebError> { #[cfg(target_arch = "wasm32")] { - unimplemented!("check back soon"); + use wasm_bindgen::prelude::*; + use wasm_bindgen_futures::JsFuture; + use web_sys::{Request, RequestInit, RequestMode, Response}; + + let opts = RequestInit::new(); + opts.set_method("GET"); + opts.set_mode(RequestMode::Cors); + + let request = Request::new_with_str_and_init(&url, &opts) + .map_err(|_| DidWebError::Client("Request::new_with_str_and_init".to_string()))?; + + request + .headers() + .set("accept", "application/did+json") + .map_err(|_| DidWebError::Client("Set headers".to_string()))?; + + let window = web_sys::window().unwrap(); + let resp_value = JsFuture::from(window.fetch_with_request(&request)) + .await + .map_err(|_| DidWebError::Client("window.fetch_with_request".to_string()))?; + + assert!(resp_value.is_instance_of::()); + let resp: Response = resp_value.dyn_into().unwrap(); + + JsFuture::from( + resp.blob() + .map_err(|_| DidWebError::Client("window.resp.bytes()".to_string()))?, + ) + .await + .map(|blob| { + let array = js_sys::Uint8Array::new(&blob); + array.to_vec() + }) + .map_err(|e| DidWebError::Server("resp.blob()".to_string())) } } From 31bcd9aaafe6f647613f476f99b03e493f3a90c6 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Mon, 21 Oct 2024 20:18:29 -0700 Subject: [PATCH 09/10] =?UTF-8?q?Rough=20cut=20=E2=80=A6=20it=20compiles!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (Tests fails, but this is progress.) --- Cargo.toml | 4 - src/builder/credential_holder.rs | 5 +- .../cose_vc_signature_handler.rs | 3 +- src/claim_aggregation/w3c_vc/did.rs | 2 + src/claim_aggregation/w3c_vc/did_web.rs | 84 ++++++++++--------- src/claim_aggregation/w3c_vc/jwk.rs | 2 + src/identity_assertion/signature_handler.rs | 3 +- src/tests/fixtures/mod.rs | 2 + src/tests/fixtures/naive_credential_holder.rs | 6 +- src/tests/mod.rs | 3 + 10 files changed, 64 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8005ea2..0f97c5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,10 +32,6 @@ c2pa = { version = "0.33.0", features = ["openssl_ffi_mutex"] } [target.'cfg(target_arch = "wasm32")'.dependencies] c2pa = { version = "0.33.0", features = [] } -js-sys = "0.3.72" -wasm-bindgen = "0.2.95" -wasm-bindgen-futures = "0.4.45" -web-sys = "0.3.72" [dev-dependencies] serde = { version = "1.0.197", features = ["derive"] } diff --git a/src/builder/credential_holder.rs b/src/builder/credential_holder.rs index c8b725d..bdbc76e 100644 --- a/src/builder/credential_holder.rs +++ b/src/builder/credential_holder.rs @@ -11,6 +11,8 @@ // specific language governing permissions and limitations under // each license. +use async_trait::async_trait; + use crate::SignerPayload; /// An implementation of `CredentialHolder` is able to generate a signature over @@ -21,7 +23,8 @@ use crate::SignerPayload; /// methods] from the CAWG Identity Assertion specification. /// /// [§8. Credentials, signatures, and validation methods]: https://creator-assertions.github.io/identity/1.0-draft/#_credentials_signatures_and_validation_methods -#[async_trait::async_trait] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] pub trait CredentialHolder { /// Returns the designated `sig_type` value for this kind of credential. fn sig_type(&self) -> &'static str; diff --git a/src/claim_aggregation/cose_vc_signature_handler.rs b/src/claim_aggregation/cose_vc_signature_handler.rs index 3443e40..17949a5 100644 --- a/src/claim_aggregation/cose_vc_signature_handler.rs +++ b/src/claim_aggregation/cose_vc_signature_handler.rs @@ -41,7 +41,8 @@ use crate::{ /// [§3.3.1 Securing JSON-LD Verifiable Credentials with COSE]: https://w3c.github.io/vc-jose-cose/#securing-vcs-with-cose pub struct CoseVcSignatureHandler {} -#[async_trait] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] impl SignatureHandler for CoseVcSignatureHandler { fn can_handle_sig_type(sig_type: &str) -> bool { sig_type == "cawg.identity_claims_aggregation" diff --git a/src/claim_aggregation/w3c_vc/did.rs b/src/claim_aggregation/w3c_vc/did.rs index c3ab7d6..5099c32 100644 --- a/src/claim_aggregation/w3c_vc/did.rs +++ b/src/claim_aggregation/w3c_vc/did.rs @@ -18,6 +18,8 @@ // specific language governing permissions and limitations under // each license. +#![allow(unused)] // TEMPORARY + use std::{fmt, ops::Deref, str::FromStr, sync::LazyLock}; use regex::Regex; diff --git a/src/claim_aggregation/w3c_vc/did_web.rs b/src/claim_aggregation/w3c_vc/did_web.rs index bd159be..4dbe6ac 100644 --- a/src/claim_aggregation/w3c_vc/did_web.rs +++ b/src/claim_aggregation/w3c_vc/did_web.rs @@ -30,10 +30,10 @@ thread_local! { pub(crate) static PROXY: RefCell> = const { RefCell::new(None) }; } -#[cfg(not(target_arch = "wasm32"))] +// #[cfg(not(target_arch = "wasm32"))] use reqwest::Error as HttpError; -#[cfg(target_arch = "wasm32")] -use String as HttpError; +// #[cfg(target_arch = "wasm32")] +// use String as HttpError; #[derive(Debug, thiserror::Error)] pub enum DidWebError { @@ -81,7 +81,7 @@ pub(crate) async fn resolve(did: &Did<'_>) -> Result { } async fn get_did_doc(url: &str) -> Result, DidWebError> { - #[cfg(not(target_arch = "wasm32"))] + // #[cfg(not(target_arch = "wasm32"))] { use reqwest::header; @@ -116,43 +116,45 @@ async fn get_did_doc(url: &str) -> Result, DidWebError> { Ok(document.to_vec()) } - #[cfg(target_arch = "wasm32")] - { - use wasm_bindgen::prelude::*; - use wasm_bindgen_futures::JsFuture; - use web_sys::{Request, RequestInit, RequestMode, Response}; - - let opts = RequestInit::new(); - opts.set_method("GET"); - opts.set_mode(RequestMode::Cors); - - let request = Request::new_with_str_and_init(&url, &opts) - .map_err(|_| DidWebError::Client("Request::new_with_str_and_init".to_string()))?; - - request - .headers() - .set("accept", "application/did+json") - .map_err(|_| DidWebError::Client("Set headers".to_string()))?; - - let window = web_sys::window().unwrap(); - let resp_value = JsFuture::from(window.fetch_with_request(&request)) - .await - .map_err(|_| DidWebError::Client("window.fetch_with_request".to_string()))?; - - assert!(resp_value.is_instance_of::()); - let resp: Response = resp_value.dyn_into().unwrap(); - - JsFuture::from( - resp.blob() - .map_err(|_| DidWebError::Client("window.resp.bytes()".to_string()))?, - ) - .await - .map(|blob| { - let array = js_sys::Uint8Array::new(&blob); - array.to_vec() - }) - .map_err(|e| DidWebError::Server("resp.blob()".to_string())) - } + // #[cfg(target_arch = "wasm32")] + // { + // use wasm_bindgen::prelude::*; + // use wasm_bindgen_futures::JsFuture; + // use web_sys::{Request, RequestInit, RequestMode, Response}; + + // let opts = RequestInit::new(); + // opts.set_method("GET"); + // opts.set_mode(RequestMode::Cors); + + // let request = Request::new_with_str_and_init(&url, &opts) + // .map_err(|_| + // DidWebError::Client("Request::new_with_str_and_init".to_string()))?; + + // request + // .headers() + // .set("accept", "application/did+json") + // .map_err(|_| DidWebError::Client("Set headers".to_string()))?; + + // let window = web_sys::window().unwrap(); + // let resp_value = JsFuture::from(window.fetch_with_request(&request)) + // .await + // .map_err(|_| + // DidWebError::Client("window.fetch_with_request".to_string()))?; + + // assert!(resp_value.is_instance_of::()); + // let resp: Response = resp_value.dyn_into().unwrap(); + + // JsFuture::from( + // resp.blob() + // .map_err(|_| + // DidWebError::Client("window.resp.bytes()".to_string()))?, ) + // .await + // .map(|blob| { + // let array = js_sys::Uint8Array::new(&blob); + // array.to_vec() + // }) + // .map_err(|e| DidWebError::Server("resp.blob()".to_string())) + // } } pub(crate) fn to_url(did: &str) -> Result { diff --git a/src/claim_aggregation/w3c_vc/jwk.rs b/src/claim_aggregation/w3c_vc/jwk.rs index 9950609..0f7582f 100644 --- a/src/claim_aggregation/w3c_vc/jwk.rs +++ b/src/claim_aggregation/w3c_vc/jwk.rs @@ -18,6 +18,8 @@ // specific language governing permissions and limitations under // each license. +#![allow(unused)] // TEMPORARY + use std::{ convert::TryFrom, fmt, num::ParseIntError, result::Result, str::FromStr, string::FromUtf8Error, }; diff --git a/src/identity_assertion/signature_handler.rs b/src/identity_assertion/signature_handler.rs index d62592f..5120cd0 100644 --- a/src/identity_assertion/signature_handler.rs +++ b/src/identity_assertion/signature_handler.rs @@ -18,7 +18,8 @@ use crate::identity_assertion::{NamedActor, SignerPayload, ValidationResult}; /// A `SignatureHandler` can read one kind of signature from an identity /// assertion, assess the validity of the signature, and return information /// about the corresponding credential subject. -#[async_trait] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] pub trait SignatureHandler { /// Returns true if this handler can process a signature with /// the given `sig_type` code. diff --git a/src/tests/fixtures/mod.rs b/src/tests/fixtures/mod.rs index cd60f5e..a528d8f 100644 --- a/src/tests/fixtures/mod.rs +++ b/src/tests/fixtures/mod.rs @@ -11,6 +11,8 @@ // specific language governing permissions and limitations under // each license. +#![allow(unused)] // TEMPORARY + use std::{env, path::PathBuf}; #[cfg(not(target_arch = "wasm32"))] diff --git a/src/tests/fixtures/naive_credential_holder.rs b/src/tests/fixtures/naive_credential_holder.rs index 6ccb4d7..c4fa935 100644 --- a/src/tests/fixtures/naive_credential_holder.rs +++ b/src/tests/fixtures/naive_credential_holder.rs @@ -31,7 +31,8 @@ use crate::{ pub(crate) struct NaiveCredentialHolder {} -#[async_trait] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] impl CredentialHolder for NaiveCredentialHolder { fn sig_type(&self) -> &'static str { "INVALID.identity.naive_credential" @@ -55,7 +56,8 @@ impl CredentialHolder for NaiveCredentialHolder { pub(crate) struct NaiveSignatureHandler {} -#[async_trait] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] impl SignatureHandler for NaiveSignatureHandler { fn can_handle_sig_type(sig_type: &str) -> bool { sig_type == "INVALID.identity.naive_credential" diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 85fca6f..1180c32 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -24,3 +24,6 @@ mod claim_aggregation; pub(crate) mod fixtures; mod identity_assertion; mod internal; + +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); From 2c7216e9685277c68e48417ce7c12189f606332c Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Wed, 23 Oct 2024 12:31:14 -0700 Subject: [PATCH 10/10] Debug log to investigate failure --- Cargo.toml | 1 + src/tests/claim_aggregation/interop.rs | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0f97c5d..b8d5129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ c2pa = { version = "0.33.0", features = ["openssl_ffi_mutex"] } [target.'cfg(target_arch = "wasm32")'.dependencies] c2pa = { version = "0.33.0", features = [] } +wasm-bindgen = "0.2.95" [dev-dependencies] serde = { version = "1.0.197", features = ["derive"] } diff --git a/src/tests/claim_aggregation/interop.rs b/src/tests/claim_aggregation/interop.rs index ebebabc..4c056c2 100644 --- a/src/tests/claim_aggregation/interop.rs +++ b/src/tests/claim_aggregation/interop.rs @@ -13,8 +13,19 @@ use c2pa::ManifestStore; #[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; +#[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +extern "C" { + // Use `js_namespace` here to bind `console.log(..)` instead of just + // `log(..)` + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} + use crate::IdentityAssertion; #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] @@ -23,7 +34,15 @@ async fn adobe_connected_identities() { let test_image = include_bytes!("../fixtures/claim_aggregation/adobe_connected_identities.jpg"); let manifest_store = ManifestStore::from_bytes("jpg", test_image, true).unwrap(); - assert!(manifest_store.validation_status().is_none()); + let validation_status = manifest_store.validation_status(); + + #[cfg(target_arch = "wasm32")] + { + let log_str = format!("MS validation status = {validation_status:#?}"); + log(&log_str); + } + + assert!(validation_status.is_none()); let manifest = manifest_store.get_active().unwrap(); let identity: IdentityAssertion = manifest.find_assertion("cawg.identity").unwrap();