| /* |
| * Copyright (C) 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 "host/libs/confui/session.h" |
| |
| #include <algorithm> |
| |
| #include "common/libs/utils/contains.h" |
| #include "host/libs/confui/secure_input.h" |
| |
| namespace cuttlefish { |
| namespace confui { |
| |
| Session::Session(const std::string& session_name, |
| const std::uint32_t display_num, ConfUiRenderer& host_renderer, |
| HostModeCtrl& host_mode_ctrl, const std::string& locale) |
| : session_id_{session_name}, |
| display_num_{display_num}, |
| renderer_{host_renderer}, |
| host_mode_ctrl_{host_mode_ctrl}, |
| locale_{locale}, |
| state_{MainLoopState::kInit}, |
| saved_state_{MainLoopState::kInit} {} |
| |
| /** return grace period + alpha |
| * |
| * grace period is the gap between user seeing the dialog |
| * and the UI starts to take the user inputs |
| * Grace period should be at least 1s. |
| * Session requests the Renderer to render the dialog, |
| * but it might not be immediate. So, add alpha to 1s |
| */ |
| static const std::chrono::milliseconds GetGracePeriod() { |
| using std::literals::chrono_literals::operator""ms; |
| return 1000ms + 100ms; |
| } |
| |
| bool Session::IsReadyForUserInput() const { |
| using std::literals::chrono_literals::operator""ms; |
| if (!start_time_) { |
| return false; |
| } |
| const auto right_now = Clock::now(); |
| return (right_now - *start_time_) >= GetGracePeriod(); |
| } |
| |
| bool Session::RenderDialog() { |
| auto result = |
| renderer_.RenderDialog(display_num_, prompt_text_, locale_, ui_options_); |
| if (!result.ok()) { |
| LOG(ERROR) << result.error().FormatForEnv(); |
| return false; |
| } |
| return true; |
| } |
| |
| MainLoopState Session::Transition(SharedFD& hal_cli, const FsmInput fsm_input, |
| const ConfUiMessage& conf_ui_message) { |
| bool should_keep_running = false; |
| bool already_terminated = false; |
| switch (state_) { |
| case MainLoopState::kInit: { |
| should_keep_running = HandleInit(hal_cli, fsm_input, conf_ui_message); |
| } break; |
| case MainLoopState::kInSession: { |
| should_keep_running = |
| HandleInSession(hal_cli, fsm_input, conf_ui_message); |
| } break; |
| case MainLoopState::kWaitStop: { |
| if (IsUserInput(fsm_input)) { |
| ConfUiLog(VERBOSE) << "User input ignored " << ToString(fsm_input) |
| << " : " << ToString(conf_ui_message) |
| << " at the state " << ToString(state_); |
| } |
| should_keep_running = HandleWaitStop(hal_cli, fsm_input); |
| } break; |
| case MainLoopState::kTerminated: { |
| already_terminated = true; |
| } break; |
| default: |
| ConfUiLog(FATAL) << "Must not be in the state of " << ToString(state_); |
| break; |
| } |
| if (!should_keep_running && !already_terminated) { |
| ScheduleToTerminate(); |
| } |
| return state_; |
| }; |
| |
| void Session::CleanUp() { |
| if (state_ != MainLoopState::kAwaitCleanup) { |
| ConfUiLog(FATAL) << "Clean up a session only when in kAwaitCleanup"; |
| } |
| state_ = MainLoopState::kTerminated; |
| // common action done when the state is back to init state |
| host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kAndroidMode); |
| } |
| |
| void Session::ScheduleToTerminate() { |
| state_ = MainLoopState::kAwaitCleanup; |
| saved_state_ = MainLoopState::kInvalid; |
| } |
| |
| bool Session::ReportErrorToHal(SharedFD hal_cli, const std::string& msg) { |
| ScheduleToTerminate(); |
| if (!SendAck(hal_cli, session_id_, false, msg)) { |
| ConfUiLog(ERROR) << "I/O error in sending ack to report rendering failure"; |
| return false; |
| } |
| return true; |
| } |
| |
| void Session::Abort() { |
| ConfUiLog(VERBOSE) << "Abort is called"; |
| ScheduleToTerminate(); |
| return; |
| } |
| |
| void Session::UserAbort(SharedFD hal_cli) { |
| ConfUiLog(VERBOSE) << "it is a user abort input."; |
| SendAbortCmd(hal_cli, GetId()); |
| Abort(); |
| ScheduleToTerminate(); |
| } |
| |
| bool Session::HandleInit(SharedFD hal_cli, const FsmInput fsm_input, |
| const ConfUiMessage& conf_ui_message) { |
| if (IsUserInput(fsm_input)) { |
| // ignore user input |
| state_ = MainLoopState::kInit; |
| return true; |
| } |
| |
| ConfUiLog(VERBOSE) << ToString(fsm_input) << "is handled in HandleInit"; |
| if (fsm_input != FsmInput::kHalStart) { |
| ConfUiLog(ERROR) << "invalid cmd for Init State:" << ToString(fsm_input); |
| // ReportErrorToHal returns true if error report was successful |
| // However, anyway we abort this session on the host |
| ReportErrorToHal(hal_cli, HostError::kSystemError); |
| return false; |
| } |
| |
| // Start Session |
| ConfUiLog(VERBOSE) << "Sending ack to hal_cli: " |
| << Enum2Base(ConfUiCmd::kCliAck); |
| host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kConfUI_Mode); |
| |
| auto start_cmd_msg = static_cast<const ConfUiStartMessage&>(conf_ui_message); |
| prompt_text_ = start_cmd_msg.GetPromptText(); |
| locale_ = start_cmd_msg.GetLocale(); |
| extra_data_ = start_cmd_msg.GetExtraData(); |
| ui_options_ = start_cmd_msg.GetUiOpts(); |
| |
| // cbor_ can be correctly created after the session received kStart cmd |
| // at runtime |
| cbor_ = std::make_unique<Cbor>(prompt_text_, extra_data_); |
| if (cbor_->IsMessageTooLong()) { |
| ConfUiLog(ERROR) << "The prompt text and extra_data are too long to be " |
| << "properly encoded."; |
| ReportErrorToHal(hal_cli, HostError::kMessageTooLongError); |
| return false; |
| } |
| if (cbor_->IsMalformedUtf8()) { |
| ConfUiLog(ERROR) << "The prompt text appears to have incorrect UTF8 format"; |
| ReportErrorToHal(hal_cli, HostError::kIncorrectUTF8); |
| return false; |
| } |
| if (!cbor_->IsOk()) { |
| ConfUiLog(ERROR) << "Unknown Error in cbor implementation"; |
| ReportErrorToHal(hal_cli, HostError::kSystemError); |
| return false; |
| } |
| |
| if (!RenderDialog()) { |
| // the confirmation UI is driven by a user app, not running from the start |
| // automatically so that means webRTC should have been set up |
| ConfUiLog(ERROR) << "Dialog is not rendered. However, it should." |
| << "No webRTC can't initiate any confirmation UI."; |
| ReportErrorToHal(hal_cli, HostError::kUIError); |
| return false; |
| } |
| start_time_ = std::make_unique<TimePoint>(std::move(Clock::now())); |
| if (!SendAck(hal_cli, session_id_, true, "started")) { |
| ConfUiLog(ERROR) << "Ack to kStart failed in I/O"; |
| return false; |
| } |
| state_ = MainLoopState::kInSession; |
| return true; |
| } |
| |
| bool Session::HandleInSession(SharedFD hal_cli, const FsmInput fsm_input, |
| const ConfUiMessage& conf_ui_msg) { |
| auto invalid_input_handler = [&, this]() { |
| ReportErrorToHal(hal_cli, HostError::kSystemError); |
| ConfUiLog(ERROR) << "cmd " << ToString(fsm_input) |
| << " should not be handled in HandleInSession"; |
| }; |
| |
| if (!IsUserInput(fsm_input)) { |
| invalid_input_handler(); |
| return false; |
| } |
| |
| const auto& user_input_msg = |
| static_cast<const ConfUiSecureUserSelectionMessage&>(conf_ui_msg); |
| const auto response = user_input_msg.GetResponse(); |
| if (response == UserResponse::kUnknown || |
| response == UserResponse::kUserAbort) { |
| invalid_input_handler(); |
| return false; |
| } |
| const bool is_secure_input = user_input_msg.IsSecure(); |
| |
| ConfUiLog(VERBOSE) << "In HandleInSession, session " << session_id_ |
| << " is sending the user input " << ToString(fsm_input); |
| |
| bool is_success = false; |
| if (response == UserResponse::kCancel) { |
| // no need to sign |
| is_success = |
| SendResponse(hal_cli, session_id_, UserResponse::kCancel, |
| std::vector<std::uint8_t>{}, std::vector<std::uint8_t>{}); |
| } else { |
| message_ = std::move(cbor_->GetMessage()); |
| auto message_opt = (is_secure_input ? Sign(message_) : TestSign(message_)); |
| if (!message_opt) { |
| ReportErrorToHal(hal_cli, HostError::kSystemError); |
| return false; |
| } |
| signed_confirmation_ = message_opt.value(); |
| is_success = SendResponse(hal_cli, session_id_, UserResponse::kConfirm, |
| signed_confirmation_, message_); |
| } |
| |
| if (!is_success) { |
| ConfUiLog(ERROR) << "I/O error in sending user response to HAL"; |
| return false; |
| } |
| state_ = MainLoopState::kWaitStop; |
| return true; |
| } |
| |
| bool Session::HandleWaitStop(SharedFD hal_cli, const FsmInput fsm_input) { |
| if (IsUserInput(fsm_input)) { |
| // ignore user input |
| state_ = MainLoopState::kWaitStop; |
| return true; |
| } |
| if (fsm_input == FsmInput::kHalStop) { |
| ConfUiLog(VERBOSE) << "Handling Abort in kWaitStop."; |
| ScheduleToTerminate(); |
| return true; |
| } |
| ReportErrorToHal(hal_cli, HostError::kSystemError); |
| ConfUiLog(FATAL) << "In WaitStop, received wrong HAL command " |
| << ToString(fsm_input); |
| return false; |
| } |
| |
| } // end of namespace confui |
| } // end of namespace cuttlefish |