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