1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "host/libs/confui/session.h"
18
19 #include <algorithm>
20
21 #include "common/libs/utils/contains.h"
22 #include "host/libs/confui/secure_input.h"
23
24 namespace cuttlefish {
25 namespace confui {
26
Session(const std::string & session_name,const std::uint32_t display_num,ConfUiRenderer & host_renderer,HostModeCtrl & host_mode_ctrl,const std::string & locale)27 Session::Session(const std::string& session_name,
28 const std::uint32_t display_num, ConfUiRenderer& host_renderer,
29 HostModeCtrl& host_mode_ctrl, const std::string& locale)
30 : session_id_{session_name},
31 display_num_{display_num},
32 renderer_{host_renderer},
33 host_mode_ctrl_{host_mode_ctrl},
34 locale_{locale},
35 state_{MainLoopState::kInit},
36 saved_state_{MainLoopState::kInit} {}
37
38 /** return grace period + alpha
39 *
40 * grace period is the gap between user seeing the dialog
41 * and the UI starts to take the user inputs
42 * Grace period should be at least 1s.
43 * Session requests the Renderer to render the dialog,
44 * but it might not be immediate. So, add alpha to 1s
45 */
GetGracePeriod()46 static const std::chrono::milliseconds GetGracePeriod() {
47 using std::literals::chrono_literals::operator""ms;
48 return 1000ms + 100ms;
49 }
50
IsReadyForUserInput() const51 bool Session::IsReadyForUserInput() const {
52 using std::literals::chrono_literals::operator""ms;
53 if (!start_time_) {
54 return false;
55 }
56 const auto right_now = Clock::now();
57 return (right_now - *start_time_) >= GetGracePeriod();
58 }
59
RenderDialog()60 bool Session::RenderDialog() {
61 auto result =
62 renderer_.RenderDialog(display_num_, prompt_text_, locale_, ui_options_);
63 if (!result.ok()) {
64 LOG(ERROR) << result.error().FormatForEnv();
65 return false;
66 }
67 return true;
68 }
69
Transition(SharedFD & hal_cli,const FsmInput fsm_input,const ConfUiMessage & conf_ui_message)70 MainLoopState Session::Transition(SharedFD& hal_cli, const FsmInput fsm_input,
71 const ConfUiMessage& conf_ui_message) {
72 bool should_keep_running = false;
73 bool already_terminated = false;
74 switch (state_) {
75 case MainLoopState::kInit: {
76 should_keep_running = HandleInit(hal_cli, fsm_input, conf_ui_message);
77 } break;
78 case MainLoopState::kInSession: {
79 should_keep_running =
80 HandleInSession(hal_cli, fsm_input, conf_ui_message);
81 } break;
82 case MainLoopState::kWaitStop: {
83 if (IsUserInput(fsm_input)) {
84 ConfUiLog(VERBOSE) << "User input ignored " << ToString(fsm_input)
85 << " : " << ToString(conf_ui_message)
86 << " at the state " << ToString(state_);
87 }
88 should_keep_running = HandleWaitStop(hal_cli, fsm_input);
89 } break;
90 case MainLoopState::kTerminated: {
91 already_terminated = true;
92 } break;
93 default:
94 ConfUiLog(FATAL) << "Must not be in the state of " << ToString(state_);
95 break;
96 }
97 if (!should_keep_running && !already_terminated) {
98 ScheduleToTerminate();
99 }
100 return state_;
101 };
102
CleanUp()103 void Session::CleanUp() {
104 if (state_ != MainLoopState::kAwaitCleanup) {
105 ConfUiLog(FATAL) << "Clean up a session only when in kAwaitCleanup";
106 }
107 state_ = MainLoopState::kTerminated;
108 // common action done when the state is back to init state
109 host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kAndroidMode);
110 }
111
ScheduleToTerminate()112 void Session::ScheduleToTerminate() {
113 state_ = MainLoopState::kAwaitCleanup;
114 saved_state_ = MainLoopState::kInvalid;
115 }
116
ReportErrorToHal(SharedFD hal_cli,const std::string & msg)117 bool Session::ReportErrorToHal(SharedFD hal_cli, const std::string& msg) {
118 ScheduleToTerminate();
119 if (!SendAck(hal_cli, session_id_, false, msg)) {
120 ConfUiLog(ERROR) << "I/O error in sending ack to report rendering failure";
121 return false;
122 }
123 return true;
124 }
125
Abort()126 void Session::Abort() {
127 ConfUiLog(VERBOSE) << "Abort is called";
128 ScheduleToTerminate();
129 return;
130 }
131
UserAbort(SharedFD hal_cli)132 void Session::UserAbort(SharedFD hal_cli) {
133 ConfUiLog(VERBOSE) << "it is a user abort input.";
134 SendAbortCmd(hal_cli, GetId());
135 Abort();
136 ScheduleToTerminate();
137 }
138
HandleInit(SharedFD hal_cli,const FsmInput fsm_input,const ConfUiMessage & conf_ui_message)139 bool Session::HandleInit(SharedFD hal_cli, const FsmInput fsm_input,
140 const ConfUiMessage& conf_ui_message) {
141 if (IsUserInput(fsm_input)) {
142 // ignore user input
143 state_ = MainLoopState::kInit;
144 return true;
145 }
146
147 ConfUiLog(VERBOSE) << ToString(fsm_input) << "is handled in HandleInit";
148 if (fsm_input != FsmInput::kHalStart) {
149 ConfUiLog(ERROR) << "invalid cmd for Init State:" << ToString(fsm_input);
150 // ReportErrorToHal returns true if error report was successful
151 // However, anyway we abort this session on the host
152 ReportErrorToHal(hal_cli, HostError::kSystemError);
153 return false;
154 }
155
156 // Start Session
157 ConfUiLog(VERBOSE) << "Sending ack to hal_cli: "
158 << Enum2Base(ConfUiCmd::kCliAck);
159 host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kConfUI_Mode);
160
161 auto start_cmd_msg = static_cast<const ConfUiStartMessage&>(conf_ui_message);
162 prompt_text_ = start_cmd_msg.GetPromptText();
163 locale_ = start_cmd_msg.GetLocale();
164 extra_data_ = start_cmd_msg.GetExtraData();
165 ui_options_ = start_cmd_msg.GetUiOpts();
166
167 // cbor_ can be correctly created after the session received kStart cmd
168 // at runtime
169 cbor_ = std::make_unique<Cbor>(prompt_text_, extra_data_);
170 if (cbor_->IsMessageTooLong()) {
171 ConfUiLog(ERROR) << "The prompt text and extra_data are too long to be "
172 << "properly encoded.";
173 ReportErrorToHal(hal_cli, HostError::kMessageTooLongError);
174 return false;
175 }
176 if (cbor_->IsMalformedUtf8()) {
177 ConfUiLog(ERROR) << "The prompt text appears to have incorrect UTF8 format";
178 ReportErrorToHal(hal_cli, HostError::kIncorrectUTF8);
179 return false;
180 }
181 if (!cbor_->IsOk()) {
182 ConfUiLog(ERROR) << "Unknown Error in cbor implementation";
183 ReportErrorToHal(hal_cli, HostError::kSystemError);
184 return false;
185 }
186
187 if (!RenderDialog()) {
188 // the confirmation UI is driven by a user app, not running from the start
189 // automatically so that means webRTC should have been set up
190 ConfUiLog(ERROR) << "Dialog is not rendered. However, it should."
191 << "No webRTC can't initiate any confirmation UI.";
192 ReportErrorToHal(hal_cli, HostError::kUIError);
193 return false;
194 }
195 start_time_ = std::make_unique<TimePoint>(Clock::now());
196 if (!SendAck(hal_cli, session_id_, true, "started")) {
197 ConfUiLog(ERROR) << "Ack to kStart failed in I/O";
198 return false;
199 }
200 state_ = MainLoopState::kInSession;
201 return true;
202 }
203
HandleInSession(SharedFD hal_cli,const FsmInput fsm_input,const ConfUiMessage & conf_ui_msg)204 bool Session::HandleInSession(SharedFD hal_cli, const FsmInput fsm_input,
205 const ConfUiMessage& conf_ui_msg) {
206 auto invalid_input_handler = [&, this]() {
207 ReportErrorToHal(hal_cli, HostError::kSystemError);
208 ConfUiLog(ERROR) << "cmd " << ToString(fsm_input)
209 << " should not be handled in HandleInSession";
210 };
211
212 if (!IsUserInput(fsm_input)) {
213 invalid_input_handler();
214 return false;
215 }
216
217 const auto& user_input_msg =
218 static_cast<const ConfUiSecureUserSelectionMessage&>(conf_ui_msg);
219 const auto response = user_input_msg.GetResponse();
220 if (response == UserResponse::kUnknown ||
221 response == UserResponse::kUserAbort) {
222 invalid_input_handler();
223 return false;
224 }
225 const bool is_secure_input = user_input_msg.IsSecure();
226
227 ConfUiLog(VERBOSE) << "In HandleInSession, session " << session_id_
228 << " is sending the user input " << ToString(fsm_input);
229
230 bool is_success = false;
231 if (response == UserResponse::kCancel) {
232 // no need to sign
233 is_success =
234 SendResponse(hal_cli, session_id_, UserResponse::kCancel,
235 std::vector<std::uint8_t>{}, std::vector<std::uint8_t>{});
236 } else {
237 message_ = std::move(cbor_->GetMessage());
238 auto message_opt = (is_secure_input ? Sign(message_) : TestSign(message_));
239 if (!message_opt) {
240 ReportErrorToHal(hal_cli, HostError::kSystemError);
241 return false;
242 }
243 signed_confirmation_ = message_opt.value();
244 is_success = SendResponse(hal_cli, session_id_, UserResponse::kConfirm,
245 signed_confirmation_, message_);
246 }
247
248 if (!is_success) {
249 ConfUiLog(ERROR) << "I/O error in sending user response to HAL";
250 return false;
251 }
252 state_ = MainLoopState::kWaitStop;
253 return true;
254 }
255
HandleWaitStop(SharedFD hal_cli,const FsmInput fsm_input)256 bool Session::HandleWaitStop(SharedFD hal_cli, const FsmInput fsm_input) {
257 if (IsUserInput(fsm_input)) {
258 // ignore user input
259 state_ = MainLoopState::kWaitStop;
260 return true;
261 }
262 if (fsm_input == FsmInput::kHalStop) {
263 ConfUiLog(VERBOSE) << "Handling Abort in kWaitStop.";
264 ScheduleToTerminate();
265 return true;
266 }
267 ReportErrorToHal(hal_cli, HostError::kSystemError);
268 ConfUiLog(FATAL) << "In WaitStop, received wrong HAL command "
269 << ToString(fsm_input);
270 return false;
271 }
272
273 } // end of namespace confui
274 } // end of namespace cuttlefish
275