Skip to main content

sdk_rust/
ffi.rs

1use std::{
2    cell::RefCell,
3    ffi::{CStr, CString, c_char},
4    ptr,
5};
6
7use base64::Engine as _;
8use serde::{Deserialize, Serialize, de::DeserializeOwned};
9
10use crate::{
11    builder::ClientBuilder,
12    client::Client,
13    error::SdkError,
14    local::{
15        DetachedSignatureSignResult, DetachedSignatureVerifyResult, EnvelopeAccessResult,
16        EnvelopeProtectionResult, EnvelopeRewrapResult, LocalArtifactBinding, LocalAttributeEdit,
17        LocalProtectionRequest, LocalSigningKey, LocalSymmetricKey, LocalSymmetricKeySource,
18        LocalVerifyingKey, PreparedLocalProtection, ProtectedDetachedSignatureArtifact,
19        ProtectedEnvelopeArtifact, ProtectedTdfArtifact, TdfAccessResult, TdfProtectionResult,
20        TdfRewrapResult,
21    },
22    models::{
23        KeyTransportMode, SdkArtifactRegisterRequest, SdkEvidenceIngestRequest,
24        SdkKeyAccessPlanRequest, SdkPolicyResolveRequest, SdkProtectionPlanRequest,
25    },
26    providers::{CommandManagedSymmetricKeyProvider, InMemoryManagedSymmetricKeyProvider},
27};
28
29thread_local! {
30    static LAST_ERROR: RefCell<Option<String>> = const { RefCell::new(None) };
31}
32
33pub struct ClientHandle {
34    pub client: Client,
35}
36
37#[derive(Debug, Clone, Deserialize)]
38#[serde(rename_all = "snake_case")]
39struct FfiClientOptions {
40    base_url: String,
41    bearer_token: Option<String>,
42    tenant_id: Option<String>,
43    user_id: Option<String>,
44    timeout_secs: Option<u64>,
45    #[serde(default)]
46    headers: std::collections::BTreeMap<String, String>,
47    #[serde(default)]
48    managed_symmetric_key_providers: Vec<FfiManagedSymmetricKeyProviderConfig>,
49}
50
51#[derive(Debug, Clone, Deserialize)]
52#[serde(rename_all = "snake_case")]
53struct FfiManagedSymmetricKeyProviderConfig {
54    #[serde(default)]
55    kind: Option<String>,
56    name: String,
57    #[serde(default)]
58    supported_transport_modes: Vec<KeyTransportMode>,
59    #[serde(default)]
60    keys: std::collections::BTreeMap<String, String>,
61    command: Option<String>,
62    #[serde(default)]
63    args: Vec<String>,
64    #[serde(default)]
65    env: std::collections::BTreeMap<String, String>,
66}
67
68#[derive(Debug, Clone, Deserialize)]
69#[serde(rename_all = "snake_case")]
70struct FfiPrepareLocalProtectionRequest {
71    content_b64: String,
72    request: LocalProtectionRequest,
73}
74
75#[derive(Debug, Clone, Deserialize)]
76#[serde(rename_all = "snake_case")]
77struct FfiEnvelopeProtectRequest {
78    key_b64: Option<String>,
79    key_source: Option<FfiSymmetricKeySource>,
80    plaintext_b64: String,
81    request: LocalProtectionRequest,
82}
83
84#[derive(Debug, Clone, Deserialize)]
85#[serde(rename_all = "snake_case")]
86struct FfiEnvelopeAccessRequest {
87    key_b64: Option<String>,
88    key_source: Option<FfiSymmetricKeySource>,
89    artifact_bytes_b64: String,
90}
91
92#[derive(Debug, Clone, Deserialize)]
93#[serde(rename_all = "snake_case")]
94struct FfiDetachedSignatureSignRequest {
95    signing_key_b64: String,
96    content_b64: String,
97    request: LocalProtectionRequest,
98}
99
100#[derive(Debug, Clone, Deserialize)]
101#[serde(rename_all = "snake_case")]
102struct FfiDetachedSignatureVerifyRequest {
103    verifying_key_b64: String,
104    content_b64: String,
105    artifact_bytes_b64: String,
106}
107
108#[derive(Debug, Clone, Deserialize)]
109#[serde(rename_all = "snake_case")]
110struct FfiEnvelopeRewrapRequest {
111    current_key_b64: Option<String>,
112    current_key_source: Option<FfiSymmetricKeySource>,
113    new_key_b64: Option<String>,
114    new_key_source: Option<FfiSymmetricKeySource>,
115    artifact_bytes_b64: String,
116}
117
118#[derive(Debug, Clone, Deserialize)]
119#[serde(rename_all = "snake_case")]
120struct FfiTdfSetAttributesRequest {
121    key_b64: Option<String>,
122    key_source: Option<FfiSymmetricKeySource>,
123    artifact_bytes_b64: String,
124    #[serde(default)]
125    attributes: std::collections::BTreeMap<String, String>,
126}
127
128#[derive(Debug, Clone, Deserialize)]
129#[serde(rename_all = "snake_case")]
130struct FfiTdfEditAttributesRequest {
131    key_b64: Option<String>,
132    key_source: Option<FfiSymmetricKeySource>,
133    artifact_bytes_b64: String,
134    #[serde(default)]
135    edit: LocalAttributeEdit,
136}
137
138#[derive(Debug, Clone, Deserialize)]
139#[serde(rename_all = "snake_case", tag = "kind")]
140enum FfiSymmetricKeySource {
141    Inline {
142        key_b64: String,
143    },
144    ManagedReference {
145        key_reference: String,
146        provider_name: Option<String>,
147    },
148}
149
150#[derive(Debug, Clone, Serialize)]
151#[serde(rename_all = "snake_case")]
152struct FfiProtectedEnvelopeArtifact {
153    envelope: crate::local::LocalEnvelopeArtifact,
154    artifact_bytes_b64: String,
155    artifact_digest: String,
156}
157
158#[derive(Debug, Clone, Serialize)]
159#[serde(rename_all = "snake_case")]
160struct FfiEnvelopeProtectionResult {
161    prepared: PreparedLocalProtection,
162    key_access_plan: crate::models::SdkKeyAccessPlanResponse,
163    artifact: FfiProtectedEnvelopeArtifact,
164    artifact_registration: crate::models::SdkArtifactRegisterResponse,
165    evidence: crate::models::SdkEvidenceIngestResponse,
166}
167
168#[derive(Debug, Clone, Serialize)]
169#[serde(rename_all = "snake_case")]
170struct FfiEnvelopeAccessResult {
171    artifact: crate::local::LocalEnvelopeArtifact,
172    artifact_digest: String,
173    policy_resolution: crate::models::SdkPolicyResolveResponse,
174    key_access_plan: crate::models::SdkKeyAccessPlanResponse,
175    plaintext_b64: String,
176    evidence: crate::models::SdkEvidenceIngestResponse,
177}
178
179#[derive(Debug, Clone, Serialize)]
180#[serde(rename_all = "snake_case")]
181struct FfiEnvelopeRewrapResult {
182    content_binding: crate::local::LocalContentBinding,
183    policy_resolution: crate::models::SdkPolicyResolveResponse,
184    protection_plan: crate::models::SdkProtectionPlanResponse,
185    key_access_plan: crate::models::SdkKeyAccessPlanResponse,
186    original_artifact_digest: String,
187    artifact: FfiProtectedEnvelopeArtifact,
188    artifact_registration: crate::models::SdkArtifactRegisterResponse,
189    evidence: crate::models::SdkEvidenceIngestResponse,
190}
191
192#[derive(Debug, Clone, Serialize)]
193#[serde(rename_all = "snake_case")]
194struct FfiProtectedDetachedSignatureArtifact {
195    detached_signature: crate::local::LocalDetachedSignatureArtifact,
196    artifact_bytes_b64: String,
197    artifact_digest: String,
198}
199
200#[derive(Debug, Clone, Serialize)]
201#[serde(rename_all = "snake_case")]
202struct FfiDetachedSignatureSignResult {
203    prepared: PreparedLocalProtection,
204    key_access_plan: crate::models::SdkKeyAccessPlanResponse,
205    artifact: FfiProtectedDetachedSignatureArtifact,
206    artifact_registration: crate::models::SdkArtifactRegisterResponse,
207    evidence: crate::models::SdkEvidenceIngestResponse,
208}
209
210#[derive(Debug, Clone, Serialize)]
211#[serde(rename_all = "snake_case")]
212struct FfiDetachedSignatureVerifyResult {
213    artifact: crate::local::LocalDetachedSignatureArtifact,
214    artifact_digest: String,
215    policy_resolution: crate::models::SdkPolicyResolveResponse,
216    key_access_plan: crate::models::SdkKeyAccessPlanResponse,
217    content_binding: crate::local::LocalContentBinding,
218    evidence: crate::models::SdkEvidenceIngestResponse,
219}
220
221#[derive(Debug, Clone, Serialize)]
222#[serde(rename_all = "snake_case")]
223struct FfiProtectedTdfArtifact {
224    tdf: crate::local::LocalTdfArtifact,
225    artifact_bytes_b64: String,
226    artifact_digest: String,
227}
228
229#[derive(Debug, Clone, Serialize)]
230#[serde(rename_all = "snake_case")]
231struct FfiTdfProtectionResult {
232    prepared: PreparedLocalProtection,
233    key_access_plan: crate::models::SdkKeyAccessPlanResponse,
234    artifact: FfiProtectedTdfArtifact,
235    artifact_registration: crate::models::SdkArtifactRegisterResponse,
236    evidence: crate::models::SdkEvidenceIngestResponse,
237}
238
239#[derive(Debug, Clone, Serialize)]
240#[serde(rename_all = "snake_case")]
241struct FfiTdfAccessResult {
242    artifact: crate::local::LocalTdfArtifact,
243    manifest: crate::local::LocalTdfManifest,
244    artifact_digest: String,
245    policy_resolution: crate::models::SdkPolicyResolveResponse,
246    key_access_plan: crate::models::SdkKeyAccessPlanResponse,
247    plaintext_b64: String,
248    evidence: crate::models::SdkEvidenceIngestResponse,
249}
250
251#[derive(Debug, Clone, Serialize)]
252#[serde(rename_all = "snake_case")]
253struct FfiTdfRewrapResult {
254    content_binding: crate::local::LocalContentBinding,
255    manifest: crate::local::LocalTdfManifest,
256    policy_resolution: crate::models::SdkPolicyResolveResponse,
257    protection_plan: crate::models::SdkProtectionPlanResponse,
258    key_access_plan: crate::models::SdkKeyAccessPlanResponse,
259    original_artifact_digest: String,
260    artifact: FfiProtectedTdfArtifact,
261    artifact_registration: crate::models::SdkArtifactRegisterResponse,
262    evidence: crate::models::SdkEvidenceIngestResponse,
263}
264
265#[unsafe(no_mangle)]
266pub extern "C" fn lattix_sdk_version() -> *mut c_char {
267    into_c_string(env!("CARGO_PKG_VERSION"))
268}
269
270#[unsafe(no_mangle)]
271pub extern "C" fn lattix_sdk_last_error_message() -> *mut c_char {
272    LAST_ERROR.with(|slot| match slot.borrow().as_deref() {
273        Some(message) => into_c_string(message),
274        None => into_c_string(""),
275    })
276}
277
278/// Frees a string previously allocated by this library and returned over the FFI boundary.
279///
280/// # Safety
281///
282/// `value` must either be null or a pointer returned by one of this library's FFI functions
283/// that transfer ownership of a `CString` to the caller. Passing any other pointer, or freeing
284/// the same pointer more than once, is undefined behavior.
285#[unsafe(no_mangle)]
286pub unsafe extern "C" fn lattix_sdk_string_free(value: *mut c_char) {
287    if value.is_null() {
288        return;
289    }
290
291    drop(unsafe { CString::from_raw(value) });
292}
293
294#[unsafe(no_mangle)]
295pub extern "C" fn lattix_sdk_client_new(options_json: *const c_char) -> *mut ClientHandle {
296    match ffi_result(|| {
297        let options: FfiClientOptions = parse_json_arg(options_json)?;
298        let mut builder = ClientBuilder::new(options.base_url);
299
300        if let Some(bearer_token) = options.bearer_token {
301            builder = builder.with_bearer_token(bearer_token);
302        }
303        if let Some(tenant_id) = options.tenant_id {
304            builder = builder.with_tenant_id(tenant_id);
305        }
306        if let Some(user_id) = options.user_id {
307            builder = builder.with_user_id(user_id);
308        }
309        if let Some(timeout_secs) = options.timeout_secs {
310            builder = builder.with_timeout_secs(timeout_secs);
311        }
312        for (name, value) in options.headers {
313            builder = builder.with_header(name, value);
314        }
315        for provider in options.managed_symmetric_key_providers {
316            match provider.kind.as_deref().unwrap_or("in_memory") {
317                "in_memory" => {
318                    let mut keys = std::collections::BTreeMap::new();
319                    for (key_reference, key_b64) in provider.keys {
320                        keys.insert(
321                            key_reference,
322                            decode_base64_key_arg("managed_symmetric_key_provider.keys", &key_b64)?,
323                        );
324                    }
325
326                    let mut in_memory_provider =
327                        InMemoryManagedSymmetricKeyProvider::new(provider.name, keys);
328                    if !provider.supported_transport_modes.is_empty() {
329                        in_memory_provider = in_memory_provider
330                            .with_supported_transport_modes(provider.supported_transport_modes);
331                    }
332                    builder = builder.with_managed_symmetric_key_provider(in_memory_provider);
333                }
334                "command" => {
335                    let command = provider.command.ok_or_else(|| {
336                        SdkError::InvalidInput(
337                            "managed symmetric key command provider requires a command".to_string(),
338                        )
339                    })?;
340                    let mut command_provider =
341                        CommandManagedSymmetricKeyProvider::new(provider.name, command)
342                            .with_args(provider.args)
343                            .with_envs(provider.env);
344                    if !provider.supported_transport_modes.is_empty() {
345                        command_provider = command_provider
346                            .with_supported_transport_modes(provider.supported_transport_modes);
347                    }
348                    builder = builder.with_managed_symmetric_key_provider(command_provider);
349                }
350                other => {
351                    return Err(SdkError::InvalidInput(format!(
352                        "unsupported managed symmetric key provider kind {other:?}"
353                    )));
354                }
355            }
356        }
357
358        Ok(Box::into_raw(Box::new(ClientHandle {
359            client: builder.build()?,
360        })))
361    }) {
362        Ok(handle) => handle,
363        Err(_) => ptr::null_mut(),
364    }
365}
366
367/// Frees a client handle previously allocated by `lattix_sdk_client_new`.
368///
369/// # Safety
370///
371/// `handle` must either be null or a pointer returned by `lattix_sdk_client_new` that has not
372/// already been freed. Passing any other pointer, or freeing the same pointer more than once,
373/// is undefined behavior.
374#[unsafe(no_mangle)]
375pub unsafe extern "C" fn lattix_sdk_client_free(handle: *mut ClientHandle) {
376    if handle.is_null() {
377        return;
378    }
379
380    drop(unsafe { Box::from_raw(handle) });
381}
382
383macro_rules! ffi_get_method {
384    ($name:ident, $method:ident) => {
385        #[unsafe(no_mangle)]
386        pub extern "C" fn $name(handle: *mut ClientHandle) -> *mut c_char {
387            match ffi_result(|| {
388                let client = client_from_handle(handle)?;
389                let response = client.$method()?;
390                serialize_json(&response)
391            }) {
392                Ok(value) => value,
393                Err(_) => ptr::null_mut(),
394            }
395        }
396    };
397}
398
399macro_rules! ffi_post_method {
400    ($name:ident, $method:ident, $request_ty:ty) => {
401        #[unsafe(no_mangle)]
402        pub extern "C" fn $name(
403            handle: *mut ClientHandle,
404            request_json: *const c_char,
405        ) -> *mut c_char {
406            match ffi_result(|| {
407                let client = client_from_handle(handle)?;
408                let request: $request_ty = parse_json_arg(request_json)?;
409                let response = client.$method(&request)?;
410                serialize_json(&response)
411            }) {
412                Ok(value) => value,
413                Err(_) => ptr::null_mut(),
414            }
415        }
416    };
417}
418
419ffi_get_method!(lattix_sdk_capabilities, capabilities);
420ffi_get_method!(lattix_sdk_whoami, whoami);
421ffi_get_method!(lattix_sdk_bootstrap, bootstrap);
422ffi_post_method!(
423    lattix_sdk_protection_plan,
424    protection_plan,
425    SdkProtectionPlanRequest
426);
427ffi_post_method!(
428    lattix_sdk_policy_resolve,
429    policy_resolve,
430    SdkPolicyResolveRequest
431);
432ffi_post_method!(
433    lattix_sdk_key_access_plan,
434    key_access_plan,
435    SdkKeyAccessPlanRequest
436);
437ffi_post_method!(
438    lattix_sdk_artifact_register,
439    artifact_register,
440    SdkArtifactRegisterRequest
441);
442ffi_post_method!(lattix_sdk_evidence, evidence, SdkEvidenceIngestRequest);
443
444#[unsafe(no_mangle)]
445pub extern "C" fn lattix_sdk_prepare_local_protection(
446    handle: *mut ClientHandle,
447    request_json: *const c_char,
448) -> *mut c_char {
449    match ffi_result(|| {
450        let client = client_from_handle(handle)?;
451        let request: FfiPrepareLocalProtectionRequest = parse_json_arg(request_json)?;
452        let content = decode_base64_arg("content_b64", &request.content_b64)?;
453        let response = client.prepare_local_protection(&content, request.request)?;
454        serialize_json(&response)
455    }) {
456        Ok(value) => value,
457        Err(_) => ptr::null_mut(),
458    }
459}
460
461#[unsafe(no_mangle)]
462pub extern "C" fn lattix_sdk_generate_cid_binding(
463    handle: *mut ClientHandle,
464    request_json: *const c_char,
465) -> *mut c_char {
466    match ffi_result(|| {
467        let client = client_from_handle(handle)?;
468        let request: FfiPrepareLocalProtectionRequest = parse_json_arg(request_json)?;
469        let content = decode_base64_arg("content_b64", &request.content_b64)?;
470        let response: LocalArtifactBinding =
471            client.generate_cid_binding(&content, request.request)?;
472        serialize_json(&response)
473    }) {
474        Ok(value) => value,
475        Err(_) => ptr::null_mut(),
476    }
477}
478
479#[unsafe(no_mangle)]
480pub extern "C" fn lattix_sdk_sign_bytes_with_detached_signature(
481    handle: *mut ClientHandle,
482    request_json: *const c_char,
483) -> *mut c_char {
484    match ffi_result(|| {
485        let client = client_from_handle(handle)?;
486        let request: FfiDetachedSignatureSignRequest = parse_json_arg(request_json)?;
487        let signing_key = decode_signing_key(&request.signing_key_b64)?;
488        let content = decode_base64_arg("content_b64", &request.content_b64)?;
489        let response =
490            client.sign_bytes_with_detached_signature(&signing_key, &content, request.request)?;
491        serialize_json(&ffi_detached_signature_sign_result(response))
492    }) {
493        Ok(value) => value,
494        Err(_) => ptr::null_mut(),
495    }
496}
497
498#[unsafe(no_mangle)]
499pub extern "C" fn lattix_sdk_verify_bytes_with_detached_signature(
500    handle: *mut ClientHandle,
501    request_json: *const c_char,
502) -> *mut c_char {
503    match ffi_result(|| {
504        let client = client_from_handle(handle)?;
505        let request: FfiDetachedSignatureVerifyRequest = parse_json_arg(request_json)?;
506        let verifying_key = decode_verifying_key(&request.verifying_key_b64)?;
507        let content = decode_base64_arg("content_b64", &request.content_b64)?;
508        let artifact_bytes = decode_base64_arg("artifact_bytes_b64", &request.artifact_bytes_b64)?;
509        let response = client.verify_bytes_with_detached_signature(
510            &verifying_key,
511            &content,
512            &artifact_bytes,
513        )?;
514        serialize_json(&ffi_detached_signature_verify_result(response))
515    }) {
516        Ok(value) => value,
517        Err(_) => ptr::null_mut(),
518    }
519}
520
521#[unsafe(no_mangle)]
522pub extern "C" fn lattix_sdk_protect_bytes_with_envelope(
523    handle: *mut ClientHandle,
524    request_json: *const c_char,
525) -> *mut c_char {
526    match ffi_result(|| {
527        let client = client_from_handle(handle)?;
528        let request: FfiEnvelopeProtectRequest = parse_json_arg(request_json)?;
529        let key_source =
530            decode_symmetric_key_source(request.key_source, request.key_b64.as_deref(), "key_b64")?;
531        let plaintext = decode_base64_arg("plaintext_b64", &request.plaintext_b64)?;
532        let response = client.protect_bytes_with_envelope_using_key_source(
533            &key_source,
534            &plaintext,
535            request.request,
536        )?;
537        serialize_json(&ffi_envelope_protection_result(response))
538    }) {
539        Ok(value) => value,
540        Err(_) => ptr::null_mut(),
541    }
542}
543
544#[unsafe(no_mangle)]
545pub extern "C" fn lattix_sdk_protect_bytes_with_tdf(
546    handle: *mut ClientHandle,
547    request_json: *const c_char,
548) -> *mut c_char {
549    match ffi_result(|| {
550        let client = client_from_handle(handle)?;
551        let request: FfiEnvelopeProtectRequest = parse_json_arg(request_json)?;
552        let key_source =
553            decode_symmetric_key_source(request.key_source, request.key_b64.as_deref(), "key_b64")?;
554        let plaintext = decode_base64_arg("plaintext_b64", &request.plaintext_b64)?;
555        let response = client.protect_bytes_with_tdf_using_key_source(
556            &key_source,
557            &plaintext,
558            request.request,
559        )?;
560        serialize_json(&ffi_tdf_protection_result(response))
561    }) {
562        Ok(value) => value,
563        Err(_) => ptr::null_mut(),
564    }
565}
566
567#[unsafe(no_mangle)]
568pub extern "C" fn lattix_sdk_access_bytes_with_envelope(
569    handle: *mut ClientHandle,
570    request_json: *const c_char,
571) -> *mut c_char {
572    match ffi_result(|| {
573        let client = client_from_handle(handle)?;
574        let request: FfiEnvelopeAccessRequest = parse_json_arg(request_json)?;
575        let key_source =
576            decode_symmetric_key_source(request.key_source, request.key_b64.as_deref(), "key_b64")?;
577        let artifact_bytes = decode_base64_arg("artifact_bytes_b64", &request.artifact_bytes_b64)?;
578        let response =
579            client.access_bytes_with_envelope_using_key_source(&key_source, &artifact_bytes)?;
580        serialize_json(&ffi_envelope_access_result(response))
581    }) {
582        Ok(value) => value,
583        Err(_) => ptr::null_mut(),
584    }
585}
586
587#[unsafe(no_mangle)]
588pub extern "C" fn lattix_sdk_rewrap_bytes_with_envelope(
589    handle: *mut ClientHandle,
590    request_json: *const c_char,
591) -> *mut c_char {
592    match ffi_result(|| {
593        let client = client_from_handle(handle)?;
594        let request: FfiEnvelopeRewrapRequest = parse_json_arg(request_json)?;
595        let current_key_source = decode_symmetric_key_source(
596            request.current_key_source,
597            request.current_key_b64.as_deref(),
598            "current_key_b64",
599        )?;
600        let new_key_source = decode_symmetric_key_source(
601            request.new_key_source,
602            request.new_key_b64.as_deref(),
603            "new_key_b64",
604        )?;
605        let artifact_bytes = decode_base64_arg("artifact_bytes_b64", &request.artifact_bytes_b64)?;
606        let response = client.rewrap_bytes_with_envelope_using_key_sources(
607            &current_key_source,
608            &new_key_source,
609            &artifact_bytes,
610        )?;
611        serialize_json(&ffi_envelope_rewrap_result(response))
612    }) {
613        Ok(value) => value,
614        Err(_) => ptr::null_mut(),
615    }
616}
617
618#[unsafe(no_mangle)]
619pub extern "C" fn lattix_sdk_access_bytes_with_tdf(
620    handle: *mut ClientHandle,
621    request_json: *const c_char,
622) -> *mut c_char {
623    match ffi_result(|| {
624        let client = client_from_handle(handle)?;
625        let request: FfiEnvelopeAccessRequest = parse_json_arg(request_json)?;
626        let key_source =
627            decode_symmetric_key_source(request.key_source, request.key_b64.as_deref(), "key_b64")?;
628        let artifact_bytes = decode_base64_arg("artifact_bytes_b64", &request.artifact_bytes_b64)?;
629        let response =
630            client.access_bytes_with_tdf_using_key_source(&key_source, &artifact_bytes)?;
631        serialize_json(&ffi_tdf_access_result(response))
632    }) {
633        Ok(value) => value,
634        Err(_) => ptr::null_mut(),
635    }
636}
637
638#[unsafe(no_mangle)]
639pub extern "C" fn lattix_sdk_rewrap_bytes_with_tdf(
640    handle: *mut ClientHandle,
641    request_json: *const c_char,
642) -> *mut c_char {
643    match ffi_result(|| {
644        let client = client_from_handle(handle)?;
645        let request: FfiEnvelopeRewrapRequest = parse_json_arg(request_json)?;
646        let current_key_source = decode_symmetric_key_source(
647            request.current_key_source,
648            request.current_key_b64.as_deref(),
649            "current_key_b64",
650        )?;
651        let new_key_source = decode_symmetric_key_source(
652            request.new_key_source,
653            request.new_key_b64.as_deref(),
654            "new_key_b64",
655        )?;
656        let artifact_bytes = decode_base64_arg("artifact_bytes_b64", &request.artifact_bytes_b64)?;
657        let response = client.rewrap_bytes_with_tdf_using_key_sources(
658            &current_key_source,
659            &new_key_source,
660            &artifact_bytes,
661        )?;
662        serialize_json(&ffi_tdf_rewrap_result(response))
663    }) {
664        Ok(value) => value,
665        Err(_) => ptr::null_mut(),
666    }
667}
668
669#[unsafe(no_mangle)]
670pub extern "C" fn lattix_sdk_set_tdf_attributes(
671    handle: *mut ClientHandle,
672    request_json: *const c_char,
673) -> *mut c_char {
674    match ffi_result(|| {
675        let client = client_from_handle(handle)?;
676        let request: FfiTdfSetAttributesRequest = parse_json_arg(request_json)?;
677        let key_source =
678            decode_symmetric_key_source(request.key_source, request.key_b64.as_deref(), "key_b64")?;
679        let artifact_bytes = decode_base64_arg("artifact_bytes_b64", &request.artifact_bytes_b64)?;
680        let response = client.set_tdf_attributes_using_key_source(
681            &key_source,
682            &artifact_bytes,
683            request.attributes,
684        )?;
685        serialize_json(&ffi_tdf_rewrap_result(response))
686    }) {
687        Ok(value) => value,
688        Err(_) => ptr::null_mut(),
689    }
690}
691
692#[unsafe(no_mangle)]
693pub extern "C" fn lattix_sdk_edit_tdf_attributes(
694    handle: *mut ClientHandle,
695    request_json: *const c_char,
696) -> *mut c_char {
697    match ffi_result(|| {
698        let client = client_from_handle(handle)?;
699        let request: FfiTdfEditAttributesRequest = parse_json_arg(request_json)?;
700        let key_source =
701            decode_symmetric_key_source(request.key_source, request.key_b64.as_deref(), "key_b64")?;
702        let artifact_bytes = decode_base64_arg("artifact_bytes_b64", &request.artifact_bytes_b64)?;
703        let response = client.edit_tdf_attributes_using_key_source(
704            &key_source,
705            &artifact_bytes,
706            request.edit,
707        )?;
708        serialize_json(&ffi_tdf_rewrap_result(response))
709    }) {
710        Ok(value) => value,
711        Err(_) => ptr::null_mut(),
712    }
713}
714
715fn ffi_result<T>(action: impl FnOnce() -> Result<T, SdkError>) -> Result<T, ()> {
716    match action() {
717        Ok(value) => {
718            clear_last_error();
719            Ok(value)
720        }
721        Err(err) => {
722            set_last_error(err.to_string());
723            Err(())
724        }
725    }
726}
727
728fn client_from_handle(handle: *mut ClientHandle) -> Result<&'static Client, SdkError> {
729    if handle.is_null() {
730        return Err(SdkError::InvalidInput(
731            "sdk-rust client handle cannot be null".to_string(),
732        ));
733    }
734
735    let handle = unsafe { &*handle };
736    Ok(&handle.client)
737}
738
739fn parse_json_arg<T>(value: *const c_char) -> Result<T, SdkError>
740where
741    T: DeserializeOwned,
742{
743    if value.is_null() {
744        return Err(SdkError::InvalidInput(
745            "JSON argument cannot be null".to_string(),
746        ));
747    }
748
749    let raw = unsafe { CStr::from_ptr(value) };
750    let raw = raw
751        .to_str()
752        .map_err(|err| SdkError::InvalidInput(format!("invalid UTF-8 argument: {err}")))?;
753    serde_json::from_str(raw)
754        .map_err(|err| SdkError::Serialization(format!("invalid JSON argument: {err}")))
755}
756
757fn decode_base64_arg(field_name: &str, value: &str) -> Result<Vec<u8>, SdkError> {
758    base64::engine::general_purpose::STANDARD
759        .decode(value)
760        .map_err(|err| SdkError::InvalidInput(format!("invalid base64 for {field_name}: {err}")))
761}
762
763fn decode_symmetric_key_source(
764    key_source: Option<FfiSymmetricKeySource>,
765    legacy_key_b64: Option<&str>,
766    legacy_field_name: &str,
767) -> Result<LocalSymmetricKeySource, SdkError> {
768    if let Some(key_source) = key_source {
769        return match key_source {
770            FfiSymmetricKeySource::Inline { key_b64 } => Ok(LocalSymmetricKeySource::inline(
771                decode_base64_key_arg(legacy_field_name, &key_b64)?,
772            )),
773            FfiSymmetricKeySource::ManagedReference {
774                key_reference,
775                provider_name,
776            } => Ok(match provider_name {
777                Some(provider_name) => LocalSymmetricKeySource::managed_reference_with_provider(
778                    provider_name,
779                    key_reference,
780                ),
781                None => LocalSymmetricKeySource::managed_reference(key_reference),
782            }),
783        };
784    }
785
786    let legacy_key_b64 = legacy_key_b64.ok_or_else(|| {
787        SdkError::InvalidInput(format!(
788            "missing symmetric key input: provide either key_source or {legacy_field_name}"
789        ))
790    })?;
791    Ok(LocalSymmetricKeySource::inline(decode_base64_key_arg(
792        legacy_field_name,
793        legacy_key_b64,
794    )?))
795}
796
797fn decode_signing_key(value: &str) -> Result<LocalSigningKey, SdkError> {
798    let bytes = decode_base64_arg("signing_key_b64", value)?;
799    let key: [u8; 32] = bytes.try_into().map_err(|_| {
800        SdkError::InvalidInput("signing_key_b64 must decode to exactly 32 bytes".to_string())
801    })?;
802    Ok(LocalSigningKey::from(key))
803}
804
805fn decode_verifying_key(value: &str) -> Result<LocalVerifyingKey, SdkError> {
806    let bytes = decode_base64_arg("verifying_key_b64", value)?;
807    let key: [u8; 32] = bytes.try_into().map_err(|_| {
808        SdkError::InvalidInput("verifying_key_b64 must decode to exactly 32 bytes".to_string())
809    })?;
810    Ok(LocalVerifyingKey::from(key))
811}
812
813fn decode_base64_key_arg(field_name: &str, value: &str) -> Result<LocalSymmetricKey, SdkError> {
814    let bytes = decode_base64_arg(field_name, value)?;
815    let key: [u8; 32] = bytes.try_into().map_err(|_| {
816        SdkError::InvalidInput(format!("{field_name} must decode to exactly 32 bytes"))
817    })?;
818    Ok(LocalSymmetricKey::from(key))
819}
820
821fn ffi_envelope_protection_result(
822    response: EnvelopeProtectionResult,
823) -> FfiEnvelopeProtectionResult {
824    FfiEnvelopeProtectionResult {
825        prepared: response.prepared,
826        key_access_plan: response.key_access_plan,
827        artifact: ffi_protected_envelope_artifact(response.artifact),
828        artifact_registration: response.artifact_registration,
829        evidence: response.evidence,
830    }
831}
832
833fn ffi_detached_signature_sign_result(
834    response: DetachedSignatureSignResult,
835) -> FfiDetachedSignatureSignResult {
836    FfiDetachedSignatureSignResult {
837        prepared: response.prepared,
838        key_access_plan: response.key_access_plan,
839        artifact: ffi_protected_detached_signature_artifact(response.artifact),
840        artifact_registration: response.artifact_registration,
841        evidence: response.evidence,
842    }
843}
844
845fn ffi_protected_detached_signature_artifact(
846    artifact: ProtectedDetachedSignatureArtifact,
847) -> FfiProtectedDetachedSignatureArtifact {
848    FfiProtectedDetachedSignatureArtifact {
849        detached_signature: artifact.detached_signature,
850        artifact_bytes_b64: base64::engine::general_purpose::STANDARD
851            .encode(artifact.artifact_bytes),
852        artifact_digest: artifact.artifact_digest,
853    }
854}
855
856fn ffi_detached_signature_verify_result(
857    response: DetachedSignatureVerifyResult,
858) -> FfiDetachedSignatureVerifyResult {
859    FfiDetachedSignatureVerifyResult {
860        artifact: response.artifact,
861        artifact_digest: response.artifact_digest,
862        policy_resolution: response.policy_resolution,
863        key_access_plan: response.key_access_plan,
864        content_binding: response.content_binding,
865        evidence: response.evidence,
866    }
867}
868
869fn ffi_protected_envelope_artifact(
870    artifact: ProtectedEnvelopeArtifact,
871) -> FfiProtectedEnvelopeArtifact {
872    FfiProtectedEnvelopeArtifact {
873        envelope: artifact.envelope,
874        artifact_bytes_b64: base64::engine::general_purpose::STANDARD
875            .encode(artifact.artifact_bytes),
876        artifact_digest: artifact.artifact_digest,
877    }
878}
879
880fn ffi_envelope_access_result(response: EnvelopeAccessResult) -> FfiEnvelopeAccessResult {
881    FfiEnvelopeAccessResult {
882        artifact: response.artifact,
883        artifact_digest: response.artifact_digest,
884        policy_resolution: response.policy_resolution,
885        key_access_plan: response.key_access_plan,
886        plaintext_b64: base64::engine::general_purpose::STANDARD.encode(response.plaintext),
887        evidence: response.evidence,
888    }
889}
890
891fn ffi_envelope_rewrap_result(response: EnvelopeRewrapResult) -> FfiEnvelopeRewrapResult {
892    FfiEnvelopeRewrapResult {
893        content_binding: response.content_binding,
894        policy_resolution: response.policy_resolution,
895        protection_plan: response.protection_plan,
896        key_access_plan: response.key_access_plan,
897        original_artifact_digest: response.original_artifact_digest,
898        artifact: ffi_protected_envelope_artifact(response.artifact),
899        artifact_registration: response.artifact_registration,
900        evidence: response.evidence,
901    }
902}
903
904fn ffi_tdf_protection_result(response: TdfProtectionResult) -> FfiTdfProtectionResult {
905    FfiTdfProtectionResult {
906        prepared: response.prepared,
907        key_access_plan: response.key_access_plan,
908        artifact: ffi_protected_tdf_artifact(response.artifact),
909        artifact_registration: response.artifact_registration,
910        evidence: response.evidence,
911    }
912}
913
914fn ffi_protected_tdf_artifact(artifact: ProtectedTdfArtifact) -> FfiProtectedTdfArtifact {
915    FfiProtectedTdfArtifact {
916        tdf: artifact.tdf,
917        artifact_bytes_b64: base64::engine::general_purpose::STANDARD
918            .encode(artifact.artifact_bytes),
919        artifact_digest: artifact.artifact_digest,
920    }
921}
922
923fn ffi_tdf_access_result(response: TdfAccessResult) -> FfiTdfAccessResult {
924    FfiTdfAccessResult {
925        artifact: response.artifact,
926        manifest: response.manifest,
927        artifact_digest: response.artifact_digest,
928        policy_resolution: response.policy_resolution,
929        key_access_plan: response.key_access_plan,
930        plaintext_b64: base64::engine::general_purpose::STANDARD.encode(response.plaintext),
931        evidence: response.evidence,
932    }
933}
934
935fn ffi_tdf_rewrap_result(response: TdfRewrapResult) -> FfiTdfRewrapResult {
936    FfiTdfRewrapResult {
937        content_binding: response.content_binding,
938        manifest: response.manifest,
939        policy_resolution: response.policy_resolution,
940        protection_plan: response.protection_plan,
941        key_access_plan: response.key_access_plan,
942        original_artifact_digest: response.original_artifact_digest,
943        artifact: ffi_protected_tdf_artifact(response.artifact),
944        artifact_registration: response.artifact_registration,
945        evidence: response.evidence,
946    }
947}
948
949fn serialize_json<T>(value: &T) -> Result<*mut c_char, SdkError>
950where
951    T: Serialize,
952{
953    let payload = serde_json::to_string(value)
954        .map_err(|err| SdkError::Serialization(format!("failed to serialize response: {err}")))?;
955    Ok(into_c_string(payload))
956}
957
958fn set_last_error(message: String) {
959    LAST_ERROR.with(|slot| {
960        *slot.borrow_mut() = Some(message);
961    });
962}
963
964fn clear_last_error() {
965    LAST_ERROR.with(|slot| {
966        *slot.borrow_mut() = None;
967    });
968}
969
970fn into_c_string(value: impl AsRef<str>) -> *mut c_char {
971    let sanitized = value.as_ref().replace('\0', " ");
972    CString::new(sanitized)
973        .expect("CString::new should succeed after null sanitization")
974        .into_raw()
975}