blob: aa2ab1285b253e5ea090fe84757af3d3cb6b743d [file] [log] [blame]
/*
*
* Copyright 2021, 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 "guest_session.h"
#include <future>
namespace android {
namespace hardware {
namespace confirmationui {
namespace V1_0 {
namespace implementation {
using TeeuiRc = teeui::ResponseCode;
GuestSession::ResultTriple GuestSession::PromptUserConfirmation() {
std::unique_lock<std::mutex> stateLock(listener_state_lock_);
/*
* This is the main listener thread function. The listener thread life cycle
* is equivalent to the life cycle of a single confirmation request. The life
* cycle is divided in four phases.
* * The starting phase:
* * Drives the cuttlefish confirmation UI session on the host side, too
*
* Note: During the starting phase the hwbinder service thread is blocked and
* waiting for possible Errors. If the setup phase concludes successfully, the
* hwbinder service thread gets unblocked and returns successfully. Errors
* that occur after the first phase are delivered by callback interface.
*
* For cuttlefish, it means that the guest will conduct a blocking wait for
* an ack to kStart.
*
* * The 2nd phase - non interactive phase
* * After a grace period:
* * guest will pick up cuttlefish host's ack to kStart
*
* * The 3rd phase - interactive phase
* * We wait to any external event
* * Abort
* * Secure user input asserted
* * The result is fetched from the TA.
*
* * The 4th phase - cleanup
* * Sending the kStop command to the cuttlefish host, and wait for ack
*/
GuestSession::ResultTriple error;
auto& error_rc = std::get<ResponseCode>(error);
error_rc = ResponseCode::SystemError;
CHECK(listener_state_ == ListenerState::Starting) << "ListenerState should be Starting";
// initiate prompt
ConfUiLog(INFO) << "Initiating prompt";
const std::uint32_t payload_lower_bound =
static_cast<std::uint32_t>(prompt_text_.size() + extra_data_.size());
const std::uint32_t upper_bound =
static_cast<std::uint32_t>(cuttlefish::confui::kMaxMessageLength);
if (payload_lower_bound > upper_bound) {
ConfUiLog(INFO) << "UI message too long to send to the host";
// message is too long anyway, and don't send it to the host
error_rc = ResponseCode::UIErrorMessageTooLong;
return error;
}
SerializedSend(cuttlefish::confui::SendStartCmd, host_fd_, session_name_, prompt_text_,
extra_data_, locale_, ui_options_);
ConfUiLog(INFO) << "Session " << GetSessionId() << " started on both the guest and the host";
auto clean_up_and_get_first = [&]() -> std::unique_ptr<ConfUiMessage> {
// blocking wait to get the first msg that belongs to this session
while (true) {
auto first_curr_session_msg = incoming_msg_queue_.Pop();
if (!first_curr_session_msg ||
first_curr_session_msg->GetSessionId() != GetSessionId()) {
continue;
}
return std::move(first_curr_session_msg);
}
};
/*
* Unconditionally wait ack, or host abort
*
* First couple of messages could be from the previous session.
* We should clear them up.
*
* Even though the guest HAL sends kAbort to the host, the kAbort
* does not happen immediately. Between the incoming_msg_queue_.FlushAll()
* and the actual abort on the host, there could still be messages
* sent from the host to the guest. As these lines are the first read
* for the current session, we clear up the preceding messages
* from the previous session until we see the message for the current
* session.
*
* Note that abort() call puts the Abort command in the queue. So,
* it will also show up in incoming_msg_queue_
*
*/
auto first_msg = std::move(clean_up_and_get_first());
cuttlefish::confui::ConfUiAckMessage& start_ack_msg =
static_cast<cuttlefish::confui::ConfUiAckMessage&>(*first_msg);
if (!start_ack_msg.IsSuccess()) {
// handle errors: MALFORMED_UTF8 or Message too long
const std::string error_msg = start_ack_msg.GetStatusMessage();
if (error_msg == cuttlefish::confui::HostError::kMessageTooLongError) {
ConfUiLog(ERROR) << "Message + Extra data + Meta info were too long";
error_rc = ResponseCode::UIErrorMessageTooLong;
}
if (error_msg == cuttlefish::confui::HostError::kIncorrectUTF8) {
ConfUiLog(ERROR) << "Message is incorrectly UTF-encoded";
error_rc = ResponseCode::UIErrorMalformedUTF8Encoding;
}
return error;
}
// ############################## Start 2nd Phase #############################################
listener_state_ = ListenerState::SetupDone;
ConfUiLog(INFO) << "Transition to SetupDone";
stateLock.unlock();
listener_state_condv_.notify_all();
// cuttlefish does not need the second phase to implement HAL APIs
// input was already prepared before the confirmation UI screen was rendered
// ############################## Start 3rd Phase - interactive phase #########################
stateLock.lock();
listener_state_ = ListenerState::Interactive;
ConfUiLog(INFO) << "Transition to Interactive";
stateLock.unlock();
listener_state_condv_.notify_all();
// give deliverSecureInputEvent a chance to interrupt
// wait for an input but should not block deliverSecureInputEvent or Abort
// Thus, it should not hold the stateLock
std::mutex input_ready_mtx;
std::condition_variable input_ready_cv_;
std::unique_lock<std::mutex> input_ready_lock(input_ready_mtx);
bool input_ready = false;
auto wait_input_and_signal = [&]() -> std::unique_ptr<ConfUiMessage> {
auto msg = incoming_msg_queue_.Pop();
{
std::unique_lock<std::mutex> lock(input_ready_mtx);
input_ready = true;
input_ready_cv_.notify_one();
}
return msg;
};
auto input_and_signal_future = std::async(std::launch::async, wait_input_and_signal);
input_ready_cv_.wait(input_ready_lock, [&]() { return input_ready; });
// now an input is ready, so let's acquire the stateLock
stateLock.lock();
auto user_or_abort = input_and_signal_future.get();
if (user_or_abort->GetType() == cuttlefish::confui::ConfUiCmd::kAbort) {
ConfUiLog(ERROR) << "Abort called or the user/host aborted"
<< " while waiting user response";
return {ResponseCode::Aborted, {}, {}};
}
if (user_or_abort->GetType() == cuttlefish::confui::ConfUiCmd::kCliAck) {
auto& ack_msg = static_cast<cuttlefish::confui::ConfUiAckMessage&>(*user_or_abort);
if (ack_msg.IsSuccess()) {
ConfUiLog(ERROR) << "When host failed, it is supposed to send "
<< "kCliAck with fail, but this is kCliAck with success";
}
error_rc = ResponseCode::SystemError;
return error;
}
cuttlefish::confui::ConfUiCliResponseMessage& user_response =
static_cast<cuttlefish::confui::ConfUiCliResponseMessage&>(*user_or_abort);
// pick, see if it is response, abort cmd
// handle abort or error response here
ConfUiLog(INFO) << "Making up the result";
// make up the result triple
if (user_response.GetResponse() == cuttlefish::confui::UserResponse::kCancel) {
SerializedSend(cuttlefish::confui::SendStopCmd, host_fd_, GetSessionId());
return {ResponseCode::Canceled, {}, {}};
}
if (user_response.GetResponse() != cuttlefish::confui::UserResponse::kConfirm) {
ConfUiLog(ERROR) << "Unexpected user response that is " << user_response.GetResponse();
return error;
}
SerializedSend(cuttlefish::confui::SendStopCmd, host_fd_, GetSessionId());
// ############################## Start 4th Phase - cleanup ##################################
return {ResponseCode::OK, user_response.GetMessage(), user_response.GetSign()};
}
Return<ResponseCode> GuestSession::DeliverSecureInputEvent(
const android::hardware::keymaster::V4_0::HardwareAuthToken& auth_token) {
ResponseCode rc = ResponseCode::Ignored;
{
/*
* deliverSecureInputEvent is only used by the VTS test to mock human input. A correct
* implementation responds with a mock confirmation token signed with a test key. The
* problem is that the non interactive grace period was not formalized in the HAL spec,
* so that the VTS test does not account for the grace period. (It probably should.)
* This means we can only pass the VTS test if we block until the grace period is over
* (SetupDone -> Interactive) before we deliver the input event.
*
* The true secure input is delivered by a different mechanism and gets ignored -
* not queued - until the grace period is over.
*
*/
std::unique_lock<std::mutex> stateLock(listener_state_lock_);
listener_state_condv_.wait(stateLock,
[this] { return listener_state_ != ListenerState::SetupDone; });
if (listener_state_ != ListenerState::Interactive) return ResponseCode::Ignored;
if (static_cast<TestModeCommands>(auth_token.challenge) == TestModeCommands::OK_EVENT) {
SerializedSend(cuttlefish::confui::SendUserSelection, host_fd_, GetSessionId(),
cuttlefish::confui::UserResponse::kConfirm);
} else {
SerializedSend(cuttlefish::confui::SendUserSelection, host_fd_, GetSessionId(),
cuttlefish::confui::UserResponse::kCancel);
}
rc = ResponseCode::OK;
}
listener_state_condv_.notify_all();
// VTS test expect an OK response if the event was successfully delivered.
// But since the TA returns the callback response now, we have to translate
// Canceled into OK. Canceled is only returned if the delivered event canceled
// the operation, which means that the event was successfully delivered. Thus
// we return OK.
if (rc == ResponseCode::Canceled) return ResponseCode::OK;
return rc;
}
Return<void> GuestSession::Abort() {
{
std::unique_lock<std::mutex> stateLock(listener_state_lock_);
if (listener_state_ == ListenerState::SetupDone ||
listener_state_ == ListenerState::Interactive) {
if (host_fd_->IsOpen()) {
SerializedSend(cuttlefish::confui::SendAbortCmd, host_fd_, GetSessionId());
}
using cuttlefish::confui::ConfUiAbortMessage;
auto local_abort_cmd = std::make_unique<ConfUiAbortMessage>(GetSessionId());
incoming_msg_queue_.Push(std::move(local_abort_cmd));
}
}
listener_state_condv_.notify_all();
return Void();
}
} // namespace implementation
} // namespace V1_0
} // namespace confirmationui
} // namespace hardware
} // namespace android