Keystore 2.0: Implement APC service.
This patch implements the Android Protected Confirmation service in
Keystore 2.0. This includes a C++ wrapper around the HIDL confirmationui
interface which will stay a HIDL interface for now.
This patch also includes the new AIDL specification.
This patch lacks death listener registration b/176491050.
Bug: 159341464
Bug: 173546269
Test: None
Change-Id: Ida4af108e86b538ab64d1dea4809cfa3b36f74cd
diff --git a/keystore2/apc_compat/Android.bp b/keystore2/apc_compat/Android.bp
new file mode 100644
index 0000000..405e9b8
--- /dev/null
+++ b/keystore2/apc_compat/Android.bp
@@ -0,0 +1,56 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+ name: "libkeystore2_apc_compat",
+ srcs: [
+ "apc_compat.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.confirmationui@1.0",
+ "libbase",
+ "libhidlbase",
+ "libutils",
+ ],
+}
+
+rust_bindgen {
+ name: "libkeystore2_apc_compat_bindgen",
+ wrapper_src: "apc_compat.hpp",
+ crate_name: "keystore2_apc_compat_bindgen",
+ source_stem: "bindings",
+
+ bindgen_flags: [
+ "--whitelist-function=tryGetUserConfirmationService",
+ "--whitelist-function=promptUserConfirmation",
+ "--whitelist-function=abortUserConfirmation",
+ "--whitelist-function=closeUserConfirmationService",
+ "--whitelist-var=INVALID_SERVICE_HANDLE",
+ "--whitelist-var=APC_COMPAT_.*",
+ ],
+}
+
+rust_library {
+ name: "libkeystore2_apc_compat-rust",
+ crate_name: "keystore2_apc_compat",
+ srcs: [
+ "apc_compat.rs",
+ ],
+ rustlibs: [
+ "libkeystore2_apc_compat_bindgen",
+ ],
+ shared_libs: [
+ "libkeystore2_apc_compat",
+ ],
+}
diff --git a/keystore2/apc_compat/apc_compat.cpp b/keystore2/apc_compat/apc_compat.cpp
new file mode 100644
index 0000000..08a8e45
--- /dev/null
+++ b/keystore2/apc_compat/apc_compat.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apc_compat.hpp"
+#include <android-base/logging.h>
+#include <android/hardware/confirmationui/1.0/IConfirmationUI.h>
+#include <hwbinder/IBinder.h>
+
+#include <memory>
+#include <string>
+#include <thread>
+#include <vector>
+
+#define LOG_TAG "keystore2_apc_compat"
+
+namespace keystore2 {
+
+using android::sp;
+using android::hardware::hidl_death_recipient;
+using android::hardware::hidl_vec;
+using android::hardware::Return;
+using android::hardware::Status;
+using android::hardware::confirmationui::V1_0::IConfirmationResultCallback;
+using android::hardware::confirmationui::V1_0::IConfirmationUI;
+using android::hardware::confirmationui::V1_0::ResponseCode;
+using android::hardware::confirmationui::V1_0::UIOption;
+
+static uint32_t responseCode2Compat(ResponseCode rc) {
+ switch (rc) {
+ case ResponseCode::OK:
+ return APC_COMPAT_ERROR_OK;
+ case ResponseCode::Canceled:
+ return APC_COMPAT_ERROR_CANCELLED;
+ case ResponseCode::Aborted:
+ return APC_COMPAT_ERROR_ABORTED;
+ case ResponseCode::OperationPending:
+ return APC_COMPAT_ERROR_OPERATION_PENDING;
+ case ResponseCode::Ignored:
+ return APC_COMPAT_ERROR_IGNORED;
+ case ResponseCode::SystemError:
+ case ResponseCode::Unimplemented:
+ case ResponseCode::Unexpected:
+ case ResponseCode::UIError:
+ case ResponseCode::UIErrorMissingGlyph:
+ case ResponseCode::UIErrorMessageTooLong:
+ case ResponseCode::UIErrorMalformedUTF8Encoding:
+ default:
+ return APC_COMPAT_ERROR_SYSTEM_ERROR;
+ }
+}
+
+class ConfuiCompatSession : public IConfirmationResultCallback, public hidl_death_recipient {
+ public:
+ static sp<ConfuiCompatSession>* tryGetService() {
+ sp<IConfirmationUI> service = IConfirmationUI::tryGetService();
+ if (service) {
+ return new sp(new ConfuiCompatSession(std::move(service)));
+ } else {
+ return nullptr;
+ }
+ }
+
+ uint32_t promptUserConfirmation(ApcCompatCallback callback, const char* prompt_text,
+ const uint8_t* extra_data, size_t extra_data_size,
+ const char* locale, ApcCompatUiOptions ui_options) {
+ std::string hidl_prompt(prompt_text);
+ std::vector<uint8_t> hidl_extra(extra_data, extra_data + extra_data_size);
+ std::string hidl_locale(locale);
+ std::vector<UIOption> hidl_ui_options;
+ if (ui_options.inverted) {
+ hidl_ui_options.push_back(UIOption::AccessibilityInverted);
+ }
+ if (ui_options.magnified) {
+ hidl_ui_options.push_back(UIOption::AccessibilityMagnified);
+ }
+ auto lock = std::lock_guard(callback_lock_);
+ if (callback_.result != nullptr) {
+ return APC_COMPAT_ERROR_OPERATION_PENDING;
+ }
+ auto err = service_->linkToDeath(sp(this), 0);
+ if (!err.isOk()) {
+ LOG(ERROR) << "Communication error: promptUserConfirmation: "
+ "Trying to register death recipient: "
+ << err.description();
+ return APC_COMPAT_ERROR_SYSTEM_ERROR;
+ }
+
+ auto rc = service_->promptUserConfirmation(sp(this), hidl_prompt, hidl_extra, hidl_locale,
+ hidl_ui_options);
+ if (!rc.isOk()) {
+ LOG(ERROR) << "Communication error: promptUserConfirmation: " << rc.description();
+ }
+ if (rc == ResponseCode::OK) {
+ callback_ = callback;
+ }
+ return responseCode2Compat(rc.withDefault(ResponseCode::SystemError));
+ }
+
+ void abort() { service_->abort(); }
+
+ void
+ finalize(ResponseCode responseCode,
+ std::optional<std::reference_wrapper<const hidl_vec<uint8_t>>> dataConfirmed,
+ std::optional<std::reference_wrapper<const hidl_vec<uint8_t>>> confirmationToken) {
+ ApcCompatCallback callback;
+ {
+ auto lock = std::lock_guard(callback_lock_);
+ // Calling the callback consumes the callback data structure. We have to make
+ // sure that it can only be called once.
+ callback = callback_;
+ callback_ = {nullptr, nullptr};
+ // Unlock the callback_lock_ here. It must never be held while calling the callback.
+ }
+
+ if (callback.result != nullptr) {
+ service_->unlinkToDeath(sp(this));
+
+ size_t dataConfirmedSize = 0;
+ const uint8_t* dataConfirmedPtr = nullptr;
+ size_t confirmationTokenSize = 0;
+ const uint8_t* confirmationTokenPtr = nullptr;
+ if (responseCode == ResponseCode::OK) {
+ if (dataConfirmed) {
+ dataConfirmedPtr = dataConfirmed->get().data();
+ dataConfirmedSize = dataConfirmed->get().size();
+ }
+ if (dataConfirmed) {
+ confirmationTokenPtr = confirmationToken->get().data();
+ confirmationTokenSize = confirmationToken->get().size();
+ }
+ }
+ callback.result(callback.data, responseCode2Compat(responseCode), dataConfirmedPtr,
+ dataConfirmedSize, confirmationTokenPtr, confirmationTokenSize);
+ }
+ }
+
+ // IConfirmationResultCallback overrides:
+ android::hardware::Return<void> result(ResponseCode responseCode,
+ const hidl_vec<uint8_t>& dataConfirmed,
+ const hidl_vec<uint8_t>& confirmationToken) override {
+ finalize(responseCode, dataConfirmed, confirmationToken);
+ return Status::ok();
+ };
+
+ void serviceDied(uint64_t /* cookie */,
+ const ::android::wp<::android::hidl::base::V1_0::IBase>& /* who */) override {
+ finalize(ResponseCode::SystemError, {}, {});
+ }
+
+ private:
+ ConfuiCompatSession(sp<IConfirmationUI> service)
+ : service_(service), callback_{nullptr, nullptr} {}
+ sp<IConfirmationUI> service_;
+
+ // The callback_lock_ protects the callback_ field against concurrent modification.
+ // IMPORTANT: It must never be held while calling the call back.
+ std::mutex callback_lock_;
+ ApcCompatCallback callback_;
+};
+
+} // namespace keystore2
+
+using namespace keystore2;
+
+ApcCompatServiceHandle tryGetUserConfirmationService() {
+ return reinterpret_cast<ApcCompatServiceHandle>(ConfuiCompatSession::tryGetService());
+}
+
+uint32_t promptUserConfirmation(ApcCompatServiceHandle handle, ApcCompatCallback callback,
+ const char* prompt_text, const uint8_t* extra_data,
+ size_t extra_data_size, char const* locale,
+ ApcCompatUiOptions ui_options) {
+ auto session = reinterpret_cast<sp<ConfuiCompatSession>*>(handle);
+ return (*session)->promptUserConfirmation(callback, prompt_text, extra_data, extra_data_size,
+ locale, ui_options);
+}
+
+void abortUserConfirmation(ApcCompatServiceHandle handle) {
+ auto session = reinterpret_cast<sp<ConfuiCompatSession>*>(handle);
+ (*session)->abort();
+}
+
+void closeUserConfirmationService(ApcCompatServiceHandle handle) {
+ // Closing the handle implicitly aborts an ongoing sessions.
+ // Note that a resulting callback is still safely conducted, because we only delete a
+ // StrongPointer below. libhwbinder still owns another StrongPointer to this session.
+ abortUserConfirmation(handle);
+ delete reinterpret_cast<sp<ConfuiCompatSession>*>(handle);
+}
+
+const ApcCompatServiceHandle INVALID_SERVICE_HANDLE = nullptr;
diff --git a/keystore2/apc_compat/apc_compat.hpp b/keystore2/apc_compat/apc_compat.hpp
new file mode 100644
index 0000000..15fa5f4
--- /dev/null
+++ b/keystore2/apc_compat/apc_compat.hpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+using ApcCompatServiceHandle = void*;
+
+#define APC_COMPAT_ERROR_OK 0
+#define APC_COMPAT_ERROR_CANCELLED 1
+#define APC_COMPAT_ERROR_ABORTED 2
+#define APC_COMPAT_ERROR_OPERATION_PENDING 3
+#define APC_COMPAT_ERROR_IGNORED 4
+#define APC_COMPAT_ERROR_SYSTEM_ERROR 5
+
+extern "C" {
+
+extern const ApcCompatServiceHandle INVALID_SERVICE_HANDLE;
+
+/**
+ * This struct holds the ui options for the protected confirmation dialog.
+ */
+struct ApcCompatUiOptions {
+ /**
+ * If set to true inverted color mode is used.
+ */
+ bool inverted;
+ /**
+ * If set to true magnified fonts are used.
+ */
+ bool magnified;
+};
+
+/**
+ * Represents a result callback that is called when a confirmation session was successfully
+ * started.
+ * The field `data` is an opaque callback context handle. It must be passed to the `result`
+ * function.
+ *
+ * IMPORTANT: The life cycle of `data` ends when `result` is called. The callback must not
+ * be called a second time.
+ *
+ * The callback function `result` has the prototype:
+ * void result(
+ * void* data,
+ * uint32_t rc,
+ * const uint8_t* tbs_message,
+ * size_t tbs_message_size,
+ * const uint8_t* confirmation_token,
+ * size_t confirmation_token_size)
+ *
+ * * data - must be the data field of the structure.
+ * * rc - response code, one of:
+ * * APC_COMPAT_ERROR_OK - The user confirmed the prompt text.
+ * * APC_COMPAT_ERROR_CANCELLED - The user rejected the prompt text.
+ * * APC_COMPAT_ERROR_ABORTED - `abortUserConfirmation` was called.
+ * * APC_COMPAT_ERROR_SYSTEM_ERROR - An unspecified system error occurred.
+ * * tbs_message(_size) - Pointer to and size of the to-be-signed message. Must
+ * be NULL and 0 respectively if `rc != APC_COMPAT_ERROR_OK`.
+ * * confirmation_token(_size) - Pointer to and size of the confirmation token. Must
+ * be NULL and 0 respectively if `rc != APC_COMPAT_ERROR_OK`.
+ */
+struct ApcCompatCallback {
+ void* data;
+ void (*result)(void*, uint32_t, const uint8_t*, size_t, const uint8_t*, size_t);
+};
+
+/**
+ * Attempts to make a connection to the confirmationui HIDL backend.
+ * If a valid service handle is returned it stays valid until
+ * `closeUserConfirmationService` is called.
+ *
+ * @return A valid service handle on success or INVALID_SERVICE_HANDLE
+ * on failure.
+ */
+ApcCompatServiceHandle tryGetUserConfirmationService();
+
+/**
+ * Attempts to start a protected confirmation session on the given service handle.
+ * The function takes ownership of the callback object (`cb`) IFF APC_COMPAT_ERROR_OK
+ * is returned. The resources referenced by the callback object must stay valid
+ * until the callback is called.
+ *
+ * @param handle A valid service handle as returned by `tryGetUserConfirmationService()`.
+ * @cb A ApcCompatCallback structure that represents a callback function with session data.
+ * @param prompt_text A UTF-8 encoded prompt string.
+ * @param extra_data Free form extra data.
+ * @param extra_data_size size of the extra data buffer in bytes.
+ * @param locale A locale string.
+ * @param ui_options A UI options. See ApcCompatUiOptions above.
+ * @retval APC_COMPAT_ERROR_OK on success.
+ * @retval APC_COMPAT_ERROR_OPERATION_PENDING if another operation was already in progress.
+ * @retval APC_COMPAT_ERROR_SYSTEM_ERROR if an unspecified system error occurred.
+ */
+uint32_t promptUserConfirmation(ApcCompatServiceHandle handle, struct ApcCompatCallback cb,
+ const char* prompt_text, const uint8_t* extra_data,
+ size_t extra_data_size, char const* locale,
+ ApcCompatUiOptions ui_options);
+
+/**
+ * Aborts a running confirmation session or no-op if no session is running.
+ * If a session is running this results in a `result` callback with
+ * `rc == APC_COMPAT_ERROR_ABORTED`. Mind though that the callback can still yield other
+ * results even after this function was called, because it may race with an actual user
+ * response. In any case, there will be only one callback response for each session
+ * successfully started with promptUserConfirmation.
+ *
+ * @param handle A valid session handle as returned by `tryGetUserConfirmationService()`
+ */
+void abortUserConfirmation(ApcCompatServiceHandle handle);
+
+/**
+ * Closes a valid service session as returned by `tryGetUserConfirmationService()`.
+ * If a session is still running it is implicitly aborted. In this case, freeing up of the resources
+ * referenced by the service handle is deferred until the callback has completed.
+ *
+ * @param handle A valid session handle as returned by `tryGetUserConfirmationService()`
+ */
+void closeUserConfirmationService(ApcCompatServiceHandle);
+
+}
diff --git a/keystore2/apc_compat/apc_compat.rs b/keystore2/apc_compat/apc_compat.rs
new file mode 100644
index 0000000..4391f5b
--- /dev/null
+++ b/keystore2/apc_compat/apc_compat.rs
@@ -0,0 +1,201 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This crate implements a safe wrapper around the ConfirmationUI HIDL spec, which
+//! is the backend for Android Protected Confirmation (APC).
+//!
+//! It provides a safe wrapper around a C++ implementation of ConfirmationUI
+//! client.
+
+use keystore2_apc_compat_bindgen::{
+ abortUserConfirmation, closeUserConfirmationService, promptUserConfirmation, size_t,
+ tryGetUserConfirmationService, ApcCompatCallback, ApcCompatServiceHandle,
+};
+pub use keystore2_apc_compat_bindgen::{
+ ApcCompatUiOptions, APC_COMPAT_ERROR_ABORTED, APC_COMPAT_ERROR_CANCELLED,
+ APC_COMPAT_ERROR_IGNORED, APC_COMPAT_ERROR_OK, APC_COMPAT_ERROR_OPERATION_PENDING,
+ APC_COMPAT_ERROR_SYSTEM_ERROR, INVALID_SERVICE_HANDLE,
+};
+use std::{ffi::CString, slice};
+
+/// Safe wrapper around the ConfirmationUI HIDL spec.
+///
+/// # Example
+/// ```
+/// struct Cb();
+/// impl ApcHalCallback for Cb {
+/// fn result(
+/// &self,
+/// rc: u32,
+/// message: Option<&[u8]>,
+/// token: Option<&[u8]>,
+/// ) {
+/// println!("Callback called with rc: {}, message: {}, token: {}", rc, message, token);
+/// }
+/// };
+///
+/// fn prompt() -> Result<(), u32> {
+/// let hal = ApcHal::try_get_service()?;
+/// hal.prompt_user_confirmation(Box::new(Cb()), "Do you agree?", b"extra data", "en", 0)?;
+/// }
+///
+/// ```
+pub struct ApcHal(ApcCompatServiceHandle);
+
+unsafe impl Send for ApcHal {}
+unsafe impl Sync for ApcHal {}
+
+impl Drop for ApcHal {
+ fn drop(&mut self) {
+ // # Safety:
+ // This ends the life cycle of the contained `ApcCompatServiceHandle` owned by this
+ // `ApcHal` object.
+ //
+ // `ApcHal` objects are only created if a valid handle was acquired so self.0 is
+ // always valid when dropped.
+ unsafe {
+ closeUserConfirmationService(self.0);
+ }
+ }
+}
+
+type Callback = dyn FnOnce(u32, Option<&[u8]>, Option<&[u8]>);
+
+extern "C" fn confirmation_result_callback(
+ handle: *mut ::std::os::raw::c_void,
+ rc: u32,
+ tbs_message: *const u8,
+ tbs_message_size: size_t,
+ confirmation_token: *const u8,
+ confirmation_token_size: size_t,
+) {
+ // # Safety:
+ // The C/C++ implementation must pass to us the handle that was created
+ // and assigned to the `ApcCompatCallback::data` field in
+ // `ApcHal::prompt_user_confirmation` below. Also we consume the handle,
+ // by letting `hal_cb` go out of scope with this function call. So
+ // the C/C++ implementation must assure that each `ApcCompatCallback` is only used once.
+ let hal_cb: Box<Box<Callback>> = unsafe { Box::from_raw(handle as *mut Box<Callback>) };
+ let tbs_message = match (tbs_message.is_null(), tbs_message_size) {
+ (true, _) | (_, 0) => None,
+ (false, s) => Some(
+ // # Safety:
+ // If the pointer and size is not nullptr and not 0 respectively, the C/C++
+ // implementation must pass a valid pointer to an allocation of at least size bytes,
+ // and the pointer must be valid until this function returns.
+ unsafe { slice::from_raw_parts(tbs_message, s as usize) },
+ ),
+ };
+ let confirmation_token = match (confirmation_token.is_null(), confirmation_token_size) {
+ (true, _) | (_, 0) => None,
+ (false, s) => Some(
+ // # Safety:
+ // If the pointer and size is not nullptr and not 0 respectively, the C/C++
+ // implementation must pass a valid pointer to an allocation of at least size bytes,
+ // and the pointer must be valid until this function returns.
+ unsafe { slice::from_raw_parts(confirmation_token, s as usize) },
+ ),
+ };
+ hal_cb(rc, tbs_message, confirmation_token)
+}
+
+impl ApcHal {
+ /// Attempts to connect to the APC (confirmationui) backend. On success, it returns an
+ /// initialized `ApcHal` object.
+ pub fn try_get_service() -> Option<Self> {
+ // # Safety:
+ // `tryGetUserConfirmationService` returns a valid handle or INVALID_SERVICE_HANDLE.
+ // On success, `ApcHal` takes ownership of this handle and frees it with
+ // `closeUserConfirmationService` when dropped.
+ let handle = unsafe { tryGetUserConfirmationService() };
+ match handle {
+ h if h == unsafe { INVALID_SERVICE_HANDLE } => None,
+ h => Some(Self(h)),
+ }
+ }
+
+ /// Attempts to start a confirmation prompt. The given callback is consumed, and it is
+ /// guaranteed to be called eventually IFF this function returns `APC_COMPAT_ERROR_OK`.
+ ///
+ /// The callback has the following arguments:
+ /// rc: u32 - The reason for the termination which takes one of the values.
+ /// * `APC_COMPAT_ERROR_OK` - The user confirmed the prompted message.
+ /// * `APC_COMPAT_ERROR_CANCELLED` - The user rejected the prompted message.
+ /// * `APC_COMPAT_ERROR_ABORTED` - The prompt was aborted either because the client
+ /// aborted. the session or an asynchronous system event occurred that ended the
+ /// prompt prematurely.
+ /// * `APC_COMPAT_ERROR_SYSTEMERROR` - An unspecified system error occurred. Logs may
+ /// have more information.
+ ///
+ /// data_confirmed: Option<&[u8]> and
+ /// confirmation_token: Option<&[u8]> hold the confirmed message and the confirmation token
+ /// respectively. They must be `Some()` if `rc == APC_COMPAT_ERROR_OK` and `None` otherwise.
+ pub fn prompt_user_confirmation<F>(
+ &self,
+ prompt_text: &str,
+ extra_data: &[u8],
+ locale: &str,
+ ui_opts: ApcCompatUiOptions,
+ cb: F,
+ ) -> Result<(), u32>
+ where
+ F: FnOnce(u32, Option<&[u8]>, Option<&[u8]>),
+ {
+ let cb_data_ptr = Box::into_raw(Box::new(Box::new(cb)));
+ let cb = ApcCompatCallback {
+ data: cb_data_ptr as *mut std::ffi::c_void,
+ result: Some(confirmation_result_callback),
+ };
+ let prompt_text = CString::new(prompt_text).unwrap();
+ let locale = CString::new(locale).unwrap();
+ // # Safety:
+ // The `ApcCompatCallback` object (`cb`) is passed to the callee by value, and with it
+ // ownership of the `data` field pointer. The data pointer is guaranteed to be valid
+ // until the C/C++ implementation calls the callback. Calling the callback consumes
+ // the data pointer. The C/C++ implementation must not access it after calling the
+ // callback and it must not call the callback a second time.
+ //
+ // The C/C++ must make no assumptions about the life time of the other parameters after
+ // the function returns.
+ let rc = unsafe {
+ promptUserConfirmation(
+ self.0,
+ cb,
+ prompt_text.as_ptr(),
+ extra_data.as_ptr(),
+ extra_data.len() as size_t,
+ locale.as_ptr(),
+ ui_opts,
+ )
+ };
+ match rc {
+ APC_COMPAT_ERROR_OK => Ok(()),
+ rc => {
+ // # Safety:
+ // If promptUserConfirmation does not succeed, it must not take ownership of the
+ // callback, so we must destroy it.
+ unsafe { Box::from_raw(cb_data_ptr) };
+ Err(rc)
+ }
+ }
+ }
+
+ /// Aborts a running confirmation session, or no-op if none is running.
+ pub fn abort(&self) {
+ // # Safety:
+ // It is always safe to call `abortUserConfirmation`, because spurious calls are ignored.
+ // The handle argument must be valid, but this is an invariant of `ApcHal`.
+ unsafe { abortUserConfirmation(self.0) }
+ }
+}