use std::{
os::raw::{c_char, c_int, c_uchar, c_void},
sync::Arc,
};
#[cfg(feature = "file_io")]
use c2pa::Ingredient;
use c2pa::{
assertions::DataHash, identity::validator::CawgValidator, Builder as C2paBuilder,
CallbackSigner, Context, ProgressPhase, Reader as C2paReader, Settings as C2paSettings,
SigningAlg,
};
use tokio::runtime::Builder;
#[cfg(feature = "file_io")]
use crate::json_api::{read_file, sign_file};
#[cfg(test)]
use crate::safe_slice_from_raw_parts;
#[allow(unused_imports)] use crate::{
box_tracked, bytes_or_return_int, bytes_or_return_null, c2pa_stream::C2paStream, cimpl_free,
cstr_or_return_int, cstr_or_return_null, deref_mut_or_return, deref_mut_or_return_int,
deref_mut_or_return_null, deref_or_return_int, deref_or_return_null, error::Error,
ok_or_return_int, ok_or_return_null, option_to_c_string, ptr_or_return_int,
signer_info::SignerInfo, to_c_bytes, to_c_string, CimplError,
};
#[allow(dead_code)]
unsafe fn is_safe_buffer_size(size: usize, ptr: *const c_uchar) -> bool {
if size == 0 || size > isize::MAX as usize {
return false;
}
if !ptr.is_null() {
let end_ptr = ptr.add(size);
if end_ptr < ptr {
return false; }
}
true
}
mod cbindgen_fix {
#[repr(C)]
#[allow(dead_code)]
pub struct C2paBuilder;
#[repr(C)]
#[allow(dead_code)]
pub struct C2paReader;
#[repr(C)]
#[allow(dead_code)]
pub struct C2paContextBuilder;
#[repr(C)]
#[allow(dead_code)]
pub struct C2paContext;
#[repr(C)]
#[allow(dead_code)]
pub struct C2paSettings;
#[repr(C)]
#[allow(dead_code)]
pub struct C2paHttpResolver;
}
type C2paContextBuilder = Context;
type C2paContext = Arc<Context>;
#[repr(C)]
pub enum C2paProgressPhase {
Reading = 0,
VerifyingManifest = 1,
VerifyingSignature = 2,
VerifyingIngredient = 3,
VerifyingAssetHash = 4,
AddingIngredient = 5,
Thumbnail = 6,
Hashing = 7,
Signing = 8,
Embedding = 9,
FetchingRemoteManifest = 10,
Writing = 11,
FetchingOCSP = 12,
FetchingTimestamp = 13,
}
impl From<ProgressPhase> for C2paProgressPhase {
fn from(phase: ProgressPhase) -> Self {
match phase {
ProgressPhase::Reading => Self::Reading,
ProgressPhase::VerifyingManifest => Self::VerifyingManifest,
ProgressPhase::VerifyingSignature => Self::VerifyingSignature,
ProgressPhase::VerifyingIngredient => Self::VerifyingIngredient,
ProgressPhase::VerifyingAssetHash => Self::VerifyingAssetHash,
ProgressPhase::AddingIngredient => Self::AddingIngredient,
ProgressPhase::Thumbnail => Self::Thumbnail,
ProgressPhase::Hashing => Self::Hashing,
ProgressPhase::Signing => Self::Signing,
ProgressPhase::Embedding => Self::Embedding,
ProgressPhase::FetchingRemoteManifest => Self::FetchingRemoteManifest,
ProgressPhase::Writing => Self::Writing,
ProgressPhase::FetchingOCSP => Self::FetchingOCSP,
ProgressPhase::FetchingTimestamp => Self::FetchingTimestamp,
_ => Self::Reading, }
}
}
#[repr(C)]
pub enum C2paSigningAlg {
Es256,
Es384,
Es512,
Ps256,
Ps384,
Ps512,
Ed25519,
}
impl From<C2paSigningAlg> for SigningAlg {
fn from(alg: C2paSigningAlg) -> Self {
match alg {
C2paSigningAlg::Es256 => SigningAlg::Es256,
C2paSigningAlg::Es384 => SigningAlg::Es384,
C2paSigningAlg::Es512 => SigningAlg::Es512,
C2paSigningAlg::Ps256 => SigningAlg::Ps256,
C2paSigningAlg::Ps384 => SigningAlg::Ps384,
C2paSigningAlg::Ps512 => SigningAlg::Ps512,
C2paSigningAlg::Ed25519 => SigningAlg::Ed25519,
}
}
}
#[repr(C)]
pub enum C2paDigitalSourceType {
Empty,
TrainedAlgorithmicData,
DigitalCapture,
ComputationalCapture,
NegativeFilm,
PositiveFilm,
Print,
HumanEdits,
CompositeWithTrainedAlgorithmicMedia,
AlgorithmicallyEnhanced,
DigitalCreation,
DataDrivenMedia,
TrainedAlgorithmicMedia,
AlgorithmicMedia,
ScreenCapture,
VirtualRecording,
Composite,
CompositeCapture,
CompositeSynthetic,
}
impl From<C2paDigitalSourceType> for c2pa::DigitalSourceType {
fn from(source_type: C2paDigitalSourceType) -> Self {
match source_type {
C2paDigitalSourceType::Empty => c2pa::DigitalSourceType::Empty,
C2paDigitalSourceType::TrainedAlgorithmicData => {
c2pa::DigitalSourceType::TrainedAlgorithmicData
}
C2paDigitalSourceType::DigitalCapture => c2pa::DigitalSourceType::DigitalCapture,
C2paDigitalSourceType::ComputationalCapture => {
c2pa::DigitalSourceType::ComputationalCapture
}
C2paDigitalSourceType::NegativeFilm => c2pa::DigitalSourceType::NegativeFilm,
C2paDigitalSourceType::PositiveFilm => c2pa::DigitalSourceType::PositiveFilm,
C2paDigitalSourceType::Print => c2pa::DigitalSourceType::Print,
C2paDigitalSourceType::HumanEdits => c2pa::DigitalSourceType::HumanEdits,
C2paDigitalSourceType::CompositeWithTrainedAlgorithmicMedia => {
c2pa::DigitalSourceType::CompositeWithTrainedAlgorithmicMedia
}
C2paDigitalSourceType::AlgorithmicallyEnhanced => {
c2pa::DigitalSourceType::AlgorithmicallyEnhanced
}
C2paDigitalSourceType::DigitalCreation => c2pa::DigitalSourceType::DigitalCreation,
C2paDigitalSourceType::DataDrivenMedia => c2pa::DigitalSourceType::DataDrivenMedia,
C2paDigitalSourceType::TrainedAlgorithmicMedia => {
c2pa::DigitalSourceType::TrainedAlgorithmicMedia
}
C2paDigitalSourceType::AlgorithmicMedia => c2pa::DigitalSourceType::AlgorithmicMedia,
C2paDigitalSourceType::ScreenCapture => c2pa::DigitalSourceType::ScreenCapture,
C2paDigitalSourceType::VirtualRecording => c2pa::DigitalSourceType::VirtualRecording,
C2paDigitalSourceType::Composite => c2pa::DigitalSourceType::Composite,
C2paDigitalSourceType::CompositeCapture => c2pa::DigitalSourceType::CompositeCapture,
C2paDigitalSourceType::CompositeSynthetic => {
c2pa::DigitalSourceType::CompositeSynthetic
}
}
}
}
#[repr(C)]
pub enum C2paBuilderIntent {
Create,
Edit,
Update,
}
#[repr(C)]
pub enum C2paHashType {
DataHash = 0,
BmffHash = 1,
BoxHash = 2,
}
#[repr(C)]
pub struct C2paSigner {
pub signer: Box<dyn crate::maybe_send_sync::C2paSignerObject>,
}
pub type SignerCallback = unsafe extern "C" fn(
context: *const (),
data: *const c_uchar,
len: usize,
signed_bytes: *mut c_uchar,
signed_len: usize,
) -> isize;
#[repr(C)]
pub struct C2paHttpRequest {
pub url: *const c_char,
pub method: *const c_char,
pub headers: *const c_char,
pub body: *const c_uchar,
pub body_len: usize,
}
#[repr(C)]
pub struct C2paHttpResponse {
pub status: i32,
pub body: *mut c_uchar,
pub body_len: usize,
}
struct OwnedC2paHttpRequest {
url: std::ffi::CString,
method: std::ffi::CString,
headers: std::ffi::CString,
body: Vec<u8>,
}
impl TryFrom<c2pa::http::http::Request<Vec<u8>>> for OwnedC2paHttpRequest {
type Error = c2pa::http::HttpResolverError;
fn try_from(request: c2pa::http::http::Request<Vec<u8>>) -> Result<Self, Self::Error> {
use std::ffi::CString;
use c2pa::http::HttpResolverError;
let url = CString::new(request.uri().to_string())
.map_err(|e| HttpResolverError::Other(Box::new(e)))?;
let method = CString::new(request.method().as_str())
.map_err(|e| HttpResolverError::Other(Box::new(e)))?;
let headers_str: String = request
.headers()
.iter()
.filter_map(|(k, v)| v.to_str().ok().map(|v| format!("{k}: {v}\n")))
.collect();
let headers =
CString::new(headers_str).map_err(|e| HttpResolverError::Other(Box::new(e)))?;
let body = request.into_body();
Ok(Self {
url,
method,
headers,
body,
})
}
}
impl OwnedC2paHttpRequest {
fn as_ffi(&self) -> C2paHttpRequest {
let (body, body_len) = if self.body.is_empty() {
(std::ptr::null(), 0)
} else {
(self.body.as_ptr(), self.body.len())
};
C2paHttpRequest {
url: self.url.as_ptr(),
method: self.method.as_ptr(),
headers: self.headers.as_ptr(),
body,
body_len,
}
}
}
impl TryFrom<C2paHttpResponse> for c2pa::http::http::Response<Box<dyn std::io::Read>> {
type Error = c2pa::http::HttpResolverError;
fn try_from(resp: C2paHttpResponse) -> Result<Self, Self::Error> {
let body_vec = if resp.body.is_null() || resp.body_len == 0 {
Vec::new()
} else {
let v = unsafe { std::slice::from_raw_parts(resp.body, resp.body_len) }.to_vec();
unsafe { libc::free(resp.body as *mut c_void) };
v
};
c2pa::http::http::Response::builder()
.status(resp.status as u16)
.body(Box::new(std::io::Cursor::new(body_vec)) as Box<dyn std::io::Read>)
.map_err(c2pa::http::HttpResolverError::Http)
}
}
pub type C2paHttpResolverCallback = unsafe extern "C" fn(
context: *mut c_void,
request: *const C2paHttpRequest,
response: *mut C2paHttpResponse,
) -> c_int;
pub struct C2paHttpResolver {
context: *const c_void,
callback: C2paHttpResolverCallback,
}
unsafe impl Send for C2paHttpResolver {}
unsafe impl Sync for C2paHttpResolver {}
impl c2pa::http::SyncHttpResolver for C2paHttpResolver {
fn http_resolve(
&self,
request: c2pa::http::http::Request<Vec<u8>>,
) -> Result<c2pa::http::http::Response<Box<dyn std::io::Read>>, c2pa::http::HttpResolverError>
{
use c2pa::http::HttpResolverError;
let owned = OwnedC2paHttpRequest::try_from(request)?;
let c_request = owned.as_ffi();
let mut c_response = C2paHttpResponse {
status: 0,
body: std::ptr::null_mut(),
body_len: 0,
};
let rc =
unsafe { (self.callback)(self.context as *mut c_void, &c_request, &mut c_response) };
if rc != 0 {
if !c_response.body.is_null() {
unsafe { libc::free(c_response.body as *mut c_void) };
}
let msg = CimplError::last_message()
.unwrap_or_else(|| "HTTP callback returned error".to_string());
return Err(HttpResolverError::Other(msg.into()));
}
c_response.try_into()
}
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_version() -> *mut c_char {
let version = format!(
"{}/{} {}/{}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
c2pa::NAME,
c2pa::VERSION
);
to_c_string(version)
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_error() -> *mut c_char {
to_c_string(Error::last_message())
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_error_set_last(error_str: *const c_char) -> c_int {
let error_str = cstr_or_return_int!(error_str);
CimplError::from(Error::from(error_str)).set_last();
0
}
#[no_mangle]
#[deprecated(
note = "Use c2pa_settings_new() and c2pa_context_builder_set_settings() to configure a context explicitly."
)]
pub unsafe extern "C" fn c2pa_load_settings(
settings: *const c_char,
format: *const c_char,
) -> c_int {
let settings = cstr_or_return_int!(settings);
let format = cstr_or_return_int!(format);
#[allow(deprecated)]
let result = C2paSettings::from_string(&settings, &format);
ok_or_return_int!(result);
0 }
#[no_mangle]
pub unsafe extern "C" fn c2pa_settings_new() -> *mut C2paSettings {
box_tracked!(C2paSettings::new())
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_settings_update_from_string(
settings: *mut C2paSettings,
settings_str: *const c_char,
format: *const c_char,
) -> c_int {
let settings = deref_mut_or_return_int!(settings, C2paSettings);
let settings_str = cstr_or_return_int!(settings_str);
let format = cstr_or_return_int!(format);
let result = settings.update_from_str(&settings_str, &format);
ok_or_return_int!(result);
0
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_settings_set_value(
settings: *mut C2paSettings,
path: *const c_char,
value: *const c_char,
) -> c_int {
let settings = deref_mut_or_return_int!(settings, C2paSettings);
let path = cstr_or_return_int!(path);
let value_str = cstr_or_return_int!(value);
let parsed_value: serde_json::Value = ok_or_return_int!(serde_json::from_str(&value_str)
.map_err(|e| c2pa::Error::BadParam(format!("Invalid JSON value: {e}"))));
let result = match parsed_value {
serde_json::Value::Bool(b) => settings.set_value(&path, b),
serde_json::Value::Number(n) if n.is_i64() => {
settings.set_value(&path, n.as_i64().unwrap())
}
serde_json::Value::Number(n) if n.is_f64() => {
settings.set_value(&path, n.as_f64().unwrap())
}
serde_json::Value::Number(_) => {
Err(c2pa::Error::BadParam("Invalid number format".to_string()))
}
serde_json::Value::String(s) => settings.set_value(&path, s),
serde_json::Value::Array(arr) => {
let strings: Result<Vec<String>, _> = arr
.into_iter()
.map(|v| {
v.as_str().map(String::from).ok_or_else(|| {
c2pa::Error::BadParam("Array values must be strings".to_string())
})
})
.collect();
strings.and_then(|vec| settings.set_value(&path, vec))
}
serde_json::Value::Null => Err(c2pa::Error::BadParam("Cannot set null values".to_string())),
serde_json::Value::Object(_) => Err(c2pa::Error::BadParam(
"Cannot set object values directly, use update_from_string instead".to_string(),
)),
};
ok_or_return_int!(result);
0
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_context_builder_new() -> *mut C2paContextBuilder {
box_tracked!(Context::new())
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_context_builder_set_settings(
builder: *mut C2paContextBuilder,
settings: *mut C2paSettings,
) -> c_int {
let builder = deref_mut_or_return_int!(builder, C2paContextBuilder);
let settings = deref_or_return_int!(settings, C2paSettings);
let result = builder.set_settings(settings);
ok_or_return_int!(result);
0
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_context_builder_set_signer(
builder: *mut C2paContextBuilder,
signer_ptr: *mut C2paSigner,
) -> c_int {
let builder = deref_mut_or_return_int!(builder, C2paContextBuilder);
untrack_or_return_int!(signer_ptr, C2paSigner);
let c2pa_signer = Box::from_raw(signer_ptr);
let result = builder.set_signer(c2pa_signer.signer);
ok_or_return_int!(result);
0
}
pub type ProgressCCallback = unsafe extern "C" fn(
context: *const c_void,
phase: C2paProgressPhase,
step: u32,
total: u32,
) -> c_int;
#[no_mangle]
pub unsafe extern "C" fn c2pa_context_builder_set_progress_callback(
builder: *mut C2paContextBuilder,
user_data: *const c_void,
callback: ProgressCCallback,
) -> c_int {
let builder = deref_mut_or_return_int!(builder, C2paContextBuilder);
let ud = user_data as usize;
let c_callback = move |phase: ProgressPhase, step: u32, total: u32| unsafe {
(callback)(ud as *const c_void, phase.into(), step, total) != 0
};
builder.set_progress_callback(c_callback);
0
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_http_resolver_create(
context: *const c_void,
callback: C2paHttpResolverCallback,
) -> *mut C2paHttpResolver {
box_tracked!(C2paHttpResolver { context, callback })
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_context_builder_set_http_resolver(
builder: *mut C2paContextBuilder,
resolver_ptr: *mut C2paHttpResolver,
) -> c_int {
let builder = deref_mut_or_return_int!(builder, C2paContextBuilder);
untrack_or_return_int!(resolver_ptr, C2paHttpResolver);
let c2pa_resolver = Box::from_raw(resolver_ptr);
let result = builder.set_resolver(*c2pa_resolver);
ok_or_return_int!(result);
0
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_context_builder_build(
builder: *mut C2paContextBuilder,
) -> *mut C2paContext {
untrack_or_return_null!(builder, C2paContextBuilder);
let context = Box::from_raw(builder);
box_tracked!((*context).into_shared())
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_context_new() -> *mut C2paContext {
box_tracked!(Context::new().into_shared())
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_context_cancel(ctx: *mut C2paContext) -> c_int {
let ctx = deref_or_return_int!(ctx, C2paContext);
ctx.cancel();
0
}
#[cfg(feature = "file_io")]
#[no_mangle]
#[deprecated(
note = "Use c2pa_reader_from_context() with an explicit context for new implementations."
)]
#[allow(deprecated)]
pub unsafe extern "C" fn c2pa_read_file(
path: *const c_char,
data_dir: *const c_char,
) -> *mut c_char {
let path = cstr_or_return_null!(path);
let data_dir = cstr_option!(data_dir);
let result = read_file(&path, data_dir);
let json = ok_or_return_null!(result);
to_c_string(json)
}
#[cfg(feature = "file_io")]
#[no_mangle]
#[deprecated(
note = "Use c2pa_builder_add_ingredient_from_stream() with an explicit context for new implementations."
)]
#[allow(deprecated)]
pub unsafe extern "C" fn c2pa_read_ingredient_file(
path: *const c_char,
data_dir: *const c_char,
) -> *mut c_char {
let path = cstr_or_return_null!(path);
let data_dir = cstr_or_return_null!(data_dir);
let result = Ingredient::from_file_with_folder(path, data_dir).map_err(Error::from_c2pa_error);
let ingredient = ok_or_return_null!(result);
let json = serde_json::to_string(&ingredient).unwrap_or_default();
to_c_string(json)
}
#[repr(C)]
pub struct C2paSignerInfo {
pub alg: *const c_char,
pub sign_cert: *const c_char,
pub private_key: *const c_char,
pub ta_url: *const c_char,
}
#[cfg(feature = "file_io")]
#[no_mangle]
#[deprecated(
note = "Use c2pa_builder_from_context() with c2pa_builder_sign_to_stream() for new implementations."
)]
#[allow(deprecated)]
pub unsafe extern "C" fn c2pa_sign_file(
source_path: *const c_char,
dest_path: *const c_char,
manifest: *const c_char,
signer_info: &C2paSignerInfo,
data_dir: *const c_char,
) -> *mut c_char {
let source_path = cstr_or_return_null!(source_path);
let dest_path = cstr_or_return_null!(dest_path);
let manifest = cstr_or_return_null!(manifest);
let data_dir = cstr_option!(data_dir);
let signer_info = SignerInfo {
alg: cstr_or_return_null!(signer_info.alg),
sign_cert: cstr_or_return_null!(signer_info.sign_cert).into_bytes(),
private_key: cstr_or_return_null!(signer_info.private_key).into_bytes(),
ta_url: cstr_option!(signer_info.ta_url),
};
let result = sign_file(&source_path, &dest_path, &manifest, &signer_info, data_dir);
ok_or_return_null!(result); to_c_string("".to_string())
}
#[no_mangle]
#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
pub unsafe extern "C" fn c2pa_release_string(s: *mut c_char) {
cimpl_free!(s);
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_free(ptr: *const c_void) -> c_int {
cimpl_free!(ptr as *mut c_void)
}
#[no_mangle]
#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
pub unsafe extern "C" fn c2pa_string_free(s: *mut c_char) {
cimpl_free!(s);
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_free_string_array(ptr: *const *const c_char, count: usize) {
if ptr.is_null() {
return;
}
let mut_ptr = ptr as *mut *mut c_char;
#[allow(deprecated)]
for i in 0..count {
c2pa_string_free(*mut_ptr.add(i));
}
Vec::from_raw_parts(mut_ptr, count, count);
}
fn post_validate(result: Result<C2paReader, c2pa::Error>) -> Result<C2paReader, c2pa::Error> {
match result {
Ok(mut reader) => {
#[cfg(target_arch = "wasm32")]
let runtime = Builder::new_current_thread().enable_all().build();
#[cfg(not(target_arch = "wasm32"))]
let runtime = Builder::new_multi_thread().enable_all().build();
let runtime = match runtime {
Ok(runtime) => runtime,
Err(err) => return Err(c2pa::Error::OtherError(Box::new(err))),
};
match runtime.block_on(reader.post_validate_async(&CawgValidator {})) {
Ok(_) => Ok(reader),
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_reader_new() -> *mut C2paReader {
box_tracked!(C2paReader::from_context(Context::default()))
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_reader_from_context(context: *mut C2paContext) -> *mut C2paReader {
let context = deref_or_return_null!(context, C2paContext);
box_tracked!(C2paReader::from_shared_context(context))
}
#[no_mangle]
#[deprecated(
note = "Use c2pa_reader_from_context() with an explicit context instead of relying on thread-local settings."
)]
pub unsafe extern "C" fn c2pa_reader_from_stream(
format: *const c_char,
stream: *mut C2paStream,
) -> *mut C2paReader {
let format = cstr_or_return_null!(format);
let stream = deref_mut_or_return_null!(stream, C2paStream);
#[allow(deprecated)]
let result = C2paReader::from_stream(&format, stream);
let result = ok_or_return_null!(post_validate(result));
box_tracked!(result)
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_reader_with_stream(
reader: *mut C2paReader,
format: *const c_char,
stream: *mut C2paStream,
) -> *mut C2paReader {
let format = cstr_or_return_null!(format);
let stream = deref_mut_or_return_null!(stream, C2paStream);
untrack_or_return_null!(reader, C2paReader);
let reader = Box::from_raw(reader);
let result = (*reader).with_stream(&format, stream);
let result = ok_or_return_null!(post_validate(result));
box_tracked!(result)
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_reader_with_manifest_data_and_stream(
reader: *mut C2paReader,
format: *const c_char,
stream: *mut C2paStream,
manifest_data: *const c_uchar,
manifest_size: usize,
) -> *mut C2paReader {
let format = cstr_or_return_null!(format);
let stream = deref_mut_or_return_null!(stream, C2paStream);
let manifest_bytes = bytes_or_return_null!(manifest_data, manifest_size, "manifest_data");
untrack_or_return_null!(reader, C2paReader);
let reader = Box::from_raw(reader);
let result = (*reader).with_manifest_data_and_stream(manifest_bytes, &format, stream);
let result = ok_or_return_null!(post_validate(result));
box_tracked!(result)
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_reader_with_fragment(
reader: *mut C2paReader,
format: *const c_char,
stream: *mut C2paStream,
fragment: *mut C2paStream,
) -> *mut C2paReader {
let format = cstr_or_return_null!(format);
let stream = deref_mut_or_return_null!(stream, C2paStream);
let fragment = deref_mut_or_return_null!(fragment, C2paStream);
untrack_or_return_null!(reader, C2paReader);
let reader = Box::from_raw(reader);
let result = (*reader).with_fragment(&format, stream, fragment);
let result = ok_or_return_null!(post_validate(result));
box_tracked!(result)
}
#[cfg(feature = "file_io")]
#[no_mangle]
#[deprecated(
note = "Use c2pa_reader_from_context() with an explicit context instead of relying on thread-local settings."
)]
#[allow(deprecated)]
pub unsafe fn c2pa_reader_from_file(path: *const c_char) -> *mut C2paReader {
let path = cstr_or_return_null!(path);
let result = C2paReader::from_file(&path);
box_tracked!(ok_or_return_null!(post_validate(result)))
}
#[no_mangle]
#[deprecated(
note = "Use c2pa_reader_from_context() then c2pa_reader_with_manifest_data_and_stream() instead."
)]
pub unsafe extern "C" fn c2pa_reader_from_manifest_data_and_stream(
format: *const c_char,
stream: *mut C2paStream,
manifest_data: *const c_uchar,
manifest_size: usize,
) -> *mut C2paReader {
let format = cstr_or_return_null!(format);
let stream = deref_mut_or_return_null!(stream, C2paStream);
let manifest_bytes = bytes_or_return_null!(manifest_data, manifest_size, "manifest_data");
#[allow(deprecated)]
let result = C2paReader::from_manifest_data_and_stream(manifest_bytes, &format, stream);
box_tracked!(ok_or_return_null!(post_validate(result)))
}
#[no_mangle]
#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
pub unsafe extern "C" fn c2pa_reader_free(reader_ptr: *mut C2paReader) {
cimpl_free!(reader_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_reader_json(reader_ptr: *mut C2paReader) -> *mut c_char {
let c2pa_reader = deref_or_return_null!(reader_ptr, C2paReader);
to_c_string(c2pa_reader.json())
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_reader_detailed_json(reader_ptr: *mut C2paReader) -> *mut c_char {
let c2pa_reader = deref_or_return_null!(reader_ptr, C2paReader);
to_c_string(c2pa_reader.detailed_json())
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_reader_remote_url(reader_ptr: *mut C2paReader) -> *const c_char {
let c2pa_reader = deref_or_return_null!(reader_ptr, C2paReader);
option_to_c_string!(c2pa_reader.remote_url())
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_reader_is_embedded(reader_ptr: *mut C2paReader) -> bool {
let c2pa_reader = deref_or_return_false!(reader_ptr, C2paReader);
c2pa_reader.is_embedded()
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_reader_resource_to_stream(
reader_ptr: *mut C2paReader,
uri: *const c_char,
stream: *mut C2paStream,
) -> i64 {
let uri = cstr_or_return_int!(uri);
let reader = deref_mut_or_return_int!(reader_ptr, C2paReader);
let result = reader.resource_to_stream(&uri, &mut (*stream));
let len = ok_or_return_int!(result);
len as i64
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_reader_supported_mime_types(
count: *mut usize,
) -> *const *const c_char {
c2pa_mime_types_to_c_array(C2paReader::supported_mime_types(), count)
}
#[no_mangle]
#[deprecated(note = "Use c2pa_builder_from_context() then c2pa_builder_set_definition() instead.")]
pub unsafe extern "C" fn c2pa_builder_from_json(manifest_json: *const c_char) -> *mut C2paBuilder {
let manifest_json = cstr_or_return_null!(manifest_json);
#[allow(deprecated)]
let result = C2paBuilder::from_json(&manifest_json);
let result = ok_or_return_null!(result);
box_tracked!(result)
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_from_context(context: *mut C2paContext) -> *mut C2paBuilder {
let context = deref_or_return_null!(context, C2paContext);
box_tracked!(C2paBuilder::from_shared_context(context))
}
#[no_mangle]
#[deprecated(note = "Use c2pa_builder_from_context() then c2pa_builder_with_archive() instead.")]
#[allow(deprecated)]
pub unsafe extern "C" fn c2pa_builder_from_archive(stream: *mut C2paStream) -> *mut C2paBuilder {
let stream = deref_mut_or_return_null!(stream, C2paStream);
box_tracked!(ok_or_return_null!(C2paBuilder::from_archive(
&mut (*stream)
)))
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_supported_mime_types(
count: *mut usize,
) -> *const *const c_char {
c2pa_mime_types_to_c_array(C2paBuilder::supported_mime_types(), count)
}
#[no_mangle]
#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
pub unsafe extern "C" fn c2pa_builder_free(builder_ptr: *mut C2paBuilder) {
cimpl_free!(builder_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_with_definition(
builder: *mut C2paBuilder,
manifest_json: *const c_char,
) -> *mut C2paBuilder {
let manifest_json = cstr_or_return_null!(manifest_json);
untrack_or_return_null!(builder, C2paBuilder);
let builder = Box::from_raw(builder);
let result = (*builder).with_definition(manifest_json);
box_tracked!(ok_or_return_null!(result))
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_with_archive(
builder: *mut C2paBuilder,
stream: *mut C2paStream,
) -> *mut C2paBuilder {
let stream = deref_mut_or_return_null!(stream, C2paStream);
untrack_or_return_null!(builder, C2paBuilder);
let builder = Box::from_raw(builder);
let result = (*builder).with_archive(stream);
box_tracked!(ok_or_return_null!(result))
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_set_intent(
builder_ptr: *mut C2paBuilder,
intent: C2paBuilderIntent,
digital_source_type: C2paDigitalSourceType,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let builder_intent = match intent {
C2paBuilderIntent::Create => c2pa::BuilderIntent::Create(digital_source_type.into()),
C2paBuilderIntent::Edit => c2pa::BuilderIntent::Edit,
C2paBuilderIntent::Update => c2pa::BuilderIntent::Update,
};
builder.set_intent(builder_intent);
0 as c_int
}
#[no_mangle]
#[allow(clippy::unused_unit)] pub unsafe extern "C" fn c2pa_builder_set_no_embed(builder_ptr: *mut C2paBuilder) {
let builder = deref_mut_or_return!(builder_ptr, C2paBuilder, ());
builder.set_no_embed(true);
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_set_remote_url(
builder_ptr: *mut C2paBuilder,
remote_url: *const c_char,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let remote_url = cstr_or_return_int!(remote_url);
builder.set_remote_url(&remote_url);
0 as c_int
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_set_base_path(
builder_ptr: *mut C2paBuilder,
base_path: *const c_char,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let base_path = cstr_or_return_int!(base_path);
builder.set_base_path(&base_path);
0 as c_int
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_add_resource(
builder_ptr: *mut C2paBuilder,
uri: *const c_char,
stream: *mut C2paStream,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let uri = cstr_or_return_int!(uri);
let result = builder.add_resource(&uri, &mut (*stream));
ok_or_return_int!(result);
0 }
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_add_ingredient_from_stream(
builder_ptr: *mut C2paBuilder,
ingredient_json: *const c_char,
format: *const c_char,
source: *mut C2paStream,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let ingredient_json = cstr_or_return_int!(ingredient_json);
let format = cstr_or_return_int!(format);
let source = deref_mut_or_return_int!(source, C2paStream);
let result = builder.add_ingredient_from_stream(&ingredient_json, &format, &mut (*source));
ok_or_return_int!(result);
0 }
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_add_action(
builder_ptr: *mut C2paBuilder,
action_json: *const c_char,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let action_json = cstr_or_return_int!(action_json);
let action_value: serde_json::Value = ok_or_return_int!(serde_json::from_str(&action_json));
ok_or_return_int!(builder.add_action(action_value));
0
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_to_archive(
builder_ptr: *mut C2paBuilder,
stream: *mut C2paStream,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let stream = deref_mut_or_return_int!(stream, C2paStream);
let result = builder.to_archive(&mut *stream);
ok_or_return_int!(result);
0 }
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_add_ingredient_from_archive(
builder_ptr: *mut C2paBuilder,
stream: *mut C2paStream,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let stream = deref_mut_or_return_int!(stream, C2paStream);
let result = builder.add_ingredient_from_archive(&mut *stream);
ok_or_return_int!(result);
0 }
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_write_ingredient_archive(
builder_ptr: *mut C2paBuilder,
ingredient_id: *const c_char,
stream: *mut C2paStream,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let ingredient_id = cstr_or_return_int!(ingredient_id);
let stream = deref_mut_or_return_int!(stream, C2paStream);
let result = builder.write_ingredient_archive(&ingredient_id, &mut *stream);
ok_or_return_int!(result);
0 }
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_sign(
builder_ptr: *mut C2paBuilder,
format: *const c_char,
source: *mut C2paStream,
dest: *mut C2paStream,
signer_ptr: *mut C2paSigner,
manifest_bytes_ptr: *mut *const c_uchar,
) -> i64 {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let format = cstr_or_return_int!(format);
let source = deref_mut_or_return_int!(source, C2paStream);
let dest = deref_mut_or_return_int!(dest, C2paStream);
let c2pa_signer = deref_mut_or_return_int!(signer_ptr, C2paSigner);
ptr_or_return_int!(manifest_bytes_ptr);
let result = builder.sign(
c2pa_signer.signer.as_ref(),
&format,
&mut *source,
&mut *dest,
);
let manifest_bytes = ok_or_return_int!(result);
let len = manifest_bytes.len() as i64;
if !manifest_bytes_ptr.is_null() {
*manifest_bytes_ptr = to_c_bytes(manifest_bytes);
}
len
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_sign_context(
builder_ptr: *mut C2paBuilder,
format: *const c_char,
source: *mut C2paStream,
dest: *mut C2paStream,
manifest_bytes_ptr: *mut *const c_uchar,
) -> i64 {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let format = cstr_or_return_int!(format);
let source = deref_mut_or_return_int!(source, C2paStream);
let dest = deref_mut_or_return_int!(dest, C2paStream);
ptr_or_return_int!(manifest_bytes_ptr);
let result = builder.save_to_stream(&format, &mut *source, &mut *dest);
let manifest_bytes = ok_or_return_int!(result);
let len = manifest_bytes.len() as i64;
if !manifest_bytes_ptr.is_null() {
*manifest_bytes_ptr = to_c_bytes(manifest_bytes);
}
len
}
#[no_mangle]
#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
pub unsafe extern "C" fn c2pa_manifest_bytes_free(manifest_bytes_ptr: *const c_uchar) {
cimpl_free!(manifest_bytes_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_data_hashed_placeholder(
builder_ptr: *mut C2paBuilder,
reserved_size: usize,
format: *const c_char,
manifest_bytes_ptr: *mut *const c_uchar,
) -> i64 {
ptr_or_return_int!(manifest_bytes_ptr);
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let format = cstr_or_return_int!(format);
let result = builder.data_hashed_placeholder(reserved_size, &format);
let manifest_bytes = ok_or_return_int!(result);
let len = manifest_bytes.len() as i64;
if !manifest_bytes_ptr.is_null() {
*manifest_bytes_ptr = to_c_bytes(manifest_bytes);
}
len
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_sign_data_hashed_embeddable(
builder_ptr: *mut C2paBuilder,
signer_ptr: *mut C2paSigner,
data_hash: *const c_char,
format: *const c_char,
asset: *mut C2paStream,
manifest_bytes_ptr: *mut *const c_uchar,
) -> i64 {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let c2pa_signer = deref_mut_or_return_int!(signer_ptr, C2paSigner);
let data_hash_json = cstr_or_return_int!(data_hash);
let format = cstr_or_return_int!(format);
ptr_or_return_int!(manifest_bytes_ptr);
let mut data_hash: DataHash = ok_or_return_int!(serde_json::from_str(&data_hash_json)
.map_err(|e| Error::from_c2pa_error(c2pa::Error::JsonError(e))));
if !asset.is_null() {
ok_or_return_int!(data_hash
.gen_hash_from_stream(&mut *asset)
.map_err(Error::from_c2pa_error));
}
let result =
builder.sign_data_hashed_embeddable(c2pa_signer.signer.as_ref(), &data_hash, &format);
let manifest_bytes = ok_or_return_int!(result);
let len = manifest_bytes.len() as i64;
if !manifest_bytes_ptr.is_null() {
*manifest_bytes_ptr = to_c_bytes(manifest_bytes);
}
len
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_needs_placeholder(
builder_ptr: *mut C2paBuilder,
format: *const c_char,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let format = cstr_or_return_int!(format);
if builder.needs_placeholder(&format) {
1
} else {
0
}
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_hash_type(
builder_ptr: *mut C2paBuilder,
format: *const c_char,
out_hash_type: *mut C2paHashType,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let format = cstr_or_return_int!(format);
if out_hash_type.is_null() {
return -1;
}
let hash_type = match builder.hash_type(&format) {
c2pa::HashType::Data => C2paHashType::DataHash,
c2pa::HashType::Bmff => C2paHashType::BmffHash,
c2pa::HashType::Box => C2paHashType::BoxHash,
};
*out_hash_type = hash_type;
0
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_placeholder(
builder_ptr: *mut C2paBuilder,
format: *const c_char,
manifest_bytes_ptr: *mut *const c_uchar,
) -> i64 {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let format = cstr_or_return_int!(format);
let result = builder.placeholder(&format);
let manifest_bytes = ok_or_return_int!(result);
let len = manifest_bytes.len() as i64;
if !manifest_bytes_ptr.is_null() {
*manifest_bytes_ptr = to_c_bytes(manifest_bytes);
}
len
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_sign_embeddable(
builder_ptr: *mut C2paBuilder,
format: *const c_char,
manifest_bytes_ptr: *mut *const c_uchar,
) -> i64 {
ptr_or_return_int!(manifest_bytes_ptr);
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let format = cstr_or_return_int!(format);
let result = builder.sign_embeddable(&format);
let manifest_bytes = ok_or_return_int!(result);
let len = manifest_bytes.len() as i64;
if !manifest_bytes_ptr.is_null() {
*manifest_bytes_ptr = to_c_bytes(manifest_bytes);
}
len
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_set_data_hash_exclusions(
builder_ptr: *mut C2paBuilder,
exclusions_ptr: *const u64,
exclusion_count: usize,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
if exclusion_count == 0 || exclusions_ptr.is_null() {
ok_or_return_int!(builder.set_data_hash_exclusions(vec![]));
return 0;
}
let flat = std::slice::from_raw_parts(exclusions_ptr, exclusion_count * 2);
let exclusions: Vec<c2pa::HashRange> = flat
.chunks_exact(2)
.map(|pair| c2pa::HashRange::new(pair[0], pair[1]))
.collect();
ok_or_return_int!(builder.set_data_hash_exclusions(exclusions));
0
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_set_fixed_size_merkle(
builder_ptr: *mut C2paBuilder,
fixed_size_kb: usize,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
builder.set_bmff_hash_fixed_leaf_size(fixed_size_kb);
0
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_hash_mdat_bytes(
builder_ptr: *mut C2paBuilder,
mdat_id: usize,
data_ptr: *const c_uchar,
data_len: usize,
large_size: bool,
) -> c_int {
ptr_or_return_int!(data_ptr);
ptr_or_return_int!(builder_ptr);
let data = bytes_or_return_int!(data_ptr, data_len, "mdat_data");
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
ok_or_return_int!(builder.hash_bmff_mdat_bytes(mdat_id, data, large_size));
0
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_builder_update_hash_from_stream(
builder_ptr: *mut C2paBuilder,
format: *const c_char,
stream: *mut C2paStream,
) -> c_int {
let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
let format = cstr_or_return_int!(format);
let stream = deref_mut_or_return_int!(stream, C2paStream);
ok_or_return_int!(builder.update_hash_from_stream(&format, &mut *stream));
0
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_format_embeddable(
format: *const c_char,
manifest_bytes_ptr: *const c_uchar,
manifest_bytes_size: usize,
result_bytes_ptr: *mut *const c_uchar,
) -> i64 {
let format = cstr_or_return_int!(format);
ptr_or_return_int!(manifest_bytes_ptr);
ptr_or_return_int!(result_bytes_ptr);
let bytes = bytes_or_return_int!(
manifest_bytes_ptr,
manifest_bytes_size,
"manifest_bytes_ptr"
);
let result = c2pa::Builder::composed_manifest(bytes, &format);
let result_bytes = ok_or_return_int!(result);
let len = result_bytes.len() as i64;
if !result_bytes_ptr.is_null() {
*result_bytes_ptr = to_c_bytes(result_bytes);
}
len
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_signer_create(
context: *const c_void,
callback: SignerCallback,
alg: C2paSigningAlg,
certs: *const c_char,
tsa_url: *const c_char,
) -> *mut C2paSigner {
let certs = cstr_or_return_null!(certs);
let tsa_url = cstr_option!(tsa_url);
let context = context as *const ();
let c_callback = move |context: *const (), data: &[u8]| {
let signed_len_max = data.len() * 2;
let mut signed_bytes: Vec<u8> = vec![0; signed_len_max];
let signed_size = unsafe {
(callback)(
context,
data.as_ptr(),
data.len(),
signed_bytes.as_mut_ptr(),
signed_len_max,
)
};
if signed_size < 0 {
return Err(c2pa::Error::CoseSignature); }
signed_bytes.set_len(signed_size as usize);
Ok(signed_bytes)
};
let mut signer = CallbackSigner::new(c_callback, alg.into(), certs).set_context(context);
if let Some(tsa_url) = tsa_url.as_ref() {
signer = signer.set_tsa_url(tsa_url);
}
box_tracked!(C2paSigner {
signer: Box::new(signer),
})
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_signer_from_info(signer_info: &C2paSignerInfo) -> *mut C2paSigner {
let signer_info = SignerInfo {
alg: cstr_or_return_null!(signer_info.alg),
sign_cert: cstr_or_return_null!(signer_info.sign_cert).into_bytes(),
private_key: cstr_or_return_null!(signer_info.private_key).into_bytes(),
ta_url: cstr_option!(signer_info.ta_url),
};
let signer = signer_info.signer();
match signer {
Ok(signer) => box_tracked!(C2paSigner {
signer: Box::new(signer),
}),
Err(err) => {
CimplError::from(err).set_last();
std::ptr::null_mut()
}
}
}
#[no_mangle]
#[deprecated(
note = "Use c2pa_context_builder_set_signer() to configure a signer on a context instead."
)]
pub unsafe extern "C" fn c2pa_signer_from_settings() -> *mut C2paSigner {
#[allow(deprecated)]
let signer = ok_or_return_null!(C2paSettings::signer());
box_tracked!(C2paSigner {
signer: Box::new(signer),
})
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_signer_reserve_size(signer_ptr: *mut C2paSigner) -> i64 {
let c2pa_signer = deref_mut_or_return_int!(signer_ptr, C2paSigner);
c2pa_signer.signer.reserve_size() as i64
}
#[no_mangle]
#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
pub unsafe extern "C" fn c2pa_signer_free(signer_ptr: *const C2paSigner) {
cimpl_free!(signer_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn c2pa_ed25519_sign(
bytes: *const c_uchar,
len: usize,
private_key: *const c_char,
) -> *const c_uchar {
let private_key = cstr_or_return_null!(private_key);
let bytes = bytes_or_return_null!(bytes, len, "bytes");
let signed_bytes =
ok_or_return_null!(CallbackSigner::ed25519_sign(bytes, private_key.as_bytes()));
to_c_bytes(signed_bytes)
}
#[no_mangle]
#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
pub unsafe extern "C" fn c2pa_signature_free(signature_ptr: *const u8) {
cimpl_free!(signature_ptr);
}
unsafe fn c2pa_mime_types_to_c_array(strs: Vec<String>, count: *mut usize) -> *const *const c_char {
let mut mime_ptrs: Vec<*mut c_char> = strs.into_iter().map(to_c_string).collect();
mime_ptrs.shrink_to_fit();
debug_assert_eq!(mime_ptrs.len(), mime_ptrs.capacity());
*count = mime_ptrs.len();
let ptr = mime_ptrs.as_ptr();
std::mem::forget(mime_ptrs);
ptr as *const *const c_char
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use std::{ffi::CString, io::Seek, panic::catch_unwind};
use super::*;
use crate::TestStream;
macro_rules! fixture_path {
($path:expr) => {
concat!("../../sdk/tests/fixtures/", $path)
};
}
fn setup_signer_and_builder_for_signing_tests() -> (*mut C2paSigner, *mut C2paBuilder) {
let certs = include_str!(fixture_path!("certs/ed25519.pub"));
let private_key = include_bytes!(fixture_path!("certs/ed25519.pem"));
let alg = CString::new("Ed25519").unwrap();
let sign_cert = CString::new(certs).unwrap();
let private_key = CString::new(private_key).unwrap();
let signer_info = C2paSignerInfo {
alg: alg.as_ptr(),
sign_cert: sign_cert.as_ptr(),
private_key: private_key.as_ptr(),
ta_url: std::ptr::null(),
};
let signer = unsafe { c2pa_signer_from_info(&signer_info) };
assert!(!signer.is_null());
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
(signer, builder)
}
#[test]
fn test_ed25519_sign() {
let bytes = b"test";
let private_key = include_bytes!(fixture_path!("certs/ed25519.pem"));
let private_key = CString::new(private_key).unwrap();
let signature =
unsafe { c2pa_ed25519_sign(bytes.as_ptr(), bytes.len(), private_key.as_ptr()) };
assert!(!signature.is_null());
unsafe { c2pa_signature_free(signature) };
}
#[test]
fn test_c2pa_signer_from_info() {
let certs = include_str!(fixture_path!("certs/ed25519.pub"));
let private_key = include_bytes!(fixture_path!("certs/ed25519.pem"));
let alg = CString::new("Ed25519").unwrap();
let sign_cert = CString::new(certs).unwrap();
let private_key = CString::new(private_key).unwrap();
let signer_info = C2paSignerInfo {
alg: alg.as_ptr(),
sign_cert: sign_cert.as_ptr(),
private_key: private_key.as_ptr(),
ta_url: std::ptr::null(),
};
let signer = unsafe { c2pa_signer_from_info(&signer_info) };
assert!(!signer.is_null());
unsafe { c2pa_signer_free(signer) };
}
#[test]
fn test_signer_from_info_bad_alg() {
let alg = CString::new("BadAlg").unwrap();
let sign_cert = CString::new("certs").unwrap();
let private_key = CString::new("private_key").unwrap();
let signer_info = C2paSignerInfo {
alg: alg.as_ptr(),
sign_cert: sign_cert.as_ptr(),
private_key: private_key.as_ptr(),
ta_url: std::ptr::null(),
};
let signer = unsafe { c2pa_signer_from_info(&signer_info) };
assert!(signer.is_null());
let error = unsafe { c2pa_error() };
let error = unsafe { CString::from_raw(error) };
assert_eq!(error.to_str().unwrap(), "Other: Invalid signing algorithm");
}
#[test]
fn test_sign_with_info() {
let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
let mut source_stream = TestStream::new(source_image.to_vec());
let dest_vec = Vec::new();
let mut dest_stream = TestStream::new(dest_vec);
let (signer, builder) = setup_signer_and_builder_for_signing_tests();
let format = CString::new("image/jpeg").unwrap();
let mut manifest_bytes_ptr = std::ptr::null();
let _ = unsafe {
c2pa_builder_sign(
builder,
format.as_ptr(),
source_stream.as_ptr(),
dest_stream.as_ptr(),
signer,
&mut manifest_bytes_ptr,
)
};
unsafe {
c2pa_manifest_bytes_free(manifest_bytes_ptr);
}
unsafe { c2pa_builder_free(builder) };
unsafe { c2pa_signer_free(signer) };
}
#[test]
fn builder_add_actions_and_sign() {
let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
let mut source_stream = TestStream::new(source_image.to_vec());
let dest_vec = Vec::new();
let mut dest_stream = TestStream::new(dest_vec);
let (signer, builder) = setup_signer_and_builder_for_signing_tests();
let action_json = CString::new(
r#"{
"action": "com.example.test-action",
"parameters": {
"key1": "value1",
"key2": "value2"
}
}"#,
)
.unwrap();
let result = unsafe { c2pa_builder_add_action(builder, action_json.as_ptr()) };
assert_eq!(result, 0);
let format = CString::new("image/jpeg").unwrap();
let mut manifest_bytes_ptr = std::ptr::null();
let _ = unsafe {
c2pa_builder_sign(
builder,
format.as_ptr(),
source_stream.as_ptr(),
dest_stream.as_ptr(),
signer,
&mut manifest_bytes_ptr,
)
};
dest_stream.stream_mut().rewind().unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), dest_stream.as_ptr()) };
if reader.is_null() {
if let Some(msg) = CimplError::last_message() {
panic!("Reader creation failed: {}", msg);
}
}
assert!(!reader.is_null());
let json = unsafe { c2pa_reader_json(reader) };
assert!(!json.is_null());
let json_str = unsafe { CString::from_raw(json) };
let json_content = json_str.to_str().unwrap();
assert!(json_content.contains("manifest"));
assert!(json_content.contains("com.example.test-action"));
unsafe {
c2pa_manifest_bytes_free(manifest_bytes_ptr);
c2pa_builder_free(builder);
c2pa_signer_free(signer);
c2pa_reader_free(reader);
}
}
#[test]
fn builder_create_intent_digital_creation_and_sign() {
let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
let mut source_stream = TestStream::new(source_image.to_vec());
let dest_vec = Vec::new();
let mut dest_stream = TestStream::new(dest_vec);
let (signer, builder) = setup_signer_and_builder_for_signing_tests();
let result = unsafe {
c2pa_builder_set_intent(
builder,
C2paBuilderIntent::Create,
C2paDigitalSourceType::DigitalCreation,
)
};
assert_eq!(result, 0);
let format = CString::new("image/jpeg").unwrap();
let mut manifest_bytes_ptr = std::ptr::null();
let _ = unsafe {
c2pa_builder_sign(
builder,
format.as_ptr(),
source_stream.as_ptr(),
dest_stream.as_ptr(),
signer,
&mut manifest_bytes_ptr,
)
};
dest_stream.stream_mut().rewind().unwrap();
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), dest_stream.as_ptr()) };
assert!(!reader.is_null());
let json = unsafe { c2pa_reader_json(reader) };
assert!(!json.is_null());
let json_str = unsafe { CString::from_raw(json) };
let json_content = json_str.to_str().unwrap();
assert!(json_content.contains("c2pa.created"));
assert!(json_content.contains("digitalSourceType"));
assert!(json_content.contains("digitalCreation"));
assert_eq!(
json_content.matches("\"action\": \"c2pa.created\"").count(),
1
);
unsafe {
c2pa_manifest_bytes_free(manifest_bytes_ptr);
c2pa_builder_free(builder);
c2pa_signer_free(signer);
c2pa_reader_free(reader);
}
}
#[test]
fn builder_create_intent_empty_and_sign() {
let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
let mut source_stream = TestStream::new(source_image.to_vec());
let dest_vec = Vec::new();
let mut dest_stream = TestStream::new(dest_vec);
let (signer, builder) = setup_signer_and_builder_for_signing_tests();
let result = unsafe {
c2pa_builder_set_intent(
builder,
C2paBuilderIntent::Create,
C2paDigitalSourceType::Empty,
)
};
assert_eq!(result, 0);
let format = CString::new("image/jpeg").unwrap();
let mut manifest_bytes_ptr = std::ptr::null();
let _ = unsafe {
c2pa_builder_sign(
builder,
format.as_ptr(),
source_stream.as_ptr(),
dest_stream.as_ptr(),
signer,
&mut manifest_bytes_ptr,
)
};
dest_stream.stream_mut().rewind().unwrap();
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), dest_stream.as_ptr()) };
assert!(!reader.is_null());
let json = unsafe { c2pa_reader_json(reader) };
assert!(!json.is_null());
let json_str = unsafe { CString::from_raw(json) };
let json_content = json_str.to_str().unwrap();
assert!(json_content.contains("c2pa.created"));
assert!(json_content.contains("digitalsourcetype/empty"));
unsafe {
c2pa_manifest_bytes_free(manifest_bytes_ptr);
c2pa_builder_free(builder);
c2pa_signer_free(signer);
c2pa_reader_free(reader);
}
}
#[test]
fn builder_edit_intent_and_sign() {
let signed_source_image = include_bytes!(fixture_path!("C.jpg"));
let mut source_stream = TestStream::new(signed_source_image.to_vec());
let dest_vec = Vec::new();
let mut dest_stream = TestStream::new(dest_vec);
let (signer, builder) = setup_signer_and_builder_for_signing_tests();
let result = unsafe {
c2pa_builder_set_intent(
builder,
C2paBuilderIntent::Edit,
C2paDigitalSourceType::Empty,
)
};
assert_eq!(result, 0);
let format = CString::new("image/jpeg").unwrap();
let mut manifest_bytes_ptr = std::ptr::null();
let _ = unsafe {
c2pa_builder_sign(
builder,
format.as_ptr(),
source_stream.as_ptr(),
dest_stream.as_ptr(),
signer,
&mut manifest_bytes_ptr,
)
};
dest_stream.stream_mut().rewind().unwrap();
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), dest_stream.as_ptr()) };
assert!(!reader.is_null());
let json = unsafe { c2pa_reader_json(reader) };
assert!(!json.is_null());
let json_str = unsafe { CString::from_raw(json) };
let json_content = json_str.to_str().unwrap();
assert!(json_content.contains("c2pa.opened"));
assert!(!json_content.contains("digitalsourcetype/empty"));
unsafe {
c2pa_manifest_bytes_free(manifest_bytes_ptr);
c2pa_builder_free(builder);
c2pa_signer_free(signer);
c2pa_reader_free(reader);
}
}
#[test]
fn test_c2pa_builder_no_embed_null() {
let builder: *mut c2pa::Builder = std::ptr::null_mut();
unsafe { c2pa_builder_set_no_embed(builder) };
}
#[test]
fn test_c2pa_builder_set_remote_url_null() {
let builder: *mut c2pa::Builder = std::ptr::null_mut();
let remote_url = CString::new("https://example.com").unwrap();
let result = unsafe { c2pa_builder_set_remote_url(builder, remote_url.as_ptr()) };
assert_eq!(result, -1);
let error = unsafe { c2pa_error() };
let error = unsafe { CString::from_raw(error) };
assert_eq!(error.to_str().unwrap(), "NullParameter: builder_ptr");
}
#[test]
fn test_c2pa_builder_no_embed() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
unsafe { c2pa_builder_set_no_embed(builder) };
unsafe { c2pa_builder_free(builder) };
}
#[test]
fn test_c2pa_version() {
let version = unsafe { c2pa_version() };
assert!(!version.is_null());
let version_str = unsafe { CString::from_raw(version) };
assert!(!version_str.to_str().unwrap().is_empty());
}
#[test]
fn test_c2pa_error_no_error() {
let error = unsafe { c2pa_error() };
assert!(!error.is_null());
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "");
}
#[test]
fn test_c2pa_load_settings() {
let settings = CString::new("{}").unwrap();
let format = CString::new("json").unwrap();
let result = unsafe { c2pa_load_settings(settings.as_ptr(), format.as_ptr()) };
assert_eq!(result, 0);
}
#[test]
#[cfg(feature = "file_io")]
fn test_c2pa_read_file_null_path() {
let data_dir = CString::new("/tmp").unwrap();
let result = unsafe { c2pa_read_file(std::ptr::null(), data_dir.as_ptr()) };
assert!(result.is_null());
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: path");
}
#[test]
#[cfg(feature = "file_io")]
fn test_c2pa_read_ingredient_file_null_path() {
let data_dir = CString::new("/tmp").unwrap();
let result = unsafe { c2pa_read_ingredient_file(std::ptr::null(), data_dir.as_ptr()) };
assert!(result.is_null());
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: path");
}
#[test]
#[cfg(feature = "file_io")]
fn test_c2pa_sign_file_null_source_path() {
let dest_path = CString::new("/tmp/output.jpg").unwrap();
let manifest = CString::new("{}").unwrap();
let signer_info = C2paSignerInfo {
alg: std::ptr::null(),
sign_cert: std::ptr::null(),
private_key: std::ptr::null(),
ta_url: std::ptr::null(),
};
let result = unsafe {
c2pa_sign_file(
std::ptr::null(),
dest_path.as_ptr(),
manifest.as_ptr(),
&signer_info,
std::ptr::null(),
)
};
assert!(result.is_null());
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: source_path");
}
#[test]
#[cfg(feature = "file_io")]
fn test_c2pa_sign_file_success() {
use std::{fs, path::PathBuf};
let base = env!("CARGO_MANIFEST_DIR");
let source = format!("{base}/../sdk/tests/fixtures/IMG_0003.jpg");
let temp_dir = PathBuf::from(base).join("../target/tmp");
fs::create_dir_all(&temp_dir).unwrap();
let dest = temp_dir.join("c2pa_sign_file_test_output.jpg");
let source_path = CString::new(source).unwrap();
let dest_path = CString::new(dest.to_str().unwrap()).unwrap();
let manifest = CString::new("{}").unwrap();
let alg = CString::new("es256").unwrap();
let cert = CString::new(include_str!(fixture_path!("certs/es256.pub"))).unwrap();
let key =
CString::new(include_bytes!(fixture_path!("certs/es256.pem")).as_slice()).unwrap();
let signer_info = C2paSignerInfo {
alg: alg.as_ptr(),
sign_cert: cert.as_ptr(),
private_key: key.as_ptr(),
ta_url: std::ptr::null(),
};
let result = unsafe {
c2pa_sign_file(
source_path.as_ptr(),
dest_path.as_ptr(),
manifest.as_ptr(),
&signer_info,
std::ptr::null(),
)
};
assert!(
!result.is_null(),
"c2pa_sign_file should return non-null on success"
);
let result_str = unsafe { CString::from_raw(result) };
assert_eq!(
result_str.to_str().unwrap(),
"",
"c2pa_sign_file should return empty string on success"
);
assert!(dest.exists(), "Output file should exist");
let metadata = fs::metadata(&dest).unwrap();
assert!(metadata.len() > 0, "Output file should have content");
fs::remove_file(dest).ok();
}
#[test]
fn test_c2pa_reader_remote_url() {
let mut stream = TestStream::new(include_bytes!(fixture_path!("cloud.jpg")).to_vec());
let format = CString::new("image/jpeg").unwrap();
let result = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
if result.is_null() {
if let Some(msg) = CimplError::last_message() {
panic!("Reader creation failed: {}", msg);
} else {
panic!("Reader creation failed with no error message");
}
}
assert!(!result.is_null());
let remote_url = unsafe { c2pa_reader_remote_url(result) };
assert!(!remote_url.is_null());
let remote_url = unsafe { std::ffi::CStr::from_ptr(remote_url) };
assert_eq!(remote_url, c"https://cai-manifests.adobe.com/manifests/adobe-urn-uuid-5f37e182-3687-462e-a7fb-573462780391");
unsafe { c2pa_reader_free(result) };
}
#[test]
fn test_reader_file_with_wrong_label() {
let mut stream = TestStream::new(
include_bytes!(fixture_path!("adobe-20220124-E-clm-CAICAI.jpg")).to_vec(),
);
let format = CString::new("image/jpeg").unwrap();
let result: *mut C2paReader =
unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
assert!(!result.is_null());
unsafe { c2pa_reader_free(result) };
}
#[test]
fn test_c2pa_reader_from_stream_null_format() {
let mut stream = TestStream::new(Vec::new());
let result = unsafe { c2pa_reader_from_stream(std::ptr::null(), stream.as_ptr()) };
assert!(result.is_null());
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: format");
}
#[test]
fn test_c2pa_reader_from_stream_cawg() {
let source_image = include_bytes!(
"../../sdk/src/identity/tests/fixtures/claim_aggregation/ica_validation/success.jpg"
);
let mut stream = TestStream::new(source_image.to_vec());
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
assert!(!reader.is_null());
let json = unsafe { c2pa_reader_json(reader) };
assert!(!json.is_null());
let json_str = unsafe { CString::from_raw(json) };
assert!(json_str.to_str().unwrap().contains("Silly Cats 929"));
assert!(json_str
.to_str()
.unwrap()
.contains("cawg.ica.credential_valid"));
unsafe { c2pa_reader_free(reader) };
}
#[test]
fn test_c2pa_reader_with_stream_from_context() {
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let json = CString::new(r#"{"verify": {"verify_after_sign": true}}"#).unwrap();
let format = CString::new("json").unwrap();
let result =
unsafe { c2pa_settings_update_from_string(settings, json.as_ptr(), format.as_ptr()) };
assert_eq!(result, 0);
let result = unsafe { c2pa_context_builder_set_settings(builder, settings) };
assert_eq!(result, 0);
let context = unsafe { c2pa_context_builder_build(builder) };
assert!(!context.is_null());
let reader = unsafe { c2pa_reader_from_context(context) };
assert!(!reader.is_null());
let source_image = include_bytes!(fixture_path!("adobe-20220124-E-clm-CAICAI.jpg"));
let mut stream = TestStream::new(source_image.to_vec());
let format = CString::new("image/jpeg").unwrap();
let configured_reader =
unsafe { c2pa_reader_with_stream(reader, format.as_ptr(), stream.as_ptr()) };
assert!(!configured_reader.is_null());
let free_result = unsafe { c2pa_free(reader as *const c_void) };
assert_eq!(free_result, -1);
let json = unsafe { c2pa_reader_json(configured_reader) };
assert!(!json.is_null());
let json_str = unsafe { CString::from_raw(json) };
let json_content = json_str.to_str().unwrap();
assert!(
json_content.contains("claim") || json_content.contains("manifest"),
"Expected manifest content in JSON"
);
unsafe {
c2pa_free(settings as *mut c_void);
c2pa_free(context as *mut c_void);
c2pa_free(configured_reader as *mut c_void);
};
}
#[test]
fn test_c2pa_reader_with_manifest_data_and_stream() {
let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
let mut source_stream = TestStream::new(source_image.to_vec());
let mut dest_stream = TestStream::new(Vec::new());
let (signer, builder) = setup_signer_and_builder_for_signing_tests();
let format = CString::new("image/jpeg").unwrap();
let mut manifest_bytes_ptr = std::ptr::null();
let result = unsafe {
c2pa_builder_sign(
builder,
format.as_ptr(),
source_stream.as_ptr(),
dest_stream.as_ptr(),
signer,
&mut manifest_bytes_ptr,
)
};
assert!(result > 0, "Signing should succeed");
assert!(
!manifest_bytes_ptr.is_null(),
"Manifest bytes should be returned"
);
let manifest_size = result as usize;
let context = unsafe { c2pa_context_new() };
assert!(!context.is_null());
let reader = unsafe { c2pa_reader_from_context(context) };
assert!(!reader.is_null());
let mut validation_stream = TestStream::new(source_image.to_vec());
let configured_reader = unsafe {
c2pa_reader_with_manifest_data_and_stream(
reader,
format.as_ptr(),
validation_stream.as_ptr(),
manifest_bytes_ptr,
manifest_size,
)
};
assert!(
!configured_reader.is_null(),
"Reader should be configured with manifest data and stream"
);
let free_result = unsafe { c2pa_free(reader as *const c_void) };
assert_eq!(free_result, -1);
let json = unsafe { c2pa_reader_json(configured_reader) };
assert!(!json.is_null(), "Should be able to get JSON from reader");
unsafe {
c2pa_free(json as *const c_void);
c2pa_free(configured_reader as *const c_void);
c2pa_free(manifest_bytes_ptr as *const c_void);
c2pa_free(builder as *const c_void);
c2pa_free(signer as *const c_void);
c2pa_free(context as *const c_void);
}
}
#[test]
fn test_c2pa_reader_json_null_reader() {
let result = unsafe { c2pa_reader_json(std::ptr::null_mut()) };
assert!(result.is_null());
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: reader_ptr");
}
#[test]
fn test_c2pa_builder_add_resource_null_uri() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let mut stream = TestStream::new(Vec::new());
let result =
unsafe { c2pa_builder_add_resource(builder, std::ptr::null(), stream.as_ptr()) };
assert_eq!(result, -1);
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: uri");
unsafe { c2pa_builder_free(builder) };
}
#[test]
fn test_c2pa_builder_to_archive_null_stream() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let result = unsafe { c2pa_builder_to_archive(builder, std::ptr::null_mut()) };
assert_eq!(result, -1);
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: stream");
unsafe { c2pa_builder_free(builder) };
}
#[test]
fn test_c2pa_builder_add_ingredient_from_archive_null_stream() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let result =
unsafe { c2pa_builder_add_ingredient_from_archive(builder, std::ptr::null_mut()) };
assert_eq!(result, -1);
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: stream");
unsafe { c2pa_builder_free(builder) };
}
#[test]
fn test_c2pa_builder_add_ingredient_from_archive_null_builder() {
let archive_bytes = include_bytes!(fixture_path!("cloud.jpg"));
let mut stream = TestStream::new(archive_bytes.to_vec());
let result = unsafe {
c2pa_builder_add_ingredient_from_archive(std::ptr::null_mut(), stream.as_ptr())
};
assert_eq!(result, -1);
}
#[test]
fn test_c2pa_builder_write_ingredient_archive_null_stream() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let ingredient_id = CString::new("test-ingredient").unwrap();
let result = unsafe {
c2pa_builder_write_ingredient_archive(
builder,
ingredient_id.as_ptr(),
std::ptr::null_mut(),
)
};
assert_eq!(result, -1);
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: stream");
unsafe { c2pa_builder_free(builder) };
}
#[test]
fn test_c2pa_builder_write_ingredient_archive_null_ingredient_id() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let archive_bytes = vec![0u8; 0];
let mut stream = TestStream::new(archive_bytes);
let result = unsafe {
c2pa_builder_write_ingredient_archive(builder, std::ptr::null(), stream.as_ptr())
};
assert_eq!(result, -1);
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: ingredient_id");
unsafe { c2pa_builder_free(builder) };
}
#[test]
fn test_c2pa_builder_read_supported_mime_types() {
let mut count = 0;
let mime_types = unsafe { c2pa_builder_supported_mime_types(&mut count) };
assert!(!mime_types.is_null());
assert_eq!(count, C2paBuilder::supported_mime_types().len());
unsafe { c2pa_free_string_array(mime_types, count) };
}
#[test]
fn test_c2pa_reader_read_supported_mime_types() {
let mut count = 0;
let mime_types = unsafe { c2pa_reader_supported_mime_types(&mut count) };
assert!(!mime_types.is_null());
assert_eq!(count, C2paReader::supported_mime_types().len());
unsafe { c2pa_free_string_array(mime_types, count) };
}
#[test]
fn test_c2pa_free_string_array_with_nullptr() {
assert!(catch_unwind(|| {
unsafe {
c2pa_free_string_array(std::ptr::null_mut(), 0);
}
})
.is_ok());
}
#[test]
fn test_c2pa_free_string_array_with_count_1() {
let mut ptrs = vec![to_c_string("image/jpeg".to_string())];
let count = ptrs.len();
let ptr = ptrs.as_mut_ptr() as *const *const c_char;
std::mem::forget(ptrs);
assert!(catch_unwind(|| {
unsafe {
c2pa_free_string_array(ptr, count);
}
})
.is_ok());
}
#[test]
fn test_create_callback_signer() {
extern "C" fn test_callback(
_context: *const (),
_data: *const c_uchar,
_len: usize,
_signed_bytes: *mut c_uchar,
_signed_len: usize,
) -> isize {
1
}
let certs = include_str!(fixture_path!("certs/ed25519.pub"));
let certs_cstr = CString::new(certs).unwrap();
let signer = unsafe {
c2pa_signer_create(
std::ptr::null(),
test_callback,
C2paSigningAlg::Ed25519,
certs_cstr.as_ptr(),
std::ptr::null(),
)
};
assert!(!signer.is_null());
unsafe { c2pa_signer_free(signer) };
}
#[test]
fn test_sign_with_callback_signer() {
extern "C" fn test_callback(
_context: *const (),
data: *const c_uchar,
len: usize,
signed_bytes: *mut c_uchar,
signed_len: usize,
) -> isize {
let private_key = include_bytes!(fixture_path!("certs/ed25519.pem"));
let private_key_cstr = match CString::new(private_key) {
Ok(s) => s,
Err(_) => return -1,
};
let signature = unsafe { c2pa_ed25519_sign(data, len, private_key_cstr.as_ptr()) };
if signature.is_null() {
return -1;
}
let signature_len = 64;
if signed_len < signature_len {
unsafe { c2pa_signature_free(signature) };
return -1;
}
let signature_slice =
match unsafe { safe_slice_from_raw_parts(signature, signature_len, "signature") } {
Ok(slice) => slice,
Err(_) => {
unsafe { c2pa_signature_free(signature) };
return -1;
}
};
if !unsafe { is_safe_buffer_size(signed_len, signed_bytes) } {
unsafe { c2pa_signature_free(signature) };
return -1;
}
let signed_slice = unsafe { std::slice::from_raw_parts_mut(signed_bytes, signed_len) };
if signature_len <= signed_slice.len() {
signed_slice[..signature_len].copy_from_slice(signature_slice);
} else {
unsafe { c2pa_signature_free(signature) };
return -1;
}
unsafe { c2pa_signature_free(signature) };
signature_len as isize
}
let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
let mut source_stream = TestStream::new(source_image.to_vec());
let dest_vec = Vec::new();
let mut dest_stream = TestStream::new(dest_vec);
let certs = include_str!(fixture_path!("certs/ed25519.pub"));
let certs_cstr = CString::new(certs).unwrap();
let signer = unsafe {
c2pa_signer_create(
std::ptr::null(), test_callback,
C2paSigningAlg::Ed25519,
certs_cstr.as_ptr(),
std::ptr::null(), )
};
assert!(!signer.is_null());
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let result = unsafe {
c2pa_builder_set_intent(
builder,
C2paBuilderIntent::Create,
C2paDigitalSourceType::Empty,
)
};
assert_eq!(result, 0);
let format = CString::new("image/jpeg").unwrap();
let mut manifest_bytes_ptr = std::ptr::null();
let result = unsafe {
c2pa_builder_sign(
builder,
format.as_ptr(),
source_stream.as_ptr(),
dest_stream.as_ptr(),
signer,
&mut manifest_bytes_ptr,
)
};
assert!(result > 0);
dest_stream.stream_mut().rewind().unwrap();
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), dest_stream.as_ptr()) };
if reader.is_null() {
if let Some(msg) = CimplError::last_message() {
panic!("Reader creation failed: {}", msg);
}
}
assert!(!reader.is_null());
let json = unsafe { c2pa_reader_json(reader) };
assert!(!json.is_null());
let json_str = unsafe { CString::from_raw(json) };
let json_content = json_str.to_str().unwrap();
assert!(json_content.contains("manifest"));
unsafe {
c2pa_manifest_bytes_free(manifest_bytes_ptr);
c2pa_reader_free(reader);
}
unsafe { c2pa_builder_free(builder) };
unsafe { c2pa_signer_free(signer) };
}
#[test]
#[cfg(feature = "file_io")]
fn test_reader_from_file_cawg_identity() {
let settings = CString::new(include_bytes!(
"../../cli/tests/fixtures/trust/cawg_test_settings.toml"
))
.unwrap();
let format = CString::new("toml").unwrap();
let result = unsafe { c2pa_load_settings(settings.as_ptr(), format.as_ptr()) };
assert_eq!(result, 0);
let base = env!("CARGO_MANIFEST_DIR");
let path =
CString::new(format!("{base}/../sdk/tests/fixtures/C_with_CAWG_data.jpg")).unwrap();
let reader = unsafe { c2pa_reader_from_file(path.as_ptr()) };
if reader.is_null() {
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
panic!("Failed to create reader: {}", error_str.to_str().unwrap());
}
assert!(!reader.is_null());
let json = unsafe { c2pa_reader_json(reader) };
assert!(!json.is_null());
let json_str = unsafe { CString::from_raw(json) };
println!("JSON Report: {}", json_str.to_str().unwrap());
let json_report = json_str.to_str().unwrap();
assert!(json_report.contains("cawg.identity"));
assert!(json_report.contains("cawg.identity.well-formed"));
unsafe { c2pa_reader_free(reader) };
}
#[test]
fn test_c2pa_signer_from_settings() {
const SETTINGS: &str = include_str!("../../sdk/tests/fixtures/test_settings.json");
let settings = CString::new(SETTINGS).unwrap();
let format = CString::new("json").unwrap();
let result = unsafe { c2pa_load_settings(settings.as_ptr(), format.as_ptr()) };
assert_eq!(result, 0);
let signer = unsafe { c2pa_signer_from_settings() };
assert!(!signer.is_null());
unsafe { c2pa_signer_free(signer) };
}
#[test]
fn test_c2pa_settings_new() {
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
unsafe { c2pa_free(settings as *mut c_void) };
}
#[test]
fn test_c2pa_settings_update_from_json_string() {
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let json = CString::new(r#"{"verify": {"verify_after_sign": true}}"#).unwrap();
let format = CString::new("json").unwrap();
let result =
unsafe { c2pa_settings_update_from_string(settings, json.as_ptr(), format.as_ptr()) };
assert_eq!(result, 0);
unsafe { c2pa_free(settings as *mut c_void) };
}
#[test]
fn test_c2pa_settings_update_from_toml_string() {
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let toml = CString::new(
r#"
[verify]
verify_after_sign = true
"#,
)
.unwrap();
let format = CString::new("toml").unwrap();
let result =
unsafe { c2pa_settings_update_from_string(settings, toml.as_ptr(), format.as_ptr()) };
assert_eq!(result, 0);
unsafe { c2pa_free(settings as *mut c_void) };
}
#[test]
fn test_c2pa_settings_update_from_string_invalid() {
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let invalid_json = CString::new(r#"{"verify": {"verify_after_sign": "#).unwrap();
let format = CString::new("json").unwrap();
let result = unsafe {
c2pa_settings_update_from_string(settings, invalid_json.as_ptr(), format.as_ptr())
};
assert_ne!(result, 0);
unsafe { c2pa_free(settings as *mut c_void) };
}
#[test]
fn test_c2pa_settings_set_value() {
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let path = CString::new("verify.verify_after_sign").unwrap();
let value = CString::new("true").unwrap();
let result = unsafe { c2pa_settings_set_value(settings, path.as_ptr(), value.as_ptr()) };
assert_eq!(result, 0);
unsafe { c2pa_free(settings as *mut c_void) };
}
#[test]
fn test_c2pa_settings_set_value_string() {
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let path = CString::new("core.allowed_network_hosts").unwrap();
let value = CString::new(r#"["example.com", "test.org"]"#).unwrap();
let result = unsafe { c2pa_settings_set_value(settings, path.as_ptr(), value.as_ptr()) };
assert_eq!(result, 0);
unsafe { c2pa_free(settings as *mut c_void) };
}
#[test]
fn test_c2pa_settings_set_value_number() {
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let path = CString::new("verify.max_memory_usage").unwrap();
let value = CString::new("1000000").unwrap();
let result = unsafe { c2pa_settings_set_value(settings, path.as_ptr(), value.as_ptr()) };
assert_eq!(result, 0);
unsafe { c2pa_free(settings as *mut c_void) };
}
#[test]
fn test_c2pa_settings_set_value_invalid_json() {
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let path = CString::new("verify.verify_after_sign").unwrap();
let invalid_value = CString::new("not valid json").unwrap();
let result =
unsafe { c2pa_settings_set_value(settings, path.as_ptr(), invalid_value.as_ptr()) };
assert_ne!(result, 0);
unsafe { c2pa_free(settings as *mut c_void) };
}
#[test]
fn test_c2pa_context_new() {
let context = unsafe { c2pa_context_new() };
assert!(!context.is_null());
unsafe { c2pa_free(context as *mut c_void) };
}
#[test]
fn test_c2pa_context_builder_new() {
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
unsafe { c2pa_free(builder as *mut c_void) };
}
#[test]
fn test_c2pa_context_builder_set_settings() {
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let json = CString::new(r#"{"verify": {"verify_after_sign": true}}"#).unwrap();
let format = CString::new("json").unwrap();
let result =
unsafe { c2pa_settings_update_from_string(settings, json.as_ptr(), format.as_ptr()) };
assert_eq!(result, 0);
let result = unsafe { c2pa_context_builder_set_settings(builder, settings) };
assert_eq!(result, 0);
unsafe {
c2pa_free(settings as *mut c_void);
c2pa_free(builder as *mut c_void);
};
}
#[test]
fn test_c2pa_context_builder_build() {
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let json = CString::new(r#"{"verify": {"verify_after_sign": true}}"#).unwrap();
let format = CString::new("json").unwrap();
let result =
unsafe { c2pa_settings_update_from_string(settings, json.as_ptr(), format.as_ptr()) };
assert_eq!(result, 0);
let result = unsafe { c2pa_context_builder_set_settings(builder, settings) };
assert_eq!(result, 0);
let context = unsafe { c2pa_context_builder_build(builder) };
assert!(!context.is_null());
let free_result = unsafe { c2pa_free(builder as *const c_void) };
assert_eq!(free_result, -1);
unsafe {
c2pa_free(settings as *mut c_void);
c2pa_free(context as *mut c_void);
};
}
#[test]
fn test_c2pa_context_builder_set_settings_multiple_times() {
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
let settings1 = unsafe { c2pa_settings_new() };
assert!(!settings1.is_null());
let json1 = CString::new(r#"{"verify": {"verify_after_sign": true}}"#).unwrap();
let format = CString::new("json").unwrap();
let result =
unsafe { c2pa_settings_update_from_string(settings1, json1.as_ptr(), format.as_ptr()) };
assert_eq!(result, 0);
let result = unsafe { c2pa_context_builder_set_settings(builder, settings1) };
assert_eq!(result, 0);
let settings2 = unsafe { c2pa_settings_new() };
assert!(!settings2.is_null());
let json2 = CString::new(r#"{"verify": {"verify_after_sign": false}}"#).unwrap();
let result =
unsafe { c2pa_settings_update_from_string(settings2, json2.as_ptr(), format.as_ptr()) };
assert_eq!(result, 0);
let result = unsafe { c2pa_context_builder_set_settings(builder, settings2) };
assert_eq!(result, 0);
let context = unsafe { c2pa_context_builder_build(builder) };
assert!(!context.is_null());
unsafe {
c2pa_free(settings1 as *mut c_void);
c2pa_free(settings2 as *mut c_void);
c2pa_free(context as *mut c_void);
};
}
#[test]
fn test_c2pa_context_builder_with_full_config() {
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
const SETTINGS: &str = include_str!("../../sdk/tests/fixtures/test_settings.json");
let settings_str = CString::new(SETTINGS).unwrap();
let format = CString::new("json").unwrap();
let result = unsafe {
c2pa_settings_update_from_string(settings, settings_str.as_ptr(), format.as_ptr())
};
assert_eq!(result, 0);
let result = unsafe { c2pa_context_builder_set_settings(builder, settings) };
assert_eq!(result, 0);
let context = unsafe { c2pa_context_builder_build(builder) };
assert!(!context.is_null());
unsafe {
c2pa_free(settings as *mut c_void);
c2pa_free(context as *mut c_void);
};
}
#[test]
fn test_c2pa_context_can_be_shared() {
let context = unsafe { c2pa_context_new() };
assert!(!context.is_null());
let reader1 = unsafe { c2pa_reader_from_context(context) };
assert!(!reader1.is_null());
let reader2 = unsafe { c2pa_reader_from_context(context) };
assert!(!reader2.is_null());
unsafe {
c2pa_free(reader1 as *mut c_void);
c2pa_free(reader2 as *mut c_void);
c2pa_free(context as *mut c_void);
};
}
#[test]
fn test_c2pa_free_works_for_all_types() {
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let result = unsafe { c2pa_free(settings as *mut c_void) };
assert_eq!(result, 0);
let context = unsafe { c2pa_context_new() };
assert!(!context.is_null());
let result = unsafe { c2pa_free(context as *mut c_void) };
assert_eq!(result, 0);
let test_str = CString::new("test").unwrap();
let c_str = to_c_string(test_str.to_str().unwrap().to_string());
assert!(!c_str.is_null());
let result = unsafe { c2pa_free(c_str as *mut c_void) };
assert_eq!(result, 0);
}
#[test]
fn test_c2pa_reader_detailed_json() {
use std::ffi::CStr;
let source_image = include_bytes!(fixture_path!("C.jpg"));
let mut stream = TestStream::new(source_image.to_vec());
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
assert!(!reader.is_null());
let detailed_json = unsafe { c2pa_reader_detailed_json(reader) };
assert!(!detailed_json.is_null());
let json_str = unsafe { CStr::from_ptr(detailed_json).to_str().unwrap() };
assert!(!json_str.is_empty(), "Detailed JSON should not be empty");
let json_value: serde_json::Value = serde_json::from_str(json_str).unwrap();
assert!(json_value.is_object(), "Detailed JSON should be an object");
unsafe {
c2pa_free(detailed_json as *mut c_void);
c2pa_free(reader as *mut c_void);
}
}
#[test]
fn test_c2pa_reader_is_embedded() {
let source_image = include_bytes!(fixture_path!("C.jpg"));
let mut stream = TestStream::new(source_image.to_vec());
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
assert!(!reader.is_null());
let _is_embedded = unsafe { c2pa_reader_is_embedded(reader) };
unsafe {
c2pa_free(reader as *mut c_void);
}
}
#[test]
fn test_c2pa_builder_from_context() {
let context = unsafe { c2pa_context_new() };
assert!(!context.is_null());
let builder = unsafe { c2pa_builder_from_context(context) };
assert!(!builder.is_null());
let manifest_json = CString::new(r#"{"claim_generator": "test"}"#).unwrap();
let builder = unsafe { c2pa_builder_with_definition(builder, manifest_json.as_ptr()) };
assert!(!builder.is_null());
unsafe {
c2pa_free(builder as *mut c_void);
c2pa_free(context as *mut c_void);
}
}
#[test]
fn test_c2pa_format_embeddable() {
let jpeg_format = CString::new("image/jpeg").unwrap();
let placeholder_bytes = b"placeholder";
let mut result_ptr: *const c_uchar = std::ptr::null();
let _result = unsafe {
c2pa_format_embeddable(
jpeg_format.as_ptr(),
placeholder_bytes.as_ptr(),
placeholder_bytes.len(),
&mut result_ptr,
)
};
if !result_ptr.is_null() {
unsafe { c2pa_free(result_ptr as *const c_void) };
}
}
#[test]
fn test_c2pa_builder_add_ingredient_from_stream() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let ingredient_image = include_bytes!(fixture_path!("C.jpg"));
let mut ingredient_stream = TestStream::new(ingredient_image.to_vec());
let ingredient_json = CString::new(r#"{"title": "Test Ingredient"}"#).unwrap();
let format = CString::new("image/jpeg").unwrap();
let result = unsafe {
c2pa_builder_add_ingredient_from_stream(
builder,
ingredient_json.as_ptr(),
format.as_ptr(),
ingredient_stream.as_ptr(),
)
};
assert_eq!(result, 0, "Should successfully add ingredient");
unsafe {
c2pa_free(builder as *mut c_void);
}
}
#[test]
fn test_c2pa_builder_with_definition() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let new_manifest = CString::new(r#"{"claim_generator": "test_with_definition"}"#).unwrap();
let new_builder = unsafe { c2pa_builder_with_definition(builder, new_manifest.as_ptr()) };
assert!(!new_builder.is_null(), "Should return new builder");
let free_result = unsafe { c2pa_free(builder as *const c_void) };
assert_eq!(free_result, -1);
unsafe {
c2pa_free(new_builder as *mut c_void);
}
}
#[test]
fn test_c2pa_builder_with_definition_null_json() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let new_builder = unsafe { c2pa_builder_with_definition(builder, std::ptr::null()) };
assert!(
new_builder.is_null(),
"Should return null for invalid input"
);
let free_result = unsafe { c2pa_free(builder as *mut c_void) };
assert_eq!(free_result, 0);
}
#[test]
fn test_c2pa_builder_with_archive() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let archive_bytes = include_bytes!(fixture_path!("C.jpg"));
let mut archive_stream = TestStream::new(archive_bytes.to_vec());
let new_builder = unsafe { c2pa_builder_with_archive(builder, archive_stream.as_ptr()) };
let free_result = unsafe { c2pa_free(builder as *const c_void) };
assert_eq!(free_result, -1);
if !new_builder.is_null() {
unsafe {
c2pa_free(new_builder as *mut c_void);
}
}
}
#[test]
fn test_c2pa_builder_with_archive_null_stream() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let new_builder = unsafe { c2pa_builder_with_archive(builder, std::ptr::null_mut()) };
assert!(
new_builder.is_null(),
"Should return null for invalid stream"
);
let free_result = unsafe { c2pa_free(builder as *mut c_void) };
assert_eq!(free_result, 0);
}
#[test]
fn test_c2pa_reader_with_fragment() {
let source_image = include_bytes!(fixture_path!("C.jpg"));
let mut stream = TestStream::new(source_image.to_vec());
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
assert!(!reader.is_null());
let fragment_bytes = include_bytes!(fixture_path!("C.jpg"));
let mut fragment_stream = TestStream::new(fragment_bytes.to_vec());
let mut main_stream = TestStream::new(source_image.to_vec());
let new_reader = unsafe {
c2pa_reader_with_fragment(
reader,
format.as_ptr(),
main_stream.as_ptr(),
fragment_stream.as_ptr(),
)
};
let free_result = unsafe { c2pa_free(reader as *const c_void) };
assert_eq!(free_result, -1);
if !new_reader.is_null() {
unsafe {
c2pa_free(new_reader as *mut c_void);
}
}
}
#[test]
fn test_c2pa_reader_with_fragment_null_format() {
let source_image = include_bytes!(fixture_path!("C.jpg"));
let mut stream = TestStream::new(source_image.to_vec());
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
assert!(!reader.is_null());
let mut fragment_stream = TestStream::new(source_image.to_vec());
let mut main_stream = TestStream::new(source_image.to_vec());
let new_reader = unsafe {
c2pa_reader_with_fragment(
reader,
std::ptr::null(),
main_stream.as_ptr(),
fragment_stream.as_ptr(),
)
};
assert!(
new_reader.is_null(),
"Should return null for invalid format"
);
let free_result = unsafe { c2pa_free(reader as *const c_void) };
assert_eq!(free_result, 0);
}
#[test]
fn test_c2pa_reader_new() {
let reader = unsafe { c2pa_reader_new() };
assert!(!reader.is_null(), "Should create a default reader");
unsafe {
c2pa_free(reader as *mut c_void);
}
}
#[test]
fn test_c2pa_reader_is_embedded_null() {
let result = unsafe { c2pa_reader_is_embedded(std::ptr::null_mut()) };
assert!(!result, "Null reader should return false");
}
#[test]
fn test_c2pa_reader_remote_url_null() {
let result = unsafe { c2pa_reader_remote_url(std::ptr::null_mut()) };
assert!(result.is_null(), "Null reader should return null URL");
}
#[test]
fn test_c2pa_builder_set_intent_null() {
let result = unsafe {
c2pa_builder_set_intent(
std::ptr::null_mut(),
C2paBuilderIntent::Create,
C2paDigitalSourceType::DigitalCapture,
)
};
assert_eq!(result, -1, "Null builder should return -1");
}
#[test]
fn test_c2pa_builder_add_action_null_builder() {
let action = CString::new(r#"{"action": "c2pa.edited"}"#).unwrap();
let result = unsafe { c2pa_builder_add_action(std::ptr::null_mut(), action.as_ptr()) };
assert_eq!(result, -1, "Null builder should return error");
}
#[test]
fn test_c2pa_builder_add_action_null_action() {
let manifest_def = CString::new("{}").unwrap();
let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
assert!(!builder.is_null());
let result = unsafe { c2pa_builder_add_action(builder, std::ptr::null()) };
assert_eq!(result, -1, "Null action should return error");
unsafe {
c2pa_free(builder as *mut c_void);
}
}
#[test]
fn test_c2pa_context_builder_set_settings_null_settings() {
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
let result = unsafe { c2pa_context_builder_set_settings(builder, std::ptr::null_mut()) };
assert_eq!(result, -1, "Null settings should return -1");
unsafe {
c2pa_free(builder as *mut c_void);
}
}
#[test]
fn test_c2pa_signer_reserve_size() {
let (signer, builder) = setup_signer_and_builder_for_signing_tests();
let size = unsafe { c2pa_signer_reserve_size(signer) };
assert!(size > 0, "Reserve size should be positive");
unsafe {
c2pa_free(signer as *mut c_void);
c2pa_free(builder as *mut c_void);
}
}
#[test]
fn test_c2pa_signer_reserve_size_null() {
let size = unsafe { c2pa_signer_reserve_size(std::ptr::null_mut()) };
assert_eq!(size, -1, "Null signer should return -1");
}
#[test]
fn test_c2pa_string_free_backward_compat() {
let test_str = CString::new("test string").unwrap();
let c_str = to_c_string(test_str.to_str().unwrap().to_string());
assert!(!c_str.is_null());
unsafe {
c2pa_string_free(c_str);
}
}
#[test]
fn test_c2pa_string_free_null() {
unsafe {
c2pa_string_free(std::ptr::null_mut());
}
}
#[test]
fn test_c2pa_release_string() {
let test_str = CString::new("test string for release").unwrap();
let c_str = to_c_string(test_str.to_str().unwrap().to_string());
assert!(!c_str.is_null());
unsafe {
c2pa_release_string(c_str);
}
}
#[test]
fn test_c2pa_ed25519_sign_actually_calls_function() {
let bytes = b"test data to sign";
let private_key_pem = include_bytes!(fixture_path!("certs/ed25519.pem"));
let private_key = CString::new(private_key_pem.as_slice()).unwrap();
let signature =
unsafe { c2pa_ed25519_sign(bytes.as_ptr(), bytes.len(), private_key.as_ptr()) };
assert!(!signature.is_null(), "Should return signature");
unsafe {
c2pa_signature_free(signature);
}
}
#[test]
fn test_c2pa_ed25519_sign_null_bytes() {
let private_key_path = CString::new(fixture_path!("certs/ed25519.pem")).unwrap();
let signature =
unsafe { c2pa_ed25519_sign(std::ptr::null(), 10, private_key_path.as_ptr()) };
assert!(signature.is_null(), "Null bytes should return null");
}
#[test]
fn test_c2pa_ed25519_sign_null_key() {
let bytes = b"test data";
let signature = unsafe { c2pa_ed25519_sign(bytes.as_ptr(), bytes.len(), std::ptr::null()) };
assert!(signature.is_null(), "Null key should return null");
let error = unsafe { c2pa_error() };
assert!(!error.is_null());
unsafe {
c2pa_string_free(error);
}
}
#[test]
fn test_c2pa_reader_detailed_json_null() {
let result = unsafe { c2pa_reader_detailed_json(std::ptr::null_mut()) };
assert!(result.is_null(), "Null reader should return null");
}
#[test]
fn test_c2pa_reader_json_better_coverage() {
let source_image = include_bytes!(fixture_path!("C.jpg"));
let mut stream = TestStream::new(source_image.to_vec());
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
assert!(!reader.is_null());
let json = unsafe { c2pa_reader_json(reader) };
assert!(!json.is_null(), "Should return JSON");
use std::ffi::CStr;
let json_str = unsafe { CStr::from_ptr(json).to_str().unwrap() };
assert!(!json_str.is_empty());
let _: serde_json::Value = serde_json::from_str(json_str).unwrap();
unsafe {
c2pa_free(json as *mut c_void);
c2pa_free(reader as *mut c_void);
}
}
#[test]
fn test_c2pa_error_set_last() {
let error_msg = CString::new("Custom error message").unwrap();
let result = unsafe { c2pa_error_set_last(error_msg.as_ptr()) };
assert_eq!(result, 0, "c2pa_error_set_last should return 0 on success");
let error = unsafe { c2pa_error() };
assert!(
!error.is_null(),
"Error should be retrievable after set_last"
);
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(
error_str.to_str().unwrap(),
"Other: Custom error message",
"Error message should match what was set"
);
}
#[test]
fn test_c2pa_error_set_last_null() {
let result = unsafe { c2pa_error_set_last(std::ptr::null()) };
assert_eq!(
result, -1,
"c2pa_error_set_last should return -1 for null parameter"
);
}
#[test]
fn test_c2pa_reader_from_manifest_data_and_stream() {
let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
let mut source_stream = TestStream::new(source_image.to_vec());
let dest_vec = Vec::new();
let mut dest_stream = TestStream::new(dest_vec);
let (signer, builder) = setup_signer_and_builder_for_signing_tests();
let format = CString::new("image/jpeg").unwrap();
let mut manifest_bytes_ptr = std::ptr::null();
let result = unsafe {
c2pa_builder_sign(
builder,
format.as_ptr(),
source_stream.as_ptr(),
dest_stream.as_ptr(),
signer,
&mut manifest_bytes_ptr,
)
};
assert!(result > 0, "Signing should succeed");
assert!(
!manifest_bytes_ptr.is_null(),
"Manifest bytes should be returned"
);
let manifest_size = result as usize;
let mut validation_stream = TestStream::new(source_image.to_vec());
let reader = unsafe {
c2pa_reader_from_manifest_data_and_stream(
format.as_ptr(),
validation_stream.as_ptr(),
manifest_bytes_ptr,
manifest_size,
)
};
assert!(
!reader.is_null(),
"Reader should be created from manifest data and stream"
);
let json = unsafe { c2pa_reader_json(reader) };
assert!(!json.is_null(), "Should be able to get JSON from reader");
unsafe {
c2pa_free(json as *const c_void);
c2pa_free(reader as *const c_void);
c2pa_free(manifest_bytes_ptr as *const c_void);
c2pa_free(builder as *const c_void);
c2pa_free(signer as *const c_void);
}
}
#[test]
fn test_c2pa_reader_from_manifest_data_and_stream_null_format() {
let source_image = include_bytes!(fixture_path!("C.jpg"));
let mut stream = TestStream::new(source_image.to_vec());
let manifest_data = [0u8; 100];
let reader = unsafe {
c2pa_reader_from_manifest_data_and_stream(
std::ptr::null(),
stream.as_ptr(),
manifest_data.as_ptr(),
manifest_data.len(),
)
};
assert!(reader.is_null(), "Reader should be null for null format");
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: format");
}
#[test]
fn test_c2pa_reader_resource_to_stream() {
let source_image = include_bytes!(fixture_path!("C.jpg"));
let mut stream = TestStream::new(source_image.to_vec());
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
assert!(
!reader.is_null(),
"Reader should be created from C2PA image"
);
let resource_uri =
CString::new("self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.jpeg").unwrap();
let mut output_stream = TestStream::new(Vec::new());
let result = unsafe {
c2pa_reader_resource_to_stream(reader, resource_uri.as_ptr(), output_stream.as_ptr())
};
assert!(result >= 0, "resource_to_stream should return >= 0");
unsafe {
c2pa_free(reader as *const c_void);
}
}
#[test]
fn test_c2pa_reader_resource_to_stream_null_reader() {
let resource_uri = CString::new("some_uri").unwrap();
let mut output_stream = TestStream::new(Vec::new());
let result = unsafe {
c2pa_reader_resource_to_stream(
std::ptr::null_mut(),
resource_uri.as_ptr(),
output_stream.as_ptr(),
)
};
assert_eq!(
result, -1,
"resource_to_stream should return -1 for null reader"
);
let error = unsafe { c2pa_error() };
let error_str = unsafe { CString::from_raw(error) };
assert_eq!(error_str.to_str().unwrap(), "NullParameter: reader_ptr");
}
#[test]
fn test_c2pa_reader_resource_to_stream_null_uri() {
let source_image = include_bytes!(fixture_path!("C.jpg"));
let mut stream = TestStream::new(source_image.to_vec());
let format = CString::new("image/jpeg").unwrap();
let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
assert!(!reader.is_null());
let mut output_stream = TestStream::new(Vec::new());
let result = unsafe {
c2pa_reader_resource_to_stream(reader, std::ptr::null(), output_stream.as_ptr())
};
assert_eq!(
result, -1,
"resource_to_stream should return -1 for null uri"
);
unsafe {
c2pa_free(reader as *const c_void);
}
}
#[test]
fn test_data_hash_embeddable_workflow() {
const SETTINGS: &str = include_str!(fixture_path!("test_settings.json"));
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let json_str = CString::new(SETTINGS).unwrap();
let fmt = CString::new("json").unwrap();
let result =
unsafe { c2pa_settings_update_from_string(settings, json_str.as_ptr(), fmt.as_ptr()) };
assert_eq!(result, 0);
let ctx_builder = unsafe { c2pa_context_builder_new() };
assert!(!ctx_builder.is_null());
let result = unsafe { c2pa_context_builder_set_settings(ctx_builder, settings) };
assert_eq!(result, 0);
let context = unsafe { c2pa_context_builder_build(ctx_builder) };
assert!(!context.is_null());
let builder = unsafe { c2pa_builder_from_context(context) };
assert!(!builder.is_null());
let format = CString::new("image/jpeg").unwrap();
let needs = unsafe { c2pa_builder_needs_placeholder(builder, format.as_ptr()) };
assert!(needs >= 0, "needs_placeholder should not error");
assert!(needs <= 1, "needs_placeholder returns 0 or 1");
let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
let mut source_stream = TestStream::new(source_image.to_vec());
let result = unsafe {
c2pa_builder_update_hash_from_stream(builder, format.as_ptr(), source_stream.as_ptr())
};
assert_eq!(result, 0, "update_hash_from_stream failed");
let mut signed_bytes_ptr: *const c_uchar = std::ptr::null();
let len = unsafe {
c2pa_builder_sign_embeddable(builder, format.as_ptr(), &mut signed_bytes_ptr)
};
assert!(len > 0, "sign_embeddable should return positive length");
assert!(!signed_bytes_ptr.is_null());
unsafe {
c2pa_free(signed_bytes_ptr as *mut c_void);
c2pa_free(settings as *mut c_void);
c2pa_free(context as *mut c_void);
c2pa_free(builder as *mut c_void);
}
}
#[test]
fn test_bmff_embeddable_workflow_with_mdat_hashes() {
const SETTINGS: &str = include_str!(fixture_path!("test_settings.json"));
let settings = unsafe { c2pa_settings_new() };
assert!(!settings.is_null());
let json_str = CString::new(SETTINGS).unwrap();
let fmt = CString::new("json").unwrap();
let result =
unsafe { c2pa_settings_update_from_string(settings, json_str.as_ptr(), fmt.as_ptr()) };
assert_eq!(result, 0);
let ctx_builder = unsafe { c2pa_context_builder_new() };
assert!(!ctx_builder.is_null());
let result = unsafe { c2pa_context_builder_set_settings(ctx_builder, settings) };
assert_eq!(result, 0);
let context = unsafe { c2pa_context_builder_build(ctx_builder) };
assert!(!context.is_null());
let builder = unsafe { c2pa_builder_from_context(context) };
assert!(!builder.is_null());
let format = CString::new("video/mp4").unwrap();
let needs = unsafe { c2pa_builder_needs_placeholder(builder, format.as_ptr()) };
assert_eq!(
needs, 1,
"needs_placeholder should be 1 for video/mp4 before placeholder"
);
let size_only =
unsafe { c2pa_builder_placeholder(builder, format.as_ptr(), std::ptr::null_mut()) };
assert!(
size_only > 0,
"placeholder with null output should return positive size"
);
assert_ne!(
size_only, -1,
"placeholder with null output should not error"
);
let mut placeholder_ptr: *const c_uchar = std::ptr::null();
let placeholder_len =
unsafe { c2pa_builder_placeholder(builder, format.as_ptr(), &mut placeholder_ptr) };
assert!(
placeholder_len > 0,
"placeholder should return non-empty bytes"
);
assert!(!placeholder_ptr.is_null());
unsafe {
c2pa_builder_set_fixed_size_merkle(builder, 1);
}
let leaf_data: [u8; 4096] = [0xab; 4096];
let result = unsafe {
c2pa_builder_hash_mdat_bytes(builder, 0, leaf_data.as_ptr(), leaf_data.len(), true)
};
assert_eq!(result, 0, "set_bmff_mdat_hashes failed");
let video = include_bytes!(fixture_path!("video1.mp4"));
let mut video_stream = TestStream::new(video.to_vec());
let result = unsafe {
c2pa_builder_update_hash_from_stream(builder, format.as_ptr(), video_stream.as_ptr())
};
assert_eq!(result, 0, "update_hash_from_stream failed");
let mut signed_bytes_ptr: *const c_uchar = std::ptr::null();
let len = unsafe {
c2pa_builder_sign_embeddable(builder, format.as_ptr(), &mut signed_bytes_ptr)
};
assert!(len > 0, "sign_embeddable should return positive length");
assert!(!signed_bytes_ptr.is_null());
unsafe {
c2pa_free(placeholder_ptr as *mut c_void);
c2pa_free(signed_bytes_ptr as *mut c_void);
c2pa_free(settings as *mut c_void);
c2pa_free(context as *mut c_void);
c2pa_free(builder as *mut c_void);
}
}
#[test]
fn test_context_builder_set_signer() {
let certs = include_str!(fixture_path!("certs/ed25519.pub"));
let private_key = include_bytes!(fixture_path!("certs/ed25519.pem"));
let alg = CString::new("Ed25519").unwrap();
let sign_cert = CString::new(certs).unwrap();
let private_key = CString::new(private_key.as_slice()).unwrap();
let signer_info = C2paSignerInfo {
alg: alg.as_ptr(),
sign_cert: sign_cert.as_ptr(),
private_key: private_key.as_ptr(),
ta_url: std::ptr::null(),
};
let signer = unsafe { c2pa_signer_from_info(&signer_info) };
assert!(!signer.is_null());
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
let result = unsafe { c2pa_context_builder_set_signer(builder, signer) };
assert_eq!(result, 0);
let free_result = unsafe { c2pa_free(signer as *const c_void) };
assert_eq!(free_result, -1);
let context = unsafe { c2pa_context_builder_build(builder) };
assert!(!context.is_null());
let builder = unsafe { c2pa_builder_from_context(context) };
assert!(!builder.is_null());
unsafe {
c2pa_free(builder as *mut c_void);
c2pa_free(context as *mut c_void);
}
}
#[test]
fn test_context_builder_set_signer_null() {
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
let result = unsafe { c2pa_context_builder_set_signer(builder, std::ptr::null_mut()) };
assert_eq!(result, -1, "Null signer should be rejected");
unsafe { c2pa_free(builder as *mut c_void) };
}
#[test]
fn test_c2pa_context_builder_set_progress_callback() {
use std::sync::atomic::{AtomicU32, Ordering};
let call_count = Arc::new(AtomicU32::new(0));
let raw_ptr = Arc::as_ptr(&call_count) as *const c_void;
unsafe extern "C" fn progress_cb(
context: *const c_void,
_phase: C2paProgressPhase,
_step: u32,
_total: u32,
) -> c_int {
let counter = &*(context as *const AtomicU32);
counter.fetch_add(1, Ordering::SeqCst);
1
}
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
let result =
unsafe { c2pa_context_builder_set_progress_callback(builder, raw_ptr, progress_cb) };
assert_eq!(result, 0, "set_progress_callback should succeed");
let context = unsafe { c2pa_context_builder_build(builder) };
assert!(!context.is_null());
unsafe { c2pa_free(context as *mut c_void) };
}
#[test]
fn test_c2pa_context_builder_set_progress_callback_null_user_data() {
unsafe extern "C" fn progress_cb(
_context: *const c_void,
_phase: C2paProgressPhase,
_step: u32,
_total: u32,
) -> c_int {
1
}
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
let result = unsafe {
c2pa_context_builder_set_progress_callback(builder, std::ptr::null(), progress_cb)
};
assert_eq!(result, 0, "NULL user_data should be accepted");
let context = unsafe { c2pa_context_builder_build(builder) };
assert!(!context.is_null());
unsafe { c2pa_free(context as *mut c_void) };
}
#[test]
fn test_c2pa_context_builder_set_progress_callback_null_builder() {
unsafe extern "C" fn progress_cb(
_context: *const c_void,
_phase: C2paProgressPhase,
_step: u32,
_total: u32,
) -> c_int {
1
}
let result = unsafe {
c2pa_context_builder_set_progress_callback(
std::ptr::null_mut(),
std::ptr::null(),
progress_cb,
)
};
assert_eq!(result, -1, "NULL builder should return error");
}
#[test]
fn test_progress_phase_to_c2pa_progress_phase() {
let cases: &[(ProgressPhase, i32)] = &[
(ProgressPhase::Reading, 0),
(ProgressPhase::VerifyingManifest, 1),
(ProgressPhase::VerifyingSignature, 2),
(ProgressPhase::VerifyingIngredient, 3),
(ProgressPhase::VerifyingAssetHash, 4),
(ProgressPhase::AddingIngredient, 5),
(ProgressPhase::Thumbnail, 6),
(ProgressPhase::Hashing, 7),
(ProgressPhase::Signing, 8),
(ProgressPhase::Embedding, 9),
(ProgressPhase::FetchingRemoteManifest, 10),
(ProgressPhase::Writing, 11),
(ProgressPhase::FetchingOCSP, 12),
(ProgressPhase::FetchingTimestamp, 13),
];
for (sdk_phase, expected) in cases {
let c_phase = C2paProgressPhase::from(sdk_phase.clone());
assert_eq!(c_phase as i32, *expected, "mismatch for {sdk_phase:?}");
}
}
#[test]
fn test_c2pa_context_cancel() {
let context = unsafe { c2pa_context_new() };
assert!(!context.is_null());
let result = unsafe { c2pa_context_cancel(context) };
assert_eq!(result, 0, "cancel should succeed on a valid context");
unsafe { c2pa_free(context as *mut c_void) };
}
#[test]
fn test_c2pa_context_cancel_null() {
let result = unsafe { c2pa_context_cancel(std::ptr::null_mut()) };
assert_eq!(result, -1, "NULL context should return error");
}
#[test]
fn test_c2pa_context_cancel_via_builder() {
let builder = unsafe { c2pa_context_builder_new() };
assert!(!builder.is_null());
let context = unsafe { c2pa_context_builder_build(builder) };
assert!(!context.is_null());
let result = unsafe { c2pa_context_cancel(context) };
assert_eq!(result, 0, "cancel should work on a built context");
unsafe { c2pa_free(context as *mut c_void) };
}
}