Skip to main content

localauthentication/
async_api.rs

1//! Async API for `LocalAuthentication`
2//!
3//! This module provides async versions of authentication operations when the `async` feature is enabled.
4//! The async API is **executor-agnostic** and works with any async runtime (Tokio, async-std, smol, etc.).
5
6use crate::la_context::LAContext;
7use crate::la_error::Result;
8use crate::la_policy::LAPolicy;
9use doom_fish_utils::completion::{error_from_cstr, AsyncCompletion, AsyncCompletionFuture};
10use doom_fish_utils::panic_safe::catch_user_panic;
11use std::ffi::c_void;
12use std::future::Future;
13use std::pin::Pin;
14use std::task::{Context, Poll};
15
16// ============================================================================
17// Callbacks for async operations
18// ============================================================================
19
20extern "C" fn evaluate_policy_callback(
21    success: u8,
22    error: *const i8,
23    user_data: *mut c_void,
24) {
25    catch_user_panic("evaluate_policy_callback", || {
26        if error.is_null() {
27            // SAFETY: user_data points to a valid AsyncCompletion<bool> created by AsyncCompletion::create()
28            unsafe { AsyncCompletion::complete_ok(user_data, success != 0) };
29        } else {
30            // SAFETY: error is a valid C string from Swift bridge
31            let error_msg = unsafe { error_from_cstr(error) };
32            // SAFETY: user_data points to a valid AsyncCompletion<bool> created by AsyncCompletion::create()
33            unsafe { AsyncCompletion::<bool>::complete_err(user_data, error_msg) };
34        }
35    });
36}
37
38extern "C" fn evaluate_access_control_callback(
39    success: u8,
40    error: *const i8,
41    user_data: *mut c_void,
42) {
43    catch_user_panic("evaluate_access_control_callback", || {
44        if error.is_null() {
45            // SAFETY: user_data points to a valid AsyncCompletion<bool> created by AsyncCompletion::create()
46            unsafe { AsyncCompletion::complete_ok(user_data, success != 0) };
47        } else {
48            // SAFETY: error is a valid C string from Swift bridge
49            let error_msg = unsafe { error_from_cstr(error) };
50            // SAFETY: user_data points to a valid AsyncCompletion<bool> created by AsyncCompletion::create()
51            unsafe { AsyncCompletion::<bool>::complete_err(user_data, error_msg) };
52        }
53    });
54}
55
56// ============================================================================
57// Future types
58// ============================================================================
59
60/// Future for async policy evaluation
61pub struct AsyncPolicyEvaluation {
62    inner: AsyncCompletionFuture<bool>,
63}
64
65impl std::fmt::Debug for AsyncPolicyEvaluation {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        f.debug_struct("AsyncPolicyEvaluation")
68            .finish_non_exhaustive()
69    }
70}
71
72impl Future for AsyncPolicyEvaluation {
73    type Output = Result<bool>;
74
75    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
76        Pin::new(&mut self.inner)
77            .poll(cx)
78            .map(|r| r.map_err(crate::la_error::LAError::BridgeFailed))
79    }
80}
81
82/// Future for async access control evaluation
83pub struct AsyncAccessControlEvaluation {
84    inner: AsyncCompletionFuture<bool>,
85}
86
87impl std::fmt::Debug for AsyncAccessControlEvaluation {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        f.debug_struct("AsyncAccessControlEvaluation")
90            .finish_non_exhaustive()
91    }
92}
93
94impl Future for AsyncAccessControlEvaluation {
95    type Output = Result<bool>;
96
97    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
98        Pin::new(&mut self.inner)
99            .poll(cx)
100            .map(|r| r.map_err(crate::la_error::LAError::BridgeFailed))
101    }
102}
103
104// ============================================================================
105// Async operations extension trait
106// ============================================================================
107
108/// Extension trait adding async methods to `LAContext`
109pub trait AsyncContextExt {
110    /// Asynchronously evaluate a policy
111    ///
112    /// # Errors
113    ///
114    /// Returns an error if the localized reason is empty or contains a null byte.
115    fn evaluate_policy_async(
116        &self,
117        policy: LAPolicy,
118        localized_reason: &str,
119    ) -> Result<AsyncPolicyEvaluation>;
120
121    /// Asynchronously evaluate an access control
122    ///
123    /// # Safety
124    ///
125    /// The `access_control` pointer must be a valid, properly initialized `SecAccessControl` reference.
126    ///
127    /// # Errors
128    ///
129    /// Returns an error if the access control is null, localized reason is empty, or contains a null byte.
130    unsafe fn evaluate_access_control_async(
131        &self,
132        access_control: *const c_void,
133        operation: crate::la_context::LAAccessControlOperation,
134        localized_reason: &str,
135    ) -> Result<AsyncAccessControlEvaluation>;
136}
137
138impl AsyncContextExt for LAContext {
139    /// Asynchronously evaluate a policy
140    ///
141    /// Uses callback-based Swift FFI for true async operation.
142    ///
143    /// # Arguments
144    ///
145    /// * `policy` - The authentication policy to evaluate
146    /// * `localized_reason` - A localized reason shown to the user
147    fn evaluate_policy_async(
148        &self,
149        policy: LAPolicy,
150        localized_reason: &str,
151    ) -> Result<AsyncPolicyEvaluation> {
152        if localized_reason.is_empty() {
153            return Err(crate::la_error::LAError::InvalidArgument(
154                "localized reason must not be empty".to_owned(),
155            ));
156        }
157
158        let (future, ctx) = AsyncCompletion::create();
159        let reason_cstring = std::ffi::CString::new(localized_reason).map_err(|_| {
160            crate::la_error::LAError::InvalidArgument("localized reason contains null byte".to_owned())
161        })?;
162
163        let context_ptr = self.as_ptr();
164
165        unsafe {
166            crate::ffi::la_context::la_context_evaluate_policy_async(
167                context_ptr,
168                policy.as_ffi(),
169                reason_cstring.as_ptr(),
170                evaluate_policy_callback,
171                ctx,
172            );
173        }
174
175        Ok(AsyncPolicyEvaluation { inner: future })
176    }
177
178    /// Asynchronously evaluate an access control
179    ///
180    /// Uses callback-based Swift FFI for true async operation.
181    ///
182    /// # Arguments
183    ///
184    /// * `access_control` - A `SecAccessControl` reference (as raw pointer)
185    /// * `operation` - The access control operation to evaluate
186    /// * `localized_reason` - A localized reason shown to the user
187    ///
188    /// # Safety
189    ///
190    /// The `access_control` pointer must be a valid, properly initialized `SecAccessControl` reference.
191    unsafe fn evaluate_access_control_async(
192        &self,
193        access_control: *const c_void,
194        operation: crate::la_context::LAAccessControlOperation,
195        localized_reason: &str,
196    ) -> Result<AsyncAccessControlEvaluation> {
197        if access_control.is_null() {
198            return Err(crate::la_error::LAError::InvalidArgument(
199                "access control pointer must not be null".to_owned(),
200            ));
201        }
202        if localized_reason.is_empty() {
203            return Err(crate::la_error::LAError::InvalidArgument(
204                "localized reason must not be empty".to_owned(),
205            ));
206        }
207
208        let (future, ctx) = AsyncCompletion::create();
209        let reason_cstring = std::ffi::CString::new(localized_reason).map_err(|_| {
210            crate::la_error::LAError::InvalidArgument("localized reason contains null byte".to_owned())
211        })?;
212
213        let context_ptr = self.as_ptr();
214
215        unsafe {
216            crate::ffi::la_context::la_context_evaluate_access_control_async(
217                context_ptr,
218                access_control,
219                operation.raw_value(),
220                reason_cstring.as_ptr(),
221                evaluate_access_control_callback,
222                ctx,
223            );
224        }
225
226        Ok(AsyncAccessControlEvaluation { inner: future })
227    }
228}