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#[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#[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 ¤t_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 ¤t_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}