summaryrefslogtreecommitdiff
path: root/keystore2/apc_compat/apc_compat.rs
blob: 57f8710e9f57596884a45760165e9cec4b961764 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// 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.
    ///
    /// `cb` does not get called if this function returns an error.
    /// (Thus the allow(unused_must_use))
    #[allow(unused_must_use)]
    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]>) + 'static,
    {
        let cb_data_ptr = Box::into_raw(Box::new(Box::new(cb) as Box<Callback>));
        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) }
    }
}