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 "host/libs/confui/secure_input.h"
22
23 namespace cuttlefish {
24 namespace confui {
25
Session(const std::string & session_name,const std::uint32_t display_num,HostModeCtrl & host_mode_ctrl,ScreenConnectorFrameRenderer & screen_connector,const std::string & locale)26 Session::Session(const std::string& session_name,
27 const std::uint32_t display_num, HostModeCtrl& host_mode_ctrl,
28 ScreenConnectorFrameRenderer& screen_connector,
29 const std::string& locale)
30 : session_id_{session_name},
31 display_num_{display_num},
32 host_mode_ctrl_{host_mode_ctrl},
33 screen_connector_{screen_connector},
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
IsConfUiActive() const60 bool Session::IsConfUiActive() const {
61 if (state_ == MainLoopState::kInSession ||
62 state_ == MainLoopState::kWaitStop) {
63 return true;
64 }
65 return false;
66 }
67
68 template <typename C, typename T>
Contains(const C & c,T && item)69 static bool Contains(const C& c, T&& item) {
70 auto itr = std::find(c.begin(), c.end(), std::forward<T>(item));
71 return itr != c.end();
72 }
73
IsInverted() const74 bool Session::IsInverted() const {
75 return Contains(ui_options_, teeui::UIOption::AccessibilityInverted);
76 }
77
IsMagnified() const78 bool Session::IsMagnified() const {
79 return Contains(ui_options_, teeui::UIOption::AccessibilityMagnified);
80 }
81
RenderDialog()82 bool Session::RenderDialog() {
83 renderer_ = ConfUiRenderer::GenerateRenderer(
84 display_num_, prompt_text_, locale_, IsInverted(), IsMagnified());
85 if (!renderer_) {
86 return false;
87 }
88 auto teeui_frame = renderer_->RenderRawFrame();
89 if (!teeui_frame) {
90 return false;
91 }
92 ConfUiLog(VERBOSE) << "actually trying to render the frame"
93 << thread::GetName();
94 auto frame_width = teeui_frame->Width();
95 auto frame_height = teeui_frame->Height();
96 auto frame_stride_bytes = teeui_frame->ScreenStrideBytes();
97 auto frame_bytes = reinterpret_cast<std::uint8_t*>(teeui_frame->data());
98 return screen_connector_.RenderConfirmationUi(
99 display_num_, frame_width, frame_height, frame_stride_bytes, frame_bytes);
100 }
101
Transition(SharedFD & hal_cli,const FsmInput fsm_input,const ConfUiMessage & conf_ui_message)102 MainLoopState Session::Transition(SharedFD& hal_cli, const FsmInput fsm_input,
103 const ConfUiMessage& conf_ui_message) {
104 bool should_keep_running = false;
105 bool already_terminated = false;
106 switch (state_) {
107 case MainLoopState::kInit: {
108 should_keep_running = HandleInit(hal_cli, fsm_input, conf_ui_message);
109 } break;
110 case MainLoopState::kInSession: {
111 should_keep_running =
112 HandleInSession(hal_cli, fsm_input, conf_ui_message);
113 } break;
114 case MainLoopState::kWaitStop: {
115 if (IsUserInput(fsm_input)) {
116 ConfUiLog(VERBOSE) << "User input ignored " << ToString(fsm_input)
117 << " : " << ToString(conf_ui_message)
118 << " at the state " << ToString(state_);
119 }
120 should_keep_running = HandleWaitStop(hal_cli, fsm_input);
121 } break;
122 case MainLoopState::kTerminated: {
123 already_terminated = true;
124 } break;
125 default:
126 ConfUiLog(FATAL) << "Must not be in the state of " << ToString(state_);
127 break;
128 }
129 if (!should_keep_running && !already_terminated) {
130 ScheduleToTerminate();
131 }
132 return state_;
133 };
134
CleanUp()135 void Session::CleanUp() {
136 if (state_ != MainLoopState::kAwaitCleanup) {
137 ConfUiLog(FATAL) << "Clean up a session only when in kAwaitCleanup";
138 }
139 state_ = MainLoopState::kTerminated;
140 // common action done when the state is back to init state
141 host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kAndroidMode);
142 }
143
ScheduleToTerminate()144 void Session::ScheduleToTerminate() {
145 state_ = MainLoopState::kAwaitCleanup;
146 saved_state_ = MainLoopState::kInvalid;
147 }
148
ReportErrorToHal(SharedFD hal_cli,const std::string & msg)149 bool Session::ReportErrorToHal(SharedFD hal_cli, const std::string& msg) {
150 ScheduleToTerminate();
151 if (!SendAck(hal_cli, session_id_, false, msg)) {
152 ConfUiLog(ERROR) << "I/O error in sending ack to report rendering failure";
153 return false;
154 }
155 return true;
156 }
157
Abort()158 void Session::Abort() {
159 ConfUiLog(VERBOSE) << "Abort is called";
160 ScheduleToTerminate();
161 return;
162 }
163
UserAbort(SharedFD hal_cli)164 void Session::UserAbort(SharedFD hal_cli) {
165 ConfUiLog(VERBOSE) << "it is a user abort input.";
166 SendAbortCmd(hal_cli, GetId());
167 Abort();
168 ScheduleToTerminate();
169 }
170
HandleInit(SharedFD hal_cli,const FsmInput fsm_input,const ConfUiMessage & conf_ui_message)171 bool Session::HandleInit(SharedFD hal_cli, const FsmInput fsm_input,
172 const ConfUiMessage& conf_ui_message) {
173 if (IsUserInput(fsm_input)) {
174 // ignore user input
175 state_ = MainLoopState::kInit;
176 return true;
177 }
178
179 ConfUiLog(VERBOSE) << ToString(fsm_input) << "is handled in HandleInit";
180 if (fsm_input != FsmInput::kHalStart) {
181 ConfUiLog(ERROR) << "invalid cmd for Init State:" << ToString(fsm_input);
182 // ReportErrorToHal returns true if error report was successful
183 // However, anyway we abort this session on the host
184 ReportErrorToHal(hal_cli, HostError::kSystemError);
185 return false;
186 }
187
188 // Start Session
189 ConfUiLog(VERBOSE) << "Sending ack to hal_cli: "
190 << Enum2Base(ConfUiCmd::kCliAck);
191 host_mode_ctrl_.SetMode(HostModeCtrl::ModeType::kConfUI_Mode);
192
193 auto start_cmd_msg = static_cast<const ConfUiStartMessage&>(conf_ui_message);
194 prompt_text_ = start_cmd_msg.GetPromptText();
195 locale_ = start_cmd_msg.GetLocale();
196 extra_data_ = start_cmd_msg.GetExtraData();
197 ui_options_ = start_cmd_msg.GetUiOpts();
198
199 // cbor_ can be correctly created after the session received kStart cmd
200 // at runtime
201 cbor_ = std::make_unique<Cbor>(prompt_text_, extra_data_);
202 if (cbor_->IsMessageTooLong()) {
203 ConfUiLog(ERROR) << "The prompt text and extra_data are too long to be "
204 << "properly encoded.";
205 ReportErrorToHal(hal_cli, HostError::kMessageTooLongError);
206 return false;
207 }
208 if (cbor_->IsMalformedUtf8()) {
209 ConfUiLog(ERROR) << "The prompt text appears to have incorrect UTF8 format";
210 ReportErrorToHal(hal_cli, HostError::kIncorrectUTF8);
211 return false;
212 }
213 if (!cbor_->IsOk()) {
214 ConfUiLog(ERROR) << "Unknown Error in cbor implementation";
215 ReportErrorToHal(hal_cli, HostError::kSystemError);
216 return false;
217 }
218
219 if (!RenderDialog()) {
220 // the confirmation UI is driven by a user app, not running from the start
221 // automatically so that means webRTC should have been set up
222 ConfUiLog(ERROR) << "Dialog is not rendered. However, it should."
223 << "No webRTC can't initiate any confirmation UI.";
224 ReportErrorToHal(hal_cli, HostError::kUIError);
225 return false;
226 }
227 start_time_ = std::make_unique<TimePoint>(std::move(Clock::now()));
228 if (!SendAck(hal_cli, session_id_, true, "started")) {
229 ConfUiLog(ERROR) << "Ack to kStart failed in I/O";
230 return false;
231 }
232 state_ = MainLoopState::kInSession;
233 return true;
234 }
235
HandleInSession(SharedFD hal_cli,const FsmInput fsm_input,const ConfUiMessage & conf_ui_msg)236 bool Session::HandleInSession(SharedFD hal_cli, const FsmInput fsm_input,
237 const ConfUiMessage& conf_ui_msg) {
238 auto invalid_input_handler = [&, this]() {
239 ReportErrorToHal(hal_cli, HostError::kSystemError);
240 ConfUiLog(ERROR) << "cmd " << ToString(fsm_input)
241 << " should not be handled in HandleInSession";
242 };
243
244 if (!IsUserInput(fsm_input)) {
245 invalid_input_handler();
246 return false;
247 }
248
249 const auto& user_input_msg =
250 static_cast<const ConfUiSecureUserSelectionMessage&>(conf_ui_msg);
251 const auto response = user_input_msg.GetResponse();
252 if (response == UserResponse::kUnknown ||
253 response == UserResponse::kUserAbort) {
254 invalid_input_handler();
255 return false;
256 }
257 const bool is_secure_input = user_input_msg.IsSecure();
258
259 ConfUiLog(VERBOSE) << "In HandleInSession, session " << session_id_
260 << " is sending the user input " << ToString(fsm_input);
261
262 bool is_success = false;
263 if (response == UserResponse::kCancel) {
264 // no need to sign
265 is_success =
266 SendResponse(hal_cli, session_id_, UserResponse::kCancel,
267 std::vector<std::uint8_t>{}, std::vector<std::uint8_t>{});
268 } else {
269 message_ = std::move(cbor_->GetMessage());
270 auto message_opt = (is_secure_input ? Sign(message_) : TestSign(message_));
271 if (!message_opt) {
272 ReportErrorToHal(hal_cli, HostError::kSystemError);
273 return false;
274 }
275 signed_confirmation_ = message_opt.value();
276 is_success = SendResponse(hal_cli, session_id_, UserResponse::kConfirm,
277 signed_confirmation_, message_);
278 }
279
280 if (!is_success) {
281 ConfUiLog(ERROR) << "I/O error in sending user response to HAL";
282 return false;
283 }
284 state_ = MainLoopState::kWaitStop;
285 return true;
286 }
287
HandleWaitStop(SharedFD hal_cli,const FsmInput fsm_input)288 bool Session::HandleWaitStop(SharedFD hal_cli, const FsmInput fsm_input) {
289 if (IsUserInput(fsm_input)) {
290 // ignore user input
291 state_ = MainLoopState::kWaitStop;
292 return true;
293 }
294 if (fsm_input == FsmInput::kHalStop) {
295 ConfUiLog(VERBOSE) << "Handling Abort in kWaitStop.";
296 ScheduleToTerminate();
297 return true;
298 }
299 ReportErrorToHal(hal_cli, HostError::kSystemError);
300 ConfUiLog(FATAL) << "In WaitStop, received wrong HAL command "
301 << ToString(fsm_input);
302 return false;
303 }
304
305 } // end of namespace confui
306 } // end of namespace cuttlefish
307