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 <chrono>
20 #include <functional>
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 {
CuttlefishConfigDefaultInstance()32 static auto CuttlefishConfigDefaultInstance() {
33 auto config = cuttlefish::CuttlefishConfig::Get();
34 CHECK(config) << "Config must not be null";
35 return config->ForDefaultInstance();
36 }
37
HalHostVsockPort()38 static int HalHostVsockPort() {
39 return CuttlefishConfigDefaultInstance().confui_host_vsock_port();
40 }
41
42 /**
43 * null if not user/touch, or wrap it and ConfUiSecure{Selection,Touch}Message
44 *
45 * ConfUiMessage must NOT ConfUiSecure{Selection,Touch}Message types
46 */
WrapWithSecureFlag(const ConfUiMessage & base_msg,const bool secure)47 static std::unique_ptr<ConfUiMessage> WrapWithSecureFlag(
48 const ConfUiMessage& base_msg, const bool secure) {
49 switch (base_msg.GetType()) {
50 case ConfUiCmd::kUserInputEvent: {
51 const ConfUiUserSelectionMessage& as_selection =
52 static_cast<const ConfUiUserSelectionMessage&>(base_msg);
53 return ToSecureSelectionMessage(as_selection, secure);
54 }
55 case ConfUiCmd::kUserTouchEvent: {
56 const ConfUiUserTouchMessage& as_touch =
57 static_cast<const ConfUiUserTouchMessage&>(base_msg);
58 return ToSecureTouchMessage(as_touch, secure);
59 }
60 default:
61 return nullptr;
62 }
63 }
64
Get(HostModeCtrl & host_mode_ctrl,cuttlefish::ScreenConnectorFrameRenderer & screen_connector)65 HostServer& HostServer::Get(
66 HostModeCtrl& host_mode_ctrl,
67 cuttlefish::ScreenConnectorFrameRenderer& screen_connector) {
68 static HostServer host_server{host_mode_ctrl, screen_connector};
69 return host_server;
70 }
71
HostServer(cuttlefish::HostModeCtrl & host_mode_ctrl,cuttlefish::ScreenConnectorFrameRenderer & screen_connector)72 HostServer::HostServer(
73 cuttlefish::HostModeCtrl& host_mode_ctrl,
74 cuttlefish::ScreenConnectorFrameRenderer& screen_connector)
75 : display_num_(0),
76 host_mode_ctrl_(host_mode_ctrl),
77 screen_connector_{screen_connector},
78 hal_vsock_port_(HalHostVsockPort()) {
79 ConfUiLog(DEBUG) << "Confirmation UI Host session is listening on: "
80 << hal_vsock_port_;
81 const size_t max_elements = 20;
82 auto ignore_new =
__anon441b9e010102(ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>::QueueImpl*) 83 [](ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>::QueueImpl*) {
84 // no op, so the queue is still full, and the new item will be discarded
85 return;
86 };
87 hal_cmd_q_id_ = input_multiplexer_.RegisterQueue(
88 HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
89 user_input_evt_q_id_ = input_multiplexer_.RegisterQueue(
90 HostServer::Multiplexer::CreateQueue(max_elements, ignore_new));
91 }
92
Start()93 void HostServer::Start() {
94 guest_hal_socket_ =
95 cuttlefish::SharedFD::VsockServer(hal_vsock_port_, SOCK_STREAM);
96 if (!guest_hal_socket_->IsOpen()) {
97 ConfUiLog(FATAL) << "Confirmation UI host service mandates a server socket"
98 << "to which the guest HAL to connect.";
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) << "configured internal vsock based input.";
107 return;
108 }
109
HalCmdFetcherLoop()110 void HostServer::HalCmdFetcherLoop() {
111 while (true) {
112 if (!hal_cli_socket_->IsOpen()) {
113 ConfUiLog(DEBUG) << "client is disconnected";
114 std::unique_lock<std::mutex> lk(socket_flag_mtx_);
115 hal_cli_socket_ = EstablishHalConnection();
116 is_socket_ok_ = true;
117 continue;
118 }
119 auto msg = RecvConfUiMsg(hal_cli_socket_);
120 if (!msg) {
121 ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL";
122 hal_cli_socket_->Close();
123 is_socket_ok_ = false;
124 continue;
125 }
126 /*
127 * In case of Vts test, the msg could be a user input. For now, we do not
128 * enforce the input grace period for Vts. However, if ever we do, here is
129 * where the time point check should happen. Once it is enqueued, it is not
130 * always guaranteed to be picked up reasonably soon.
131 */
132 constexpr bool is_secure = false;
133 auto to_override_if_user_input = WrapWithSecureFlag(*msg, is_secure);
134 if (to_override_if_user_input) {
135 msg = std::move(to_override_if_user_input);
136 }
137 input_multiplexer_.Push(hal_cmd_q_id_, std::move(msg));
138 }
139 }
140
SendUserSelection(std::unique_ptr<ConfUiMessage> & input)141 void HostServer::SendUserSelection(std::unique_ptr<ConfUiMessage>& input) {
142 if (!curr_session_ ||
143 curr_session_->GetState() != MainLoopState::kInSession ||
144 !curr_session_->IsReadyForUserInput()) {
145 // ignore
146 return;
147 }
148 constexpr bool is_secure = true;
149 auto secure_input = WrapWithSecureFlag(*input, is_secure);
150 input_multiplexer_.Push(user_input_evt_q_id_, std::move(secure_input));
151 }
152
TouchEvent(const int x,const int y,const bool is_down)153 void HostServer::TouchEvent(const int x, const int y, const bool is_down) {
154 if (!is_down || !curr_session_) {
155 return;
156 }
157 std::unique_ptr<ConfUiMessage> input =
158 std::make_unique<ConfUiUserTouchMessage>(GetCurrentSessionId(), x, y);
159 constexpr bool is_secure = true;
160 auto secure_input = WrapWithSecureFlag(*input, is_secure);
161 SendUserSelection(secure_input);
162 }
163
UserAbortEvent()164 void HostServer::UserAbortEvent() {
165 if (!curr_session_) {
166 return;
167 }
168 std::unique_ptr<ConfUiMessage> input =
169 std::make_unique<ConfUiUserSelectionMessage>(GetCurrentSessionId(),
170 UserResponse::kUserAbort);
171 constexpr bool is_secure = true;
172 auto secure_input = WrapWithSecureFlag(*input, is_secure);
173 SendUserSelection(secure_input);
174 }
175
IsConfUiActive()176 bool HostServer::IsConfUiActive() {
177 if (!curr_session_) {
178 return false;
179 }
180 return curr_session_->IsConfUiActive();
181 }
182
EstablishHalConnection()183 SharedFD HostServer::EstablishHalConnection() {
184 using namespace std::chrono_literals;
185 while (true) {
186 ConfUiLog(VERBOSE) << "Waiting hal accepting";
187 auto new_cli = SharedFD::Accept(*guest_hal_socket_);
188 ConfUiLog(VERBOSE) << "hal client accepted";
189 if (new_cli->IsOpen()) {
190 return new_cli;
191 }
192 std::this_thread::sleep_for(500ms);
193 }
194 }
195
196 // read the comments in the header file
MainLoop()197 [[noreturn]] void HostServer::MainLoop() {
198 while (true) {
199 // this gets one input from either queue:
200 // from HAL or from all webrtc clients
201 // if no input, sleep until there is
202 auto input_ptr = input_multiplexer_.Pop();
203 auto& input = *input_ptr;
204 const auto session_id = input.GetSessionId();
205 const auto cmd = input.GetType();
206 const std::string cmd_str(ToString(cmd));
207
208 // take input for the Finite States Machine below
209 std::string src = input.IsUserInput() ? "input" : "hal";
210 ConfUiLog(VERBOSE) << "In Session " << GetCurrentSessionId() << ", "
211 << "in state " << GetCurrentState() << ", "
212 << "received input from " << src << " cmd =" << cmd_str
213 << " going to session " << session_id;
214
215 if (!curr_session_) {
216 if (cmd != ConfUiCmd::kStart) {
217 ConfUiLog(VERBOSE) << ToString(cmd) << " to " << session_id
218 << " is ignored as there is no session to receive";
219 continue;
220 }
221 // the session is created as kInit
222 curr_session_ = CreateSession(input.GetSessionId());
223 }
224 if (cmd == ConfUiCmd::kUserTouchEvent) {
225 ConfUiSecureUserTouchMessage& touch_event =
226 static_cast<ConfUiSecureUserTouchMessage&>(input);
227 auto [x, y] = touch_event.GetLocation();
228 const bool is_confirm = curr_session_->IsConfirm(x, y);
229 const bool is_cancel = curr_session_->IsCancel(x, y);
230 if (!is_confirm && !is_cancel) {
231 // ignore, take the next input
232 continue;
233 }
234 decltype(input_ptr) tmp_input_ptr =
235 std::make_unique<ConfUiUserSelectionMessage>(
236 GetCurrentSessionId(),
237 (is_confirm ? UserResponse::kConfirm : UserResponse::kCancel));
238 input_ptr = WrapWithSecureFlag(*tmp_input_ptr, touch_event.IsSecure());
239 }
240 Transition(input_ptr);
241
242 // finalize
243 if (curr_session_ &&
244 curr_session_->GetState() == MainLoopState::kAwaitCleanup) {
245 curr_session_->CleanUp();
246 curr_session_ = nullptr;
247 }
248 } // end of the infinite while loop
249 }
250
CreateSession(const std::string & name)251 std::shared_ptr<Session> HostServer::CreateSession(const std::string& name) {
252 return std::make_shared<Session>(name, display_num_, host_mode_ctrl_,
253 screen_connector_);
254 }
255
IsUserAbort(ConfUiMessage & msg)256 static bool IsUserAbort(ConfUiMessage& msg) {
257 if (msg.GetType() != ConfUiCmd::kUserInputEvent) {
258 return false;
259 }
260 ConfUiUserSelectionMessage& selection =
261 static_cast<ConfUiUserSelectionMessage&>(msg);
262 return (selection.GetResponse() == UserResponse::kUserAbort);
263 }
264
Transition(std::unique_ptr<ConfUiMessage> & input_ptr)265 void HostServer::Transition(std::unique_ptr<ConfUiMessage>& input_ptr) {
266 auto& input = *input_ptr;
267 const auto session_id = input.GetSessionId();
268 const auto cmd = input.GetType();
269 const std::string cmd_str(ToString(cmd));
270 FsmInput fsm_input = ToFsmInput(input);
271 ConfUiLog(VERBOSE) << "Handling " << ToString(cmd);
272 if (IsUserAbort(input)) {
273 curr_session_->UserAbort(hal_cli_socket_);
274 return;
275 }
276
277 if (cmd == ConfUiCmd::kAbort) {
278 curr_session_->Abort();
279 return;
280 }
281 curr_session_->Transition(hal_cli_socket_, fsm_input, input);
282 }
283
284 } // end of namespace confui
285 } // end of namespace cuttlefish
286