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