blob: e97ac59a9fafd69a8c8cf4018053c5487a34d041 [file] [log] [blame]
Janis Danisevskis7a1cf382020-11-20 11:22:14 -08001// Copyright 2020, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! This crate implements a safe wrapper around the ConfirmationUI HIDL spec, which
16//! is the backend for Android Protected Confirmation (APC).
17//!
18//! It provides a safe wrapper around a C++ implementation of ConfirmationUI
19//! client.
20
21use keystore2_apc_compat_bindgen::{
Jeff Vander Stoep76c0f282022-12-07 11:39:27 +010022 abortUserConfirmation, closeUserConfirmationService, promptUserConfirmation,
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080023 tryGetUserConfirmationService, ApcCompatCallback, ApcCompatServiceHandle,
24};
25pub use keystore2_apc_compat_bindgen::{
26 ApcCompatUiOptions, APC_COMPAT_ERROR_ABORTED, APC_COMPAT_ERROR_CANCELLED,
27 APC_COMPAT_ERROR_IGNORED, APC_COMPAT_ERROR_OK, APC_COMPAT_ERROR_OPERATION_PENDING,
28 APC_COMPAT_ERROR_SYSTEM_ERROR, INVALID_SERVICE_HANDLE,
29};
30use std::{ffi::CString, slice};
31
32/// Safe wrapper around the ConfirmationUI HIDL spec.
33///
34/// # Example
35/// ```
36/// struct Cb();
37/// impl ApcHalCallback for Cb {
38/// fn result(
39/// &self,
40/// rc: u32,
41/// message: Option<&[u8]>,
42/// token: Option<&[u8]>,
43/// ) {
44/// println!("Callback called with rc: {}, message: {}, token: {}", rc, message, token);
45/// }
46/// };
47///
48/// fn prompt() -> Result<(), u32> {
49/// let hal = ApcHal::try_get_service()?;
50/// hal.prompt_user_confirmation(Box::new(Cb()), "Do you agree?", b"extra data", "en", 0)?;
51/// }
52///
53/// ```
54pub struct ApcHal(ApcCompatServiceHandle);
55
Andrew Walbran094a9c42023-07-14 14:50:38 +010056// SAFETY: This is a wrapper around `ApcCompatSession`, which can be used from any thread.
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080057unsafe impl Send for ApcHal {}
Andrew Walbran094a9c42023-07-14 14:50:38 +010058// SAFETY: `ApcCompatSession` can be called simultaneously from different threads because AIDL and
59// HIDL are thread-safe.
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080060unsafe impl Sync for ApcHal {}
61
62impl Drop for ApcHal {
63 fn drop(&mut self) {
64 // # Safety:
65 // This ends the life cycle of the contained `ApcCompatServiceHandle` owned by this
66 // `ApcHal` object.
67 //
68 // `ApcHal` objects are only created if a valid handle was acquired so self.0 is
69 // always valid when dropped.
70 unsafe {
71 closeUserConfirmationService(self.0);
72 }
73 }
74}
75
76type Callback = dyn FnOnce(u32, Option<&[u8]>, Option<&[u8]>);
77
78extern "C" fn confirmation_result_callback(
79 handle: *mut ::std::os::raw::c_void,
80 rc: u32,
81 tbs_message: *const u8,
Jeff Vander Stoep76c0f282022-12-07 11:39:27 +010082 tbs_message_size: usize,
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080083 confirmation_token: *const u8,
Jeff Vander Stoep76c0f282022-12-07 11:39:27 +010084 confirmation_token_size: usize,
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080085) {
86 // # Safety:
87 // The C/C++ implementation must pass to us the handle that was created
88 // and assigned to the `ApcCompatCallback::data` field in
89 // `ApcHal::prompt_user_confirmation` below. Also we consume the handle,
90 // by letting `hal_cb` go out of scope with this function call. So
91 // the C/C++ implementation must assure that each `ApcCompatCallback` is only used once.
92 let hal_cb: Box<Box<Callback>> = unsafe { Box::from_raw(handle as *mut Box<Callback>) };
93 let tbs_message = match (tbs_message.is_null(), tbs_message_size) {
94 (true, _) | (_, 0) => None,
95 (false, s) => Some(
96 // # Safety:
97 // If the pointer and size is not nullptr and not 0 respectively, the C/C++
98 // implementation must pass a valid pointer to an allocation of at least size bytes,
99 // and the pointer must be valid until this function returns.
Charisee03e00842023-01-25 01:41:23 +0000100 unsafe { slice::from_raw_parts(tbs_message, s) },
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800101 ),
102 };
103 let confirmation_token = match (confirmation_token.is_null(), confirmation_token_size) {
104 (true, _) | (_, 0) => None,
105 (false, s) => Some(
106 // # Safety:
107 // If the pointer and size is not nullptr and not 0 respectively, the C/C++
108 // implementation must pass a valid pointer to an allocation of at least size bytes,
109 // and the pointer must be valid until this function returns.
Charisee03e00842023-01-25 01:41:23 +0000110 unsafe { slice::from_raw_parts(confirmation_token, s) },
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800111 ),
112 };
113 hal_cb(rc, tbs_message, confirmation_token)
114}
115
116impl ApcHal {
117 /// Attempts to connect to the APC (confirmationui) backend. On success, it returns an
118 /// initialized `ApcHal` object.
119 pub fn try_get_service() -> Option<Self> {
120 // # Safety:
121 // `tryGetUserConfirmationService` returns a valid handle or INVALID_SERVICE_HANDLE.
122 // On success, `ApcHal` takes ownership of this handle and frees it with
123 // `closeUserConfirmationService` when dropped.
124 let handle = unsafe { tryGetUserConfirmationService() };
125 match handle {
Andrew Walbran094a9c42023-07-14 14:50:38 +0100126 // SAFETY: This is just a constant.
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800127 h if h == unsafe { INVALID_SERVICE_HANDLE } => None,
128 h => Some(Self(h)),
129 }
130 }
131
132 /// Attempts to start a confirmation prompt. The given callback is consumed, and it is
133 /// guaranteed to be called eventually IFF this function returns `APC_COMPAT_ERROR_OK`.
134 ///
135 /// The callback has the following arguments:
136 /// rc: u32 - The reason for the termination which takes one of the values.
137 /// * `APC_COMPAT_ERROR_OK` - The user confirmed the prompted message.
138 /// * `APC_COMPAT_ERROR_CANCELLED` - The user rejected the prompted message.
139 /// * `APC_COMPAT_ERROR_ABORTED` - The prompt was aborted either because the client
140 /// aborted. the session or an asynchronous system event occurred that ended the
141 /// prompt prematurely.
142 /// * `APC_COMPAT_ERROR_SYSTEMERROR` - An unspecified system error occurred. Logs may
143 /// have more information.
144 ///
145 /// data_confirmed: Option<&[u8]> and
146 /// confirmation_token: Option<&[u8]> hold the confirmed message and the confirmation token
147 /// respectively. They must be `Some()` if `rc == APC_COMPAT_ERROR_OK` and `None` otherwise.
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800148 ///
149 /// `cb` does not get called if this function returns an error.
150 /// (Thus the allow(unused_must_use))
151 #[allow(unused_must_use)]
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800152 pub fn prompt_user_confirmation<F>(
153 &self,
154 prompt_text: &str,
155 extra_data: &[u8],
156 locale: &str,
157 ui_opts: ApcCompatUiOptions,
158 cb: F,
159 ) -> Result<(), u32>
160 where
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800161 F: FnOnce(u32, Option<&[u8]>, Option<&[u8]>) + 'static,
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800162 {
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800163 let cb_data_ptr = Box::into_raw(Box::new(Box::new(cb) as Box<Callback>));
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800164 let cb = ApcCompatCallback {
165 data: cb_data_ptr as *mut std::ffi::c_void,
166 result: Some(confirmation_result_callback),
167 };
168 let prompt_text = CString::new(prompt_text).unwrap();
169 let locale = CString::new(locale).unwrap();
170 // # Safety:
171 // The `ApcCompatCallback` object (`cb`) is passed to the callee by value, and with it
172 // ownership of the `data` field pointer. The data pointer is guaranteed to be valid
173 // until the C/C++ implementation calls the callback. Calling the callback consumes
174 // the data pointer. The C/C++ implementation must not access it after calling the
175 // callback and it must not call the callback a second time.
176 //
177 // The C/C++ must make no assumptions about the life time of the other parameters after
178 // the function returns.
179 let rc = unsafe {
180 promptUserConfirmation(
181 self.0,
182 cb,
183 prompt_text.as_ptr(),
184 extra_data.as_ptr(),
Charisee03e00842023-01-25 01:41:23 +0000185 extra_data.len(),
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800186 locale.as_ptr(),
187 ui_opts,
188 )
189 };
190 match rc {
191 APC_COMPAT_ERROR_OK => Ok(()),
192 rc => {
193 // # Safety:
194 // If promptUserConfirmation does not succeed, it must not take ownership of the
195 // callback, so we must destroy it.
196 unsafe { Box::from_raw(cb_data_ptr) };
197 Err(rc)
198 }
199 }
200 }
201
202 /// Aborts a running confirmation session, or no-op if none is running.
203 pub fn abort(&self) {
204 // # Safety:
205 // It is always safe to call `abortUserConfirmation`, because spurious calls are ignored.
206 // The handle argument must be valid, but this is an invariant of `ApcHal`.
207 unsafe { abortUserConfirmation(self.0) }
208 }
209}