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 =
__anond25b145a0202(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()) return true;
91 ConfUiLog(FATAL) << "Virtio console is not open";
92 return false;
93 }
94
Start()95 void HostServer::Start() {
96 if (!CheckVirtioConsole()) {
97 return;
98 }
99 auto hal_cmd_fetching = [this]() { this->HalCmdFetcherLoop(); };
100 auto main = [this]() { this->MainLoop(); };
101 hal_input_fetcher_thread_ =
102 thread::RunThread("HalInputLoop", hal_cmd_fetching);
103 main_loop_thread_ = thread::RunThread("MainLoop", main);
104 ConfUiLog(DEBUG) << "host service started.";
105 return;
106 }
107
HalCmdFetcherLoop()108 void HostServer::HalCmdFetcherLoop() {
109 while (true) {
110 if (!CheckVirtioConsole()) {
111 return;
112 }
113 auto msg = RecvConfUiMsg(from_guest_fifo_fd_);
114 if (!msg) {
115 ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL";
116 // TODO(kwstephenkim): error handling
117 // either file is not open, or ill-formatted message
118 continue;
119 }
120 /*
121 * In case of Vts test, the msg could be a user input. For now, we do not
122 * enforce the input grace period for Vts. However, if ever we do, here is
123 * where the time point check should happen. Once it is enqueued, it is not
124 * always guaranteed to be picked up reasonably soon.
125 */
126 constexpr bool is_secure = false;
127 auto to_override_if_user_input =
128 WrapWithSecureFlag(std::move(msg), is_secure);
129 if (to_override_if_user_input) {
130 msg = std::move(to_override_if_user_input);
131 }
132 input_multiplexer_.Push(hal_cmd_q_id_, std::move(msg));
133 }
134 }
135
136 /**
137 * Send inputs generated not by auto-tester but by the human users
138 *
139 * Send such inputs into the command queue consumed by the state machine
140 * in the main loop/current session.
141 */
SendUserSelection(std::unique_ptr<ConfUiMessage> & input)142 void HostServer::SendUserSelection(std::unique_ptr<ConfUiMessage>& input) {
143 if (!curr_session_ ||
144 curr_session_->GetState() != MainLoopState::kInSession ||
145 !curr_session_->IsReadyForUserInput()) {
146 // ignore
147 return;
148 }
149 constexpr bool is_secure = true;
150 auto secure_input = WrapWithSecureFlag(std::move(input), is_secure);
151 input_multiplexer_.Push(user_input_evt_q_id_, std::move(secure_input));
152 }
153
TouchEvent(const int x,const int y,const bool is_down)154 void HostServer::TouchEvent(const int x, const int y, const bool is_down) {
155 if (!is_down || !curr_session_) {
156 return;
157 }
158 std::unique_ptr<ConfUiMessage> input =
159 std::make_unique<ConfUiUserTouchMessage>(GetCurrentSessionId(), x, y);
160 SendUserSelection(input);
161 }
162
UserAbortEvent()163 void HostServer::UserAbortEvent() {
164 if (!curr_session_) {
165 return;
166 }
167 std::unique_ptr<ConfUiMessage> input =
168 std::make_unique<ConfUiUserSelectionMessage>(GetCurrentSessionId(),
169 UserResponse::kUserAbort);
170 SendUserSelection(input);
171 }
172
173 // read the comments in the header file
MainLoop()174 [[noreturn]] void HostServer::MainLoop() {
175 while (true) {
176 // this gets one input from either queue:
177 // from HAL or from all webrtc clients
178 // if no input, sleep until there is
179 auto input_ptr = input_multiplexer_.Pop();
180 auto& input = *input_ptr;
181 const auto session_id = input.GetSessionId();
182 const auto cmd = input.GetType();
183 const std::string cmd_str(ToString(cmd));
184
185 // take input for the Finite States Machine below
186 std::string src = input.IsUserInput() ? "input" : "hal";
187 ConfUiLog(VERBOSE) << "In Session " << GetCurrentSessionId() << ", "
188 << "in state " << GetCurrentState() << ", "
189 << "received input from " << src << " cmd =" << cmd_str
190 << " going to session " << session_id;
191
192 if (!curr_session_) {
193 if (cmd != ConfUiCmd::kStart) {
194 ConfUiLog(VERBOSE) << ToString(cmd) << " to " << session_id
195 << " is ignored as there is no session to receive";
196 continue;
197 }
198 // the session is created as kInit
199 curr_session_ = CreateSession(input.GetSessionId());
200 }
201 if (cmd == ConfUiCmd::kUserTouchEvent) {
202 ConfUiSecureUserTouchMessage& touch_event =
203 static_cast<ConfUiSecureUserTouchMessage&>(input);
204 auto [x, y] = touch_event.GetLocation();
205 const bool is_confirm = curr_session_->IsConfirm(x, y);
206 const bool is_cancel = curr_session_->IsCancel(x, y);
207 ConfUiLog(INFO) << "Touch at [" << x << ", " << y << "] was "
208 << (is_cancel ? "CANCEL"
209 : (is_confirm ? "CONFIRM" : "INVALID"));
210 if (!is_confirm && !is_cancel) {
211 // ignore, take the next input
212 continue;
213 }
214 decltype(input_ptr) tmp_input_ptr =
215 std::make_unique<ConfUiUserSelectionMessage>(
216 GetCurrentSessionId(),
217 (is_confirm ? UserResponse::kConfirm : UserResponse::kCancel));
218 input_ptr =
219 WrapWithSecureFlag(std::move(tmp_input_ptr), touch_event.IsSecure());
220 }
221 Transition(input_ptr);
222
223 // finalize
224 if (curr_session_ &&
225 curr_session_->GetState() == MainLoopState::kAwaitCleanup) {
226 curr_session_->CleanUp();
227 curr_session_ = nullptr;
228 }
229 } // end of the infinite while loop
230 }
231
CreateSession(const std::string & name)232 std::shared_ptr<Session> HostServer::CreateSession(const std::string& name) {
233 return std::make_shared<Session>(name, display_num_, host_renderer_,
234 host_mode_ctrl_);
235 }
236
IsUserAbort(ConfUiMessage & msg)237 static bool IsUserAbort(ConfUiMessage& msg) {
238 if (msg.GetType() != ConfUiCmd::kUserInputEvent) {
239 return false;
240 }
241 ConfUiUserSelectionMessage& selection =
242 static_cast<ConfUiUserSelectionMessage&>(msg);
243 return (selection.GetResponse() == UserResponse::kUserAbort);
244 }
245
Transition(std::unique_ptr<ConfUiMessage> & input_ptr)246 void HostServer::Transition(std::unique_ptr<ConfUiMessage>& input_ptr) {
247 auto& input = *input_ptr;
248 const auto session_id = input.GetSessionId();
249 const auto cmd = input.GetType();
250 const std::string cmd_str(ToString(cmd));
251 FsmInput fsm_input = ToFsmInput(input);
252 ConfUiLog(VERBOSE) << "Handling " << ToString(cmd);
253 if (IsUserAbort(input)) {
254 curr_session_->UserAbort(to_guest_fifo_fd_);
255 return;
256 }
257
258 if (cmd == ConfUiCmd::kAbort) {
259 curr_session_->Abort();
260 return;
261 }
262 curr_session_->Transition(to_guest_fifo_fd_, fsm_input, input);
263 }
264
265 } // end of namespace confui
266 } // end of namespace cuttlefish
267