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/host_server.h"
18 
19 #include <functional>
20 #include <memory>
21 #include <optional>
22 #include <tuple>
23 
24 #include "common/libs/confui/confui.h"
25 #include "common/libs/fs/shared_buf.h"
26 #include "host/libs/config/cuttlefish_config.h"
27 #include "host/libs/confui/host_utils.h"
28 #include "host/libs/confui/secure_input.h"
29 
30 namespace cuttlefish {
31 namespace confui {
32 namespace {
33 
34 template <typename Derived, typename Base>
DowncastTo(std::unique_ptr<Base> && base)35 std::unique_ptr<Derived> DowncastTo(std::unique_ptr<Base>&& base) {
36   Base* tmp = base.release();
37   Derived* derived = static_cast<Derived*>(tmp);
38   return std::unique_ptr<Derived>(derived);
39 }
40 
41 }  // namespace
42 
43 /**
44  * null if not user/touch, or wrap it and ConfUiSecure{Selection,Touch}Message
45  *
46  * ConfUiMessage must NOT ConfUiSecure{Selection,Touch}Message types
47  */
WrapWithSecureFlag(std::unique_ptr<ConfUiMessage> && base_msg,const bool secure)48 static std::unique_ptr<ConfUiMessage> WrapWithSecureFlag(
49     std::unique_ptr<ConfUiMessage>&& base_msg, const bool secure) {
50   switch (base_msg->GetType()) {
51     case ConfUiCmd::kUserInputEvent: {
52       auto as_selection =
53           DowncastTo<ConfUiUserSelectionMessage>(std::move(base_msg));
54       return ToSecureSelectionMessage(std::move(as_selection), secure);
55     }
56     case ConfUiCmd::kUserTouchEvent: {
57       auto as_touch = DowncastTo<ConfUiUserTouchMessage>(std::move(base_msg));
58       return ToSecureTouchMessage(std::move(as_touch), secure);
59     }
60     default:
61       return nullptr;
62   }
63 }
64 
HostServer(HostModeCtrl & host_mode_ctrl,ConfUiRenderer & host_renderer,const PipeConnectionPair & fd_pair)65 HostServer::HostServer(HostModeCtrl& host_mode_ctrl,
66                        ConfUiRenderer& host_renderer,
67                        const PipeConnectionPair& fd_pair)
68     : display_num_(0),
69       host_renderer_{host_renderer},
70       host_mode_ctrl_(host_mode_ctrl),
71       from_guest_fifo_fd_(fd_pair.from_guest_),
72       to_guest_fifo_fd_(fd_pair.to_guest_) {
73   const size_t max_elements = 20;
74   auto ignore_new =
__anon61fa840b0202(ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>::QueueImpl*) 75       [](ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>::QueueImpl*) {
76         // no op, so the queue is still full, and the new item will be discarded
77         return;
78       };
79   hal_cmd_q_id_ = input_multiplexer_.RegisterQueue(
80       HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
81   user_input_evt_q_id_ = input_multiplexer_.RegisterQueue(
82       HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
83 }
84 
IsVirtioConsoleOpen() const85 bool HostServer::IsVirtioConsoleOpen() const {
86   return from_guest_fifo_fd_->IsOpen() && to_guest_fifo_fd_->IsOpen();
87 }
88 
CheckVirtioConsole()89 bool HostServer::CheckVirtioConsole() {
90   if (IsVirtioConsoleOpen()) {
91     return true;
92   }
93   ConfUiLog(FATAL) << "Virtio console is not open";
94   return false;
95 }
96 
Start()97 void HostServer::Start() {
98   if (!CheckVirtioConsole()) {
99     return;
100   }
101   auto hal_cmd_fetching = [this]() { this->HalCmdFetcherLoop(); };
102   auto main = [this]() { this->MainLoop(); };
103   hal_input_fetcher_thread_ =
104       thread::RunThread("HalInputLoop", hal_cmd_fetching);
105   main_loop_thread_ = thread::RunThread("MainLoop", main);
106   ConfUiLog(DEBUG) << "host service started.";
107   return;
108 }
109 
HalCmdFetcherLoop()110 void HostServer::HalCmdFetcherLoop() {
111   while (true) {
112     if (!CheckVirtioConsole()) {
113       return;
114     }
115     auto msg = RecvConfUiMsg(from_guest_fifo_fd_);
116     if (!msg) {
117       ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL";
118       // TODO(kwstephenkim): error handling
119       // either file is not open, or ill-formatted message
120       continue;
121     }
122     /*
123      * In case of Vts test, the msg could be a user input. For now, we do not
124      * enforce the input grace period for Vts. However, if ever we do, here is
125      * where the time point check should happen. Once it is enqueued, it is not
126      * always guaranteed to be picked up reasonably soon.
127      */
128     constexpr bool is_secure = false;
129     auto to_override_if_user_input =
130         WrapWithSecureFlag(std::move(msg), is_secure);
131     if (to_override_if_user_input) {
132       msg = std::move(to_override_if_user_input);
133     }
134     input_multiplexer_.Push(hal_cmd_q_id_, std::move(msg));
135   }
136 }
137 
138 /**
139  * Send inputs generated not by auto-tester but by the human users
140  *
141  * Send such inputs into the command queue consumed by the state machine
142  * in the main loop/current session.
143  */
SendUserSelection(std::unique_ptr<ConfUiMessage> & input)144 void HostServer::SendUserSelection(std::unique_ptr<ConfUiMessage>& input) {
145   if (!curr_session_ ||
146       curr_session_->GetState() != MainLoopState::kInSession ||
147       !curr_session_->IsReadyForUserInput()) {
148     // ignore
149     return;
150   }
151   constexpr bool is_secure = true;
152   auto secure_input = WrapWithSecureFlag(std::move(input), is_secure);
153   input_multiplexer_.Push(user_input_evt_q_id_, std::move(secure_input));
154 }
155 
TouchEvent(const int x,const int y,const bool is_down)156 void HostServer::TouchEvent(const int x, const int y, const bool is_down) {
157   if (!is_down || !curr_session_) {
158     return;
159   }
160   std::unique_ptr<ConfUiMessage> input =
161       std::make_unique<ConfUiUserTouchMessage>(GetCurrentSessionId(), x, y);
162   SendUserSelection(input);
163 }
164 
UserAbortEvent()165 void HostServer::UserAbortEvent() {
166   if (!curr_session_) {
167     return;
168   }
169   std::unique_ptr<ConfUiMessage> input =
170       std::make_unique<ConfUiUserSelectionMessage>(GetCurrentSessionId(),
171                                                    UserResponse::kUserAbort);
172   SendUserSelection(input);
173 }
174 
175 // read the comments in the header file
MainLoop()176 [[noreturn]] void HostServer::MainLoop() {
177   while (true) {
178     // this gets one input from either queue:
179     // from HAL or from all webrtc clients
180     // if no input, sleep until there is
181     auto input_ptr = input_multiplexer_.Pop();
182     auto& input = *input_ptr;
183     const auto session_id = input.GetSessionId();
184     const auto cmd = input.GetType();
185     const std::string cmd_str(ToString(cmd));
186 
187     // take input for the Finite States Machine below
188     std::string src = input.IsUserInput() ? "input" : "hal";
189     ConfUiLog(VERBOSE) << "In Session " << GetCurrentSessionId() << ", "
190                        << "in state " << GetCurrentState() << ", "
191                        << "received input from " << src << " cmd =" << cmd_str
192                        << " going to session " << session_id;
193 
194     if (!curr_session_) {
195       if (cmd != ConfUiCmd::kStart) {
196         ConfUiLog(VERBOSE) << ToString(cmd) << " to " << session_id
197                            << " is ignored as there is no session to receive";
198         continue;
199       }
200       // the session is created as kInit
201       curr_session_ = CreateSession(input.GetSessionId());
202     }
203     if (cmd == ConfUiCmd::kUserTouchEvent) {
204       ConfUiSecureUserTouchMessage& touch_event =
205           static_cast<ConfUiSecureUserTouchMessage&>(input);
206       auto [x, y] = touch_event.GetLocation();
207       const bool is_confirm = curr_session_->IsConfirm(x, y);
208       const bool is_cancel = curr_session_->IsCancel(x, y);
209       ConfUiLog(INFO) << "Touch at [" << x << ", " << y << "] was "
210                       << (is_cancel ? "CANCEL"
211                                     : (is_confirm ? "CONFIRM" : "INVALID"));
212       if (!is_confirm && !is_cancel) {
213         // ignore, take the next input
214         continue;
215       }
216       decltype(input_ptr) tmp_input_ptr =
217           std::make_unique<ConfUiUserSelectionMessage>(
218               GetCurrentSessionId(),
219               (is_confirm ? UserResponse::kConfirm : UserResponse::kCancel));
220       input_ptr =
221           WrapWithSecureFlag(std::move(tmp_input_ptr), touch_event.IsSecure());
222     }
223     Transition(input_ptr);
224 
225     // finalize
226     if (curr_session_ &&
227         curr_session_->GetState() == MainLoopState::kAwaitCleanup) {
228       curr_session_->CleanUp();
229       curr_session_ = nullptr;
230     }
231   }  // end of the infinite while loop
232 }
233 
CreateSession(const std::string & name)234 std::shared_ptr<Session> HostServer::CreateSession(const std::string& name) {
235   return std::make_shared<Session>(name, display_num_, host_renderer_,
236                                    host_mode_ctrl_);
237 }
238 
IsUserAbort(ConfUiMessage & msg)239 static bool IsUserAbort(ConfUiMessage& msg) {
240   if (msg.GetType() != ConfUiCmd::kUserInputEvent) {
241     return false;
242   }
243   ConfUiUserSelectionMessage& selection =
244       static_cast<ConfUiUserSelectionMessage&>(msg);
245   return (selection.GetResponse() == UserResponse::kUserAbort);
246 }
247 
Transition(std::unique_ptr<ConfUiMessage> & input_ptr)248 void HostServer::Transition(std::unique_ptr<ConfUiMessage>& input_ptr) {
249   auto& input = *input_ptr;
250   const auto session_id = input.GetSessionId();
251   const auto cmd = input.GetType();
252   const std::string cmd_str(ToString(cmd));
253   FsmInput fsm_input = ToFsmInput(input);
254   ConfUiLog(VERBOSE) << "Handling " << ToString(cmd);
255   if (IsUserAbort(input)) {
256     curr_session_->UserAbort(to_guest_fifo_fd_);
257     return;
258   }
259 
260   if (cmd == ConfUiCmd::kAbort) {
261     curr_session_->Abort();
262     return;
263   }
264   curr_session_->Transition(to_guest_fifo_fd_, fsm_input, input);
265 }
266 
267 }  // end of namespace confui
268 }  // end of namespace cuttlefish
269