Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
aa52999
feat: Allow `Builder::sign_async` future to be spawnable on Tokio run…
ok-nick Feb 10, 2026
e9bd453
fix: change `MaybeSync` to `Sync` for async HTTP resolver methods
ok-nick Feb 10, 2026
1a2804d
fix: return `BoxedAsyncResolver` for `Context::resolver_async`
ok-nick Feb 11, 2026
617bf3a
feat: pass HTTP resolvers to timestamp networking
ok-nick Feb 11, 2026
7a89ba5
refactor: return arc-wrapped resolver from context
ok-nick Feb 12, 2026
fe274bd
docs: add back docs for HTTP resolver types
ok-nick Feb 12, 2026
41f7d8c
Merge branch 'ok-nick/sign-async-send' into ok-nick/send-timestamp-re…
ok-nick Feb 12, 2026
1622beb
fix: use arc for sync resolver and use impls instead of trait objects
ok-nick Feb 12, 2026
59dd42c
refactor: require MaybeSend and MaybeSync on the core HTTP resolver t…
ok-nick Feb 12, 2026
0b2fcdf
refactor: simplify by requiring MaybeSend and MaybeSync bounds on HTT…
ok-nick Feb 12, 2026
4c6b2e4
Merge branch 'main' of github.com:contentauth/c2pa-rs into ok-nick/se…
ok-nick Feb 12, 2026
deb378d
fix: new code to use http resolvers in send_timestamp_request
ok-nick Feb 12, 2026
40eb0ca
docs: fix doc links
ok-nick Feb 12, 2026
82ee8d9
fix: private http module for now in this PR
ok-nick Feb 12, 2026
be355f1
Merge branch 'ok-nick/sign-async-send' into ok-nick/send-timestamp-re…
ok-nick Feb 12, 2026
0811740
docs: link TODO to issue
ok-nick Feb 12, 2026
432670a
docs: clarify note about AsyncRawSignerWrapper timestamping impl
ok-nick Feb 13, 2026
e42aa5e
Merge branch 'main' of github.com:contentauth/c2pa-rs into ok-nick/se…
ok-nick Feb 17, 2026
9e38bda
Merge branch 'main' into ok-nick/send-timestamp-request-respect
ok-nick Feb 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions sdk/src/assertions/timestamp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ impl TimeStamp {
tsa_url: &str,
manifest_id: &str,
signature: &[u8],
http_resolver: &(impl AsyncHttpResolver + ?Sized),
http_resolver: &impl AsyncHttpResolver,
))]
pub(crate) fn refresh_timestamp(
&mut self,
tsa_url: &str,
manifest_id: &str,
signature: &[u8],
http_resolver: &(impl SyncHttpResolver + ?Sized),
http_resolver: &impl SyncHttpResolver,
) -> Result<()> {
let timestamp_token = if _sync {
TimeStamp::send_timestamp_token_request(tsa_url, signature, http_resolver)?
Expand All @@ -97,12 +97,12 @@ impl TimeStamp {
#[async_generic(async_signature(
tsa_url: &str,
message: &[u8],
http_resolver: &(impl AsyncHttpResolver + ?Sized),
http_resolver: &impl AsyncHttpResolver,
))]
pub(crate) fn send_timestamp_token_request(
tsa_url: &str,
message: &[u8],
http_resolver: &(impl SyncHttpResolver + ?Sized),
http_resolver: &impl SyncHttpResolver,
) -> Result<Vec<u8>> {
let body = crate::crypto::time_stamp::default_rfc3161_message(message)?;
let headers = None;
Expand Down
25 changes: 22 additions & 3 deletions sdk/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1872,15 +1872,15 @@ impl Builder {
tsa_url,
&manifest_label,
&signature,
self.context().resolver(),
&self.context().resolver(),
)?;
} else {
timestamp_assertion
.refresh_timestamp_async(
tsa_url,
&manifest_label,
&signature,
self.context().resolver_async(),
&self.context().resolver_async(),
)
.await?;
}
Expand Down Expand Up @@ -2475,7 +2475,10 @@ impl std::fmt::Display for Builder {
mod tests {
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
use std::{io::Cursor, vec};
use std::{
io::{self, Cursor},
vec,
};

use c2pa_macros::c2pa_test_async;
use serde_json::json;
Expand All @@ -2489,6 +2492,7 @@ mod tests {
assertions::{c2pa_action, BoxHash, DigitalSourceType},
crypto::raw_signature::SigningAlg,
hash_stream_by_alg,
maybe_send_sync::MaybeSend,
settings::Settings,
utils::{
test::{test_context, write_jpeg_placeholder_stream},
Expand Down Expand Up @@ -4628,4 +4632,19 @@ mod tests {

assert!(reader.active_manifest().is_some());
}

// Ensures that the future returned by `Builder::sign_async` implements `Send`, thus making it
// possible to spawn on a Tokio runtime.
#[test]
fn test_sign_async_future_is_send() {
fn assert_send<T: MaybeSend>(_: T) {}

let signer = async_test_signer(SigningAlg::Ps256);
let mut builder = Builder::new();
let mut src = io::empty();
let mut dst = io::empty();

let future = builder.sign_async(&signer, "image/jpeg", &mut src, &mut dst);
assert_send(future);
}
}
69 changes: 43 additions & 26 deletions sdk/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
use std::sync::OnceLock;
// Copyright 2026 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

use std::sync::{Arc, OnceLock};

use crate::{
http::{
restricted::RestrictedResolver, AsyncGenericResolver, AsyncHttpResolver,
BoxedAsyncResolver, BoxedSyncResolver, SyncGenericResolver, SyncHttpResolver,
SyncGenericResolver, SyncHttpResolver,
},
maybe_send_sync::{MaybeSend, MaybeSync},
settings::Settings,
Expand All @@ -14,17 +27,17 @@ use crate::{
/// Internal state for sync HTTP resolver selection.
enum SyncResolverState {
/// User-provided custom resolver.
Custom(BoxedSyncResolver),
Custom(Arc<dyn SyncHttpResolver>),
/// Default resolver with lazy initialization.
Default(OnceLock<RestrictedResolver<SyncGenericResolver>>),
Default(OnceLock<Arc<dyn SyncHttpResolver>>),
}

/// Internal state for async HTTP resolver selection.
enum AsyncResolverState {
/// User-provided custom resolver.
Custom(BoxedAsyncResolver),
Custom(Arc<dyn AsyncHttpResolver>),
/// Default resolver with lazy initialization.
Default(OnceLock<RestrictedResolver<AsyncGenericResolver>>),
Default(OnceLock<Arc<dyn AsyncHttpResolver>>),
}

/// Internal state for signer selection.
Expand Down Expand Up @@ -312,15 +325,15 @@ impl Context {
mut self,
resolver: T,
) -> Self {
self.sync_resolver = SyncResolverState::Custom(Box::new(resolver));
self.sync_resolver = SyncResolverState::Custom(Arc::new(resolver));
self
}

pub fn set_resolver<T: SyncHttpResolver + MaybeSend + MaybeSync + 'static>(
&mut self,
resolver: T,
) -> Result<()> {
self.sync_resolver = SyncResolverState::Custom(Box::new(resolver));
self.sync_resolver = SyncResolverState::Custom(Arc::new(resolver));
Ok(())
}

Expand All @@ -335,47 +348,51 @@ impl Context {
mut self,
resolver: T,
) -> Self {
self.async_resolver = AsyncResolverState::Custom(Box::new(resolver));
self.async_resolver = AsyncResolverState::Custom(Arc::new(resolver));
self
}

pub fn set_resolver_async<T: AsyncHttpResolver + MaybeSend + MaybeSync + 'static>(
&mut self,
resolver: T,
) -> Result<()> {
self.async_resolver = AsyncResolverState::Custom(Box::new(resolver));
self.async_resolver = AsyncResolverState::Custom(Arc::new(resolver));
Ok(())
}

/// Returns a reference to the sync resolver.
///
/// The default resolver is a `SyncGenericResolver` wrapped with `RestrictedResolver`
/// to apply host filtering from the settings.
pub fn resolver(&self) -> &dyn SyncHttpResolver {
pub fn resolver(&self) -> Arc<dyn SyncHttpResolver> {
match &self.sync_resolver {
SyncResolverState::Custom(resolver) => resolver.as_ref(),
SyncResolverState::Default(once_lock) => once_lock.get_or_init(|| {
let inner = SyncGenericResolver::new();
let mut resolver = RestrictedResolver::new(inner);
resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone());
resolver
}),
SyncResolverState::Custom(resolver) => resolver.clone(),
SyncResolverState::Default(once_lock) => once_lock
.get_or_init(|| {
let inner = SyncGenericResolver::new();
let mut resolver = RestrictedResolver::new(inner);
resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone());
Arc::new(resolver)
})
.clone(),
}
}

/// Returns a reference to the async resolver.
///
/// The default resolver is an `AsyncGenericResolver` wrapped with `RestrictedResolver`
/// to apply host filtering from the settings.
pub fn resolver_async(&self) -> &dyn AsyncHttpResolver {
pub fn resolver_async(&self) -> Arc<dyn AsyncHttpResolver> {
match &self.async_resolver {
AsyncResolverState::Custom(resolver) => resolver.as_ref(),
AsyncResolverState::Default(once_lock) => once_lock.get_or_init(|| {
let inner = AsyncGenericResolver::new();
let mut resolver = RestrictedResolver::new(inner);
resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone());
resolver
}),
AsyncResolverState::Custom(resolver) => resolver.clone(),
AsyncResolverState::Default(once_lock) => once_lock
.get_or_init(|| {
let inner = AsyncGenericResolver::new();
let mut resolver = RestrictedResolver::new(inner);
resolver.set_allowed_hosts(self.settings.core.allowed_network_hosts.clone());
Arc::new(resolver)
})
.clone(),
}
}

Expand Down
Loading
Loading