Skip to main content

c2pa_c/
c_api.rs

1// Copyright 2024 Adobe. All rights reserved.
2// This file is licensed to you under the Apache License,
3// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
4// or the MIT license (http://opensource.org/licenses/MIT),
5// at your option.
6
7// Unless required by applicable law or agreed to in writing,
8// this software is distributed on an "AS IS" BASIS, WITHOUT
9// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
10// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
11// specific language governing permissions and limitations under
12// each license.
13
14use std::{
15    os::raw::{c_char, c_int, c_uchar, c_void},
16    sync::Arc,
17};
18
19// C has no namespace so we prefix things with C2PA to make them unique (as namespace)
20#[cfg(feature = "file_io")]
21use c2pa::Ingredient;
22use c2pa::{
23    assertions::DataHash, identity::validator::CawgValidator, Builder as C2paBuilder,
24    CallbackSigner, Context, ProgressPhase, Reader as C2paReader, Settings as C2paSettings,
25    SigningAlg,
26};
27use tokio::runtime::Builder;
28
29#[cfg(feature = "file_io")]
30use crate::json_api::{read_file, sign_file};
31#[cfg(test)]
32use crate::safe_slice_from_raw_parts;
33// Import macros and utilities from cimpl
34#[allow(unused_imports)] // Usage varies by feature flags and test/non-test builds
35use crate::{
36    box_tracked, bytes_or_return_int, bytes_or_return_null, c2pa_stream::C2paStream, cimpl_free,
37    cstr_or_return_int, cstr_or_return_null, deref_mut_or_return, deref_mut_or_return_int,
38    deref_mut_or_return_null, deref_or_return_int, deref_or_return_null, error::Error,
39    ok_or_return_int, ok_or_return_null, option_to_c_string, ptr_or_return_int,
40    signer_info::SignerInfo, to_c_bytes, to_c_string, CimplError,
41};
42
43/// Validates that a buffer size is within safe bounds and doesn't cause integer overflow
44/// when used with pointer arithmetic.
45///
46/// # Arguments
47/// * `size` - Size to validate
48/// * `ptr` - Pointer to validate against (for address space checks)
49///
50/// # Returns
51/// * `true` if the size is safe to use
52/// * `false` if the size would cause integer overflow
53#[allow(dead_code)]
54unsafe fn is_safe_buffer_size(size: usize, ptr: *const c_uchar) -> bool {
55    // Combined checks for early return - improves branch prediction
56    if size == 0 || size > isize::MAX as usize {
57        return false;
58    }
59
60    // Check if the buffer would extend beyond address space to fail fast
61    if !ptr.is_null() {
62        let end_ptr = ptr.add(size);
63        if end_ptr < ptr {
64            return false; // Wrapped around
65        }
66    }
67
68    true
69}
70
71// Work around limitations in cbindgen.
72mod cbindgen_fix {
73    #[repr(C)]
74    #[allow(dead_code)]
75    pub struct C2paBuilder;
76
77    #[repr(C)]
78    #[allow(dead_code)]
79    pub struct C2paReader;
80
81    #[repr(C)]
82    #[allow(dead_code)]
83    pub struct C2paContextBuilder;
84
85    #[repr(C)]
86    #[allow(dead_code)]
87    pub struct C2paContext;
88
89    #[repr(C)]
90    #[allow(dead_code)]
91    pub struct C2paSettings;
92
93    #[repr(C)]
94    #[allow(dead_code)]
95    pub struct C2paHttpResolver;
96}
97
98type C2paContextBuilder = Context;
99type C2paContext = Arc<Context>;
100
101/// Progress phase constants passed to C progress callbacks.
102/// These mirror [`c2pa::ProgressPhase`] variants.
103#[repr(C)]
104pub enum C2paProgressPhase {
105    Reading = 0,
106    VerifyingManifest = 1,
107    VerifyingSignature = 2,
108    VerifyingIngredient = 3,
109    VerifyingAssetHash = 4,
110    AddingIngredient = 5,
111    Thumbnail = 6,
112    Hashing = 7,
113    Signing = 8,
114    Embedding = 9,
115    FetchingRemoteManifest = 10,
116    Writing = 11,
117    FetchingOCSP = 12,
118    FetchingTimestamp = 13,
119}
120
121impl From<ProgressPhase> for C2paProgressPhase {
122    fn from(phase: ProgressPhase) -> Self {
123        match phase {
124            ProgressPhase::Reading => Self::Reading,
125            ProgressPhase::VerifyingManifest => Self::VerifyingManifest,
126            ProgressPhase::VerifyingSignature => Self::VerifyingSignature,
127            ProgressPhase::VerifyingIngredient => Self::VerifyingIngredient,
128            ProgressPhase::VerifyingAssetHash => Self::VerifyingAssetHash,
129            ProgressPhase::AddingIngredient => Self::AddingIngredient,
130            ProgressPhase::Thumbnail => Self::Thumbnail,
131            ProgressPhase::Hashing => Self::Hashing,
132            ProgressPhase::Signing => Self::Signing,
133            ProgressPhase::Embedding => Self::Embedding,
134            ProgressPhase::FetchingRemoteManifest => Self::FetchingRemoteManifest,
135            ProgressPhase::Writing => Self::Writing,
136            ProgressPhase::FetchingOCSP => Self::FetchingOCSP,
137            ProgressPhase::FetchingTimestamp => Self::FetchingTimestamp,
138            _ => Self::Reading, // fallback for #[non_exhaustive]
139        }
140    }
141}
142
143/// List of supported signing algorithms.
144#[repr(C)]
145pub enum C2paSigningAlg {
146    Es256,
147    Es384,
148    Es512,
149    Ps256,
150    Ps384,
151    Ps512,
152    Ed25519,
153}
154
155impl From<C2paSigningAlg> for SigningAlg {
156    fn from(alg: C2paSigningAlg) -> Self {
157        match alg {
158            C2paSigningAlg::Es256 => SigningAlg::Es256,
159            C2paSigningAlg::Es384 => SigningAlg::Es384,
160            C2paSigningAlg::Es512 => SigningAlg::Es512,
161            C2paSigningAlg::Ps256 => SigningAlg::Ps256,
162            C2paSigningAlg::Ps384 => SigningAlg::Ps384,
163            C2paSigningAlg::Ps512 => SigningAlg::Ps512,
164            C2paSigningAlg::Ed25519 => SigningAlg::Ed25519,
165        }
166    }
167}
168
169/// List of possible digital source types.
170#[repr(C)]
171pub enum C2paDigitalSourceType {
172    Empty,
173    TrainedAlgorithmicData,
174    DigitalCapture,
175    ComputationalCapture,
176    NegativeFilm,
177    PositiveFilm,
178    Print,
179    HumanEdits,
180    CompositeWithTrainedAlgorithmicMedia,
181    AlgorithmicallyEnhanced,
182    DigitalCreation,
183    DataDrivenMedia,
184    TrainedAlgorithmicMedia,
185    AlgorithmicMedia,
186    ScreenCapture,
187    VirtualRecording,
188    Composite,
189    CompositeCapture,
190    CompositeSynthetic,
191}
192
193impl From<C2paDigitalSourceType> for c2pa::DigitalSourceType {
194    fn from(source_type: C2paDigitalSourceType) -> Self {
195        match source_type {
196            C2paDigitalSourceType::Empty => c2pa::DigitalSourceType::Empty,
197            C2paDigitalSourceType::TrainedAlgorithmicData => {
198                c2pa::DigitalSourceType::TrainedAlgorithmicData
199            }
200            C2paDigitalSourceType::DigitalCapture => c2pa::DigitalSourceType::DigitalCapture,
201            C2paDigitalSourceType::ComputationalCapture => {
202                c2pa::DigitalSourceType::ComputationalCapture
203            }
204            C2paDigitalSourceType::NegativeFilm => c2pa::DigitalSourceType::NegativeFilm,
205            C2paDigitalSourceType::PositiveFilm => c2pa::DigitalSourceType::PositiveFilm,
206            C2paDigitalSourceType::Print => c2pa::DigitalSourceType::Print,
207            C2paDigitalSourceType::HumanEdits => c2pa::DigitalSourceType::HumanEdits,
208            C2paDigitalSourceType::CompositeWithTrainedAlgorithmicMedia => {
209                c2pa::DigitalSourceType::CompositeWithTrainedAlgorithmicMedia
210            }
211            C2paDigitalSourceType::AlgorithmicallyEnhanced => {
212                c2pa::DigitalSourceType::AlgorithmicallyEnhanced
213            }
214            C2paDigitalSourceType::DigitalCreation => c2pa::DigitalSourceType::DigitalCreation,
215            C2paDigitalSourceType::DataDrivenMedia => c2pa::DigitalSourceType::DataDrivenMedia,
216            C2paDigitalSourceType::TrainedAlgorithmicMedia => {
217                c2pa::DigitalSourceType::TrainedAlgorithmicMedia
218            }
219            C2paDigitalSourceType::AlgorithmicMedia => c2pa::DigitalSourceType::AlgorithmicMedia,
220            C2paDigitalSourceType::ScreenCapture => c2pa::DigitalSourceType::ScreenCapture,
221            C2paDigitalSourceType::VirtualRecording => c2pa::DigitalSourceType::VirtualRecording,
222            C2paDigitalSourceType::Composite => c2pa::DigitalSourceType::Composite,
223            C2paDigitalSourceType::CompositeCapture => c2pa::DigitalSourceType::CompositeCapture,
224            C2paDigitalSourceType::CompositeSynthetic => {
225                c2pa::DigitalSourceType::CompositeSynthetic
226            }
227        }
228    }
229}
230
231/// Builder intent enumeration.
232/// This specifies what kind of manifest to create.
233#[repr(C)]
234pub enum C2paBuilderIntent {
235    /// This is a new digital creation with the specified digital source type.
236    /// The Manifest must not have a parent ingredient.
237    /// A `c2pa.created` action will be added if not provided.
238    Create,
239    /// This is an edit of a pre-existing parent asset.
240    /// The Manifest must have a parent ingredient.
241    /// A parent ingredient will be generated from the source stream if not otherwise provided.
242    /// A `c2pa.opened` action will be tied to the parent ingredient.
243    Edit,
244    /// A restricted version of Edit for non-editorial changes.
245    /// There must be only one ingredient, as a parent.
246    /// No changes can be made to the hashed content of the parent.
247    Update,
248}
249
250/// Hash binding type for embeddable signing workflows.
251#[repr(C)]
252pub enum C2paHashType {
253    /// Placeholder + exclusions + hash + sign (JPEG, PNG, etc.).
254    DataHash = 0,
255
256    /// Placeholder + hash + sign (MP4, AVIF, HEIF/HEIC).
257    BmffHash = 1,
258
259    /// Hash + sign, no placeholder needed.
260    BoxHash = 2,
261}
262
263#[repr(C)]
264pub struct C2paSigner {
265    pub signer: Box<dyn crate::maybe_send_sync::C2paSignerObject>,
266}
267
268/// Defines a callback to read from a stream.
269///
270/// # Parameters
271/// * context: A generic context value to used by the C code, often a file or stream reference.
272pub type SignerCallback = unsafe extern "C" fn(
273    context: *const (),
274    data: *const c_uchar,
275    len: usize,
276    signed_bytes: *mut c_uchar,
277    signed_len: usize,
278) -> isize;
279
280/// HTTP request passed to the resolver callback.
281///
282/// All string fields are NULL-terminated UTF-8. The struct and all
283/// pointed-to data remain valid for the duration of the callback.
284#[repr(C)]
285pub struct C2paHttpRequest {
286    /// URL (e.g. `https://example.com/manifest`)
287    pub url: *const c_char,
288    /// HTTP method (e.g. "GET", "POST")
289    pub method: *const c_char,
290    /// Newline-delimited "Name: Value\n" pairs, or NULL if none
291    pub headers: *const c_char,
292    /// Request body bytes, or NULL if none
293    pub body: *const c_uchar,
294    /// Length of `body` in bytes
295    pub body_len: usize,
296}
297
298/// HTTP response filled in by the resolver callback.
299///
300/// The callback must set `status`, `body`, and `body_len`.
301/// `body` must be allocated with `malloc()`. Rust will call `free()` on it
302/// after copying the data.
303#[repr(C)]
304pub struct C2paHttpResponse {
305    /// HTTP status code (e.g. 200, 404)
306    pub status: i32,
307    /// Response body bytes, allocated by the callback with `malloc()`.
308    /// Rust takes ownership and will call `free()`.
309    pub body: *mut c_uchar,
310    /// Length of `body` in bytes
311    pub body_len: usize,
312}
313
314/// Owns the backing storage for a [`C2paHttpRequest`].
315///
316/// Use [`as_ffi`](OwnedC2paHttpRequest::as_ffi) to obtain the `#[repr(C)]` view
317/// whose pointers borrow from this struct.
318struct OwnedC2paHttpRequest {
319    url: std::ffi::CString,
320    method: std::ffi::CString,
321    headers: std::ffi::CString,
322    body: Vec<u8>,
323}
324
325impl TryFrom<c2pa::http::http::Request<Vec<u8>>> for OwnedC2paHttpRequest {
326    type Error = c2pa::http::HttpResolverError;
327
328    fn try_from(request: c2pa::http::http::Request<Vec<u8>>) -> Result<Self, Self::Error> {
329        use std::ffi::CString;
330
331        use c2pa::http::HttpResolverError;
332
333        let url = CString::new(request.uri().to_string())
334            .map_err(|e| HttpResolverError::Other(Box::new(e)))?;
335        let method = CString::new(request.method().as_str())
336            .map_err(|e| HttpResolverError::Other(Box::new(e)))?;
337        let headers_str: String = request
338            .headers()
339            .iter()
340            .filter_map(|(k, v)| v.to_str().ok().map(|v| format!("{k}: {v}\n")))
341            .collect();
342        let headers =
343            CString::new(headers_str).map_err(|e| HttpResolverError::Other(Box::new(e)))?;
344        let body = request.into_body();
345
346        Ok(Self {
347            url,
348            method,
349            headers,
350            body,
351        })
352    }
353}
354
355impl OwnedC2paHttpRequest {
356    /// Returns the `#[repr(C)]` view. The returned struct borrows from `self`
357    /// and must not outlive it.
358    fn as_ffi(&self) -> C2paHttpRequest {
359        let (body, body_len) = if self.body.is_empty() {
360            (std::ptr::null(), 0)
361        } else {
362            (self.body.as_ptr(), self.body.len())
363        };
364        C2paHttpRequest {
365            url: self.url.as_ptr(),
366            method: self.method.as_ptr(),
367            headers: self.headers.as_ptr(),
368            body,
369            body_len,
370        }
371    }
372}
373
374/// Converts a [`C2paHttpResponse`] into an `http::Response`.
375///
376/// Copies the body, then calls `free()` on the C-allocated `body` pointer.
377///
378/// # Safety
379/// `body` must have been allocated with `malloc()` (or be null).
380/// This impl is intentionally *not* marked `unsafe` because `TryFrom`
381/// does not support it; callers must uphold the `malloc` invariant.
382impl TryFrom<C2paHttpResponse> for c2pa::http::http::Response<Box<dyn std::io::Read>> {
383    type Error = c2pa::http::HttpResolverError;
384
385    fn try_from(resp: C2paHttpResponse) -> Result<Self, Self::Error> {
386        let body_vec = if resp.body.is_null() || resp.body_len == 0 {
387            Vec::new()
388        } else {
389            let v = unsafe { std::slice::from_raw_parts(resp.body, resp.body_len) }.to_vec();
390            unsafe { libc::free(resp.body as *mut c_void) };
391            v
392        };
393
394        c2pa::http::http::Response::builder()
395            .status(resp.status as u16)
396            .body(Box::new(std::io::Cursor::new(body_vec)) as Box<dyn std::io::Read>)
397            .map_err(c2pa::http::HttpResolverError::Http)
398    }
399}
400
401/// Callback type for custom HTTP request resolution.
402///
403/// Called synchronously by Rust when an HTTP request is needed
404/// (remote manifest fetch, OCSP, timestamp, etc.).
405///
406/// Returns 0 on success, non-zero on error. On error, call
407/// `c2pa_error_set_last()` before returning.
408pub type C2paHttpResolverCallback = unsafe extern "C" fn(
409    context: *mut c_void,
410    request: *const C2paHttpRequest,
411    response: *mut C2paHttpResponse,
412) -> c_int;
413
414/// Opaque handle for a C-callback-based HTTP resolver.
415/// Created by `c2pa_http_resolver_create()`. Either consumed by
416/// `c2pa_context_builder_set_http_resolver()` or freed via `c2pa_free()`.
417pub struct C2paHttpResolver {
418    context: *const c_void,
419    callback: C2paHttpResolverCallback,
420}
421
422// Safety: the caller guarantees that `context` is safe to use from any thread.
423// On wasm32, MaybeSend/MaybeSync are blanket-implemented so these are not needed,
424// but they are harmless and keep the code uniform.
425unsafe impl Send for C2paHttpResolver {}
426unsafe impl Sync for C2paHttpResolver {}
427
428impl c2pa::http::SyncHttpResolver for C2paHttpResolver {
429    fn http_resolve(
430        &self,
431        request: c2pa::http::http::Request<Vec<u8>>,
432    ) -> Result<c2pa::http::http::Response<Box<dyn std::io::Read>>, c2pa::http::HttpResolverError>
433    {
434        use c2pa::http::HttpResolverError;
435
436        let owned = OwnedC2paHttpRequest::try_from(request)?;
437        let c_request = owned.as_ffi();
438
439        let mut c_response = C2paHttpResponse {
440            status: 0,
441            body: std::ptr::null_mut(),
442            body_len: 0,
443        };
444
445        let rc =
446            unsafe { (self.callback)(self.context as *mut c_void, &c_request, &mut c_response) };
447
448        if rc != 0 {
449            // Free any body the callback may have allocated before the error.
450            if !c_response.body.is_null() {
451                unsafe { libc::free(c_response.body as *mut c_void) };
452            }
453            let msg = CimplError::last_message()
454                .unwrap_or_else(|| "HTTP callback returned error".to_string());
455            return Err(HttpResolverError::Other(msg.into()));
456        }
457
458        c_response.try_into()
459    }
460}
461
462// // Internal routine to return a rust String reference to C as *mut c_char.
463// // The returned value MUST be released by calling release_string
464// // and it is no longer valid after that call.
465// unsafe fn to_c_string(s: String) -> *mut c_char {
466//     match CString::new(s) {
467//         Ok(c_str) => c_str.into_raw(),
468//         Err(_) => std::ptr::null_mut(),
469//     }
470// }
471
472/// Returns a version string for logging.
473///
474/// # Safety
475/// The returned value MUST be released by calling release_string
476/// and it is no longer valid after that call.
477#[no_mangle]
478pub unsafe extern "C" fn c2pa_version() -> *mut c_char {
479    let version = format!(
480        "{}/{} {}/{}",
481        env!("CARGO_PKG_NAME"),
482        env!("CARGO_PKG_VERSION"),
483        c2pa::NAME,
484        c2pa::VERSION
485    );
486    to_c_string(version)
487}
488
489/// Returns the last error message.
490///
491/// # Safety
492/// The returned value MUST be released by calling release_string
493/// and it is no longer valid after that call.
494#[no_mangle]
495pub unsafe extern "C" fn c2pa_error() -> *mut c_char {
496    to_c_string(Error::last_message())
497}
498
499/// Sets the last error message.
500/// This is used by callbacks so they can set a return error message.
501/// THe error should be in the form of "ErrorType: ErrorMessage".
502/// If ErrorType is missing or invalid, it will be set to "Other".
503/// and the message will include the original error string.
504/// Can return -1 if the error string is NULL.
505///
506/// # Safety
507/// Reads from NULL-terminated C strings.
508#[no_mangle]
509pub unsafe extern "C" fn c2pa_error_set_last(error_str: *const c_char) -> c_int {
510    let error_str = cstr_or_return_int!(error_str);
511    CimplError::from(Error::from(error_str)).set_last();
512    0
513}
514
515/// Load Settings from a string.
516/// Sets thread-local settings.
517///
518/// # Errors
519/// Returns -1 if there were errors, otherwise returns 0.
520/// The error string can be retrieved by calling c2pa_error.
521///
522/// # Safety
523/// Reads from NULL-terminated C strings.
524#[no_mangle]
525#[deprecated(
526    note = "Use c2pa_settings_new() and c2pa_context_builder_set_settings() to configure a context explicitly."
527)]
528pub unsafe extern "C" fn c2pa_load_settings(
529    settings: *const c_char,
530    format: *const c_char,
531) -> c_int {
532    let settings = cstr_or_return_int!(settings);
533    let format = cstr_or_return_int!(format);
534    // The C API is inherently stateful: callers invoke c2pa_load_settings once and subsequent
535    // C API calls inherit those settings via thread-local storage. This is by design.
536    #[allow(deprecated)]
537    let result = C2paSettings::from_string(&settings, &format);
538    ok_or_return_int!(result);
539    0 // returns 0 on success
540}
541
542/// Creates a new C2PA settings object with default values.
543///
544/// # Safety
545///
546/// The returned pointer must be freed with `c2pa_free()`.
547///
548/// # Returns
549///
550/// A pointer to a newly allocated C2paSettings object, or NULL on allocation failure.
551#[no_mangle]
552pub unsafe extern "C" fn c2pa_settings_new() -> *mut C2paSettings {
553    box_tracked!(C2paSettings::new())
554}
555
556/// Updates settings from a JSON or TOML string.
557///
558/// # Safety
559///
560/// * `settings` must be a valid pointer to a C2paSettings object previously
561///   created by `c2pa_settings_new()` and not yet freed.
562/// * `settings_str` must be a valid pointer to a null-terminated UTF-8 string.
563/// * `format` must be a valid pointer to a null-terminated string containing
564///   either "json" or "toml".
565/// * The pointers must remain valid for the duration of this call.
566/// * This function is not thread-safe - do not call concurrently on the same `settings`.
567///
568/// # Returns
569///
570/// 0 on success, negative value on error.
571#[no_mangle]
572pub unsafe extern "C" fn c2pa_settings_update_from_string(
573    settings: *mut C2paSettings,
574    settings_str: *const c_char,
575    format: *const c_char,
576) -> c_int {
577    let settings = deref_mut_or_return_int!(settings, C2paSettings);
578    let settings_str = cstr_or_return_int!(settings_str);
579    let format = cstr_or_return_int!(format);
580    let result = settings.update_from_str(&settings_str, &format);
581    ok_or_return_int!(result);
582    0
583}
584
585/// Sets a specific configuration value in the settings using dot notation.
586///
587/// # Safety
588///
589/// * `settings` must be a valid pointer to a C2paSettings object previously
590///   created by `c2pa_settings_new()` and not yet freed.
591/// * `path` must be a valid pointer to a null-terminated UTF-8 string containing
592///   a dot-separated path (e.g., "verify.verify_after_sign").
593/// * `value` must be a valid pointer to a null-terminated UTF-8 string containing
594///   a JSON value (e.g., "true", "\"ps256\"", "42").
595/// * The pointers must remain valid for the duration of this call.
596/// * This function is not thread-safe - do not call concurrently on the same `settings`.
597///
598/// # Returns
599///
600/// 0 on success, negative value on error.
601#[no_mangle]
602pub unsafe extern "C" fn c2pa_settings_set_value(
603    settings: *mut C2paSettings,
604    path: *const c_char,
605    value: *const c_char,
606) -> c_int {
607    let settings = deref_mut_or_return_int!(settings, C2paSettings);
608    let path = cstr_or_return_int!(path);
609    let value_str = cstr_or_return_int!(value);
610
611    // Parse JSON value to determine type
612    let parsed_value: serde_json::Value = ok_or_return_int!(serde_json::from_str(&value_str)
613        .map_err(|e| c2pa::Error::BadParam(format!("Invalid JSON value: {e}"))));
614
615    // Convert to appropriate type and set value
616    let result = match parsed_value {
617        serde_json::Value::Bool(b) => settings.set_value(&path, b),
618        serde_json::Value::Number(n) if n.is_i64() => {
619            settings.set_value(&path, n.as_i64().unwrap())
620        }
621        serde_json::Value::Number(n) if n.is_f64() => {
622            settings.set_value(&path, n.as_f64().unwrap())
623        }
624        serde_json::Value::Number(_) => {
625            Err(c2pa::Error::BadParam("Invalid number format".to_string()))
626        }
627        serde_json::Value::String(s) => settings.set_value(&path, s),
628        serde_json::Value::Array(arr) => {
629            // Convert array to Vec<serde_json::Value> and try to extract strings
630            let strings: Result<Vec<String>, _> = arr
631                .into_iter()
632                .map(|v| {
633                    v.as_str().map(String::from).ok_or_else(|| {
634                        c2pa::Error::BadParam("Array values must be strings".to_string())
635                    })
636                })
637                .collect();
638            strings.and_then(|vec| settings.set_value(&path, vec))
639        }
640        serde_json::Value::Null => Err(c2pa::Error::BadParam("Cannot set null values".to_string())),
641        serde_json::Value::Object(_) => Err(c2pa::Error::BadParam(
642            "Cannot set object values directly, use update_from_string instead".to_string(),
643        )),
644    };
645
646    ok_or_return_int!(result);
647    0
648}
649
650/// Creates a new context builder with default settings.
651///
652/// Use this to construct a context with custom configuration. After setting up
653/// the builder, call `c2pa_context_builder_build()` to create an immutable context.
654///
655/// # Safety
656///
657/// The returned pointer must be freed by calling
658/// `c2pa_free()` or converted to a context with `c2pa_context_builder_build()`.
659///
660/// # Returns
661///
662/// A pointer to a newly allocated C2paContextBuilder object, or NULL on allocation failure.
663///
664/// # Example
665///
666/// ```c
667/// // Create and configure a builder
668/// C2paContextBuilder* builder = c2pa_context_builder_new();
669/// C2paSettings* settings = c2pa_settings_new();
670/// c2pa_settings_set_value(settings, "verify.verify_after_sign", "true");
671/// c2pa_context_builder_set_settings(builder, settings);
672/// c2pa_free(settings);
673///
674/// // Build immutable context
675/// C2paContext* ctx = c2pa_context_builder_build(builder);
676/// // builder is now invalid, ctx can be shared
677/// ```
678#[no_mangle]
679pub unsafe extern "C" fn c2pa_context_builder_new() -> *mut C2paContextBuilder {
680    box_tracked!(Context::new())
681}
682
683/// Updates the builder with settings.
684///
685/// This configures the builder with the provided settings. The settings are cloned
686/// internally, so the caller retains ownership. If this call fails, the builder
687/// remains in its previous valid state.
688///
689/// # Safety
690///
691/// * `builder` must be a valid pointer to a C2paContextBuilder object previously
692///   created by `c2pa_context_builder_new()` and not yet built.
693/// * `settings` must be a valid pointer to a C2paSettings object.
694/// * The pointers must remain valid for the duration of this call.
695/// * This function is not thread-safe - do not call concurrently on the same `builder`.
696///
697/// # Returns
698///
699/// 0 on success, negative value on error.
700#[no_mangle]
701pub unsafe extern "C" fn c2pa_context_builder_set_settings(
702    builder: *mut C2paContextBuilder,
703    settings: *mut C2paSettings,
704) -> c_int {
705    let builder = deref_mut_or_return_int!(builder, C2paContextBuilder);
706    let settings = deref_or_return_int!(settings, C2paSettings);
707    let result = builder.set_settings(settings);
708    ok_or_return_int!(result);
709    0
710}
711
712/// Set a Signer into the Builder's context.
713/// (The context will own the Signer from that point on).
714/// The signer will be available via `context.signer()` after
715/// building the context. If a signer is also configured in settings,
716/// the programmatic signer takes priority regardless of call order.
717///
718/// Works with any C2paSigner pointer, whether created by
719/// `c2pa_signer_from_info` or `c2pa_signer_create`.
720///
721/// # Safety
722///
723/// * `builder` must be a valid C2paContextBuilder pointer (not yet built).
724/// * `signer_ptr` must be a valid C2paSigner pointer. It is consumed by this
725///   call and must not be used or freed afterward.
726///
727/// # Returns
728///
729/// 0 on success, negative value on error.
730#[no_mangle]
731pub unsafe extern "C" fn c2pa_context_builder_set_signer(
732    builder: *mut C2paContextBuilder,
733    signer_ptr: *mut C2paSigner,
734) -> c_int {
735    let builder = deref_mut_or_return_int!(builder, C2paContextBuilder);
736    // Untrack the signer before taking ownership via Box::from_raw.
737    // This prevents double-free if C code later calls c2pa_signer_free().
738    untrack_or_return_int!(signer_ptr, C2paSigner);
739    let c2pa_signer = Box::from_raw(signer_ptr);
740    let result = builder.set_signer(c2pa_signer.signer);
741    ok_or_return_int!(result);
742    0
743}
744
745/// C-callable progress callback function type.
746///
747/// # Parameters
748/// * `context` – the opaque `user_data` pointer passed to
749///   `c2pa_context_builder_set_progress_callback`.
750/// * `phase`   – a [`C2paProgressPhase`] value identifying the current operation.
751///   Callers should derive any user-visible text from this value in the appropriate language.
752/// * `step`    – monotonically increasing counter within the current phase, starting at
753///   `1`.  Resets to `1` at the start of each new phase.  Use as a liveness heartbeat:
754///   a rising `step` means the SDK is making forward progress.  The unit is
755///   phase-specific and should otherwise be treated as opaque.
756/// * `total`   – `0` = indeterminate (show a spinner, use `step` as liveness signal);
757///   `1` = single-shot phase (the callback itself is the notification);
758///   `> 1` = determinate (`step / total` gives a completion fraction for a progress bar).
759///
760/// # Return value
761/// Return non-zero to continue the operation, zero to cancel.
762pub type ProgressCCallback = unsafe extern "C" fn(
763    context: *const c_void,
764    phase: C2paProgressPhase,
765    step: u32,
766    total: u32,
767) -> c_int;
768
769/// Attaches a C progress callback to a context builder.
770///
771/// The `callback` is invoked at key checkpoints during signing and reading
772/// operations.  Returning `0` from the callback requests cancellation; the SDK
773/// will return an error at the next safe stopping point.
774///
775/// # Parameters
776/// * `builder`  – a valid `C2paContextBuilder` pointer.
777/// * `user_data` – opaque `void*` captured by the closure and passed as the first argument
778///   of every `callback` invocation.  Pass `NULL` if the callback does not need user data.
779/// * `callback` – C function pointer matching [`ProgressCCallback`].
780///
781/// # Returns
782/// `0` on success, non-zero on error (check `c2pa_error()`).
783///
784/// # Safety
785/// * `builder` must be valid and not yet built.
786/// * `user_data` must remain valid for the entire lifetime of the built context.
787#[no_mangle]
788pub unsafe extern "C" fn c2pa_context_builder_set_progress_callback(
789    builder: *mut C2paContextBuilder,
790    user_data: *const c_void,
791    callback: ProgressCCallback,
792) -> c_int {
793    let builder = deref_mut_or_return_int!(builder, C2paContextBuilder);
794    let ud = user_data as usize;
795    let c_callback = move |phase: ProgressPhase, step: u32, total: u32| unsafe {
796        (callback)(ud as *const c_void, phase.into(), step, total) != 0
797    };
798    builder.set_progress_callback(c_callback);
799    0
800}
801
802/// Creates a new HTTP resolver backed by a C callback.
803///
804/// The `context` pointer is passed unmodified to every callback invocation and
805/// must remain valid for the lifetime of the resolver and any context built from it.
806///
807/// # Safety
808///
809/// * `callback` must be a valid function pointer that remains valid for the
810///   lifetime of the resolver.
811/// * `context` must remain valid for the lifetime of the resolver and any
812///   context that uses it.
813/// * `context` must be safe to use from any thread (i.e. the caller upholds
814///   `Send + Sync` semantics for the pointed-to data).
815///
816/// # Returns
817///
818/// A new `C2paHttpResolver*`, or NULL on error. Must be freed with `c2pa_free()`
819/// OR consumed by `c2pa_context_builder_set_http_resolver()`.
820#[no_mangle]
821pub unsafe extern "C" fn c2pa_http_resolver_create(
822    context: *const c_void,
823    callback: C2paHttpResolverCallback,
824) -> *mut C2paHttpResolver {
825    box_tracked!(C2paHttpResolver { context, callback })
826}
827
828/// Sets a custom HTTP resolver on the context builder.
829///
830/// The builder takes ownership of the resolver; the caller must NOT free it afterward.
831///
832/// # Safety
833///
834/// * `builder` must be a valid C2paContextBuilder pointer (not yet built).
835/// * `resolver_ptr` is consumed and must not be used or freed afterward.
836///
837/// # Returns
838///
839/// 0 on success, -1 on error.
840#[no_mangle]
841pub unsafe extern "C" fn c2pa_context_builder_set_http_resolver(
842    builder: *mut C2paContextBuilder,
843    resolver_ptr: *mut C2paHttpResolver,
844) -> c_int {
845    let builder = deref_mut_or_return_int!(builder, C2paContextBuilder);
846    untrack_or_return_int!(resolver_ptr, C2paHttpResolver);
847    let c2pa_resolver = Box::from_raw(resolver_ptr);
848    let result = builder.set_resolver(*c2pa_resolver);
849    ok_or_return_int!(result);
850    0
851}
852
853/// Builds an immutable, shareable context from the builder.
854///
855/// The builder is consumed by this operation and becomes invalid.
856/// The returned context is immutable and can be shared between multiple
857/// Reader and Builder instances.
858///
859/// # Safety
860///
861/// * `builder` must be a valid pointer to a C2paContextBuilder.
862/// * After calling this function, the builder pointer is INVALID and must not be used again.
863/// * The returned context must be freed with `c2pa_free()`.
864///
865/// # Returns
866///
867/// A pointer to an immutable C2paContext that can be shared, or NULL on error.
868#[no_mangle]
869pub unsafe extern "C" fn c2pa_context_builder_build(
870    builder: *mut C2paContextBuilder,
871) -> *mut C2paContext {
872    untrack_or_return_null!(builder, C2paContextBuilder);
873    let context = Box::from_raw(builder);
874    box_tracked!((*context).into_shared())
875}
876
877/// Creates a new immutable context with default settings.
878///
879/// This is a convenience function equivalent to:
880/// ```c
881/// builder = c2pa_context_builder_new();
882/// ctx = c2pa_context_builder_build(builder);
883/// ```
884///
885/// Use `c2pa_context_builder_new()` if you need to configure the context
886/// before building it.
887///
888/// # Safety
889///
890/// The returned pointer must be freed with `c2pa_free()`.
891///
892/// # Returns
893///
894/// A pointer to a newly allocated immutable C2paContext object, or NULL on allocation failure.
895#[no_mangle]
896pub unsafe extern "C" fn c2pa_context_new() -> *mut C2paContext {
897    box_tracked!(Context::new().into_shared())
898}
899
900/// Requests cancellation of any in-progress operation on this context.
901///
902/// Thread-safe — may be called from any thread that holds a valid `C2paContext`
903/// pointer.  The SDK will return an `OperationCancelled` error at the next safe
904/// checkpoint inside the running operation.
905///
906/// # Parameters
907/// * `ctx` – a valid, non-null `C2paContext` pointer obtained from
908///   `c2pa_context_builder_build()` or `c2pa_context_new()`.
909///
910/// # Returns
911/// `0` on success, non-zero if `ctx` is null or invalid.
912///
913/// # Safety
914/// `ctx` must be a valid pointer and must not be freed concurrently with this call.
915#[no_mangle]
916pub unsafe extern "C" fn c2pa_context_cancel(ctx: *mut C2paContext) -> c_int {
917    let ctx = deref_or_return_int!(ctx, C2paContext);
918    ctx.cancel();
919    0
920}
921
922///
923/// # Errors
924/// Returns NULL if there were errors, otherwise returns a JSON string.
925/// The error string can be retrieved by calling c2pa_error.
926///
927/// # Safety
928/// Reads from NULL-terminated C strings.
929/// The returned value MUST be released by calling release_string
930/// and it is no longer valid after that call.
931#[cfg(feature = "file_io")]
932#[no_mangle]
933#[deprecated(
934    note = "Use c2pa_reader_from_context() with an explicit context for new implementations."
935)]
936#[allow(deprecated)]
937pub unsafe extern "C" fn c2pa_read_file(
938    path: *const c_char,
939    data_dir: *const c_char,
940) -> *mut c_char {
941    let path = cstr_or_return_null!(path);
942    let data_dir = cstr_option!(data_dir);
943
944    let result = read_file(&path, data_dir);
945    let json = ok_or_return_null!(result);
946    to_c_string(json)
947}
948
949/// Returns an Ingredient JSON string from a file path.
950///
951/// Any thumbnail or C2PA data will be written to data_dir if provided.
952///
953/// # Errors
954/// Returns NULL if there were errors, otherwise returns a JSON string
955/// containing the Ingredient.
956/// The error string can be retrieved by calling c2pa_error.
957///
958/// # Safety
959/// Reads from NULL-terminated C strings.
960/// The returned value MUST be released by calling release_string
961/// and it is no longer valid after that call.
962#[cfg(feature = "file_io")]
963#[no_mangle]
964#[deprecated(
965    note = "Use c2pa_builder_add_ingredient_from_stream() with an explicit context for new implementations."
966)]
967#[allow(deprecated)]
968pub unsafe extern "C" fn c2pa_read_ingredient_file(
969    path: *const c_char,
970    data_dir: *const c_char,
971) -> *mut c_char {
972    let path = cstr_or_return_null!(path);
973    let data_dir = cstr_or_return_null!(data_dir);
974    // Legacy C API: uses thread-local settings. Use c2pa_reader_from_context for new implementations.
975    let result = Ingredient::from_file_with_folder(path, data_dir).map_err(Error::from_c2pa_error);
976    let ingredient = ok_or_return_null!(result);
977    let json = serde_json::to_string(&ingredient).unwrap_or_default();
978    to_c_string(json)
979}
980
981#[repr(C)]
982/// Defines the configuration for a Signer.
983///
984/// The signer is created from the sign_cert and private_key fields.
985/// an optional url to an RFC 3161 compliant time server will ensure the signature is timestamped.
986pub struct C2paSignerInfo {
987    /// The signing algorithm.
988    pub alg: *const c_char,
989    /// The public certificate chain in PEM format.
990    pub sign_cert: *const c_char,
991    /// The private key in PEM format.
992    pub private_key: *const c_char,
993    /// The timestamp authority URL or NULL.
994    pub ta_url: *const c_char,
995}
996
997/// Add a signed manifest to the file at path with the given signer information.
998///
999/// # Errors
1000/// Returns an error field if there were errors.
1001///
1002/// # Safety
1003/// Reads from NULL-terminated C strings.
1004/// The returned value MUST be released by calling release_string
1005/// and it is no longer valid after that call.
1006#[cfg(feature = "file_io")]
1007#[no_mangle]
1008#[deprecated(
1009    note = "Use c2pa_builder_from_context() with c2pa_builder_sign_to_stream() for new implementations."
1010)]
1011#[allow(deprecated)]
1012pub unsafe extern "C" fn c2pa_sign_file(
1013    source_path: *const c_char,
1014    dest_path: *const c_char,
1015    manifest: *const c_char,
1016    signer_info: &C2paSignerInfo,
1017    data_dir: *const c_char,
1018) -> *mut c_char {
1019    // Convert C pointers into Rust.
1020    let source_path = cstr_or_return_null!(source_path);
1021    let dest_path = cstr_or_return_null!(dest_path);
1022    let manifest = cstr_or_return_null!(manifest);
1023    let data_dir = cstr_option!(data_dir);
1024
1025    let signer_info = SignerInfo {
1026        alg: cstr_or_return_null!(signer_info.alg),
1027        sign_cert: cstr_or_return_null!(signer_info.sign_cert).into_bytes(),
1028        private_key: cstr_or_return_null!(signer_info.private_key).into_bytes(),
1029        ta_url: cstr_option!(signer_info.ta_url),
1030    };
1031    // Read manifest from JSON and then sign and write it.
1032    let result = sign_file(&source_path, &dest_path, &manifest, &signer_info, data_dir);
1033    ok_or_return_null!(result); // we don't return the bytes, just an empty string for ok
1034    to_c_string("".to_string())
1035}
1036
1037/// Frees a string allocated by Rust.
1038///
1039/// # Safety
1040/// The string must not have been modified in C.
1041/// The string can only be freed once and is invalid after this call.
1042#[no_mangle]
1043#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
1044pub unsafe extern "C" fn c2pa_release_string(s: *mut c_char) {
1045    cimpl_free!(s);
1046}
1047
1048/// Frees any pointer allocated by this library.
1049///
1050/// This is the **recommended** free function for all C2PA objects. It replaces the
1051/// type-specific free functions (like `c2pa_string_free`, `c2pa_reader_free`, etc.)
1052/// which are maintained only for backward compatibility.
1053///
1054/// ## Why use c2pa_free?
1055///
1056/// - **Simpler API**: One function to remember instead of multiple type-specific functions
1057/// - **Less error-prone**: No need to match the correct free function to each type
1058/// - **Consistent**: Works uniformly across all pointer types
1059/// - **Returns error codes**: Returns 0 on success, -1 on error for better error handling
1060///
1061/// ## Supported Types
1062///
1063/// This function works for all C2PA objects including:
1064/// - C2paContext
1065/// - C2paSettings
1066/// - C2paBuilder
1067/// - C2paReader
1068/// - C2paSigner
1069/// - strings (c_char*)
1070/// - byte arrays (c_uchar*)
1071/// - manifest bytes
1072/// - signatures
1073/// - and any other objects created by this library
1074///
1075/// # Safety
1076///
1077/// * The pointer must have been allocated by this library (e.g., from c2pa_context_new(),
1078///   c2pa_settings_new(), c2pa_builder_from_json(), etc.)
1079/// * The pointer must not have been modified in C.
1080/// * The pointer can only be freed once and is invalid after this call.
1081/// * Do not mix this with type-specific free functions for the same pointer.
1082///
1083/// # Returns
1084///
1085/// 0 on success, -1 on error (e.g., if the pointer was not allocated by this library).
1086///
1087/// # Example (C)
1088///
1089/// ```c
1090/// // Create various objects
1091/// C2paReader* reader = c2pa_reader_from_file("image.jpg", "json");
1092/// char* json = c2pa_reader_json(reader);
1093/// C2paBuilder* builder = c2pa_builder_from_json(json);
1094///
1095/// // Free everything with the same function
1096/// c2pa_free(json);
1097/// c2pa_free(reader);
1098/// c2pa_free(builder);
1099/// ```
1100#[no_mangle]
1101pub unsafe extern "C" fn c2pa_free(ptr: *const c_void) -> c_int {
1102    cimpl_free!(ptr as *mut c_void)
1103}
1104
1105/// Frees a string allocated by Rust.
1106///
1107/// **Note**: This function is maintained for backward compatibility. New code should
1108/// use [`c2pa_free`] instead, which works for all pointer types.
1109///
1110/// # Safety
1111/// Reads from NULL-terminated C strings.
1112/// The string must not have been modified in C.
1113/// The string can only be freed once and is invalid after this call.
1114#[no_mangle]
1115#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
1116pub unsafe extern "C" fn c2pa_string_free(s: *mut c_char) {
1117    cimpl_free!(s);
1118}
1119
1120/// Frees an array of char* pointers created by Rust.
1121///
1122/// # Parameters
1123/// * ptr: pointer to the array of char* pointers.
1124/// * count: number of elements in the array.
1125///
1126/// # Safety
1127/// * The ptr passed into this function must point to memory that was allocated
1128///   by our library.
1129/// * The array and its strings must not have been modified in C.
1130/// * The array and its contents can only be freed once and is invalid after this call.
1131#[no_mangle]
1132pub unsafe extern "C" fn c2pa_free_string_array(ptr: *const *const c_char, count: usize) {
1133    if ptr.is_null() {
1134        return;
1135    }
1136
1137    let mut_ptr = ptr as *mut *mut c_char;
1138    // Free each string directly using the pointer.
1139    #[allow(deprecated)]
1140    for i in 0..count {
1141        c2pa_string_free(*mut_ptr.add(i));
1142    }
1143
1144    // Free the array.
1145    Vec::from_raw_parts(mut_ptr, count, count);
1146}
1147
1148// Run CAWG post-validation - this is async and requires a runtime.
1149fn post_validate(result: Result<C2paReader, c2pa::Error>) -> Result<C2paReader, c2pa::Error> {
1150    match result {
1151        Ok(mut reader) => {
1152            #[cfg(target_arch = "wasm32")]
1153            let runtime = Builder::new_current_thread().enable_all().build();
1154
1155            #[cfg(not(target_arch = "wasm32"))]
1156            let runtime = Builder::new_multi_thread().enable_all().build();
1157
1158            let runtime = match runtime {
1159                Ok(runtime) => runtime,
1160                Err(err) => return Err(c2pa::Error::OtherError(Box::new(err))),
1161            };
1162
1163            match runtime.block_on(reader.post_validate_async(&CawgValidator {})) {
1164                Ok(_) => Ok(reader),
1165                Err(err) => Err(err),
1166            }
1167        }
1168        Err(err) => Err(err),
1169    }
1170}
1171
1172/// Creates a new C2paReader from a default context.
1173///
1174/// # Safety
1175///
1176/// This function is safe to call with no preconditions.
1177///
1178/// # Returns
1179/// A pointer to a newly allocated C2paReader, or NULL on error.
1180/// The returned pointer must be freed with `c2pa_free()`.
1181#[no_mangle]
1182pub unsafe extern "C" fn c2pa_reader_new() -> *mut C2paReader {
1183    box_tracked!(C2paReader::from_context(Context::default()))
1184}
1185
1186/// Creates a new C2paReader from a shared context.
1187///
1188/// The context can be reused to create multiple readers and builders.
1189///
1190/// # Safety
1191///
1192/// * `context` must be a valid pointer to a C2paContext object.
1193/// * The context pointer remains valid after this call and can be reused.
1194///
1195/// # Returns
1196///
1197/// A pointer to a newly allocated C2paReader, or NULL on error.
1198#[no_mangle]
1199pub unsafe extern "C" fn c2pa_reader_from_context(context: *mut C2paContext) -> *mut C2paReader {
1200    let context = deref_or_return_null!(context, C2paContext);
1201    box_tracked!(C2paReader::from_shared_context(context))
1202}
1203
1204/// # Example
1205/// ```c
1206/// auto result = c2pa_reader_from_stream("image/jpeg", stream);
1207/// if (result == NULL) {
1208///     let error = c2pa_error();
1209///     printf("Error: %s\n", error);
1210///     c2pa_string_free(error);
1211/// }
1212/// ```
1213///
1214/// # Safety
1215/// format must be a valid NULL-terminated C string pointer.
1216/// stream must be a valid pointer to a C2paStream.
1217#[no_mangle]
1218#[deprecated(
1219    note = "Use c2pa_reader_from_context() with an explicit context instead of relying on thread-local settings."
1220)]
1221pub unsafe extern "C" fn c2pa_reader_from_stream(
1222    format: *const c_char,
1223    stream: *mut C2paStream,
1224) -> *mut C2paReader {
1225    let format = cstr_or_return_null!(format);
1226    let stream = deref_mut_or_return_null!(stream, C2paStream);
1227
1228    // Legacy C API: inherits thread-local settings set by c2pa_load_settings.
1229    // Prefer c2pa_reader_from_context for new C API usage.
1230    #[allow(deprecated)]
1231    let result = C2paReader::from_stream(&format, stream);
1232    let result = ok_or_return_null!(post_validate(result));
1233    box_tracked!(result)
1234}
1235
1236/// Configures an existing reader with a stream.
1237///
1238/// This method consumes the original reader and returns a new configured reader.
1239/// The original reader pointer becomes invalid after this call.
1240///
1241/// # Safety
1242///
1243/// * `reader` must be a valid pointer to a C2paReader.
1244/// * `format` must be a valid null-terminated string with the MIME type.
1245/// * `stream` must be a valid pointer to a C2paStream.
1246/// * After calling this function, the `reader` pointer is INVALID.
1247///
1248/// # Returns
1249///
1250/// A pointer to a newly configured C2paReader, or NULL on error.
1251#[no_mangle]
1252pub unsafe extern "C" fn c2pa_reader_with_stream(
1253    reader: *mut C2paReader,
1254    format: *const c_char,
1255    stream: *mut C2paStream,
1256) -> *mut C2paReader {
1257    // Validate inputs first, while reader is still tracked
1258    let format = cstr_or_return_null!(format);
1259    let stream = deref_mut_or_return_null!(stream, C2paStream);
1260
1261    // Now safe to take ownership - all validations passed
1262    untrack_or_return_null!(reader, C2paReader);
1263    let reader = Box::from_raw(reader);
1264    let result = (*reader).with_stream(&format, stream);
1265    let result = ok_or_return_null!(post_validate(result));
1266    box_tracked!(result)
1267}
1268
1269/// Configures an existing passed in Reader with manifest data and a stream.
1270/// This covers the case when a Reader needs to be able to re-read signed
1271/// manifest bytes. This method consumes the original Reader and returns a
1272/// new configured Reader. The original Reader pointer becomes invalid after
1273/// this call and should not be reused.
1274///
1275/// # Safety
1276///
1277/// * `reader` must be a valid pointer to a configured C2paReader
1278///   (usually with a Context).
1279/// * `format` must be a valid null-terminated string with the MIME type.
1280/// * `stream` must be a valid pointer to a C2paStream.
1281/// * `manifest_data` must be a valid pointer to manifest bytes.
1282/// * `manifest_size` must be the length of the manifest_data buffer.
1283/// * After calling this function, the `reader` pointer becomes invalid.
1284///
1285/// # Returns
1286///
1287/// A pointer to a newly configured C2paReader, or NULL on error.
1288#[no_mangle]
1289pub unsafe extern "C" fn c2pa_reader_with_manifest_data_and_stream(
1290    reader: *mut C2paReader,
1291    format: *const c_char,
1292    stream: *mut C2paStream,
1293    manifest_data: *const c_uchar,
1294    manifest_size: usize,
1295) -> *mut C2paReader {
1296    let format = cstr_or_return_null!(format);
1297    let stream = deref_mut_or_return_null!(stream, C2paStream);
1298    let manifest_bytes = bytes_or_return_null!(manifest_data, manifest_size, "manifest_data");
1299
1300    // Take ownership of the Reader (needs to remove it from tracking to take it)
1301    untrack_or_return_null!(reader, C2paReader);
1302    let reader = Box::from_raw(reader);
1303    let result = (*reader).with_manifest_data_and_stream(manifest_bytes, &format, stream);
1304    let result = ok_or_return_null!(post_validate(result));
1305
1306    // New reader, will be tracked now too
1307    box_tracked!(result)
1308}
1309
1310/// Configures an existing reader with a fragment stream.
1311///
1312/// This is used for fragmented BMFF media formats where manifests are stored
1313/// in separate fragments. This method consumes the original reader and returns
1314/// a new configured reader. The original reader pointer becomes invalid after this call.
1315///
1316/// # Safety
1317///
1318/// * `reader` must be a valid pointer to a C2paReader.
1319/// * `format` must be a valid null-terminated string with the MIME type.
1320/// * `stream` must be a valid pointer to a C2paStream (the main asset stream).
1321/// * `fragment` must be a valid pointer to a C2paStream (the fragment stream).
1322/// * After calling this function, the `reader` pointer is INVALID.
1323///
1324/// # Returns
1325///
1326/// A pointer to a newly configured C2paReader, or NULL on error.
1327///
1328/// # Example
1329///
1330/// ```c
1331/// C2paReader* reader = c2pa_reader_from_context(ctx);
1332/// C2paReader* new_reader = c2pa_reader_with_fragment(reader, "video/mp4", main_stream, fragment_stream);
1333/// // reader is now invalid, use new_reader
1334/// ```
1335#[no_mangle]
1336pub unsafe extern "C" fn c2pa_reader_with_fragment(
1337    reader: *mut C2paReader,
1338    format: *const c_char,
1339    stream: *mut C2paStream,
1340    fragment: *mut C2paStream,
1341) -> *mut C2paReader {
1342    // Validate inputs first, while reader is still tracked
1343    let format = cstr_or_return_null!(format);
1344    let stream = deref_mut_or_return_null!(stream, C2paStream);
1345    let fragment = deref_mut_or_return_null!(fragment, C2paStream);
1346
1347    // Now safe to take ownership - all validations passed
1348    untrack_or_return_null!(reader, C2paReader);
1349    let reader = Box::from_raw(reader);
1350    let result = (*reader).with_fragment(&format, stream, fragment);
1351    let result = ok_or_return_null!(post_validate(result));
1352    box_tracked!(result)
1353}
1354
1355/// Creates a new C2paReader from a shared Context.
1356///
1357/// # Safety
1358///
1359/// * `context` must be a valid pointer to a C2paContext object.
1360/// * The context pointer remains valid after this call and can be reused.
1361///
1362/// # Returns
1363///
1364/// A pointer to a newly allocated C2paReader, or NULL on error.
1365/// Creates and verifies a C2paReader from a file path.
1366/// This allows a client to use Rust's file I/O to read the file
1367/// Parameters
1368/// * path: pointer to a C string with the file path in UTF-8.
1369///
1370/// # Errors
1371/// Returns NULL if there were errors, otherwise returns a pointer to a ManifestStore.
1372/// The error string can be retrieved by calling c2pa_error.
1373///
1374/// # Safety
1375/// Reads from NULL-terminated C strings.
1376/// The returned value MUST be released by calling c2pa_free
1377/// and it is no longer valid after that call.
1378///
1379/// # Example
1380/// ```c
1381/// auto result = c2pa_reader_from_file("path/to/file.jpg");
1382/// if (result == NULL) {
1383///    let error = c2pa_error();
1384///   printf("Error: %s\n", error);
1385///   c2pa_string_free(error);
1386/// }
1387/// }
1388/// ```
1389#[cfg(feature = "file_io")]
1390#[no_mangle]
1391#[deprecated(
1392    note = "Use c2pa_reader_from_context() with an explicit context instead of relying on thread-local settings."
1393)]
1394#[allow(deprecated)]
1395pub unsafe fn c2pa_reader_from_file(path: *const c_char) -> *mut C2paReader {
1396    let path = cstr_or_return_null!(path);
1397    // Legacy C API: inherits thread-local settings set by c2pa_load_settings.
1398    let result = C2paReader::from_file(&path);
1399    box_tracked!(ok_or_return_null!(post_validate(result)))
1400}
1401
1402/// Creates and verifies a C2paReader from an asset stream with the given format and manifest data.
1403///
1404/// Parameters
1405/// * format: pointer to a C string with the mime type or extension.
1406/// * stream: pointer to a C2paStream.
1407/// * manifest_data: pointer to the manifest data bytes.
1408/// * manifest_size: size of the manifest data bytes.
1409///
1410/// # Errors
1411/// Returns NULL if there were errors, otherwise returns a pointer to a ManifestStore.
1412/// The error string can be retrieved by calling c2pa_error.
1413///
1414/// # Safety
1415/// Reads from NULL-terminated C strings.
1416/// The returned value MUST be released by calling c2pa_free
1417/// and it is no longer valid after that call.
1418#[no_mangle]
1419#[deprecated(
1420    note = "Use c2pa_reader_from_context() then c2pa_reader_with_manifest_data_and_stream() instead."
1421)]
1422pub unsafe extern "C" fn c2pa_reader_from_manifest_data_and_stream(
1423    format: *const c_char,
1424    stream: *mut C2paStream,
1425    manifest_data: *const c_uchar,
1426    manifest_size: usize,
1427) -> *mut C2paReader {
1428    let format = cstr_or_return_null!(format);
1429    let stream = deref_mut_or_return_null!(stream, C2paStream);
1430
1431    let manifest_bytes = bytes_or_return_null!(manifest_data, manifest_size, "manifest_data");
1432
1433    // Legacy C API: inherits thread-local settings set by c2pa_load_settings.
1434    #[allow(deprecated)]
1435    let result = C2paReader::from_manifest_data_and_stream(manifest_bytes, &format, stream);
1436    box_tracked!(ok_or_return_null!(post_validate(result)))
1437}
1438
1439/// Frees a C2paReader allocated by Rust.
1440///
1441/// **Note**: This function is maintained for backward compatibility. New code should
1442/// use [`c2pa_free`] instead, which works for all pointer types.
1443///
1444/// # Safety
1445/// The C2paReader can only be freed once and is invalid after this call.
1446#[no_mangle]
1447#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
1448pub unsafe extern "C" fn c2pa_reader_free(reader_ptr: *mut C2paReader) {
1449    cimpl_free!(reader_ptr);
1450}
1451
1452/// Returns a JSON string generated from a C2paReader.
1453///
1454/// # Safety
1455/// The returned value MUST be released by calling c2pa_free
1456/// and it is no longer valid after that call.
1457#[no_mangle]
1458pub unsafe extern "C" fn c2pa_reader_json(reader_ptr: *mut C2paReader) -> *mut c_char {
1459    let c2pa_reader = deref_or_return_null!(reader_ptr, C2paReader);
1460    to_c_string(c2pa_reader.json())
1461}
1462
1463/// Returns a detailed JSON string generated from a C2paReader.
1464///
1465/// # Safety
1466/// The returned value MUST be released by calling c2pa_free
1467/// and it is no longer valid after that call.
1468#[no_mangle]
1469pub unsafe extern "C" fn c2pa_reader_detailed_json(reader_ptr: *mut C2paReader) -> *mut c_char {
1470    let c2pa_reader = deref_or_return_null!(reader_ptr, C2paReader);
1471
1472    to_c_string(c2pa_reader.detailed_json())
1473}
1474
1475/// Returns the remote url of the manifest if it was obtained remotely.
1476///
1477/// # Parameters
1478/// * reader_ptr: pointer to a C2paReader.
1479///
1480/// # Safety
1481/// reader_ptr must be a valid pointer to a C2paReader.
1482#[no_mangle]
1483pub unsafe extern "C" fn c2pa_reader_remote_url(reader_ptr: *mut C2paReader) -> *const c_char {
1484    let c2pa_reader = deref_or_return_null!(reader_ptr, C2paReader);
1485
1486    option_to_c_string!(c2pa_reader.remote_url())
1487}
1488
1489/// Returns if the reader was created from an embedded manifest.
1490///
1491/// # Parameters
1492/// * reader_ptr: pointer to a C2paReader.
1493///
1494/// # Safety
1495/// reader_ptr must be a valid pointer to a C2paReader.
1496#[no_mangle]
1497pub unsafe extern "C" fn c2pa_reader_is_embedded(reader_ptr: *mut C2paReader) -> bool {
1498    let c2pa_reader = deref_or_return_false!(reader_ptr, C2paReader);
1499
1500    c2pa_reader.is_embedded()
1501}
1502
1503/// Writes a C2paReader resource to a stream given a URI.
1504///
1505/// The resource uri should match an identifier in the the manifest store.
1506///
1507/// # Parameters
1508/// * reader_ptr: pointer to a Reader.
1509/// * uri: pointer to a C string with the URI to identify the resource.
1510/// * stream: pointer to a writable C2paStream.
1511///
1512/// # Errors
1513/// Returns -1 if there were errors, otherwise returns size of stream written.
1514///
1515/// # Safety
1516/// Reads from NULL-terminated C strings.
1517///
1518/// # Example
1519/// ```c
1520/// auto result = c2pa_reader_resource_to_stream(store, "uri", stream);
1521/// if (result < 0) {
1522///     auto error = c2pa_error();
1523///     printf("Error: %s\n", error);
1524///     c2pa_string_free(error);
1525/// }
1526/// ```
1527#[no_mangle]
1528pub unsafe extern "C" fn c2pa_reader_resource_to_stream(
1529    reader_ptr: *mut C2paReader,
1530    uri: *const c_char,
1531    stream: *mut C2paStream,
1532) -> i64 {
1533    let uri = cstr_or_return_int!(uri);
1534    let reader = deref_mut_or_return_int!(reader_ptr, C2paReader);
1535    let result = reader.resource_to_stream(&uri, &mut (*stream));
1536    let len = ok_or_return_int!(result);
1537    len as i64
1538}
1539
1540/// Returns an array of char* pointers to c2pa::Reader's supported mime types.
1541/// The caller is responsible for freeing the array.
1542///
1543/// # Parameters
1544/// * count: pointer to a usize to return the number of mime types.
1545///
1546/// # Safety
1547/// The returned value MUST be released by calling [c2pa_free_string_array].
1548/// The array and its contents are no longer valid after that call.
1549#[no_mangle]
1550pub unsafe extern "C" fn c2pa_reader_supported_mime_types(
1551    count: *mut usize,
1552) -> *const *const c_char {
1553    c2pa_mime_types_to_c_array(C2paReader::supported_mime_types(), count)
1554}
1555
1556/// Creates a C2paBuilder from a JSON manifest definition string.
1557///
1558/// # Errors
1559/// Returns NULL if there were errors, otherwise returns a pointer to a Builder.
1560/// The error string can be retrieved by calling c2pa_error.
1561///
1562/// # Safety
1563/// Reads from NULL-terminated C strings.
1564/// The returned value MUST be released by calling c2pa_free
1565/// and it is no longer valid after that call.
1566///
1567/// # Example
1568/// ```c
1569/// auto result = c2pa_builder_from_json(manifest_json);
1570/// if (result == NULL) {
1571///     auto error = c2pa_error();
1572///     printf("Error: %s\n", error);
1573///     c2pa_string_free(error);
1574/// }
1575/// ```
1576#[no_mangle]
1577#[deprecated(note = "Use c2pa_builder_from_context() then c2pa_builder_set_definition() instead.")]
1578pub unsafe extern "C" fn c2pa_builder_from_json(manifest_json: *const c_char) -> *mut C2paBuilder {
1579    let manifest_json = cstr_or_return_null!(manifest_json);
1580    // Legacy C API: inherits thread-local settings set by c2pa_load_settings.
1581    // Prefer c2pa_builder_from_context for new C API usage.
1582    #[allow(deprecated)]
1583    let result = C2paBuilder::from_json(&manifest_json);
1584    let result = ok_or_return_null!(result);
1585    box_tracked!(result)
1586}
1587
1588/// Creates a C2paBuilder from a shared Context.
1589///
1590/// The context can be reused to create multiple builders and readers.
1591/// The builder will inherit the context's settings, signers, and resolvers.
1592///
1593/// # Safety
1594///
1595/// * `context` must be a valid pointer to a C2paContext object.
1596/// * The context pointer remains valid after this call and can be reused.
1597///
1598/// # Returns
1599///
1600/// A pointer to a newly allocated C2paBuilder, or NULL on error.
1601///
1602/// # Example
1603///
1604/// ```c
1605/// C2paContext* ctx = c2pa_context_new();
1606/// C2paBuilder* builder = c2pa_builder_from_context(ctx);
1607/// // context can still be used
1608/// C2paReader* reader = c2pa_reader_from_context(ctx);
1609/// ```
1610#[no_mangle]
1611pub unsafe extern "C" fn c2pa_builder_from_context(context: *mut C2paContext) -> *mut C2paBuilder {
1612    let context = deref_or_return_null!(context, C2paContext);
1613    box_tracked!(C2paBuilder::from_shared_context(context))
1614}
1615
1616/// Create a C2paBuilder from an archive stream.
1617///
1618/// # Errors
1619/// Returns NULL if there were errors, otherwise returns a pointer to a Builder.
1620/// The error string can be retrieved by calling c2pa_error.
1621///
1622/// # Safety
1623/// Reads from NULL-terminated C strings.
1624/// The returned value MUST be released by calling c2pa_free
1625/// and it is no longer valid after that call.
1626///
1627/// # Example
1628/// ```c
1629/// auto result = c2pa_builder_from_archive(stream);
1630/// if (result == NULL) {
1631///     auto error = c2pa_error();
1632///     printf("Error: %s\n", error);
1633///     c2pa_string_free(error);
1634/// }
1635/// ```
1636#[no_mangle]
1637#[deprecated(note = "Use c2pa_builder_from_context() then c2pa_builder_with_archive() instead.")]
1638#[allow(deprecated)]
1639pub unsafe extern "C" fn c2pa_builder_from_archive(stream: *mut C2paStream) -> *mut C2paBuilder {
1640    let stream = deref_mut_or_return_null!(stream, C2paStream);
1641    box_tracked!(ok_or_return_null!(C2paBuilder::from_archive(
1642        &mut (*stream)
1643    )))
1644}
1645
1646/// Returns an array of char* pointers to the supported mime types.
1647/// The caller is responsible for freeing the array.
1648///
1649/// # Parameters
1650/// * count: pointer to a usize to return the number of mime types.
1651///
1652/// # Safety
1653/// The returned value MUST be released by calling [c2pa_free_string_array].
1654/// The array and its contents are no longer valid after that call.
1655#[no_mangle]
1656pub unsafe extern "C" fn c2pa_builder_supported_mime_types(
1657    count: *mut usize,
1658) -> *const *const c_char {
1659    c2pa_mime_types_to_c_array(C2paBuilder::supported_mime_types(), count)
1660}
1661
1662/// Frees a C2paBuilder allocated by Rust.
1663///
1664/// **Note**: This function is maintained for backward compatibility. New code should
1665/// use [`c2pa_free`] instead, which works for all pointer types.
1666///
1667/// # Safety
1668/// The C2paBuilder can only be freed once and is invalid after this call.
1669#[no_mangle]
1670#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
1671pub unsafe extern "C" fn c2pa_builder_free(builder_ptr: *mut C2paBuilder) {
1672    cimpl_free!(builder_ptr);
1673}
1674
1675/// Updates the builder with a new manifest definition.
1676///
1677/// This consumes the original builder and returns a new configured builder.
1678/// The original builder pointer becomes invalid after this call.
1679///
1680/// # Safety
1681///
1682/// * `builder` must be a valid pointer to a C2paBuilder.
1683/// * `manifest_json` must be a valid null-terminated JSON string.
1684/// * After calling this function, the `builder` pointer is INVALID.
1685///
1686/// # Returns
1687///
1688/// A pointer to a newly configured C2paBuilder, or NULL on error.
1689///
1690/// # Example
1691///
1692/// ```c
1693/// C2paBuilder* builder = c2pa_builder_from_context(ctx);
1694/// const char* json = "{\"title\": \"Updated Title\"}";
1695/// C2paBuilder* new_builder = c2pa_builder_with_definition(builder, json);
1696/// // builder is now invalid, use new_builder
1697/// ```
1698#[no_mangle]
1699pub unsafe extern "C" fn c2pa_builder_with_definition(
1700    builder: *mut C2paBuilder,
1701    manifest_json: *const c_char,
1702) -> *mut C2paBuilder {
1703    // Validate inputs first, while builder is still tracked
1704    let manifest_json = cstr_or_return_null!(manifest_json);
1705
1706    // Now safe to take ownership - all validations passed
1707    untrack_or_return_null!(builder, C2paBuilder);
1708    let builder = Box::from_raw(builder);
1709    let result = (*builder).with_definition(manifest_json);
1710    box_tracked!(ok_or_return_null!(result))
1711}
1712
1713/// Configures an existing builder with an archive stream.
1714///
1715/// This consumes the original builder and returns a new configured builder.
1716/// The original builder pointer becomes invalid after this call.
1717///
1718/// # Safety
1719///
1720/// * `builder` must be a valid pointer to a C2paBuilder.
1721/// * `stream` must be a valid pointer to a C2paStream.
1722/// * After calling this function, the `builder` pointer is INVALID.
1723///
1724/// # Returns
1725///
1726/// A pointer to a newly configured C2paBuilder, or NULL on error.
1727///
1728/// # Example
1729///
1730/// ```c
1731/// C2paBuilder* builder = c2pa_builder_from_context(ctx);
1732/// C2paBuilder* new_builder = c2pa_builder_with_archive(builder, archive_stream);
1733/// // builder is now invalid, use new_builder
1734/// ```
1735#[no_mangle]
1736pub unsafe extern "C" fn c2pa_builder_with_archive(
1737    builder: *mut C2paBuilder,
1738    stream: *mut C2paStream,
1739) -> *mut C2paBuilder {
1740    // Validate stream first, while builder is still tracked
1741    let stream = deref_mut_or_return_null!(stream, C2paStream);
1742
1743    // Now safe to take ownership - stream is valid
1744    untrack_or_return_null!(builder, C2paBuilder);
1745    let builder = Box::from_raw(builder);
1746    let result = (*builder).with_archive(stream);
1747    box_tracked!(ok_or_return_null!(result))
1748}
1749
1750/// Sets the builder intent on the Builder.
1751///
1752/// An intent lets the API know what kind of manifest to create.
1753/// Intents are `Create`, `Edit`, or `Update`.
1754///
1755/// Create requires a `DigitalSourceType`. It is used for assets without a parent ingredient.
1756/// Edit requires a parent ingredient and is used for most assets that are being edited.
1757/// Update is a special case with many restrictions but is more compact than Edit.
1758///
1759/// For the `Create` intent, a valid `digital_source_type` must be provided.
1760/// For `Edit` and `Update` intents, `digital_source_type` will be ignored (any value is allowed).
1761///
1762/// # Parameters
1763/// * builder_ptr: pointer to a Builder.
1764/// * intent: the builder intent (Create, Edit, or Update).
1765/// * digital_source_type: the digital source type (required for Create intent).
1766///
1767/// # Errors
1768/// Returns -1 if there were errors (null pointer for builder_ptr), otherwise returns 0.
1769/// The error string can be retrieved by calling c2pa_error.
1770///
1771/// # Safety
1772/// builder_ptr must be a valid pointer to a Builder.
1773#[no_mangle]
1774pub unsafe extern "C" fn c2pa_builder_set_intent(
1775    builder_ptr: *mut C2paBuilder,
1776    intent: C2paBuilderIntent,
1777    digital_source_type: C2paDigitalSourceType,
1778) -> c_int {
1779    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
1780
1781    let builder_intent = match intent {
1782        C2paBuilderIntent::Create => c2pa::BuilderIntent::Create(digital_source_type.into()),
1783        C2paBuilderIntent::Edit => c2pa::BuilderIntent::Edit,
1784        C2paBuilderIntent::Update => c2pa::BuilderIntent::Update,
1785    };
1786
1787    builder.set_intent(builder_intent);
1788    0 as c_int
1789}
1790
1791/// Sets the no-embed flag on the Builder.
1792/// When set, the builder will not embed a C2PA manifest store into the asset when signing.
1793/// This is useful when creating cloud or sidecar manifests.
1794/// # Parameters
1795/// * builder_ptr: pointer to a Builder.
1796/// # Safety
1797/// builder_ptr must be a valid pointer to a Builder.
1798#[no_mangle]
1799#[allow(clippy::unused_unit)] // clippy doesn't like the () return type on the macro
1800pub unsafe extern "C" fn c2pa_builder_set_no_embed(builder_ptr: *mut C2paBuilder) {
1801    let builder = deref_mut_or_return!(builder_ptr, C2paBuilder, ());
1802    builder.set_no_embed(true);
1803}
1804
1805/// Sets the remote URL on the Builder.
1806/// When set, the builder will embed a remote URL into the asset when signing.
1807/// This is useful when creating cloud based Manifests.
1808/// # Parameters
1809/// * builder_ptr: pointer to a Builder.
1810/// * remote_url: pointer to a C string with the remote URL.
1811/// # Errors
1812/// Returns -1 if there were errors, otherwise returns 0.
1813/// The error string can be retrieved by calling c2pa_error.
1814/// # Safety
1815/// Reads from NULL-terminated C strings.
1816#[no_mangle]
1817pub unsafe extern "C" fn c2pa_builder_set_remote_url(
1818    builder_ptr: *mut C2paBuilder,
1819    remote_url: *const c_char,
1820) -> c_int {
1821    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
1822    let remote_url = cstr_or_return_int!(remote_url);
1823    builder.set_remote_url(&remote_url);
1824    0 as c_int
1825}
1826
1827/// ⚠️ **Deprecated Soon**
1828/// This method is planned to be deprecated in a future release.
1829/// Usage should be limited and temporary.
1830///
1831/// Sets the resource directory on the Builder.
1832/// When set, resources that are not found in memory will be searched for in the given directory.
1833/// # Parameters
1834/// * builder_ptr: pointer to a Builder.
1835/// * base_path: pointer to a C string with the resource directory.
1836/// # Errors
1837/// Returns -1 if there were errors, otherwise returns 0.
1838/// The error string can be retrieved by calling c2pa_error.
1839/// # Safety
1840/// Reads from NULL-terminated C strings.
1841#[no_mangle]
1842pub unsafe extern "C" fn c2pa_builder_set_base_path(
1843    builder_ptr: *mut C2paBuilder,
1844    base_path: *const c_char,
1845) -> c_int {
1846    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
1847    let base_path = cstr_or_return_int!(base_path);
1848    builder.set_base_path(&base_path);
1849    0 as c_int
1850}
1851
1852/// Adds a resource to the C2paBuilder.
1853///
1854/// The resource uri should match an identifier in the manifest definition.
1855///
1856/// # Parameters
1857/// * builder_ptr: pointer to a Builder.
1858/// * uri: pointer to a C string with the URI to identify the resource.
1859/// * stream: pointer to a C2paStream.
1860/// # Errors
1861/// Returns -1 if there were errors, otherwise returns 0.
1862/// The error string can be retrieved by calling c2pa_error.
1863///
1864/// # Safety
1865/// Reads from NULL-terminated C strings
1866#[no_mangle]
1867pub unsafe extern "C" fn c2pa_builder_add_resource(
1868    builder_ptr: *mut C2paBuilder,
1869    uri: *const c_char,
1870    stream: *mut C2paStream,
1871) -> c_int {
1872    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
1873    let uri = cstr_or_return_int!(uri);
1874    let result = builder.add_resource(&uri, &mut (*stream));
1875    ok_or_return_int!(result);
1876    0 // returns 0 on success
1877}
1878
1879/// Adds an ingredient to the C2paBuilder.
1880///
1881/// # Parameters
1882/// * builder_ptr: pointer to a Builder.
1883/// * ingredient_json: pointer to a C string with the JSON ingredient definition.
1884/// * format: pointer to a C string with the mime type or extension.
1885/// * source: pointer to a C2paStream.
1886///
1887/// # Errors
1888/// Returns -1 if there were errors, otherwise returns 0.
1889/// The error string can be retrieved by calling c2pa_error.
1890///
1891/// # Safety
1892/// Reads from NULL-terminated C strings.
1893#[no_mangle]
1894pub unsafe extern "C" fn c2pa_builder_add_ingredient_from_stream(
1895    builder_ptr: *mut C2paBuilder,
1896    ingredient_json: *const c_char,
1897    format: *const c_char,
1898    source: *mut C2paStream,
1899) -> c_int {
1900    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
1901    let ingredient_json = cstr_or_return_int!(ingredient_json);
1902    let format = cstr_or_return_int!(format);
1903    let source = deref_mut_or_return_int!(source, C2paStream);
1904    let result = builder.add_ingredient_from_stream(&ingredient_json, &format, &mut (*source));
1905    ok_or_return_int!(result);
1906    0 // returns 0 on success
1907}
1908
1909/// Adds an action to the manifest the Builder is constructing.
1910///
1911/// # Parameters
1912/// * builder_ptr: pointer to a Builder.
1913/// * action_json: JSON string containing the action data.
1914///
1915/// # Errors
1916/// Returns -1 if there were errors, otherwise returns 0.
1917/// The error string can be retrieved by calling [c2pa_error].
1918///
1919/// # Safety
1920/// Reads from NULL-terminated C strings.
1921///
1922/// # Example
1923/// ```C
1924/// const char* manifest_def = "{}";
1925/// C2paBuilder* builder = c2pa_builder_from_json(manifest_def);
1926///
1927/// const char* action_json = "{\n"
1928///     "    \"action\": \"com.example.test-action\",\n"
1929///     "    \"parameters\": {\n"
1930///     "        \"key1\": \"value1\",\n"
1931///     "        \"key2\": \"value2\"\n"
1932///     "    }\n"
1933/// "}";
1934///
1935/// int result = c2pa_builder_add_action(builder, action_json);
1936/// ```
1937///
1938/// This creates a manifest with an actions assertion
1939/// containing the added action (excerpt of the full manifest):
1940/// ```json
1941/// "assertions": [
1942///   {
1943///     "label": "c2pa.actions.v2",
1944///     "data": {
1945///       "actions": [
1946///         {
1947///           "action": "c2pa.created",
1948///           "digitalSourceType": "http://c2pa.org/digitalsourcetype/empty"
1949///         },
1950///         {
1951///           "action": "com.example.test-action",
1952///           "parameters": {
1953///             "key2": "value2",
1954///             "key1": "value1"
1955///           }
1956///         }
1957///       ],
1958///     }
1959///   }
1960/// ]
1961/// ```
1962#[no_mangle]
1963pub unsafe extern "C" fn c2pa_builder_add_action(
1964    builder_ptr: *mut C2paBuilder,
1965    action_json: *const c_char,
1966) -> c_int {
1967    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
1968    let action_json = cstr_or_return_int!(action_json);
1969
1970    // Parse the JSON into a serde Value to use with the Builder
1971    let action_value: serde_json::Value = ok_or_return_int!(serde_json::from_str(&action_json));
1972
1973    ok_or_return_int!(builder.add_action(action_value));
1974
1975    0
1976}
1977
1978/// Writes an Archive of the Builder to the destination stream.
1979///
1980/// # Parameters
1981/// * builder_ptr: pointer to a Builder.
1982/// * stream: pointer to a writable C2paStream.
1983///
1984/// # Errors
1985/// Returns -1 if there were errors, otherwise returns 0.
1986/// The error string can be retrieved by calling c2pa_error.
1987///
1988/// # Safety
1989/// Reads from NULL-terminated C strings.
1990///
1991/// # Example
1992/// ```c
1993/// auto result = c2pa_builder_to_archive(builder, stream);
1994/// if (result < 0) {
1995///     auto error = c2pa_error();
1996///     printf("Error: %s\n", error);
1997///     c2pa_string_free(error);
1998/// }
1999/// ```
2000#[no_mangle]
2001pub unsafe extern "C" fn c2pa_builder_to_archive(
2002    builder_ptr: *mut C2paBuilder,
2003    stream: *mut C2paStream,
2004) -> c_int {
2005    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2006    let stream = deref_mut_or_return_int!(stream, C2paStream);
2007    let result = builder.to_archive(&mut *stream);
2008    ok_or_return_int!(result);
2009    0 // returns 0 on success
2010}
2011
2012/// Adds an ingredient to the C2paBuilder from a C2PA ingredient archive stream.
2013///
2014/// The stream must contain a C2PA ingredient archive produced by
2015/// `c2pa_builder_write_ingredient_archive`. Use
2016/// `c2pa_builder_add_ingredient_from_stream` for regular asset streams.
2017///
2018/// # Parameters
2019/// * builder_ptr: pointer to a Builder.
2020/// * stream: pointer to a readable, seekable C2paStream containing the ingredient archive.
2021///
2022/// # Errors
2023/// Returns -1 if there were errors, otherwise returns 0.
2024/// The error string can be retrieved by calling c2pa_error.
2025///
2026/// # Safety
2027/// Pointers must be valid and non-NULL.
2028///
2029/// # Example
2030/// ```c
2031/// // Write the ingredient archive first
2032/// C2paStream* archive = c2pa_create_stream(...);
2033/// int result = c2pa_builder_write_ingredient_archive(ingredient_builder, "ingredient-id", archive);
2034///
2035/// // Rewind and add it to the parent builder
2036/// c2pa_stream_seek(archive, 0, C2PA_SEEK_START);
2037/// result = c2pa_builder_add_ingredient_from_archive(parent_builder, archive);
2038/// ```
2039#[no_mangle]
2040pub unsafe extern "C" fn c2pa_builder_add_ingredient_from_archive(
2041    builder_ptr: *mut C2paBuilder,
2042    stream: *mut C2paStream,
2043) -> c_int {
2044    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2045    let stream = deref_mut_or_return_int!(stream, C2paStream);
2046    let result = builder.add_ingredient_from_archive(&mut *stream);
2047    ok_or_return_int!(result);
2048    0 // returns 0 on success
2049}
2050
2051/// Writes a single-ingredient C2PA archive to the destination stream.
2052///
2053/// The archive can later be loaded with `c2pa_builder_add_ingredient_from_archive`.
2054/// This requires the `generate_c2pa_archive` builder setting to be enabled via
2055/// `c2pa_builder_with_settings` / `c2pa_context_with_settings` before calling.
2056///
2057/// # Parameters
2058/// * builder_ptr: pointer to a Builder.
2059/// * ingredient_id: pointer to a C string identifying the ingredient within the builder.
2060/// * stream: pointer to a writable C2paStream.
2061///
2062/// # Errors
2063/// Returns -1 if there were errors, otherwise returns 0.
2064/// The error string can be retrieved by calling c2pa_error.
2065///
2066/// # Safety
2067/// Reads from NULL-terminated C strings. Pointers must be valid and non-NULL.
2068///
2069/// # Example
2070/// ```c
2071/// C2paStream* archive = c2pa_create_stream(...);
2072/// int result = c2pa_builder_write_ingredient_archive(builder, "my-ingredient", archive);
2073/// if (result < 0) {
2074///     char* error = c2pa_error();
2075///     printf("Error: %s\n", error);
2076///     c2pa_string_free(error);
2077/// }
2078/// ```
2079#[no_mangle]
2080pub unsafe extern "C" fn c2pa_builder_write_ingredient_archive(
2081    builder_ptr: *mut C2paBuilder,
2082    ingredient_id: *const c_char,
2083    stream: *mut C2paStream,
2084) -> c_int {
2085    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2086    let ingredient_id = cstr_or_return_int!(ingredient_id);
2087    let stream = deref_mut_or_return_int!(stream, C2paStream);
2088    let result = builder.write_ingredient_archive(&ingredient_id, &mut *stream);
2089    ok_or_return_int!(result);
2090    0 // returns 0 on success
2091}
2092
2093/// Creates and writes signed manifest from the C2paBuilder to the destination stream.
2094///
2095/// # Parameters
2096/// * builder_ptr: pointer to a Builder.
2097/// * format: pointer to a C string with the mime type or extension.
2098/// * source: pointer to a C2paStream.
2099/// * dest: pointer to a writable C2paStream.
2100/// * signer: pointer to a C2paSigner.
2101/// * c2pa_bytes_ptr: pointer to a pointer to a c_uchar to return manifest_bytes (optional, can be NULL).
2102///
2103/// # Errors
2104/// Returns -1 if there were errors, otherwise returns the size of the c2pa data.
2105/// The error string can be retrieved by calling c2pa_error.
2106///
2107/// # Safety
2108/// Reads from NULL-terminated C strings
2109/// If manifest_bytes_ptr is not NULL, the returned value MUST be released by calling c2pa_free
2110/// and it is no longer valid after that call.
2111#[no_mangle]
2112pub unsafe extern "C" fn c2pa_builder_sign(
2113    builder_ptr: *mut C2paBuilder,
2114    format: *const c_char,
2115    source: *mut C2paStream,
2116    dest: *mut C2paStream,
2117    signer_ptr: *mut C2paSigner,
2118    manifest_bytes_ptr: *mut *const c_uchar,
2119) -> i64 {
2120    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2121    let format = cstr_or_return_int!(format);
2122    let source = deref_mut_or_return_int!(source, C2paStream);
2123    let dest = deref_mut_or_return_int!(dest, C2paStream);
2124    let c2pa_signer = deref_mut_or_return_int!(signer_ptr, C2paSigner);
2125    ptr_or_return_int!(manifest_bytes_ptr);
2126
2127    let result = builder.sign(
2128        c2pa_signer.signer.as_ref(),
2129        &format,
2130        &mut *source,
2131        &mut *dest,
2132    );
2133    let manifest_bytes = ok_or_return_int!(result);
2134    let len = manifest_bytes.len() as i64;
2135    if !manifest_bytes_ptr.is_null() {
2136        *manifest_bytes_ptr = to_c_bytes(manifest_bytes);
2137    }
2138    len
2139}
2140
2141/// Sign using the Signer from the Context.
2142///
2143/// Equivalent to `c2pa_builder_sign` but the signer comes from the Builder's
2144/// context instead of being passed explicitly.
2145///
2146/// If the context has no signer (neither programmatic via
2147/// `c2pa_context_builder_set_signer` nor from settings), an error
2148/// will be returned.
2149///
2150/// # Parameters
2151///
2152/// * `builder_ptr` - pointer to a Builder whose context has a signer set.
2153/// * `format` - MIME type or file extension (null-terminated C string).
2154/// * `source` - pointer to a readable C2paStream.
2155/// * `dest` - pointer to a read+write+seek C2paStream.
2156/// * `manifest_bytes_ptr` - out-pointer for the manifest bytes.
2157///
2158/// # Safety
2159///
2160/// Reads from NULL-terminated C strings.
2161/// The returned bytes MUST be released by calling `c2pa_free`.
2162///
2163/// # Returns
2164///
2165/// The length of the manifest bytes on success, or -1 on error.
2166#[no_mangle]
2167pub unsafe extern "C" fn c2pa_builder_sign_context(
2168    builder_ptr: *mut C2paBuilder,
2169    format: *const c_char,
2170    source: *mut C2paStream,
2171    dest: *mut C2paStream,
2172    manifest_bytes_ptr: *mut *const c_uchar,
2173) -> i64 {
2174    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2175    let format = cstr_or_return_int!(format);
2176    let source = deref_mut_or_return_int!(source, C2paStream);
2177    let dest = deref_mut_or_return_int!(dest, C2paStream);
2178    ptr_or_return_int!(manifest_bytes_ptr);
2179
2180    let result = builder.save_to_stream(&format, &mut *source, &mut *dest);
2181    let manifest_bytes = ok_or_return_int!(result);
2182    let len = manifest_bytes.len() as i64;
2183    if !manifest_bytes_ptr.is_null() {
2184        *manifest_bytes_ptr = to_c_bytes(manifest_bytes);
2185    }
2186    len
2187}
2188
2189/// Frees a C2PA manifest returned by c2pa_builder_sign.
2190///
2191/// **Note**: This function is maintained for backward compatibility. New code should
2192/// use [`c2pa_free`] instead, which works for all pointer types.
2193///
2194/// # Safety
2195/// The bytes can only be freed once and are invalid after this call.
2196#[no_mangle]
2197#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
2198pub unsafe extern "C" fn c2pa_manifest_bytes_free(manifest_bytes_ptr: *const c_uchar) {
2199    cimpl_free!(manifest_bytes_ptr);
2200}
2201
2202/// Creates a hashed placeholder from a Builder.
2203/// The placeholder is used to reserve size in an asset for later signing.
2204///
2205/// # Parameters
2206/// * builder_ptr: pointer to a Builder.
2207/// * reserved_size: the size required for a signature from the intended signer.
2208/// * format: pointer to a C string with the mime type or extension.
2209/// * manifest_bytes_ptr: pointer to a pointer to a c_uchar to return manifest_bytes.
2210///
2211/// # Errors
2212/// Returns -1 if there were errors, otherwise returns the size of the manifest_bytes.
2213/// The error string can be retrieved by calling c2pa_error.
2214///
2215/// # Safety
2216/// Reads from NULL-terminated C strings.
2217/// If manifest_bytes_ptr is not NULL, the returned value MUST be released by calling c2pa_free
2218/// and it is no longer valid after that call.
2219#[no_mangle]
2220pub unsafe extern "C" fn c2pa_builder_data_hashed_placeholder(
2221    builder_ptr: *mut C2paBuilder,
2222    reserved_size: usize,
2223    format: *const c_char,
2224    manifest_bytes_ptr: *mut *const c_uchar,
2225) -> i64 {
2226    ptr_or_return_int!(manifest_bytes_ptr);
2227    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2228    let format = cstr_or_return_int!(format);
2229    let result = builder.data_hashed_placeholder(reserved_size, &format);
2230    let manifest_bytes = ok_or_return_int!(result);
2231    let len = manifest_bytes.len() as i64;
2232    if !manifest_bytes_ptr.is_null() {
2233        *manifest_bytes_ptr = to_c_bytes(manifest_bytes);
2234    }
2235    len
2236}
2237
2238/// Sign a Builder using the specified signer and data hash.
2239/// The data hash is a JSON string containing DataHash information for the asset.
2240/// This is a low-level method for advanced use cases where the caller handles embedding the manifest.
2241///
2242/// # Parameters
2243/// * builder_ptr: pointer to a Builder.
2244/// * signer: pointer to a C2paSigner.
2245/// * data_hash: pointer to a C string with the JSON data hash.
2246/// * format: pointer to a C string with the mime type or extension.
2247/// * asset: pointer to a C2paStream (may be NULL to use pre calculated hashes).
2248/// * manifest_bytes_ptr: pointer to a pointer to a c_uchar to return manifest_bytes (optional, can be NULL).
2249///
2250/// # Errors
2251/// Returns -1 if there were errors, otherwise returns the size of the manifest_bytes.
2252/// The error string can be retrieved by calling c2pa_error.
2253///
2254/// # Safety
2255/// Reads from NULL-terminated C strings.
2256/// If manifest_bytes_ptr is not NULL, the returned value MUST be released by calling c2pa_free
2257/// and it is no longer valid after that call.
2258#[no_mangle]
2259pub unsafe extern "C" fn c2pa_builder_sign_data_hashed_embeddable(
2260    builder_ptr: *mut C2paBuilder,
2261    signer_ptr: *mut C2paSigner,
2262    data_hash: *const c_char,
2263    format: *const c_char,
2264    asset: *mut C2paStream,
2265    manifest_bytes_ptr: *mut *const c_uchar,
2266) -> i64 {
2267    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2268    let c2pa_signer = deref_mut_or_return_int!(signer_ptr, C2paSigner);
2269    let data_hash_json = cstr_or_return_int!(data_hash);
2270    let format = cstr_or_return_int!(format);
2271    ptr_or_return_int!(manifest_bytes_ptr);
2272
2273    let mut data_hash: DataHash = ok_or_return_int!(serde_json::from_str(&data_hash_json)
2274        .map_err(|e| Error::from_c2pa_error(c2pa::Error::JsonError(e))));
2275
2276    if !asset.is_null() {
2277        // calc hashes from the asset stream
2278        ok_or_return_int!(data_hash
2279            .gen_hash_from_stream(&mut *asset)
2280            .map_err(Error::from_c2pa_error));
2281    }
2282
2283    let result =
2284        builder.sign_data_hashed_embeddable(c2pa_signer.signer.as_ref(), &data_hash, &format);
2285
2286    let manifest_bytes = ok_or_return_int!(result);
2287    let len = manifest_bytes.len() as i64;
2288    if !manifest_bytes_ptr.is_null() {
2289        *manifest_bytes_ptr = to_c_bytes(manifest_bytes);
2290    }
2291    len
2292}
2293
2294/// Returns whether a placeholder manifest is required for the given format.
2295///
2296/// # Parameters
2297/// * builder_ptr: pointer to a Builder.
2298/// * format: pointer to a C string with the mime type or extension.
2299///
2300/// # Returns
2301/// Returns 1 if a placeholder is required, 0 if not, or -1 on error.
2302/// Use [`c2pa_error`] to retrieve the error message when -1 is returned.
2303///
2304/// # Safety
2305/// Reads from NULL-terminated C strings.
2306#[no_mangle]
2307pub unsafe extern "C" fn c2pa_builder_needs_placeholder(
2308    builder_ptr: *mut C2paBuilder,
2309    format: *const c_char,
2310) -> c_int {
2311    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2312    let format = cstr_or_return_int!(format);
2313    if builder.needs_placeholder(&format) {
2314        1
2315    } else {
2316        0
2317    }
2318}
2319
2320/// Returns the hash binding type that the builder will use for the given format.
2321///
2322/// # Parameters
2323/// * builder_ptr: pointer to a Builder.
2324/// * format: pointer to a C string with the MIME type or extension.
2325/// * out_hash_type: pointer to a C2paHashType that receives the result on success.
2326///
2327/// # Returns
2328/// 0 on success, -1 on error (null pointer or invalid string).
2329///
2330/// # Safety
2331/// Reads from NULL-terminated C strings. Writes to `out_hash_type` only on success.
2332#[no_mangle]
2333pub unsafe extern "C" fn c2pa_builder_hash_type(
2334    builder_ptr: *mut C2paBuilder,
2335    format: *const c_char,
2336    out_hash_type: *mut C2paHashType,
2337) -> c_int {
2338    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2339    let format = cstr_or_return_int!(format);
2340    if out_hash_type.is_null() {
2341        return -1;
2342    }
2343    let hash_type = match builder.hash_type(&format) {
2344        c2pa::HashType::Data => C2paHashType::DataHash,
2345        c2pa::HashType::Bmff => C2paHashType::BmffHash,
2346        c2pa::HashType::Box => C2paHashType::BoxHash,
2347    };
2348    *out_hash_type = hash_type;
2349    0
2350}
2351
2352/// Creates a composed placeholder manifest from a Builder.
2353///
2354/// The placeholder is a format-specific (e.g. C2PA UUID box for MP4, APP11 for JPEG)
2355/// byte sequence that can be embedded directly into an asset to reserve space for the
2356/// final signed manifest.  The placeholder JUMBF length is stored internally in the
2357/// Builder so that [`c2pa_builder_sign_embeddable`] returns bytes of the identical size.
2358///
2359/// The signer (including its reserve size) is obtained from the Builder's Context.
2360/// For BMFF assets, if `core.merkle_tree_chunk_size_in_kb` is set in the Context settings,
2361/// the placeholder will include pre-allocated Merkle map slots for up to 4 mdat boxes.
2362///
2363/// # Parameters
2364/// * builder_ptr: pointer to a Builder.
2365/// * format: pointer to a C string with the mime type or extension.
2366/// * manifest_bytes_ptr: pointer to a pointer to a c_uchar to return the composed placeholder bytes.
2367/// * (the pointer may be NULL if the caller does not want to receive the bytes)
2368///
2369/// # Errors
2370/// Returns -1 on error (call c2pa_error() for the message).
2371/// On success, returns the byte length of the composed placeholder.
2372///
2373/// # Safety
2374/// Reads from NULL-terminated C strings.
2375/// The returned bytes MUST be released by calling c2pa_free.
2376#[no_mangle]
2377pub unsafe extern "C" fn c2pa_builder_placeholder(
2378    builder_ptr: *mut C2paBuilder,
2379    format: *const c_char,
2380    manifest_bytes_ptr: *mut *const c_uchar,
2381) -> i64 {
2382    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2383    let format = cstr_or_return_int!(format);
2384    let result = builder.placeholder(&format);
2385    let manifest_bytes = ok_or_return_int!(result);
2386    let len = manifest_bytes.len() as i64;
2387    if !manifest_bytes_ptr.is_null() {
2388        *manifest_bytes_ptr = to_c_bytes(manifest_bytes);
2389    }
2390    len
2391}
2392
2393/// Signs the manifest and returns composed bytes ready for embedding.
2394///
2395/// Operates in two modes:
2396///
2397/// **Placeholder mode** (after calling [`c2pa_builder_placeholder`]): The Builder knows
2398/// the pre-committed size of the composed placeholder.  The returned bytes are
2399/// zero-padded to be exactly the same size, enabling in-place patching of the asset.
2400///
2401/// **Direct mode** (no placeholder): The Builder must already contain a valid hard
2402/// binding assertion (DataHash, BmffHash, or BoxHash with a real hash value), set
2403/// either by [`c2pa_builder_update_hash_from_stream`] or directly via the assertion
2404/// API.  The returned bytes reflect the actual manifest size.
2405///
2406/// The signer is obtained from the Builder's Context.
2407///
2408/// # Parameters
2409/// * builder_ptr: pointer to a Builder.
2410/// * format: pointer to a C string with the mime type or extension.
2411/// * manifest_bytes_ptr: pointer to a pointer to a c_uchar to return the signed manifest bytes.
2412///
2413/// # Errors
2414/// Returns -1 on error (call c2pa_error() for the message).
2415/// In direct mode, also returns -1 if no valid hard binding assertion exists.
2416/// On success, returns the byte length of the signed manifest.
2417///
2418/// # Safety
2419/// Reads from NULL-terminated C strings.
2420/// The returned bytes MUST be released by calling c2pa_free.
2421#[no_mangle]
2422pub unsafe extern "C" fn c2pa_builder_sign_embeddable(
2423    builder_ptr: *mut C2paBuilder,
2424    format: *const c_char,
2425    manifest_bytes_ptr: *mut *const c_uchar,
2426) -> i64 {
2427    ptr_or_return_int!(manifest_bytes_ptr);
2428    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2429    let format = cstr_or_return_int!(format);
2430    let result = builder.sign_embeddable(&format);
2431    let manifest_bytes = ok_or_return_int!(result);
2432    let len = manifest_bytes.len() as i64;
2433    if !manifest_bytes_ptr.is_null() {
2434        *manifest_bytes_ptr = to_c_bytes(manifest_bytes);
2435    }
2436    len
2437}
2438
2439/// Sets the byte exclusion ranges on the DataHash assertion in a Builder.
2440///
2441/// Call this after [`c2pa_builder_placeholder`] to register the exact byte region
2442/// where the composed placeholder was embedded in the asset.  This step is required
2443/// before [`c2pa_builder_update_hash_from_stream`] so the hash covers all asset bytes
2444/// except the manifest slot.
2445///
2446/// Exclusions are provided as a flat array of `(start, length)` pairs, each a `uint64_t`.
2447/// The layout is: `[start0, length0, start1, length1, …]`, so `exclusions_ptr` must
2448/// point to `exclusion_count * 2` consecutive `uint64_t` values.
2449///
2450/// The existing DataHash's name and algorithm are preserved; only its exclusion list
2451/// is replaced.
2452///
2453/// # Parameters
2454/// * builder_ptr: pointer to a Builder (must have called [`c2pa_builder_placeholder`] first).
2455/// * exclusions_ptr: pointer to a flat array of `(start, length)` uint64_t pairs.
2456/// * exclusion_count: number of exclusion ranges (not the number of uint64_t values).
2457///
2458/// # Errors
2459/// Returns 0 on success, -1 on error (call c2pa_error() for the message).
2460/// Fails if no DataHash assertion exists on the Builder.
2461///
2462/// # Safety
2463/// `exclusions_ptr` must point to at least `exclusion_count * 2` valid `uint64_t` values.
2464#[no_mangle]
2465pub unsafe extern "C" fn c2pa_builder_set_data_hash_exclusions(
2466    builder_ptr: *mut C2paBuilder,
2467    exclusions_ptr: *const u64,
2468    exclusion_count: usize,
2469) -> c_int {
2470    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2471
2472    if exclusion_count == 0 || exclusions_ptr.is_null() {
2473        ok_or_return_int!(builder.set_data_hash_exclusions(vec![]));
2474        return 0;
2475    }
2476
2477    let flat = std::slice::from_raw_parts(exclusions_ptr, exclusion_count * 2);
2478    let exclusions: Vec<c2pa::HashRange> = flat
2479        .chunks_exact(2)
2480        .map(|pair| c2pa::HashRange::new(pair[0], pair[1]))
2481        .collect();
2482
2483    ok_or_return_int!(builder.set_data_hash_exclusions(exclusions));
2484    0
2485}
2486
2487/// If set the hasher will hash fixed size chunks of data, padding the final block as needed.
2488/// This will produce a Merkle tree for each mdat with fixed size leaves, which can be used
2489/// for efficient hashing of large assets.
2490///
2491/// #Parameters
2492/// * builder_ptr: pointer to a Builder (must have called [`c2pa_builder_placeholder`] first).
2493/// * fixed_size_kb: length of fixed size blocks. The units are KB.
2494///
2495/// # Errors
2496/// Returns -1 if there were errors, otherwise returns 0.
2497/// The error string can be retrieved by calling c2pa_error.
2498///
2499/// # Safety
2500/// builder_ptr must not be NULL.
2501#[no_mangle]
2502pub unsafe extern "C" fn c2pa_builder_set_fixed_size_merkle(
2503    builder_ptr: *mut C2paBuilder,
2504    fixed_size_kb: usize,
2505) -> c_int {
2506    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2507
2508    builder.set_bmff_hash_fixed_leaf_size(fixed_size_kb);
2509
2510    0
2511}
2512
2513/// Generate the mdat leaf hashes for the asset, the C2paHasher will accumulate the hash values for
2514/// each mdat_id.  The data_ptr should be supplied in the order the chunks are written to the mdat.
2515/// The mdat_id should be begin with 0 and increment for each mdat in the asset.  For assets with a s
2516/// ingle mdat, the mdat_id should be 0.  If fixed size Merkle is enabled, the data will be accumulated
2517/// and hashed in fixed size chunks and the final chunk will be padded.  Otherwise the data will be hashed
2518/// as a single leaf for each mdat chunk supplied to this call.
2519///
2520/// #Parameters
2521/// * builder_ptr: pointer to the C2paBuilder.
2522/// * mdat_id:  specifies which mdat this hash leaf belongs.
2523/// * data_ptr: pointer to data to hash.
2524/// * data_len: length of data to hash.
2525///
2526/// # Errors
2527/// Returns -1 if there were errors, otherwise returns 0.
2528/// The error string can be retrieved by calling c2pa_error.
2529///
2530/// # Safety
2531/// builder_ptr must not be NULL..
2532///
2533/// # Example
2534/// ```c
2535///  auto data = std::vector<std::uint8_t> buffer(1024);
2536///
2537///  c2pa_builder_hash_mdat_bytes(builder, 1, (const uint8_t*)data.data(), 1024, true);
2538/// ```
2539#[no_mangle]
2540pub unsafe extern "C" fn c2pa_builder_hash_mdat_bytes(
2541    builder_ptr: *mut C2paBuilder,
2542    mdat_id: usize,
2543    data_ptr: *const c_uchar,
2544    data_len: usize,
2545    large_size: bool,
2546) -> c_int {
2547    ptr_or_return_int!(data_ptr);
2548    ptr_or_return_int!(builder_ptr);
2549
2550    let data = bytes_or_return_int!(data_ptr, data_len, "mdat_data");
2551
2552    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2553
2554    // save to hasher to build Merkle trees during final save
2555    ok_or_return_int!(builder.hash_bmff_mdat_bytes(mdat_id, data, large_size));
2556    0
2557}
2558
2559/// Updates the hard binding assertion in a Builder by hashing an asset stream.
2560///
2561/// Automatically detects the type of hard binding on the Builder:
2562/// - **BmffHash**: uses the assertion's own path-based exclusions (UUID box, mdat when
2563///   Merkle hashing is enabled). The hash algorithm is read from the assertion itself.
2564/// - **BoxHash**: uses the format's box-hash handler to enumerate chunks, hashes each
2565///   one individually, and stores the result.  Triggered when a BoxHash assertion is
2566///   already present or when `builder.prefer_box_hash` is enabled and the format
2567///   supports it.
2568/// - **DataHash**: reads any exclusion ranges already on the existing DataHash assertion,
2569///   hashes the stream excluding those ranges, and stores the result.  If no DataHash
2570///   exists, creates one with no exclusions (hashes the entire stream — sidecar case).
2571///
2572/// The hash algorithm is resolved in this order:
2573/// 1. The `alg` field of the existing hard binding assertion
2574/// 2. The `alg` field on the ManifestDefinition (set via JSON or builder settings)
2575/// 3. `"sha256"` (the C2PA default)
2576///
2577/// For DataHash workflows, call [`c2pa_builder_set_data_hash_exclusions`] before this
2578/// function to register where the composed placeholder was embedded.
2579///
2580/// # Parameters
2581/// * builder_ptr: pointer to a Builder.
2582/// * format: MIME type or file extension of the asset (e.g. `"image/jpeg"`).
2583/// * stream: pointer to a C2paStream of the asset to hash.
2584///
2585/// # Errors
2586/// Returns 0 on success, -1 on error (call c2pa_error() for the message).
2587///
2588/// # Safety
2589/// The stream must remain valid for the duration of the call.
2590#[no_mangle]
2591pub unsafe extern "C" fn c2pa_builder_update_hash_from_stream(
2592    builder_ptr: *mut C2paBuilder,
2593    format: *const c_char,
2594    stream: *mut C2paStream,
2595) -> c_int {
2596    let builder = deref_mut_or_return_int!(builder_ptr, C2paBuilder);
2597    let format = cstr_or_return_int!(format);
2598    let stream = deref_mut_or_return_int!(stream, C2paStream);
2599    ok_or_return_int!(builder.update_hash_from_stream(&format, &mut *stream));
2600    0
2601}
2602
2603/// Convert a binary c2pa manifest into an embeddable version for the given format.
2604/// A raw manifest (in application/c2pa format) can be uploaded to the cloud but
2605/// it cannot be embedded directly into an asset without extra processing.
2606/// This method converts the raw manifest into an embeddable version that can be
2607/// embedded into an asset.
2608///
2609/// # Parameters
2610/// * format: pointer to a C string with the mime type or extension.
2611/// * manifest_bytes_ptr: pointer to a c_uchar with the raw manifest bytes.
2612/// * manifest_bytes_size: the size of the manifest_bytes.
2613/// * result_bytes_ptr: pointer to a pointer to a c_uchar to return the embeddable manifest bytes.
2614///
2615/// # Errors
2616/// Returns -1 if there were errors, otherwise returns the size of the result_bytes.
2617/// The error string can be retrieved by calling c2pa_error.
2618///
2619/// # Safety
2620/// Reads from NULL-terminated C strings.
2621/// The returned value MUST be released by calling c2pa_free
2622/// and it is no longer valid after that call.
2623#[no_mangle]
2624pub unsafe extern "C" fn c2pa_format_embeddable(
2625    format: *const c_char,
2626    manifest_bytes_ptr: *const c_uchar,
2627    manifest_bytes_size: usize,
2628    result_bytes_ptr: *mut *const c_uchar,
2629) -> i64 {
2630    let format = cstr_or_return_int!(format);
2631    ptr_or_return_int!(manifest_bytes_ptr);
2632    ptr_or_return_int!(result_bytes_ptr);
2633
2634    let bytes = bytes_or_return_int!(
2635        manifest_bytes_ptr,
2636        manifest_bytes_size,
2637        "manifest_bytes_ptr"
2638    );
2639
2640    let result = c2pa::Builder::composed_manifest(bytes, &format);
2641    let result_bytes = ok_or_return_int!(result);
2642    let len = result_bytes.len() as i64;
2643    if !result_bytes_ptr.is_null() {
2644        *result_bytes_ptr = to_c_bytes(result_bytes);
2645    }
2646    len
2647}
2648
2649/// Creates a C2paSigner from a callback and configuration.
2650///
2651/// # Parameters
2652/// * callback: a callback function to sign data.
2653/// * alg: the signing algorithm.
2654/// * certs: a pointer to a NULL-terminated string containing the certificate chain in PEM format.
2655/// * tsa_url: a pointer to a NULL-terminated string containing the RFC 3161 compliant timestamp authority URL.
2656///
2657/// # Errors
2658/// Returns NULL if there were errors, otherwise returns a pointer to a C2paSigner.
2659/// The error string can be retrieved by calling c2pa_error.
2660///
2661/// # Safety
2662/// Reads from NULL-terminated C strings.
2663/// The returned value MUST be released by calling c2pa_free
2664/// and it is no longer valid after that call.
2665/// When binding through the C API to other languages, the callback must live long
2666/// enough, possibly being re-used and called multiple times. The callback is logically
2667/// owned by the host/caller.
2668///
2669/// # Example
2670/// ```c
2671/// auto result = c2pa_signer_create(callback, alg, certs, tsa_url);
2672/// if (result == NULL) {
2673///     auto error = c2pa_error();
2674///     printf("Error: %s\n", error);
2675///     c2pa_string_free(error);
2676/// }
2677/// ```
2678#[no_mangle]
2679pub unsafe extern "C" fn c2pa_signer_create(
2680    context: *const c_void,
2681    callback: SignerCallback,
2682    alg: C2paSigningAlg,
2683    certs: *const c_char,
2684    tsa_url: *const c_char,
2685) -> *mut C2paSigner {
2686    let certs = cstr_or_return_null!(certs);
2687    let tsa_url = cstr_option!(tsa_url);
2688    let context = context as *const ();
2689
2690    // Create a callback that uses the provided C callback function
2691    // The callback ignores its context parameter and will use
2692    // the context set on the CallbackSigner closure
2693    let c_callback = move |context: *const (), data: &[u8]| {
2694        // we need to guess at a max signed size, the callback must verify this is big enough or fail.
2695        let signed_len_max = data.len() * 2;
2696        let mut signed_bytes: Vec<u8> = vec![0; signed_len_max];
2697        let signed_size = unsafe {
2698            (callback)(
2699                context,
2700                data.as_ptr(),
2701                data.len(),
2702                signed_bytes.as_mut_ptr(),
2703                signed_len_max,
2704            )
2705        };
2706        if signed_size < 0 {
2707            return Err(c2pa::Error::CoseSignature); // todo:: return errors from callback
2708        }
2709        signed_bytes.set_len(signed_size as usize);
2710        Ok(signed_bytes)
2711    };
2712
2713    let mut signer = CallbackSigner::new(c_callback, alg.into(), certs).set_context(context);
2714    if let Some(tsa_url) = tsa_url.as_ref() {
2715        signer = signer.set_tsa_url(tsa_url);
2716    }
2717    box_tracked!(C2paSigner {
2718        signer: Box::new(signer),
2719    })
2720}
2721
2722/// Creates a C2paSigner from a SignerInfo.
2723/// The signer is created from the sign_cert and private_key fields.
2724/// an optional url to an RFC 3161 compliant time server will ensure the signature is timestamped.
2725///
2726/// # Parameters
2727/// * signer_info: pointer to a C2paSignerInfo.
2728/// # Errors
2729/// Returns NULL if there were errors, otherwise returns a pointer to a C2paSigner.
2730/// The error string can be retrieved by calling c2pa_error.
2731/// # Safety
2732/// Reads from NULL-terminated C strings.
2733/// The returned value MUST be released by calling c2pa_free
2734/// and it is no longer valid after that call.
2735/// # Example
2736/// ```c
2737/// auto result = c2pa_signer_from_info(signer_info);
2738/// if (result == NULL) {
2739///     auto error = c2pa_error();
2740///     printf("Error: %s\n", error);
2741///     c2pa_string_free(error);
2742/// }
2743/// ```
2744#[no_mangle]
2745pub unsafe extern "C" fn c2pa_signer_from_info(signer_info: &C2paSignerInfo) -> *mut C2paSigner {
2746    let signer_info = SignerInfo {
2747        alg: cstr_or_return_null!(signer_info.alg),
2748        sign_cert: cstr_or_return_null!(signer_info.sign_cert).into_bytes(),
2749        private_key: cstr_or_return_null!(signer_info.private_key).into_bytes(),
2750        ta_url: cstr_option!(signer_info.ta_url),
2751    };
2752
2753    let signer = signer_info.signer();
2754    match signer {
2755        Ok(signer) => box_tracked!(C2paSigner {
2756            signer: Box::new(signer),
2757        }),
2758        Err(err) => {
2759            CimplError::from(err).set_last();
2760            std::ptr::null_mut()
2761        }
2762    }
2763}
2764
2765/// Creates a C2paSigner from the settings.
2766/// The signer is created from the settings defined in the c2pa_settings.json file.
2767///
2768/// # Errors
2769/// Returns NULL if there were errors, otherwise returns a pointer to a C2paSigner.
2770/// The error string can be retrieved by calling c2pa_error.
2771/// # Safety
2772/// The returned value MUST be released by calling c2pa_free
2773/// and it is no longer valid after that call.
2774#[no_mangle]
2775#[deprecated(
2776    note = "Use c2pa_context_builder_set_signer() to configure a signer on a context instead."
2777)]
2778pub unsafe extern "C" fn c2pa_signer_from_settings() -> *mut C2paSigner {
2779    // Legacy C API: reads signer configuration from thread-local settings (set by c2pa_load_settings).
2780    #[allow(deprecated)]
2781    let signer = ok_or_return_null!(C2paSettings::signer());
2782    box_tracked!(C2paSigner {
2783        signer: Box::new(signer),
2784    })
2785}
2786
2787/// Returns the size to reserve for the signature for this signer.
2788///
2789/// # Parameters
2790/// * signer_ptr: pointer to a C2paSigner.
2791///
2792/// # Errors
2793/// Returns -1 if there were errors, otherwise returns the size to reserve.
2794/// The error string can be retrieved by calling c2pa_error.
2795///
2796/// # Safety
2797/// The signer_ptr must be a valid pointer to a C2paSigner.
2798#[no_mangle]
2799pub unsafe extern "C" fn c2pa_signer_reserve_size(signer_ptr: *mut C2paSigner) -> i64 {
2800    let c2pa_signer = deref_mut_or_return_int!(signer_ptr, C2paSigner);
2801    c2pa_signer.signer.reserve_size() as i64
2802}
2803
2804/// Frees a C2paSigner allocated by Rust.
2805///
2806/// **Note**: This function is maintained for backward compatibility. New code should
2807/// use [`c2pa_free`] instead, which works for all pointer types.
2808///
2809/// # Safety
2810/// The C2paSigner can only be freed once and is invalid after this call.
2811#[no_mangle]
2812#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
2813pub unsafe extern "C" fn c2pa_signer_free(signer_ptr: *const C2paSigner) {
2814    cimpl_free!(signer_ptr);
2815}
2816
2817#[no_mangle]
2818/// Signs a byte array using the Ed25519 algorithm.
2819/// # Safety
2820/// The returned value MUST be freed by calling c2pa_free
2821/// and it is no longer valid after that call.
2822pub unsafe extern "C" fn c2pa_ed25519_sign(
2823    bytes: *const c_uchar,
2824    len: usize,
2825    private_key: *const c_char,
2826) -> *const c_uchar {
2827    let private_key = cstr_or_return_null!(private_key);
2828
2829    let bytes = bytes_or_return_null!(bytes, len, "bytes");
2830
2831    let signed_bytes =
2832        ok_or_return_null!(CallbackSigner::ed25519_sign(bytes, private_key.as_bytes()));
2833
2834    to_c_bytes(signed_bytes)
2835}
2836
2837#[no_mangle]
2838/// Frees a signature allocated by Rust.
2839///
2840/// **Note**: This function is maintained for backward compatibility. New code should
2841/// use [`c2pa_free`] instead, which works for all pointer types.
2842///
2843/// # Safety
2844/// The signature can only be freed once and is invalid after this call.
2845#[deprecated(note = "Use c2pa_free() instead, which works for all pointer types.")]
2846pub unsafe extern "C" fn c2pa_signature_free(signature_ptr: *const u8) {
2847    cimpl_free!(signature_ptr);
2848}
2849
2850/// Returns a [*const *const c_char] with the contents of of the provided [Vec<String>].
2851///
2852/// # Parameters
2853/// - `strs`: The vector of Rust strings to convert into [CString]s
2854/// - `count`: Will be set to the number of strings in the array.
2855///
2856/// # Safety
2857/// - The caller is responsible for eventually freeing each C string and the array itself to
2858///   avoid memory leaks [c2pa_free_string_array].
2859/// - The function uses `std::mem::forget` to intentionally leak the vector, transferring
2860///   ownership of the memory to the caller.
2861///
2862/// # Returns
2863/// - A pointer to the first element of an array of pointers to C strings (`*const *const c_char`).
2864///
2865/// # Note
2866/// This should be used internally. We don't want to support this as a public API.
2867unsafe fn c2pa_mime_types_to_c_array(strs: Vec<String>, count: *mut usize) -> *const *const c_char {
2868    // Even if the array is exposed as a `*const *const c_char` for read-only access,
2869    // the underlying memory must be allocated as `*mut *mut c_char` because freeing
2870    // or deallocating memory requires a mutable pointer. This ensures the caller can
2871    // safely release ownership of both the array and its strings.
2872    let mut mime_ptrs: Vec<*mut c_char> = strs.into_iter().map(to_c_string).collect();
2873    mime_ptrs.shrink_to_fit();
2874
2875    // verify that the length and capacity of the vector are identitical, as we rely on this later
2876    // when de-allocating the memory associated to this vector.
2877    debug_assert_eq!(mime_ptrs.len(), mime_ptrs.capacity());
2878
2879    *count = mime_ptrs.len();
2880
2881    let ptr = mime_ptrs.as_ptr();
2882    std::mem::forget(mime_ptrs);
2883
2884    ptr as *const *const c_char
2885}
2886
2887#[cfg(test)]
2888#[allow(deprecated)]
2889mod tests {
2890    use std::{ffi::CString, io::Seek, panic::catch_unwind};
2891
2892    use super::*;
2893    use crate::TestStream;
2894
2895    macro_rules! fixture_path {
2896        ($path:expr) => {
2897            concat!("../../sdk/tests/fixtures/", $path)
2898        };
2899    }
2900
2901    /// Helper to create a signer and builder for testing
2902    /// Returns (signer, builder)
2903    fn setup_signer_and_builder_for_signing_tests() -> (*mut C2paSigner, *mut C2paBuilder) {
2904        let certs = include_str!(fixture_path!("certs/ed25519.pub"));
2905        let private_key = include_bytes!(fixture_path!("certs/ed25519.pem"));
2906        let alg = CString::new("Ed25519").unwrap();
2907        let sign_cert = CString::new(certs).unwrap();
2908        let private_key = CString::new(private_key).unwrap();
2909        let signer_info = C2paSignerInfo {
2910            alg: alg.as_ptr(),
2911            sign_cert: sign_cert.as_ptr(),
2912            private_key: private_key.as_ptr(),
2913            ta_url: std::ptr::null(),
2914        };
2915        let signer = unsafe { c2pa_signer_from_info(&signer_info) };
2916        assert!(!signer.is_null());
2917
2918        let manifest_def = CString::new("{}").unwrap();
2919        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
2920        assert!(!builder.is_null());
2921
2922        (signer, builder)
2923    }
2924
2925    #[test]
2926    fn test_ed25519_sign() {
2927        let bytes = b"test";
2928        let private_key = include_bytes!(fixture_path!("certs/ed25519.pem"));
2929        let private_key = CString::new(private_key).unwrap();
2930        let signature =
2931            unsafe { c2pa_ed25519_sign(bytes.as_ptr(), bytes.len(), private_key.as_ptr()) };
2932        assert!(!signature.is_null());
2933        unsafe { c2pa_signature_free(signature) };
2934    }
2935
2936    #[test]
2937    fn test_c2pa_signer_from_info() {
2938        let certs = include_str!(fixture_path!("certs/ed25519.pub"));
2939        let private_key = include_bytes!(fixture_path!("certs/ed25519.pem"));
2940        let alg = CString::new("Ed25519").unwrap();
2941        let sign_cert = CString::new(certs).unwrap();
2942        let private_key = CString::new(private_key).unwrap();
2943        let signer_info = C2paSignerInfo {
2944            alg: alg.as_ptr(),
2945            sign_cert: sign_cert.as_ptr(),
2946            private_key: private_key.as_ptr(),
2947            ta_url: std::ptr::null(),
2948        };
2949        let signer = unsafe { c2pa_signer_from_info(&signer_info) };
2950        assert!(!signer.is_null());
2951        unsafe { c2pa_signer_free(signer) };
2952    }
2953
2954    #[test]
2955    fn test_signer_from_info_bad_alg() {
2956        let alg = CString::new("BadAlg").unwrap();
2957        let sign_cert = CString::new("certs").unwrap();
2958        let private_key = CString::new("private_key").unwrap();
2959        let signer_info = C2paSignerInfo {
2960            alg: alg.as_ptr(),
2961            sign_cert: sign_cert.as_ptr(),
2962            private_key: private_key.as_ptr(),
2963            ta_url: std::ptr::null(),
2964        };
2965        let signer = unsafe { c2pa_signer_from_info(&signer_info) };
2966        assert!(signer.is_null());
2967        let error = unsafe { c2pa_error() };
2968        let error = unsafe { CString::from_raw(error) };
2969        assert_eq!(error.to_str().unwrap(), "Other: Invalid signing algorithm");
2970    }
2971
2972    #[test]
2973    fn test_sign_with_info() {
2974        let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
2975        let mut source_stream = TestStream::new(source_image.to_vec());
2976        let dest_vec = Vec::new();
2977        let mut dest_stream = TestStream::new(dest_vec);
2978
2979        let (signer, builder) = setup_signer_and_builder_for_signing_tests();
2980
2981        let format = CString::new("image/jpeg").unwrap();
2982        let mut manifest_bytes_ptr = std::ptr::null();
2983        let _ = unsafe {
2984            c2pa_builder_sign(
2985                builder,
2986                format.as_ptr(),
2987                source_stream.as_ptr(),
2988                dest_stream.as_ptr(),
2989                signer,
2990                &mut manifest_bytes_ptr,
2991            )
2992        };
2993        // let error = unsafe { c2pa_error() };
2994        // let error = unsafe { CString::from_raw(error) };
2995        // assert_eq!(error.to_str().unwrap(), "Other Invalid signing algorithm");
2996        // assert_eq!(result, 65485);
2997        unsafe {
2998            c2pa_manifest_bytes_free(manifest_bytes_ptr);
2999        }
3000        unsafe { c2pa_builder_free(builder) };
3001        unsafe { c2pa_signer_free(signer) };
3002    }
3003
3004    #[test]
3005    fn builder_add_actions_and_sign() {
3006        let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
3007        let mut source_stream = TestStream::new(source_image.to_vec());
3008        let dest_vec = Vec::new();
3009        let mut dest_stream = TestStream::new(dest_vec);
3010
3011        let (signer, builder) = setup_signer_and_builder_for_signing_tests();
3012
3013        let action_json = CString::new(
3014            r#"{
3015            "action": "com.example.test-action",
3016            "parameters": {
3017                "key1": "value1",
3018                "key2": "value2"
3019            }
3020        }"#,
3021        )
3022        .unwrap();
3023
3024        // multiple calls add multiple actions
3025        let result = unsafe { c2pa_builder_add_action(builder, action_json.as_ptr()) };
3026        assert_eq!(result, 0);
3027
3028        let format = CString::new("image/jpeg").unwrap();
3029        let mut manifest_bytes_ptr = std::ptr::null();
3030        let _ = unsafe {
3031            c2pa_builder_sign(
3032                builder,
3033                format.as_ptr(),
3034                source_stream.as_ptr(),
3035                dest_stream.as_ptr(),
3036                signer,
3037                &mut manifest_bytes_ptr,
3038            )
3039        };
3040
3041        // Verify we can read the signed data back
3042        dest_stream.stream_mut().rewind().unwrap();
3043
3044        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), dest_stream.as_ptr()) };
3045        if reader.is_null() {
3046            if let Some(msg) = CimplError::last_message() {
3047                panic!("Reader creation failed: {}", msg);
3048            }
3049        }
3050        assert!(!reader.is_null());
3051
3052        let json = unsafe { c2pa_reader_json(reader) };
3053        assert!(!json.is_null());
3054        let json_str = unsafe { CString::from_raw(json) };
3055        let json_content = json_str.to_str().unwrap();
3056
3057        assert!(json_content.contains("manifest"));
3058        assert!(json_content.contains("com.example.test-action"));
3059
3060        unsafe {
3061            c2pa_manifest_bytes_free(manifest_bytes_ptr);
3062            c2pa_builder_free(builder);
3063            c2pa_signer_free(signer);
3064            c2pa_reader_free(reader);
3065        }
3066    }
3067
3068    #[test]
3069    fn builder_create_intent_digital_creation_and_sign() {
3070        let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
3071        let mut source_stream = TestStream::new(source_image.to_vec());
3072        let dest_vec = Vec::new();
3073        let mut dest_stream = TestStream::new(dest_vec);
3074
3075        let (signer, builder) = setup_signer_and_builder_for_signing_tests();
3076
3077        // The create intent requires needs a digital source type
3078        let result = unsafe {
3079            c2pa_builder_set_intent(
3080                builder,
3081                C2paBuilderIntent::Create,
3082                C2paDigitalSourceType::DigitalCreation,
3083            )
3084        };
3085        assert_eq!(result, 0);
3086
3087        let format = CString::new("image/jpeg").unwrap();
3088        let mut manifest_bytes_ptr = std::ptr::null();
3089        let _ = unsafe {
3090            c2pa_builder_sign(
3091                builder,
3092                format.as_ptr(),
3093                source_stream.as_ptr(),
3094                dest_stream.as_ptr(),
3095                signer,
3096                &mut manifest_bytes_ptr,
3097            )
3098        };
3099
3100        // Verify we can read the signed data back
3101        dest_stream.stream_mut().rewind().unwrap();
3102        let format = CString::new("image/jpeg").unwrap();
3103
3104        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), dest_stream.as_ptr()) };
3105        assert!(!reader.is_null());
3106
3107        let json = unsafe { c2pa_reader_json(reader) };
3108        assert!(!json.is_null());
3109
3110        let json_str = unsafe { CString::from_raw(json) };
3111        let json_content = json_str.to_str().unwrap();
3112
3113        assert!(json_content.contains("c2pa.created"));
3114        // Verify the digital source type was used
3115        assert!(json_content.contains("digitalSourceType"));
3116        assert!(json_content.contains("digitalCreation"));
3117        // Verify there is only one c2pa.created action
3118        assert_eq!(
3119            json_content.matches("\"action\": \"c2pa.created\"").count(),
3120            1
3121        );
3122
3123        unsafe {
3124            c2pa_manifest_bytes_free(manifest_bytes_ptr);
3125            c2pa_builder_free(builder);
3126            c2pa_signer_free(signer);
3127            c2pa_reader_free(reader);
3128        }
3129    }
3130
3131    #[test]
3132    fn builder_create_intent_empty_and_sign() {
3133        let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
3134        let mut source_stream = TestStream::new(source_image.to_vec());
3135        let dest_vec = Vec::new();
3136        let mut dest_stream = TestStream::new(dest_vec);
3137
3138        let (signer, builder) = setup_signer_and_builder_for_signing_tests();
3139
3140        // The create intent requires needs a digital source type
3141        let result = unsafe {
3142            c2pa_builder_set_intent(
3143                builder,
3144                C2paBuilderIntent::Create,
3145                C2paDigitalSourceType::Empty,
3146            )
3147        };
3148        assert_eq!(result, 0);
3149
3150        let format = CString::new("image/jpeg").unwrap();
3151        let mut manifest_bytes_ptr = std::ptr::null();
3152        let _ = unsafe {
3153            c2pa_builder_sign(
3154                builder,
3155                format.as_ptr(),
3156                source_stream.as_ptr(),
3157                dest_stream.as_ptr(),
3158                signer,
3159                &mut manifest_bytes_ptr,
3160            )
3161        };
3162
3163        // Verify we can read the signed data back
3164        dest_stream.stream_mut().rewind().unwrap();
3165        let format = CString::new("image/jpeg").unwrap();
3166
3167        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), dest_stream.as_ptr()) };
3168        assert!(!reader.is_null());
3169
3170        let json = unsafe { c2pa_reader_json(reader) };
3171        assert!(!json.is_null());
3172
3173        let json_str = unsafe { CString::from_raw(json) };
3174        let json_content = json_str.to_str().unwrap();
3175
3176        assert!(json_content.contains("c2pa.created"));
3177        // Verify the digital source type we picked was used
3178        assert!(json_content.contains("digitalsourcetype/empty"));
3179
3180        unsafe {
3181            c2pa_manifest_bytes_free(manifest_bytes_ptr);
3182            c2pa_builder_free(builder);
3183            c2pa_signer_free(signer);
3184            c2pa_reader_free(reader);
3185        }
3186    }
3187
3188    #[test]
3189    fn builder_edit_intent_and_sign() {
3190        // Use an already-signed image as the source for editing
3191        let signed_source_image = include_bytes!(fixture_path!("C.jpg"));
3192        let mut source_stream = TestStream::new(signed_source_image.to_vec());
3193        let dest_vec = Vec::new();
3194        let mut dest_stream = TestStream::new(dest_vec);
3195
3196        let (signer, builder) = setup_signer_and_builder_for_signing_tests();
3197
3198        // Edit intent will extract the parent ingredient from source
3199        // (Digital source type is ignored in the case of the edit intent)
3200        let result = unsafe {
3201            c2pa_builder_set_intent(
3202                builder,
3203                C2paBuilderIntent::Edit,
3204                C2paDigitalSourceType::Empty,
3205            )
3206        };
3207        assert_eq!(result, 0);
3208
3209        // Verify we can read the signed data back
3210        let format = CString::new("image/jpeg").unwrap();
3211        let mut manifest_bytes_ptr = std::ptr::null();
3212        let _ = unsafe {
3213            c2pa_builder_sign(
3214                builder,
3215                format.as_ptr(),
3216                source_stream.as_ptr(),
3217                dest_stream.as_ptr(),
3218                signer,
3219                &mut manifest_bytes_ptr,
3220            )
3221        };
3222
3223        dest_stream.stream_mut().rewind().unwrap();
3224        let format = CString::new("image/jpeg").unwrap();
3225
3226        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), dest_stream.as_ptr()) };
3227        assert!(!reader.is_null());
3228
3229        let json = unsafe { c2pa_reader_json(reader) };
3230        assert!(!json.is_null());
3231        let json_str = unsafe { CString::from_raw(json) };
3232        let json_content = json_str.to_str().unwrap();
3233
3234        assert!(json_content.contains("c2pa.opened"));
3235        // Verify the digital source type parameter was ignored for Edit intent
3236        // and no "empty" source type appears in the JSON
3237        assert!(!json_content.contains("digitalsourcetype/empty"));
3238
3239        unsafe {
3240            c2pa_manifest_bytes_free(manifest_bytes_ptr);
3241            c2pa_builder_free(builder);
3242            c2pa_signer_free(signer);
3243            c2pa_reader_free(reader);
3244        }
3245    }
3246
3247    #[test]
3248    fn test_c2pa_builder_no_embed_null() {
3249        let builder: *mut c2pa::Builder = std::ptr::null_mut();
3250        unsafe { c2pa_builder_set_no_embed(builder) };
3251    }
3252
3253    #[test]
3254    fn test_c2pa_builder_set_remote_url_null() {
3255        let builder: *mut c2pa::Builder = std::ptr::null_mut();
3256        let remote_url = CString::new("https://example.com").unwrap();
3257        let result = unsafe { c2pa_builder_set_remote_url(builder, remote_url.as_ptr()) };
3258        assert_eq!(result, -1);
3259        let error = unsafe { c2pa_error() };
3260        let error = unsafe { CString::from_raw(error) };
3261        assert_eq!(error.to_str().unwrap(), "NullParameter: builder_ptr");
3262    }
3263
3264    #[test]
3265    fn test_c2pa_builder_no_embed() {
3266        let manifest_def = CString::new("{}").unwrap();
3267        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
3268        assert!(!builder.is_null());
3269        unsafe { c2pa_builder_set_no_embed(builder) };
3270        unsafe { c2pa_builder_free(builder) };
3271    }
3272
3273    #[test]
3274    fn test_c2pa_version() {
3275        let version = unsafe { c2pa_version() };
3276        assert!(!version.is_null());
3277        let version_str = unsafe { CString::from_raw(version) };
3278        assert!(!version_str.to_str().unwrap().is_empty());
3279    }
3280
3281    #[test]
3282    fn test_c2pa_error_no_error() {
3283        let error = unsafe { c2pa_error() };
3284        assert!(!error.is_null());
3285        let error_str = unsafe { CString::from_raw(error) };
3286        assert_eq!(error_str.to_str().unwrap(), "");
3287    }
3288
3289    #[test]
3290    fn test_c2pa_load_settings() {
3291        let settings = CString::new("{}").unwrap();
3292        let format = CString::new("json").unwrap();
3293        let result = unsafe { c2pa_load_settings(settings.as_ptr(), format.as_ptr()) };
3294        assert_eq!(result, 0);
3295    }
3296
3297    #[test]
3298    #[cfg(feature = "file_io")]
3299    fn test_c2pa_read_file_null_path() {
3300        let data_dir = CString::new("/tmp").unwrap();
3301        let result = unsafe { c2pa_read_file(std::ptr::null(), data_dir.as_ptr()) };
3302        assert!(result.is_null());
3303        let error = unsafe { c2pa_error() };
3304        let error_str = unsafe { CString::from_raw(error) };
3305        assert_eq!(error_str.to_str().unwrap(), "NullParameter: path");
3306    }
3307
3308    #[test]
3309    #[cfg(feature = "file_io")]
3310    fn test_c2pa_read_ingredient_file_null_path() {
3311        let data_dir = CString::new("/tmp").unwrap();
3312        let result = unsafe { c2pa_read_ingredient_file(std::ptr::null(), data_dir.as_ptr()) };
3313        assert!(result.is_null());
3314        let error = unsafe { c2pa_error() };
3315        let error_str = unsafe { CString::from_raw(error) };
3316        assert_eq!(error_str.to_str().unwrap(), "NullParameter: path");
3317    }
3318
3319    #[test]
3320    #[cfg(feature = "file_io")]
3321    fn test_c2pa_sign_file_null_source_path() {
3322        let dest_path = CString::new("/tmp/output.jpg").unwrap();
3323        let manifest = CString::new("{}").unwrap();
3324        let signer_info = C2paSignerInfo {
3325            alg: std::ptr::null(),
3326            sign_cert: std::ptr::null(),
3327            private_key: std::ptr::null(),
3328            ta_url: std::ptr::null(),
3329        };
3330        let result = unsafe {
3331            c2pa_sign_file(
3332                std::ptr::null(),
3333                dest_path.as_ptr(),
3334                manifest.as_ptr(),
3335                &signer_info,
3336                std::ptr::null(),
3337            )
3338        };
3339        assert!(result.is_null());
3340        let error = unsafe { c2pa_error() };
3341        let error_str = unsafe { CString::from_raw(error) };
3342        assert_eq!(error_str.to_str().unwrap(), "NullParameter: source_path");
3343    }
3344
3345    #[test]
3346    #[cfg(feature = "file_io")]
3347    fn test_c2pa_sign_file_success() {
3348        use std::{fs, path::PathBuf};
3349
3350        // Setup paths
3351        let base = env!("CARGO_MANIFEST_DIR");
3352        let source = format!("{base}/../sdk/tests/fixtures/IMG_0003.jpg");
3353        let temp_dir = PathBuf::from(base).join("../target/tmp");
3354        fs::create_dir_all(&temp_dir).unwrap();
3355        let dest = temp_dir.join("c2pa_sign_file_test_output.jpg");
3356
3357        let source_path = CString::new(source).unwrap();
3358        let dest_path = CString::new(dest.to_str().unwrap()).unwrap();
3359        let manifest = CString::new("{}").unwrap();
3360
3361        // Setup signer info
3362        let alg = CString::new("es256").unwrap();
3363        let cert = CString::new(include_str!(fixture_path!("certs/es256.pub"))).unwrap();
3364        let key =
3365            CString::new(include_bytes!(fixture_path!("certs/es256.pem")).as_slice()).unwrap();
3366
3367        let signer_info = C2paSignerInfo {
3368            alg: alg.as_ptr(),
3369            sign_cert: cert.as_ptr(),
3370            private_key: key.as_ptr(),
3371            ta_url: std::ptr::null(),
3372        };
3373
3374        // Call c2pa_sign_file
3375        let result = unsafe {
3376            c2pa_sign_file(
3377                source_path.as_ptr(),
3378                dest_path.as_ptr(),
3379                manifest.as_ptr(),
3380                &signer_info,
3381                std::ptr::null(),
3382            )
3383        };
3384
3385        // Verify result is not null and is an empty string
3386        assert!(
3387            !result.is_null(),
3388            "c2pa_sign_file should return non-null on success"
3389        );
3390        let result_str = unsafe { CString::from_raw(result) };
3391        assert_eq!(
3392            result_str.to_str().unwrap(),
3393            "",
3394            "c2pa_sign_file should return empty string on success"
3395        );
3396
3397        // Verify the output file was created and has content
3398        assert!(dest.exists(), "Output file should exist");
3399        let metadata = fs::metadata(&dest).unwrap();
3400        assert!(metadata.len() > 0, "Output file should have content");
3401
3402        // Clean up
3403        fs::remove_file(dest).ok();
3404    }
3405
3406    #[test]
3407    fn test_c2pa_reader_remote_url() {
3408        let mut stream = TestStream::new(include_bytes!(fixture_path!("cloud.jpg")).to_vec());
3409
3410        let format = CString::new("image/jpeg").unwrap();
3411        let result = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
3412        if result.is_null() {
3413            if let Some(msg) = CimplError::last_message() {
3414                panic!("Reader creation failed: {}", msg);
3415            } else {
3416                panic!("Reader creation failed with no error message");
3417            }
3418        }
3419        assert!(!result.is_null());
3420        let remote_url = unsafe { c2pa_reader_remote_url(result) };
3421        assert!(!remote_url.is_null());
3422        let remote_url = unsafe { std::ffi::CStr::from_ptr(remote_url) };
3423        assert_eq!(remote_url, c"https://cai-manifests.adobe.com/manifests/adobe-urn-uuid-5f37e182-3687-462e-a7fb-573462780391");
3424        unsafe { c2pa_reader_free(result) };
3425    }
3426
3427    // cargo test test_reader_file_with_wrong_label -- --nocapture
3428    #[test]
3429    fn test_reader_file_with_wrong_label() {
3430        let mut stream = TestStream::new(
3431            include_bytes!(fixture_path!("adobe-20220124-E-clm-CAICAI.jpg")).to_vec(),
3432        );
3433
3434        let format = CString::new("image/jpeg").unwrap();
3435        let result: *mut C2paReader =
3436            unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
3437        assert!(!result.is_null());
3438        unsafe { c2pa_reader_free(result) };
3439    }
3440
3441    #[test]
3442    fn test_c2pa_reader_from_stream_null_format() {
3443        let mut stream = TestStream::new(Vec::new());
3444
3445        let result = unsafe { c2pa_reader_from_stream(std::ptr::null(), stream.as_ptr()) };
3446        assert!(result.is_null());
3447        let error = unsafe { c2pa_error() };
3448        let error_str = unsafe { CString::from_raw(error) };
3449        assert_eq!(error_str.to_str().unwrap(), "NullParameter: format");
3450    }
3451
3452    #[test]
3453    fn test_c2pa_reader_from_stream_cawg() {
3454        let source_image = include_bytes!(
3455            "../../sdk/src/identity/tests/fixtures/claim_aggregation/ica_validation/success.jpg"
3456        );
3457        let mut stream = TestStream::new(source_image.to_vec());
3458        let format = CString::new("image/jpeg").unwrap();
3459        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
3460        assert!(!reader.is_null());
3461        let json = unsafe { c2pa_reader_json(reader) };
3462        assert!(!json.is_null());
3463        let json_str = unsafe { CString::from_raw(json) };
3464        assert!(json_str.to_str().unwrap().contains("Silly Cats 929"));
3465        assert!(json_str
3466            .to_str()
3467            .unwrap()
3468            .contains("cawg.ica.credential_valid"));
3469        unsafe { c2pa_reader_free(reader) };
3470    }
3471
3472    #[test]
3473    fn test_c2pa_reader_with_stream_from_context() {
3474        // Create a context with custom settings
3475        let builder = unsafe { c2pa_context_builder_new() };
3476        assert!(!builder.is_null());
3477
3478        let settings = unsafe { c2pa_settings_new() };
3479        assert!(!settings.is_null());
3480
3481        let json = CString::new(r#"{"verify": {"verify_after_sign": true}}"#).unwrap();
3482        let format = CString::new("json").unwrap();
3483        let result =
3484            unsafe { c2pa_settings_update_from_string(settings, json.as_ptr(), format.as_ptr()) };
3485        assert_eq!(result, 0);
3486
3487        let result = unsafe { c2pa_context_builder_set_settings(builder, settings) };
3488        assert_eq!(result, 0);
3489
3490        let context = unsafe { c2pa_context_builder_build(builder) };
3491        assert!(!context.is_null());
3492
3493        // Create a reader from the context
3494        let reader = unsafe { c2pa_reader_from_context(context) };
3495        assert!(!reader.is_null());
3496
3497        // Create a stream with test image data
3498        let source_image = include_bytes!(fixture_path!("adobe-20220124-E-clm-CAICAI.jpg"));
3499        let mut stream = TestStream::new(source_image.to_vec());
3500
3501        // Use with_stream to configure the reader
3502        let format = CString::new("image/jpeg").unwrap();
3503        let configured_reader =
3504            unsafe { c2pa_reader_with_stream(reader, format.as_ptr(), stream.as_ptr()) };
3505        assert!(!configured_reader.is_null());
3506
3507        // Verify consumed reader is no longer tracked
3508        let free_result = unsafe { c2pa_free(reader as *const c_void) };
3509        assert_eq!(free_result, -1);
3510
3511        // Verify we can read the manifest
3512        let json = unsafe { c2pa_reader_json(configured_reader) };
3513        assert!(!json.is_null());
3514        let json_str = unsafe { CString::from_raw(json) };
3515        let json_content = json_str.to_str().unwrap();
3516        // Verify the manifest has expected content (the fixture contains Adobe claims)
3517        assert!(
3518            json_content.contains("claim") || json_content.contains("manifest"),
3519            "Expected manifest content in JSON"
3520        );
3521
3522        unsafe {
3523            c2pa_free(settings as *mut c_void);
3524            c2pa_free(context as *mut c_void);
3525            c2pa_free(configured_reader as *mut c_void);
3526        };
3527    }
3528
3529    #[test]
3530    fn test_c2pa_reader_with_manifest_data_and_stream() {
3531        // Sign an image to get manifest bytes
3532        let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
3533        let mut source_stream = TestStream::new(source_image.to_vec());
3534        let mut dest_stream = TestStream::new(Vec::new());
3535
3536        let (signer, builder) = setup_signer_and_builder_for_signing_tests();
3537
3538        let format = CString::new("image/jpeg").unwrap();
3539        let mut manifest_bytes_ptr = std::ptr::null();
3540
3541        let result = unsafe {
3542            c2pa_builder_sign(
3543                builder,
3544                format.as_ptr(),
3545                source_stream.as_ptr(),
3546                dest_stream.as_ptr(),
3547                signer,
3548                &mut manifest_bytes_ptr,
3549            )
3550        };
3551        assert!(result > 0, "Signing should succeed");
3552        assert!(
3553            !manifest_bytes_ptr.is_null(),
3554            "Manifest bytes should be returned"
3555        );
3556        let manifest_size = result as usize;
3557
3558        // Create a context and reader from it
3559        let context = unsafe { c2pa_context_new() };
3560        assert!(!context.is_null());
3561
3562        let reader = unsafe { c2pa_reader_from_context(context) };
3563        assert!(!reader.is_null());
3564
3565        // Consume the reader with manifest data and stream
3566        let mut validation_stream = TestStream::new(source_image.to_vec());
3567        let configured_reader = unsafe {
3568            c2pa_reader_with_manifest_data_and_stream(
3569                reader,
3570                format.as_ptr(),
3571                validation_stream.as_ptr(),
3572                manifest_bytes_ptr,
3573                manifest_size,
3574            )
3575        };
3576        assert!(
3577            !configured_reader.is_null(),
3578            "Reader should be configured with manifest data and stream"
3579        );
3580
3581        // Verify the original reader was consumed
3582        let free_result = unsafe { c2pa_free(reader as *const c_void) };
3583        assert_eq!(free_result, -1);
3584
3585        // Verify we can read the manifest
3586        let json = unsafe { c2pa_reader_json(configured_reader) };
3587        assert!(!json.is_null(), "Should be able to get JSON from reader");
3588
3589        unsafe {
3590            c2pa_free(json as *const c_void);
3591            c2pa_free(configured_reader as *const c_void);
3592            c2pa_free(manifest_bytes_ptr as *const c_void);
3593            c2pa_free(builder as *const c_void);
3594            c2pa_free(signer as *const c_void);
3595            c2pa_free(context as *const c_void);
3596        }
3597    }
3598
3599    #[test]
3600    fn test_c2pa_reader_json_null_reader() {
3601        let result = unsafe { c2pa_reader_json(std::ptr::null_mut()) };
3602        assert!(result.is_null());
3603        let error = unsafe { c2pa_error() };
3604        let error_str = unsafe { CString::from_raw(error) };
3605        assert_eq!(error_str.to_str().unwrap(), "NullParameter: reader_ptr");
3606    }
3607
3608    #[test]
3609    fn test_c2pa_builder_add_resource_null_uri() {
3610        let manifest_def = CString::new("{}").unwrap();
3611        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
3612        assert!(!builder.is_null());
3613        let mut stream = TestStream::new(Vec::new());
3614        let result =
3615            unsafe { c2pa_builder_add_resource(builder, std::ptr::null(), stream.as_ptr()) };
3616        assert_eq!(result, -1);
3617        let error = unsafe { c2pa_error() };
3618        let error_str = unsafe { CString::from_raw(error) };
3619        assert_eq!(error_str.to_str().unwrap(), "NullParameter: uri");
3620        unsafe { c2pa_builder_free(builder) };
3621    }
3622
3623    #[test]
3624    fn test_c2pa_builder_to_archive_null_stream() {
3625        let manifest_def = CString::new("{}").unwrap();
3626        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
3627        assert!(!builder.is_null());
3628        let result = unsafe { c2pa_builder_to_archive(builder, std::ptr::null_mut()) };
3629        assert_eq!(result, -1);
3630        let error = unsafe { c2pa_error() };
3631        let error_str = unsafe { CString::from_raw(error) };
3632        assert_eq!(error_str.to_str().unwrap(), "NullParameter: stream");
3633        unsafe { c2pa_builder_free(builder) };
3634    }
3635
3636    #[test]
3637    fn test_c2pa_builder_add_ingredient_from_archive_null_stream() {
3638        let manifest_def = CString::new("{}").unwrap();
3639        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
3640        assert!(!builder.is_null());
3641        let result =
3642            unsafe { c2pa_builder_add_ingredient_from_archive(builder, std::ptr::null_mut()) };
3643        assert_eq!(result, -1);
3644        let error = unsafe { c2pa_error() };
3645        let error_str = unsafe { CString::from_raw(error) };
3646        assert_eq!(error_str.to_str().unwrap(), "NullParameter: stream");
3647        unsafe { c2pa_builder_free(builder) };
3648    }
3649
3650    #[test]
3651    fn test_c2pa_builder_add_ingredient_from_archive_null_builder() {
3652        let archive_bytes = include_bytes!(fixture_path!("cloud.jpg"));
3653        let mut stream = TestStream::new(archive_bytes.to_vec());
3654        let result = unsafe {
3655            c2pa_builder_add_ingredient_from_archive(std::ptr::null_mut(), stream.as_ptr())
3656        };
3657        assert_eq!(result, -1);
3658    }
3659
3660    #[test]
3661    fn test_c2pa_builder_write_ingredient_archive_null_stream() {
3662        let manifest_def = CString::new("{}").unwrap();
3663        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
3664        assert!(!builder.is_null());
3665        let ingredient_id = CString::new("test-ingredient").unwrap();
3666        let result = unsafe {
3667            c2pa_builder_write_ingredient_archive(
3668                builder,
3669                ingredient_id.as_ptr(),
3670                std::ptr::null_mut(),
3671            )
3672        };
3673        assert_eq!(result, -1);
3674        let error = unsafe { c2pa_error() };
3675        let error_str = unsafe { CString::from_raw(error) };
3676        assert_eq!(error_str.to_str().unwrap(), "NullParameter: stream");
3677        unsafe { c2pa_builder_free(builder) };
3678    }
3679
3680    #[test]
3681    fn test_c2pa_builder_write_ingredient_archive_null_ingredient_id() {
3682        let manifest_def = CString::new("{}").unwrap();
3683        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
3684        assert!(!builder.is_null());
3685        let archive_bytes = vec![0u8; 0];
3686        let mut stream = TestStream::new(archive_bytes);
3687        let result = unsafe {
3688            c2pa_builder_write_ingredient_archive(builder, std::ptr::null(), stream.as_ptr())
3689        };
3690        assert_eq!(result, -1);
3691        let error = unsafe { c2pa_error() };
3692        let error_str = unsafe { CString::from_raw(error) };
3693        assert_eq!(error_str.to_str().unwrap(), "NullParameter: ingredient_id");
3694        unsafe { c2pa_builder_free(builder) };
3695    }
3696
3697    #[test]
3698    fn test_c2pa_builder_read_supported_mime_types() {
3699        let mut count = 0;
3700        let mime_types = unsafe { c2pa_builder_supported_mime_types(&mut count) };
3701        assert!(!mime_types.is_null());
3702        assert_eq!(count, C2paBuilder::supported_mime_types().len());
3703        unsafe { c2pa_free_string_array(mime_types, count) };
3704    }
3705
3706    #[test]
3707    fn test_c2pa_reader_read_supported_mime_types() {
3708        let mut count = 0;
3709        let mime_types = unsafe { c2pa_reader_supported_mime_types(&mut count) };
3710        assert!(!mime_types.is_null());
3711        assert_eq!(count, C2paReader::supported_mime_types().len());
3712        unsafe { c2pa_free_string_array(mime_types, count) };
3713    }
3714
3715    #[test]
3716    fn test_c2pa_free_string_array_with_nullptr() {
3717        assert!(catch_unwind(|| {
3718            unsafe {
3719                c2pa_free_string_array(std::ptr::null_mut(), 0);
3720            }
3721        })
3722        .is_ok());
3723    }
3724
3725    #[test]
3726    fn test_c2pa_free_string_array_with_count_1() {
3727        let mut ptrs = vec![to_c_string("image/jpeg".to_string())];
3728        let count = ptrs.len();
3729        let ptr = ptrs.as_mut_ptr() as *const *const c_char;
3730        std::mem::forget(ptrs);
3731
3732        // Assert the function doesn't panic
3733        assert!(catch_unwind(|| {
3734            unsafe {
3735                c2pa_free_string_array(ptr, count);
3736            }
3737        })
3738        .is_ok());
3739    }
3740
3741    #[test]
3742    fn test_create_callback_signer() {
3743        extern "C" fn test_callback(
3744            _context: *const (),
3745            _data: *const c_uchar,
3746            _len: usize,
3747            _signed_bytes: *mut c_uchar,
3748            _signed_len: usize,
3749        ) -> isize {
3750            // Placeholder signer
3751            1
3752        }
3753
3754        let certs = include_str!(fixture_path!("certs/ed25519.pub"));
3755        let certs_cstr = CString::new(certs).unwrap();
3756
3757        let signer = unsafe {
3758            c2pa_signer_create(
3759                std::ptr::null(),
3760                test_callback,
3761                C2paSigningAlg::Ed25519,
3762                certs_cstr.as_ptr(),
3763                std::ptr::null(),
3764            )
3765        };
3766
3767        // verify signer is not null (aka could be created)
3768        assert!(!signer.is_null());
3769
3770        unsafe { c2pa_signer_free(signer) };
3771    }
3772
3773    #[test]
3774    fn test_sign_with_callback_signer() {
3775        // Create an example callback that uses the Ed25519 signing function,
3776        // since we have it around. It is important a "real" callback returns -1 on error.
3777        extern "C" fn test_callback(
3778            _context: *const (),
3779            data: *const c_uchar,
3780            len: usize,
3781            signed_bytes: *mut c_uchar,
3782            signed_len: usize,
3783        ) -> isize {
3784            let private_key = include_bytes!(fixture_path!("certs/ed25519.pem"));
3785            let private_key_cstr = match CString::new(private_key) {
3786                Ok(s) => s,
3787                Err(_) => return -1,
3788            };
3789
3790            let signature = unsafe { c2pa_ed25519_sign(data, len, private_key_cstr.as_ptr()) };
3791
3792            // This should not happen, but in a real callback implementation we should check
3793            if signature.is_null() {
3794                return -1;
3795            }
3796
3797            let signature_len = 64;
3798            if signed_len < signature_len {
3799                // This should not happen either, but in a real callback implementation we should check
3800                unsafe { c2pa_signature_free(signature) };
3801                return -1;
3802            }
3803
3804            // Safe bounds validation for test callback
3805            let signature_slice =
3806                match unsafe { safe_slice_from_raw_parts(signature, signature_len, "signature") } {
3807                    Ok(slice) => slice,
3808                    Err(_) => {
3809                        unsafe { c2pa_signature_free(signature) };
3810                        return -1;
3811                    }
3812                };
3813
3814            // Validate signed_bytes bounds
3815            if !unsafe { is_safe_buffer_size(signed_len, signed_bytes) } {
3816                unsafe { c2pa_signature_free(signature) };
3817                return -1;
3818            }
3819
3820            let signed_slice = unsafe { std::slice::from_raw_parts_mut(signed_bytes, signed_len) };
3821
3822            if signature_len <= signed_slice.len() {
3823                signed_slice[..signature_len].copy_from_slice(signature_slice);
3824            } else {
3825                unsafe { c2pa_signature_free(signature) };
3826                return -1;
3827            }
3828
3829            unsafe { c2pa_signature_free(signature) };
3830            signature_len as isize
3831        }
3832
3833        let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
3834        let mut source_stream = TestStream::new(source_image.to_vec());
3835        let dest_vec = Vec::new();
3836        let mut dest_stream = TestStream::new(dest_vec);
3837
3838        let certs = include_str!(fixture_path!("certs/ed25519.pub"));
3839        let certs_cstr = CString::new(certs).unwrap();
3840
3841        // Callback signer with a "real" callback that signs data
3842        let signer = unsafe {
3843            c2pa_signer_create(
3844                std::ptr::null(), // context
3845                test_callback,
3846                C2paSigningAlg::Ed25519,
3847                certs_cstr.as_ptr(),
3848                std::ptr::null(), // tsa_url
3849            )
3850        };
3851
3852        assert!(!signer.is_null());
3853
3854        let manifest_def = CString::new("{}").unwrap();
3855        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
3856        assert!(!builder.is_null());
3857
3858        let result = unsafe {
3859            c2pa_builder_set_intent(
3860                builder,
3861                C2paBuilderIntent::Create,
3862                C2paDigitalSourceType::Empty,
3863            )
3864        };
3865        assert_eq!(result, 0);
3866
3867        let format = CString::new("image/jpeg").unwrap();
3868        let mut manifest_bytes_ptr = std::ptr::null();
3869
3870        // Data gets signed here using the callback
3871        let result = unsafe {
3872            c2pa_builder_sign(
3873                builder,
3874                format.as_ptr(),
3875                source_stream.as_ptr(),
3876                dest_stream.as_ptr(),
3877                signer,
3878                &mut manifest_bytes_ptr,
3879            )
3880        };
3881        assert!(result > 0);
3882
3883        // Verify we can read the signed data back
3884        dest_stream.stream_mut().rewind().unwrap();
3885        let format = CString::new("image/jpeg").unwrap();
3886
3887        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), dest_stream.as_ptr()) };
3888        if reader.is_null() {
3889            if let Some(msg) = CimplError::last_message() {
3890                panic!("Reader creation failed: {}", msg);
3891            }
3892        }
3893        assert!(!reader.is_null());
3894
3895        let json = unsafe { c2pa_reader_json(reader) };
3896        assert!(!json.is_null());
3897        let json_str = unsafe { CString::from_raw(json) };
3898        let json_content = json_str.to_str().unwrap();
3899
3900        assert!(json_content.contains("manifest"));
3901
3902        unsafe {
3903            c2pa_manifest_bytes_free(manifest_bytes_ptr);
3904            c2pa_reader_free(reader);
3905        }
3906        unsafe { c2pa_builder_free(builder) };
3907        unsafe { c2pa_signer_free(signer) };
3908    }
3909
3910    #[test]
3911    #[cfg(feature = "file_io")]
3912    fn test_reader_from_file_cawg_identity() {
3913        let settings = CString::new(include_bytes!(
3914            "../../cli/tests/fixtures/trust/cawg_test_settings.toml"
3915        ))
3916        .unwrap();
3917        let format = CString::new("toml").unwrap();
3918        let result = unsafe { c2pa_load_settings(settings.as_ptr(), format.as_ptr()) };
3919        assert_eq!(result, 0);
3920
3921        let base = env!("CARGO_MANIFEST_DIR");
3922        let path =
3923            CString::new(format!("{base}/../sdk/tests/fixtures/C_with_CAWG_data.jpg")).unwrap();
3924        let reader = unsafe { c2pa_reader_from_file(path.as_ptr()) };
3925        if reader.is_null() {
3926            let error = unsafe { c2pa_error() };
3927            let error_str = unsafe { CString::from_raw(error) };
3928            panic!("Failed to create reader: {}", error_str.to_str().unwrap());
3929        }
3930        assert!(!reader.is_null());
3931        let json = unsafe { c2pa_reader_json(reader) };
3932        assert!(!json.is_null());
3933        let json_str = unsafe { CString::from_raw(json) };
3934        println!("JSON Report: {}", json_str.to_str().unwrap());
3935        let json_report = json_str.to_str().unwrap();
3936        assert!(json_report.contains("cawg.identity"));
3937        assert!(json_report.contains("cawg.identity.well-formed"));
3938        unsafe { c2pa_reader_free(reader) };
3939    }
3940
3941    #[test]
3942    fn test_c2pa_signer_from_settings() {
3943        const SETTINGS: &str = include_str!("../../sdk/tests/fixtures/test_settings.json");
3944        let settings = CString::new(SETTINGS).unwrap();
3945        let format = CString::new("json").unwrap();
3946        let result = unsafe { c2pa_load_settings(settings.as_ptr(), format.as_ptr()) };
3947        assert_eq!(result, 0);
3948        let signer = unsafe { c2pa_signer_from_settings() };
3949        assert!(!signer.is_null());
3950        unsafe { c2pa_signer_free(signer) };
3951    }
3952
3953    #[test]
3954    fn test_c2pa_settings_new() {
3955        let settings = unsafe { c2pa_settings_new() };
3956        assert!(!settings.is_null());
3957        unsafe { c2pa_free(settings as *mut c_void) };
3958    }
3959
3960    #[test]
3961    fn test_c2pa_settings_update_from_json_string() {
3962        let settings = unsafe { c2pa_settings_new() };
3963        assert!(!settings.is_null());
3964
3965        let json = CString::new(r#"{"verify": {"verify_after_sign": true}}"#).unwrap();
3966        let format = CString::new("json").unwrap();
3967
3968        let result =
3969            unsafe { c2pa_settings_update_from_string(settings, json.as_ptr(), format.as_ptr()) };
3970        assert_eq!(result, 0);
3971
3972        unsafe { c2pa_free(settings as *mut c_void) };
3973    }
3974
3975    #[test]
3976    fn test_c2pa_settings_update_from_toml_string() {
3977        let settings = unsafe { c2pa_settings_new() };
3978        assert!(!settings.is_null());
3979
3980        let toml = CString::new(
3981            r#"
3982[verify]
3983verify_after_sign = true
3984"#,
3985        )
3986        .unwrap();
3987        let format = CString::new("toml").unwrap();
3988
3989        let result =
3990            unsafe { c2pa_settings_update_from_string(settings, toml.as_ptr(), format.as_ptr()) };
3991        assert_eq!(result, 0);
3992
3993        unsafe { c2pa_free(settings as *mut c_void) };
3994    }
3995
3996    #[test]
3997    fn test_c2pa_settings_update_from_string_invalid() {
3998        let settings = unsafe { c2pa_settings_new() };
3999        assert!(!settings.is_null());
4000
4001        let invalid_json = CString::new(r#"{"verify": {"verify_after_sign": "#).unwrap();
4002        let format = CString::new("json").unwrap();
4003
4004        let result = unsafe {
4005            c2pa_settings_update_from_string(settings, invalid_json.as_ptr(), format.as_ptr())
4006        };
4007        assert_ne!(result, 0); // Should fail with invalid JSON
4008
4009        unsafe { c2pa_free(settings as *mut c_void) };
4010    }
4011
4012    #[test]
4013    fn test_c2pa_settings_set_value() {
4014        let settings = unsafe { c2pa_settings_new() };
4015        assert!(!settings.is_null());
4016
4017        let path = CString::new("verify.verify_after_sign").unwrap();
4018        let value = CString::new("true").unwrap();
4019
4020        let result = unsafe { c2pa_settings_set_value(settings, path.as_ptr(), value.as_ptr()) };
4021        assert_eq!(result, 0);
4022
4023        unsafe { c2pa_free(settings as *mut c_void) };
4024    }
4025
4026    #[test]
4027    fn test_c2pa_settings_set_value_string() {
4028        let settings = unsafe { c2pa_settings_new() };
4029        assert!(!settings.is_null());
4030
4031        // Test setting array of strings which is a common string value type
4032        let path = CString::new("core.allowed_network_hosts").unwrap();
4033        let value = CString::new(r#"["example.com", "test.org"]"#).unwrap(); // JSON array of strings
4034
4035        let result = unsafe { c2pa_settings_set_value(settings, path.as_ptr(), value.as_ptr()) };
4036        assert_eq!(result, 0);
4037
4038        unsafe { c2pa_free(settings as *mut c_void) };
4039    }
4040
4041    #[test]
4042    fn test_c2pa_settings_set_value_number() {
4043        let settings = unsafe { c2pa_settings_new() };
4044        assert!(!settings.is_null());
4045
4046        let path = CString::new("verify.max_memory_usage").unwrap();
4047        let value = CString::new("1000000").unwrap();
4048
4049        let result = unsafe { c2pa_settings_set_value(settings, path.as_ptr(), value.as_ptr()) };
4050        assert_eq!(result, 0);
4051
4052        unsafe { c2pa_free(settings as *mut c_void) };
4053    }
4054
4055    #[test]
4056    fn test_c2pa_settings_set_value_invalid_json() {
4057        let settings = unsafe { c2pa_settings_new() };
4058        assert!(!settings.is_null());
4059
4060        let path = CString::new("verify.verify_after_sign").unwrap();
4061        let invalid_value = CString::new("not valid json").unwrap(); // Not valid JSON
4062
4063        let result =
4064            unsafe { c2pa_settings_set_value(settings, path.as_ptr(), invalid_value.as_ptr()) };
4065        assert_ne!(result, 0); // Should fail with invalid JSON
4066
4067        unsafe { c2pa_free(settings as *mut c_void) };
4068    }
4069
4070    #[test]
4071    fn test_c2pa_context_new() {
4072        let context = unsafe { c2pa_context_new() };
4073        assert!(!context.is_null());
4074        unsafe { c2pa_free(context as *mut c_void) };
4075    }
4076
4077    #[test]
4078    fn test_c2pa_context_builder_new() {
4079        let builder = unsafe { c2pa_context_builder_new() };
4080        assert!(!builder.is_null());
4081        unsafe { c2pa_free(builder as *mut c_void) };
4082    }
4083
4084    #[test]
4085    fn test_c2pa_context_builder_set_settings() {
4086        let builder = unsafe { c2pa_context_builder_new() };
4087        assert!(!builder.is_null());
4088
4089        let settings = unsafe { c2pa_settings_new() };
4090        assert!(!settings.is_null());
4091
4092        // Update settings
4093        let json = CString::new(r#"{"verify": {"verify_after_sign": true}}"#).unwrap();
4094        let format = CString::new("json").unwrap();
4095        let result =
4096            unsafe { c2pa_settings_update_from_string(settings, json.as_ptr(), format.as_ptr()) };
4097        assert_eq!(result, 0);
4098
4099        // Set settings on builder
4100        let result = unsafe { c2pa_context_builder_set_settings(builder, settings) };
4101        assert_eq!(result, 0);
4102
4103        unsafe {
4104            c2pa_free(settings as *mut c_void);
4105            c2pa_free(builder as *mut c_void);
4106        };
4107    }
4108
4109    #[test]
4110    fn test_c2pa_context_builder_build() {
4111        let builder = unsafe { c2pa_context_builder_new() };
4112        assert!(!builder.is_null());
4113
4114        let settings = unsafe { c2pa_settings_new() };
4115        assert!(!settings.is_null());
4116
4117        let json = CString::new(r#"{"verify": {"verify_after_sign": true}}"#).unwrap();
4118        let format = CString::new("json").unwrap();
4119        let result =
4120            unsafe { c2pa_settings_update_from_string(settings, json.as_ptr(), format.as_ptr()) };
4121        assert_eq!(result, 0);
4122
4123        let result = unsafe { c2pa_context_builder_set_settings(builder, settings) };
4124        assert_eq!(result, 0);
4125
4126        // Build the context (consumes builder)
4127        let context = unsafe { c2pa_context_builder_build(builder) };
4128        assert!(!context.is_null());
4129
4130        // Verify consumed builder is no longer tracked
4131        let free_result = unsafe { c2pa_free(builder as *const c_void) };
4132        assert_eq!(free_result, -1);
4133
4134        unsafe {
4135            c2pa_free(settings as *mut c_void);
4136            c2pa_free(context as *mut c_void);
4137        };
4138    }
4139
4140    #[test]
4141    fn test_c2pa_context_builder_set_settings_multiple_times() {
4142        let builder = unsafe { c2pa_context_builder_new() };
4143        assert!(!builder.is_null());
4144
4145        // First settings
4146        let settings1 = unsafe { c2pa_settings_new() };
4147        assert!(!settings1.is_null());
4148        let json1 = CString::new(r#"{"verify": {"verify_after_sign": true}}"#).unwrap();
4149        let format = CString::new("json").unwrap();
4150        let result =
4151            unsafe { c2pa_settings_update_from_string(settings1, json1.as_ptr(), format.as_ptr()) };
4152        assert_eq!(result, 0);
4153
4154        let result = unsafe { c2pa_context_builder_set_settings(builder, settings1) };
4155        assert_eq!(result, 0);
4156
4157        // Second settings (update builder again)
4158        let settings2 = unsafe { c2pa_settings_new() };
4159        assert!(!settings2.is_null());
4160        let json2 = CString::new(r#"{"verify": {"verify_after_sign": false}}"#).unwrap();
4161        let result =
4162            unsafe { c2pa_settings_update_from_string(settings2, json2.as_ptr(), format.as_ptr()) };
4163        assert_eq!(result, 0);
4164
4165        let result = unsafe { c2pa_context_builder_set_settings(builder, settings2) };
4166        assert_eq!(result, 0);
4167
4168        // Build context
4169        let context = unsafe { c2pa_context_builder_build(builder) };
4170        assert!(!context.is_null());
4171
4172        unsafe {
4173            c2pa_free(settings1 as *mut c_void);
4174            c2pa_free(settings2 as *mut c_void);
4175            c2pa_free(context as *mut c_void);
4176        };
4177    }
4178
4179    #[test]
4180    fn test_c2pa_context_builder_with_full_config() {
4181        let builder = unsafe { c2pa_context_builder_new() };
4182        assert!(!builder.is_null());
4183
4184        let settings = unsafe { c2pa_settings_new() };
4185        assert!(!settings.is_null());
4186
4187        // Load full settings from test file
4188        const SETTINGS: &str = include_str!("../../sdk/tests/fixtures/test_settings.json");
4189        let settings_str = CString::new(SETTINGS).unwrap();
4190        let format = CString::new("json").unwrap();
4191        let result = unsafe {
4192            c2pa_settings_update_from_string(settings, settings_str.as_ptr(), format.as_ptr())
4193        };
4194        assert_eq!(result, 0);
4195
4196        // Apply to builder
4197        let result = unsafe { c2pa_context_builder_set_settings(builder, settings) };
4198        assert_eq!(result, 0);
4199
4200        // Build context
4201        let context = unsafe { c2pa_context_builder_build(builder) };
4202        assert!(!context.is_null());
4203
4204        unsafe {
4205            c2pa_free(settings as *mut c_void);
4206            c2pa_free(context as *mut c_void);
4207        };
4208    }
4209
4210    #[test]
4211    fn test_c2pa_context_can_be_shared() {
4212        // Test that a context can be used to create multiple readers
4213        let context = unsafe { c2pa_context_new() };
4214        assert!(!context.is_null());
4215
4216        // Create multiple readers from the same context
4217        let reader1 = unsafe { c2pa_reader_from_context(context) };
4218        assert!(!reader1.is_null());
4219
4220        let reader2 = unsafe { c2pa_reader_from_context(context) };
4221        assert!(!reader2.is_null());
4222
4223        // Context is still valid and can be reused
4224        unsafe {
4225            c2pa_free(reader1 as *mut c_void);
4226            c2pa_free(reader2 as *mut c_void);
4227            c2pa_free(context as *mut c_void);
4228        };
4229    }
4230
4231    #[test]
4232    fn test_c2pa_free_works_for_all_types() {
4233        // Test that c2pa_free works for different object types
4234        let settings = unsafe { c2pa_settings_new() };
4235        assert!(!settings.is_null());
4236        let result = unsafe { c2pa_free(settings as *mut c_void) };
4237        assert_eq!(result, 0);
4238
4239        let context = unsafe { c2pa_context_new() };
4240        assert!(!context.is_null());
4241        let result = unsafe { c2pa_free(context as *mut c_void) };
4242        assert_eq!(result, 0);
4243
4244        // Test with a string
4245        let test_str = CString::new("test").unwrap();
4246        let c_str = to_c_string(test_str.to_str().unwrap().to_string());
4247        assert!(!c_str.is_null());
4248        let result = unsafe { c2pa_free(c_str as *mut c_void) };
4249        assert_eq!(result, 0);
4250    }
4251
4252    #[test]
4253    fn test_c2pa_reader_detailed_json() {
4254        use std::ffi::CStr;
4255
4256        let source_image = include_bytes!(fixture_path!("C.jpg"));
4257        let mut stream = TestStream::new(source_image.to_vec());
4258        let format = CString::new("image/jpeg").unwrap();
4259
4260        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
4261        assert!(!reader.is_null());
4262
4263        // Get detailed JSON
4264        let detailed_json = unsafe { c2pa_reader_detailed_json(reader) };
4265        assert!(!detailed_json.is_null());
4266
4267        // Verify it's valid JSON and non-empty
4268        let json_str = unsafe { CStr::from_ptr(detailed_json).to_str().unwrap() };
4269        assert!(!json_str.is_empty(), "Detailed JSON should not be empty");
4270
4271        let json_value: serde_json::Value = serde_json::from_str(json_str).unwrap();
4272        // Just verify it's a valid JSON object
4273        assert!(json_value.is_object(), "Detailed JSON should be an object");
4274
4275        unsafe {
4276            c2pa_free(detailed_json as *mut c_void);
4277            c2pa_free(reader as *mut c_void);
4278        }
4279    }
4280
4281    #[test]
4282    fn test_c2pa_reader_is_embedded() {
4283        // Test with embedded manifest
4284        let source_image = include_bytes!(fixture_path!("C.jpg"));
4285        let mut stream = TestStream::new(source_image.to_vec());
4286        let format = CString::new("image/jpeg").unwrap();
4287
4288        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
4289        assert!(!reader.is_null());
4290
4291        // Just test that the function executes without crashing
4292        // The actual return value depends on the manifest structure
4293        let _is_embedded = unsafe { c2pa_reader_is_embedded(reader) };
4294
4295        // Function should not crash - that's the main test
4296        unsafe {
4297            c2pa_free(reader as *mut c_void);
4298        }
4299    }
4300
4301    #[test]
4302    fn test_c2pa_builder_from_context() {
4303        // Create a custom context
4304        let context = unsafe { c2pa_context_new() };
4305        assert!(!context.is_null());
4306
4307        // Create builder from context
4308        let builder = unsafe { c2pa_builder_from_context(context) };
4309        assert!(!builder.is_null());
4310
4311        // Verify builder can be used
4312        let manifest_json = CString::new(r#"{"claim_generator": "test"}"#).unwrap();
4313        let builder = unsafe { c2pa_builder_with_definition(builder, manifest_json.as_ptr()) };
4314        assert!(!builder.is_null());
4315
4316        unsafe {
4317            c2pa_free(builder as *mut c_void);
4318            c2pa_free(context as *mut c_void);
4319        }
4320    }
4321
4322    #[test]
4323    fn test_c2pa_format_embeddable() {
4324        // This function requires manifest bytes, which is complex to set up.
4325        // For now, test with minimal setup to verify it doesn't crash
4326        let jpeg_format = CString::new("image/jpeg").unwrap();
4327        let placeholder_bytes = b"placeholder";
4328        let mut result_ptr: *const c_uchar = std::ptr::null();
4329
4330        let _result = unsafe {
4331            c2pa_format_embeddable(
4332                jpeg_format.as_ptr(),
4333                placeholder_bytes.as_ptr(),
4334                placeholder_bytes.len(),
4335                &mut result_ptr,
4336            )
4337        };
4338
4339        // Function should execute without crashing - that's the main test
4340        // The result value depends on whether the placeholder is valid
4341        if !result_ptr.is_null() {
4342            unsafe { c2pa_free(result_ptr as *const c_void) };
4343        }
4344    }
4345
4346    #[test]
4347    fn test_c2pa_builder_add_ingredient_from_stream() {
4348        let manifest_def = CString::new("{}").unwrap();
4349        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
4350        assert!(!builder.is_null());
4351
4352        // Create ingredient stream
4353        let ingredient_image = include_bytes!(fixture_path!("C.jpg"));
4354        let mut ingredient_stream = TestStream::new(ingredient_image.to_vec());
4355
4356        let ingredient_json = CString::new(r#"{"title": "Test Ingredient"}"#).unwrap();
4357        let format = CString::new("image/jpeg").unwrap();
4358
4359        // Add ingredient - note the correct parameter order
4360        let result = unsafe {
4361            c2pa_builder_add_ingredient_from_stream(
4362                builder,
4363                ingredient_json.as_ptr(),
4364                format.as_ptr(),
4365                ingredient_stream.as_ptr(),
4366            )
4367        };
4368        assert_eq!(result, 0, "Should successfully add ingredient");
4369
4370        unsafe {
4371            c2pa_free(builder as *mut c_void);
4372        }
4373    }
4374
4375    #[test]
4376    fn test_c2pa_builder_with_definition() {
4377        // Create initial builder
4378        let manifest_def = CString::new("{}").unwrap();
4379        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
4380        assert!(!builder.is_null());
4381
4382        // Add definition to builder (this consumes the builder and returns a new one)
4383        let new_manifest = CString::new(r#"{"claim_generator": "test_with_definition"}"#).unwrap();
4384        let new_builder = unsafe { c2pa_builder_with_definition(builder, new_manifest.as_ptr()) };
4385        assert!(!new_builder.is_null(), "Should return new builder");
4386
4387        // Verify consumed builder is no longer tracked
4388        let free_result = unsafe { c2pa_free(builder as *const c_void) };
4389        assert_eq!(free_result, -1);
4390
4391        unsafe {
4392            c2pa_free(new_builder as *mut c_void);
4393        }
4394    }
4395
4396    #[test]
4397    fn test_c2pa_builder_with_definition_null_json() {
4398        // Create initial builder
4399        let manifest_def = CString::new("{}").unwrap();
4400        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
4401        assert!(!builder.is_null());
4402
4403        // Test with null JSON: returns null, but the builder is not consumed!
4404        let new_builder = unsafe { c2pa_builder_with_definition(builder, std::ptr::null()) };
4405        assert!(
4406            new_builder.is_null(),
4407            "Should return null for invalid input"
4408        );
4409
4410        // Builder is still tracked because validation failed before consumption
4411        let free_result = unsafe { c2pa_free(builder as *mut c_void) };
4412        assert_eq!(free_result, 0);
4413    }
4414
4415    #[test]
4416    fn test_c2pa_builder_with_archive() {
4417        // Create initial builder
4418        let manifest_def = CString::new("{}").unwrap();
4419        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
4420        assert!(!builder.is_null());
4421
4422        // Create archive stream (using a simple image as placeholder)
4423        let archive_bytes = include_bytes!(fixture_path!("C.jpg"));
4424        let mut archive_stream = TestStream::new(archive_bytes.to_vec());
4425
4426        // Add archive to builder (this consumes the builder and returns a new one)
4427        let new_builder = unsafe { c2pa_builder_with_archive(builder, archive_stream.as_ptr()) };
4428
4429        // Verify consumed builder is no longer tracked
4430        let free_result = unsafe { c2pa_free(builder as *const c_void) };
4431        assert_eq!(free_result, -1);
4432
4433        if !new_builder.is_null() {
4434            unsafe {
4435                c2pa_free(new_builder as *mut c_void);
4436            }
4437        }
4438    }
4439
4440    #[test]
4441    fn test_c2pa_builder_with_archive_null_stream() {
4442        // Create initial builder
4443        let manifest_def = CString::new("{}").unwrap();
4444        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
4445        assert!(!builder.is_null());
4446
4447        // Test with null stream: returns null, but the builder is NOT consumed
4448        let new_builder = unsafe { c2pa_builder_with_archive(builder, std::ptr::null_mut()) };
4449        assert!(
4450            new_builder.is_null(),
4451            "Should return null for invalid stream"
4452        );
4453
4454        // Builder is still tracked because validation failed before consumption
4455        let free_result = unsafe { c2pa_free(builder as *mut c_void) };
4456        assert_eq!(free_result, 0);
4457    }
4458
4459    #[test]
4460    fn test_c2pa_reader_with_fragment() {
4461        // Create initial reader
4462        let source_image = include_bytes!(fixture_path!("C.jpg"));
4463        let mut stream = TestStream::new(source_image.to_vec());
4464        let format = CString::new("image/jpeg").unwrap();
4465
4466        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
4467        assert!(!reader.is_null());
4468
4469        // Create fragment stream
4470        let fragment_bytes = include_bytes!(fixture_path!("C.jpg"));
4471        let mut fragment_stream = TestStream::new(fragment_bytes.to_vec());
4472        let mut main_stream = TestStream::new(source_image.to_vec());
4473
4474        // Add fragment to reader (this consumes the reader and returns a new one)
4475        let new_reader = unsafe {
4476            c2pa_reader_with_fragment(
4477                reader,
4478                format.as_ptr(),
4479                main_stream.as_ptr(),
4480                fragment_stream.as_ptr(),
4481            )
4482        };
4483
4484        // Verify consumed reader is no longer tracked
4485        let free_result = unsafe { c2pa_free(reader as *const c_void) };
4486        assert_eq!(free_result, -1);
4487
4488        if !new_reader.is_null() {
4489            unsafe {
4490                c2pa_free(new_reader as *mut c_void);
4491            }
4492        }
4493    }
4494
4495    #[test]
4496    fn test_c2pa_reader_with_fragment_null_format() {
4497        // Create initial reader
4498        let source_image = include_bytes!(fixture_path!("C.jpg"));
4499        let mut stream = TestStream::new(source_image.to_vec());
4500        let format = CString::new("image/jpeg").unwrap();
4501
4502        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
4503        assert!(!reader.is_null());
4504
4505        let mut fragment_stream = TestStream::new(source_image.to_vec());
4506        let mut main_stream = TestStream::new(source_image.to_vec());
4507
4508        // Test with null format: returns null, but the reader is NOT consumed
4509        let new_reader = unsafe {
4510            c2pa_reader_with_fragment(
4511                reader,
4512                std::ptr::null(),
4513                main_stream.as_ptr(),
4514                fragment_stream.as_ptr(),
4515            )
4516        };
4517        assert!(
4518            new_reader.is_null(),
4519            "Should return null for invalid format"
4520        );
4521
4522        // Reader is still tracked because validation failed before consumption
4523        let free_result = unsafe { c2pa_free(reader as *const c_void) };
4524        assert_eq!(free_result, 0);
4525    }
4526
4527    // ========== High-Value Coverage Tests ==========
4528
4529    #[test]
4530    fn test_c2pa_reader_new() {
4531        let reader = unsafe { c2pa_reader_new() };
4532        assert!(!reader.is_null(), "Should create a default reader");
4533
4534        unsafe {
4535            c2pa_free(reader as *mut c_void);
4536        }
4537    }
4538
4539    #[test]
4540    fn test_c2pa_reader_is_embedded_null() {
4541        // Test null pointer - should return false via deref_or_return_false!
4542        let result = unsafe { c2pa_reader_is_embedded(std::ptr::null_mut()) };
4543        assert!(!result, "Null reader should return false");
4544    }
4545
4546    #[test]
4547    fn test_c2pa_reader_remote_url_null() {
4548        // Test null pointer - should return null
4549        let result = unsafe { c2pa_reader_remote_url(std::ptr::null_mut()) };
4550        assert!(result.is_null(), "Null reader should return null URL");
4551    }
4552
4553    #[test]
4554    fn test_c2pa_builder_set_intent_null() {
4555        // Test null pointer - should return error
4556        let result = unsafe {
4557            c2pa_builder_set_intent(
4558                std::ptr::null_mut(),
4559                C2paBuilderIntent::Create,
4560                C2paDigitalSourceType::DigitalCapture,
4561            )
4562        };
4563        assert_eq!(result, -1, "Null builder should return -1");
4564    }
4565
4566    #[test]
4567    fn test_c2pa_builder_add_action_null_builder() {
4568        let action = CString::new(r#"{"action": "c2pa.edited"}"#).unwrap();
4569        let result = unsafe { c2pa_builder_add_action(std::ptr::null_mut(), action.as_ptr()) };
4570        assert_eq!(result, -1, "Null builder should return error");
4571    }
4572
4573    #[test]
4574    fn test_c2pa_builder_add_action_null_action() {
4575        let manifest_def = CString::new("{}").unwrap();
4576        let builder = unsafe { c2pa_builder_from_json(manifest_def.as_ptr()) };
4577        assert!(!builder.is_null());
4578
4579        let result = unsafe { c2pa_builder_add_action(builder, std::ptr::null()) };
4580        assert_eq!(result, -1, "Null action should return error");
4581
4582        unsafe {
4583            c2pa_free(builder as *mut c_void);
4584        }
4585    }
4586
4587    #[test]
4588    fn test_c2pa_context_builder_set_settings_null_settings() {
4589        let builder = unsafe { c2pa_context_builder_new() };
4590        assert!(!builder.is_null());
4591
4592        // Test with null settings pointer
4593        let result = unsafe { c2pa_context_builder_set_settings(builder, std::ptr::null_mut()) };
4594        assert_eq!(result, -1, "Null settings should return -1");
4595
4596        unsafe {
4597            c2pa_free(builder as *mut c_void);
4598        }
4599    }
4600
4601    #[test]
4602    fn test_c2pa_signer_reserve_size() {
4603        let (signer, builder) = setup_signer_and_builder_for_signing_tests();
4604
4605        let size = unsafe { c2pa_signer_reserve_size(signer) };
4606        assert!(size > 0, "Reserve size should be positive");
4607
4608        unsafe {
4609            c2pa_free(signer as *mut c_void);
4610            c2pa_free(builder as *mut c_void);
4611        }
4612    }
4613
4614    #[test]
4615    fn test_c2pa_signer_reserve_size_null() {
4616        let size = unsafe { c2pa_signer_reserve_size(std::ptr::null_mut()) };
4617        assert_eq!(size, -1, "Null signer should return -1");
4618    }
4619
4620    #[test]
4621    fn test_c2pa_string_free_backward_compat() {
4622        // Test that string_free works for backward compatibility
4623        let test_str = CString::new("test string").unwrap();
4624        let c_str = to_c_string(test_str.to_str().unwrap().to_string());
4625        assert!(!c_str.is_null());
4626
4627        // Should not crash
4628        unsafe {
4629            c2pa_string_free(c_str);
4630        }
4631    }
4632
4633    #[test]
4634    fn test_c2pa_string_free_null() {
4635        // Should handle null gracefully
4636        unsafe {
4637            c2pa_string_free(std::ptr::null_mut());
4638        }
4639        // If we get here, it handled null without crashing
4640    }
4641
4642    #[test]
4643    fn test_c2pa_release_string() {
4644        // Test the deprecated c2pa_release_string function
4645        let test_str = CString::new("test string for release").unwrap();
4646        let c_str = to_c_string(test_str.to_str().unwrap().to_string());
4647        assert!(!c_str.is_null());
4648
4649        // Should not crash
4650        unsafe {
4651            c2pa_release_string(c_str);
4652        }
4653    }
4654
4655    #[test]
4656    fn test_c2pa_ed25519_sign_actually_calls_function() {
4657        // Fix: The existing test_ed25519_sign doesn't call c2pa_ed25519_sign!
4658        let bytes = b"test data to sign";
4659        let private_key_pem = include_bytes!(fixture_path!("certs/ed25519.pem"));
4660        let private_key = CString::new(private_key_pem.as_slice()).unwrap();
4661
4662        let signature =
4663            unsafe { c2pa_ed25519_sign(bytes.as_ptr(), bytes.len(), private_key.as_ptr()) };
4664
4665        assert!(!signature.is_null(), "Should return signature");
4666
4667        unsafe {
4668            c2pa_signature_free(signature);
4669        }
4670    }
4671
4672    #[test]
4673    fn test_c2pa_ed25519_sign_null_bytes() {
4674        let private_key_path = CString::new(fixture_path!("certs/ed25519.pem")).unwrap();
4675
4676        let signature =
4677            unsafe { c2pa_ed25519_sign(std::ptr::null(), 10, private_key_path.as_ptr()) };
4678
4679        assert!(signature.is_null(), "Null bytes should return null");
4680    }
4681
4682    #[test]
4683    fn test_c2pa_ed25519_sign_null_key() {
4684        let bytes = b"test data";
4685
4686        let signature = unsafe { c2pa_ed25519_sign(bytes.as_ptr(), bytes.len(), std::ptr::null()) };
4687
4688        assert!(signature.is_null(), "Null key should return null");
4689
4690        // Verify error was set
4691        let error = unsafe { c2pa_error() };
4692        assert!(!error.is_null());
4693        unsafe {
4694            c2pa_string_free(error);
4695        }
4696    }
4697
4698    #[test]
4699    fn test_c2pa_reader_detailed_json_null() {
4700        let result = unsafe { c2pa_reader_detailed_json(std::ptr::null_mut()) };
4701        assert!(result.is_null(), "Null reader should return null");
4702    }
4703
4704    #[test]
4705    fn test_c2pa_reader_json_better_coverage() {
4706        // The existing test only tests null, let's test with valid reader
4707        let source_image = include_bytes!(fixture_path!("C.jpg"));
4708        let mut stream = TestStream::new(source_image.to_vec());
4709        let format = CString::new("image/jpeg").unwrap();
4710
4711        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
4712        assert!(!reader.is_null());
4713
4714        let json = unsafe { c2pa_reader_json(reader) };
4715        assert!(!json.is_null(), "Should return JSON");
4716
4717        // Verify it's valid JSON
4718        use std::ffi::CStr;
4719        let json_str = unsafe { CStr::from_ptr(json).to_str().unwrap() };
4720        assert!(!json_str.is_empty());
4721        let _: serde_json::Value = serde_json::from_str(json_str).unwrap();
4722
4723        unsafe {
4724            c2pa_free(json as *mut c_void);
4725            c2pa_free(reader as *mut c_void);
4726        }
4727    }
4728
4729    #[test]
4730    fn test_c2pa_error_set_last() {
4731        let error_msg = CString::new("Custom error message").unwrap();
4732        let result = unsafe { c2pa_error_set_last(error_msg.as_ptr()) };
4733        assert_eq!(result, 0, "c2pa_error_set_last should return 0 on success");
4734
4735        // Verify the error was set
4736        let error = unsafe { c2pa_error() };
4737        assert!(
4738            !error.is_null(),
4739            "Error should be retrievable after set_last"
4740        );
4741        let error_str = unsafe { CString::from_raw(error) };
4742        // Error messages are prefixed with "Other: "
4743        assert_eq!(
4744            error_str.to_str().unwrap(),
4745            "Other: Custom error message",
4746            "Error message should match what was set"
4747        );
4748    }
4749
4750    #[test]
4751    fn test_c2pa_error_set_last_null() {
4752        let result = unsafe { c2pa_error_set_last(std::ptr::null()) };
4753        assert_eq!(
4754            result, -1,
4755            "c2pa_error_set_last should return -1 for null parameter"
4756        );
4757    }
4758
4759    #[test]
4760    fn test_c2pa_reader_from_manifest_data_and_stream() {
4761        // First, create a signed image to get manifest bytes
4762        let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
4763        let mut source_stream = TestStream::new(source_image.to_vec());
4764        let dest_vec = Vec::new();
4765        let mut dest_stream = TestStream::new(dest_vec);
4766
4767        let (signer, builder) = setup_signer_and_builder_for_signing_tests();
4768
4769        let format = CString::new("image/jpeg").unwrap();
4770        let mut manifest_bytes_ptr = std::ptr::null();
4771
4772        // Sign to get manifest bytes
4773        let result = unsafe {
4774            c2pa_builder_sign(
4775                builder,
4776                format.as_ptr(),
4777                source_stream.as_ptr(),
4778                dest_stream.as_ptr(),
4779                signer,
4780                &mut manifest_bytes_ptr,
4781            )
4782        };
4783        assert!(result > 0, "Signing should succeed");
4784        assert!(
4785            !manifest_bytes_ptr.is_null(),
4786            "Manifest bytes should be returned"
4787        );
4788
4789        let manifest_size = result as usize;
4790
4791        // Now test c2pa_reader_from_manifest_data_and_stream
4792        // Reset the source stream for validation
4793        let mut validation_stream = TestStream::new(source_image.to_vec());
4794
4795        let reader = unsafe {
4796            c2pa_reader_from_manifest_data_and_stream(
4797                format.as_ptr(),
4798                validation_stream.as_ptr(),
4799                manifest_bytes_ptr,
4800                manifest_size,
4801            )
4802        };
4803
4804        assert!(
4805            !reader.is_null(),
4806            "Reader should be created from manifest data and stream"
4807        );
4808
4809        // Verify we can get JSON from the reader
4810        let json = unsafe { c2pa_reader_json(reader) };
4811        assert!(!json.is_null(), "Should be able to get JSON from reader");
4812
4813        // Clean up
4814        unsafe {
4815            c2pa_free(json as *const c_void);
4816            c2pa_free(reader as *const c_void);
4817            c2pa_free(manifest_bytes_ptr as *const c_void);
4818            c2pa_free(builder as *const c_void);
4819            c2pa_free(signer as *const c_void);
4820        }
4821    }
4822
4823    #[test]
4824    fn test_c2pa_reader_from_manifest_data_and_stream_null_format() {
4825        let source_image = include_bytes!(fixture_path!("C.jpg"));
4826        let mut stream = TestStream::new(source_image.to_vec());
4827        let manifest_data = [0u8; 100];
4828
4829        let reader = unsafe {
4830            c2pa_reader_from_manifest_data_and_stream(
4831                std::ptr::null(),
4832                stream.as_ptr(),
4833                manifest_data.as_ptr(),
4834                manifest_data.len(),
4835            )
4836        };
4837
4838        assert!(reader.is_null(), "Reader should be null for null format");
4839        let error = unsafe { c2pa_error() };
4840        let error_str = unsafe { CString::from_raw(error) };
4841        assert_eq!(error_str.to_str().unwrap(), "NullParameter: format");
4842    }
4843
4844    #[test]
4845    fn test_c2pa_reader_resource_to_stream() {
4846        // Use an existing fixture with C2PA data
4847        let source_image = include_bytes!(fixture_path!("C.jpg"));
4848        let mut stream = TestStream::new(source_image.to_vec());
4849        let format = CString::new("image/jpeg").unwrap();
4850
4851        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
4852        assert!(
4853            !reader.is_null(),
4854            "Reader should be created from C2PA image"
4855        );
4856
4857        // Try to get a resource (use a generic URI pattern)
4858        let resource_uri =
4859            CString::new("self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.jpeg").unwrap();
4860        let mut output_stream = TestStream::new(Vec::new());
4861
4862        let result = unsafe {
4863            c2pa_reader_resource_to_stream(reader, resource_uri.as_ptr(), output_stream.as_ptr())
4864        };
4865
4866        // Result can be 0 if resource doesn't exist (which is fine)
4867        // or positive if resource was written. Either is valid.
4868        assert!(result >= 0, "resource_to_stream should return >= 0");
4869
4870        // Clean up
4871        unsafe {
4872            c2pa_free(reader as *const c_void);
4873        }
4874    }
4875
4876    #[test]
4877    fn test_c2pa_reader_resource_to_stream_null_reader() {
4878        let resource_uri = CString::new("some_uri").unwrap();
4879        let mut output_stream = TestStream::new(Vec::new());
4880
4881        let result = unsafe {
4882            c2pa_reader_resource_to_stream(
4883                std::ptr::null_mut(),
4884                resource_uri.as_ptr(),
4885                output_stream.as_ptr(),
4886            )
4887        };
4888
4889        assert_eq!(
4890            result, -1,
4891            "resource_to_stream should return -1 for null reader"
4892        );
4893        let error = unsafe { c2pa_error() };
4894        let error_str = unsafe { CString::from_raw(error) };
4895        assert_eq!(error_str.to_str().unwrap(), "NullParameter: reader_ptr");
4896    }
4897
4898    #[test]
4899    fn test_c2pa_reader_resource_to_stream_null_uri() {
4900        // Use an existing fixture with C2PA data
4901        let source_image = include_bytes!(fixture_path!("C.jpg"));
4902        let mut stream = TestStream::new(source_image.to_vec());
4903        let format = CString::new("image/jpeg").unwrap();
4904
4905        let reader = unsafe { c2pa_reader_from_stream(format.as_ptr(), stream.as_ptr()) };
4906        assert!(!reader.is_null());
4907
4908        let mut output_stream = TestStream::new(Vec::new());
4909
4910        let result = unsafe {
4911            c2pa_reader_resource_to_stream(reader, std::ptr::null(), output_stream.as_ptr())
4912        };
4913
4914        assert_eq!(
4915            result, -1,
4916            "resource_to_stream should return -1 for null uri"
4917        );
4918
4919        // Clean up
4920        unsafe {
4921            c2pa_free(reader as *const c_void);
4922        }
4923    }
4924
4925    #[test]
4926
4927    fn test_data_hash_embeddable_workflow() {
4928        // Build a context with signer configured via test_settings.json.
4929        // The settings include a PS256 local signer so sign_embeddable can use it.
4930        const SETTINGS: &str = include_str!(fixture_path!("test_settings.json"));
4931
4932        let settings = unsafe { c2pa_settings_new() };
4933        assert!(!settings.is_null());
4934        let json_str = CString::new(SETTINGS).unwrap();
4935        let fmt = CString::new("json").unwrap();
4936        let result =
4937            unsafe { c2pa_settings_update_from_string(settings, json_str.as_ptr(), fmt.as_ptr()) };
4938        assert_eq!(result, 0);
4939
4940        let ctx_builder = unsafe { c2pa_context_builder_new() };
4941        assert!(!ctx_builder.is_null());
4942        let result = unsafe { c2pa_context_builder_set_settings(ctx_builder, settings) };
4943        assert_eq!(result, 0);
4944        let context = unsafe { c2pa_context_builder_build(ctx_builder) };
4945        assert!(!context.is_null());
4946
4947        // Create a manifest builder from the context.
4948        let builder = unsafe { c2pa_builder_from_context(context) };
4949        assert!(!builder.is_null());
4950
4951        let format = CString::new("image/jpeg").unwrap();
4952        // needs_placeholder returns 0 or 1 (never -1) for valid builder+format.
4953        let needs = unsafe { c2pa_builder_needs_placeholder(builder, format.as_ptr()) };
4954        assert!(needs >= 0, "needs_placeholder should not error");
4955        assert!(needs <= 1, "needs_placeholder returns 0 or 1");
4956
4957        // Hash the entire JPEG stream — auto-creates a DataHash (direct mode, no placeholder).
4958        let source_image = include_bytes!(fixture_path!("IMG_0003.jpg"));
4959        let mut source_stream = TestStream::new(source_image.to_vec());
4960        let result = unsafe {
4961            c2pa_builder_update_hash_from_stream(builder, format.as_ptr(), source_stream.as_ptr())
4962        };
4963        assert_eq!(result, 0, "update_hash_from_stream failed");
4964
4965        // Sign without a placeholder (direct mode) — signer comes from the builder's Context.
4966        let mut signed_bytes_ptr: *const c_uchar = std::ptr::null();
4967        let len = unsafe {
4968            c2pa_builder_sign_embeddable(builder, format.as_ptr(), &mut signed_bytes_ptr)
4969        };
4970        assert!(len > 0, "sign_embeddable should return positive length");
4971        assert!(!signed_bytes_ptr.is_null());
4972
4973        unsafe {
4974            c2pa_free(signed_bytes_ptr as *mut c_void);
4975            c2pa_free(settings as *mut c_void);
4976            c2pa_free(context as *mut c_void);
4977            c2pa_free(builder as *mut c_void);
4978        }
4979    }
4980
4981    #[test]
4982
4983    fn test_bmff_embeddable_workflow_with_mdat_hashes() {
4984        // Build a context with signer + Merkle chunk size for the external-mdat-hash workflow.
4985        const SETTINGS: &str = include_str!(fixture_path!("test_settings.json"));
4986
4987        let settings = unsafe { c2pa_settings_new() };
4988        assert!(!settings.is_null());
4989        let json_str = CString::new(SETTINGS).unwrap();
4990        let fmt = CString::new("json").unwrap();
4991        let result =
4992            unsafe { c2pa_settings_update_from_string(settings, json_str.as_ptr(), fmt.as_ptr()) };
4993        assert_eq!(result, 0);
4994
4995        let ctx_builder = unsafe { c2pa_context_builder_new() };
4996        assert!(!ctx_builder.is_null());
4997        let result = unsafe { c2pa_context_builder_set_settings(ctx_builder, settings) };
4998        assert_eq!(result, 0);
4999        let context = unsafe { c2pa_context_builder_build(ctx_builder) };
5000        assert!(!context.is_null());
5001
5002        // Create builder from context.
5003        let builder = unsafe { c2pa_builder_from_context(context) };
5004        assert!(!builder.is_null());
5005
5006        let format = CString::new("video/mp4").unwrap();
5007        // BMFF formats always need a placeholder.
5008        let needs = unsafe { c2pa_builder_needs_placeholder(builder, format.as_ptr()) };
5009        assert_eq!(
5010            needs, 1,
5011            "needs_placeholder should be 1 for video/mp4 before placeholder"
5012        );
5013
5014        // Passing null for manifest_bytes_ptr returns size without error (caller may only need the length).
5015        let size_only =
5016            unsafe { c2pa_builder_placeholder(builder, format.as_ptr(), std::ptr::null_mut()) };
5017        assert!(
5018            size_only > 0,
5019            "placeholder with null output should return positive size"
5020        );
5021        assert_ne!(
5022            size_only, -1,
5023            "placeholder with null output should not error"
5024        );
5025
5026        // Create a BMFF placeholder (adds a BmffHash with pre-allocated Merkle slots).
5027        let mut placeholder_ptr: *const c_uchar = std::ptr::null();
5028        let placeholder_len =
5029            unsafe { c2pa_builder_placeholder(builder, format.as_ptr(), &mut placeholder_ptr) };
5030        assert!(
5031            placeholder_len > 0,
5032            "placeholder should return non-empty bytes"
5033        );
5034        assert!(!placeholder_ptr.is_null());
5035
5036        // break the mdat in fixed sized chunks (1kb) in this example
5037        unsafe {
5038            c2pa_builder_set_fixed_size_merkle(builder, 1);
5039        }
5040
5041        // Supply a single dummy SHA-256 leaf hash for one mdat box (1 chunk).
5042        // The Merkle leaves is derived from these; the video will not validate but
5043        // this exercises the full C API call path.
5044        let leaf_data: [u8; 4096] = [0xab; 4096];
5045        let result = unsafe {
5046            c2pa_builder_hash_mdat_bytes(builder, 0, leaf_data.as_ptr(), leaf_data.len(), true)
5047        };
5048        assert_eq!(result, 0, "set_bmff_mdat_hashes failed");
5049
5050        // Hash the video stream (BmffHash uses its own path-based exclusions internally).
5051        let video = include_bytes!(fixture_path!("video1.mp4"));
5052        let mut video_stream = TestStream::new(video.to_vec());
5053        let result = unsafe {
5054            c2pa_builder_update_hash_from_stream(builder, format.as_ptr(), video_stream.as_ptr())
5055        };
5056        assert_eq!(result, 0, "update_hash_from_stream failed");
5057
5058        // Sign in placeholder mode — signer comes from the builder's Context.
5059        let mut signed_bytes_ptr: *const c_uchar = std::ptr::null();
5060        let len = unsafe {
5061            c2pa_builder_sign_embeddable(builder, format.as_ptr(), &mut signed_bytes_ptr)
5062        };
5063        assert!(len > 0, "sign_embeddable should return positive length");
5064        assert!(!signed_bytes_ptr.is_null());
5065
5066        unsafe {
5067            c2pa_free(placeholder_ptr as *mut c_void);
5068            c2pa_free(signed_bytes_ptr as *mut c_void);
5069            c2pa_free(settings as *mut c_void);
5070            c2pa_free(context as *mut c_void);
5071            c2pa_free(builder as *mut c_void);
5072        }
5073    }
5074
5075    #[test]
5076    fn test_context_builder_set_signer() {
5077        let certs = include_str!(fixture_path!("certs/ed25519.pub"));
5078        let private_key = include_bytes!(fixture_path!("certs/ed25519.pem"));
5079        let alg = CString::new("Ed25519").unwrap();
5080        let sign_cert = CString::new(certs).unwrap();
5081        let private_key = CString::new(private_key.as_slice()).unwrap();
5082        let signer_info = C2paSignerInfo {
5083            alg: alg.as_ptr(),
5084            sign_cert: sign_cert.as_ptr(),
5085            private_key: private_key.as_ptr(),
5086            ta_url: std::ptr::null(),
5087        };
5088        let signer = unsafe { c2pa_signer_from_info(&signer_info) };
5089        assert!(!signer.is_null());
5090
5091        let builder = unsafe { c2pa_context_builder_new() };
5092        assert!(!builder.is_null());
5093
5094        let result = unsafe { c2pa_context_builder_set_signer(builder, signer) };
5095        assert_eq!(result, 0);
5096
5097        // Verify the signer is consumed: freeing it should fail cleanly (-1)
5098        let free_result = unsafe { c2pa_free(signer as *const c_void) };
5099        assert_eq!(free_result, -1);
5100
5101        let context = unsafe { c2pa_context_builder_build(builder) };
5102        assert!(!context.is_null());
5103
5104        let builder = unsafe { c2pa_builder_from_context(context) };
5105        assert!(!builder.is_null());
5106
5107        unsafe {
5108            c2pa_free(builder as *mut c_void);
5109            c2pa_free(context as *mut c_void);
5110        }
5111    }
5112
5113    #[test]
5114    fn test_context_builder_set_signer_null() {
5115        let builder = unsafe { c2pa_context_builder_new() };
5116        assert!(!builder.is_null());
5117
5118        let result = unsafe { c2pa_context_builder_set_signer(builder, std::ptr::null_mut()) };
5119        assert_eq!(result, -1, "Null signer should be rejected");
5120
5121        unsafe { c2pa_free(builder as *mut c_void) };
5122    }
5123
5124    #[test]
5125    fn test_c2pa_context_builder_set_progress_callback() {
5126        use std::sync::atomic::{AtomicU32, Ordering};
5127
5128        let call_count = Arc::new(AtomicU32::new(0));
5129        let raw_ptr = Arc::as_ptr(&call_count) as *const c_void;
5130
5131        unsafe extern "C" fn progress_cb(
5132            context: *const c_void,
5133            _phase: C2paProgressPhase,
5134            _step: u32,
5135            _total: u32,
5136        ) -> c_int {
5137            let counter = &*(context as *const AtomicU32);
5138            counter.fetch_add(1, Ordering::SeqCst);
5139            1
5140        }
5141
5142        let builder = unsafe { c2pa_context_builder_new() };
5143        assert!(!builder.is_null());
5144
5145        let result =
5146            unsafe { c2pa_context_builder_set_progress_callback(builder, raw_ptr, progress_cb) };
5147        assert_eq!(result, 0, "set_progress_callback should succeed");
5148
5149        let context = unsafe { c2pa_context_builder_build(builder) };
5150        assert!(!context.is_null());
5151
5152        unsafe { c2pa_free(context as *mut c_void) };
5153        // Arc still alive here so the AtomicU32 is valid throughout.
5154    }
5155
5156    #[test]
5157    fn test_c2pa_context_builder_set_progress_callback_null_user_data() {
5158        unsafe extern "C" fn progress_cb(
5159            _context: *const c_void,
5160            _phase: C2paProgressPhase,
5161            _step: u32,
5162            _total: u32,
5163        ) -> c_int {
5164            1
5165        }
5166
5167        let builder = unsafe { c2pa_context_builder_new() };
5168        assert!(!builder.is_null());
5169
5170        let result = unsafe {
5171            c2pa_context_builder_set_progress_callback(builder, std::ptr::null(), progress_cb)
5172        };
5173        assert_eq!(result, 0, "NULL user_data should be accepted");
5174
5175        let context = unsafe { c2pa_context_builder_build(builder) };
5176        assert!(!context.is_null());
5177
5178        unsafe { c2pa_free(context as *mut c_void) };
5179    }
5180
5181    #[test]
5182    fn test_c2pa_context_builder_set_progress_callback_null_builder() {
5183        unsafe extern "C" fn progress_cb(
5184            _context: *const c_void,
5185            _phase: C2paProgressPhase,
5186            _step: u32,
5187            _total: u32,
5188        ) -> c_int {
5189            1
5190        }
5191
5192        let result = unsafe {
5193            c2pa_context_builder_set_progress_callback(
5194                std::ptr::null_mut(),
5195                std::ptr::null(),
5196                progress_cb,
5197            )
5198        };
5199        assert_eq!(result, -1, "NULL builder should return error");
5200    }
5201
5202    #[test]
5203    fn test_progress_phase_to_c2pa_progress_phase() {
5204        let cases: &[(ProgressPhase, i32)] = &[
5205            (ProgressPhase::Reading, 0),
5206            (ProgressPhase::VerifyingManifest, 1),
5207            (ProgressPhase::VerifyingSignature, 2),
5208            (ProgressPhase::VerifyingIngredient, 3),
5209            (ProgressPhase::VerifyingAssetHash, 4),
5210            (ProgressPhase::AddingIngredient, 5),
5211            (ProgressPhase::Thumbnail, 6),
5212            (ProgressPhase::Hashing, 7),
5213            (ProgressPhase::Signing, 8),
5214            (ProgressPhase::Embedding, 9),
5215            (ProgressPhase::FetchingRemoteManifest, 10),
5216            (ProgressPhase::Writing, 11),
5217            (ProgressPhase::FetchingOCSP, 12),
5218            (ProgressPhase::FetchingTimestamp, 13),
5219        ];
5220        for (sdk_phase, expected) in cases {
5221            let c_phase = C2paProgressPhase::from(sdk_phase.clone());
5222            assert_eq!(c_phase as i32, *expected, "mismatch for {sdk_phase:?}");
5223        }
5224    }
5225
5226    #[test]
5227    fn test_c2pa_context_cancel() {
5228        let context = unsafe { c2pa_context_new() };
5229        assert!(!context.is_null());
5230
5231        let result = unsafe { c2pa_context_cancel(context) };
5232        assert_eq!(result, 0, "cancel should succeed on a valid context");
5233
5234        unsafe { c2pa_free(context as *mut c_void) };
5235    }
5236
5237    #[test]
5238    fn test_c2pa_context_cancel_null() {
5239        let result = unsafe { c2pa_context_cancel(std::ptr::null_mut()) };
5240        assert_eq!(result, -1, "NULL context should return error");
5241    }
5242
5243    #[test]
5244    fn test_c2pa_context_cancel_via_builder() {
5245        let builder = unsafe { c2pa_context_builder_new() };
5246        assert!(!builder.is_null());
5247
5248        let context = unsafe { c2pa_context_builder_build(builder) };
5249        assert!(!context.is_null());
5250
5251        let result = unsafe { c2pa_context_cancel(context) };
5252        assert_eq!(result, 0, "cancel should work on a built context");
5253
5254        unsafe { c2pa_free(context as *mut c_void) };
5255    }
5256}