| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "host/libs/confui/host_server.h" |
| |
| #include <chrono> |
| #include <functional> |
| #include <optional> |
| #include <tuple> |
| |
| #include "common/libs/confui/confui.h" |
| #include "common/libs/fs/shared_buf.h" |
| #include "host/libs/config/cuttlefish_config.h" |
| #include "host/libs/confui/host_utils.h" |
| #include "host/libs/confui/secure_input.h" |
| |
| namespace cuttlefish { |
| namespace confui { |
| static auto CuttlefishConfigDefaultInstance() { |
| auto config = cuttlefish::CuttlefishConfig::Get(); |
| CHECK(config) << "Config must not be null"; |
| return config->ForDefaultInstance(); |
| } |
| |
| static int HalHostVsockPort() { |
| return CuttlefishConfigDefaultInstance().confui_host_vsock_port(); |
| } |
| |
| /** |
| * null if not user/touch, or wrap it and ConfUiSecure{Selection,Touch}Message |
| * |
| * ConfUiMessage must NOT ConfUiSecure{Selection,Touch}Message types |
| */ |
| static std::unique_ptr<ConfUiMessage> WrapWithSecureFlag( |
| const ConfUiMessage& base_msg, const bool secure) { |
| switch (base_msg.GetType()) { |
| case ConfUiCmd::kUserInputEvent: { |
| const ConfUiUserSelectionMessage& as_selection = |
| static_cast<const ConfUiUserSelectionMessage&>(base_msg); |
| return ToSecureSelectionMessage(as_selection, secure); |
| } |
| case ConfUiCmd::kUserTouchEvent: { |
| const ConfUiUserTouchMessage& as_touch = |
| static_cast<const ConfUiUserTouchMessage&>(base_msg); |
| return ToSecureTouchMessage(as_touch, secure); |
| } |
| default: |
| return nullptr; |
| } |
| } |
| |
| HostServer& HostServer::Get( |
| HostModeCtrl& host_mode_ctrl, |
| cuttlefish::ScreenConnectorFrameRenderer& screen_connector) { |
| static HostServer host_server{host_mode_ctrl, screen_connector}; |
| return host_server; |
| } |
| |
| HostServer::HostServer( |
| cuttlefish::HostModeCtrl& host_mode_ctrl, |
| cuttlefish::ScreenConnectorFrameRenderer& screen_connector) |
| : display_num_(0), |
| host_mode_ctrl_(host_mode_ctrl), |
| screen_connector_{screen_connector}, |
| hal_vsock_port_(HalHostVsockPort()) { |
| ConfUiLog(DEBUG) << "Confirmation UI Host session is listening on: " |
| << hal_vsock_port_; |
| const size_t max_elements = 20; |
| auto ignore_new = |
| [](ThreadSafeQueue<std::unique_ptr<ConfUiMessage>>::QueueImpl*) { |
| // no op, so the queue is still full, and the new item will be discarded |
| return; |
| }; |
| hal_cmd_q_id_ = input_multiplexer_.RegisterQueue( |
| HostServer::Multiplexer::CreateQueue(max_elements, ignore_new)); |
| user_input_evt_q_id_ = input_multiplexer_.RegisterQueue( |
| HostServer::Multiplexer::CreateQueue(max_elements, ignore_new)); |
| } |
| |
| void HostServer::Start() { |
| guest_hal_socket_ = |
| cuttlefish::SharedFD::VsockServer(hal_vsock_port_, SOCK_STREAM); |
| if (!guest_hal_socket_->IsOpen()) { |
| ConfUiLog(FATAL) << "Confirmation UI host service mandates a server socket" |
| << "to which the guest HAL to connect."; |
| return; |
| } |
| auto hal_cmd_fetching = [this]() { this->HalCmdFetcherLoop(); }; |
| auto main = [this]() { this->MainLoop(); }; |
| hal_input_fetcher_thread_ = |
| thread::RunThread("HalInputLoop", hal_cmd_fetching); |
| main_loop_thread_ = thread::RunThread("MainLoop", main); |
| ConfUiLog(DEBUG) << "configured internal vsock based input."; |
| return; |
| } |
| |
| void HostServer::HalCmdFetcherLoop() { |
| while (true) { |
| if (!hal_cli_socket_->IsOpen()) { |
| ConfUiLog(DEBUG) << "client is disconnected"; |
| std::unique_lock<std::mutex> lk(socket_flag_mtx_); |
| hal_cli_socket_ = EstablishHalConnection(); |
| is_socket_ok_ = true; |
| continue; |
| } |
| auto msg = RecvConfUiMsg(hal_cli_socket_); |
| if (!msg) { |
| ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL"; |
| hal_cli_socket_->Close(); |
| is_socket_ok_ = false; |
| continue; |
| } |
| /* |
| * In case of Vts test, the msg could be a user input. For now, we do not |
| * enforce the input grace period for Vts. However, if ever we do, here is |
| * where the time point check should happen. Once it is enqueued, it is not |
| * always guaranteed to be picked up reasonably soon. |
| */ |
| constexpr bool is_secure = false; |
| auto to_override_if_user_input = WrapWithSecureFlag(*msg, is_secure); |
| if (to_override_if_user_input) { |
| msg = std::move(to_override_if_user_input); |
| } |
| input_multiplexer_.Push(hal_cmd_q_id_, std::move(msg)); |
| } |
| } |
| |
| void HostServer::SendUserSelection(std::unique_ptr<ConfUiMessage>& input) { |
| if (!curr_session_ || |
| curr_session_->GetState() != MainLoopState::kInSession || |
| !curr_session_->IsReadyForUserInput()) { |
| // ignore |
| return; |
| } |
| constexpr bool is_secure = true; |
| auto secure_input = WrapWithSecureFlag(*input, is_secure); |
| input_multiplexer_.Push(user_input_evt_q_id_, std::move(secure_input)); |
| } |
| |
| void HostServer::TouchEvent(const int x, const int y, const bool is_down) { |
| if (!is_down || !curr_session_) { |
| return; |
| } |
| std::unique_ptr<ConfUiMessage> input = |
| std::make_unique<ConfUiUserTouchMessage>(GetCurrentSessionId(), x, y); |
| constexpr bool is_secure = true; |
| auto secure_input = WrapWithSecureFlag(*input, is_secure); |
| SendUserSelection(secure_input); |
| } |
| |
| void HostServer::UserAbortEvent() { |
| if (!curr_session_) { |
| return; |
| } |
| std::unique_ptr<ConfUiMessage> input = |
| std::make_unique<ConfUiUserSelectionMessage>(GetCurrentSessionId(), |
| UserResponse::kUserAbort); |
| constexpr bool is_secure = true; |
| auto secure_input = WrapWithSecureFlag(*input, is_secure); |
| SendUserSelection(secure_input); |
| } |
| |
| bool HostServer::IsConfUiActive() { |
| if (!curr_session_) { |
| return false; |
| } |
| return curr_session_->IsConfUiActive(); |
| } |
| |
| SharedFD HostServer::EstablishHalConnection() { |
| using namespace std::chrono_literals; |
| while (true) { |
| ConfUiLog(VERBOSE) << "Waiting hal accepting"; |
| auto new_cli = SharedFD::Accept(*guest_hal_socket_); |
| ConfUiLog(VERBOSE) << "hal client accepted"; |
| if (new_cli->IsOpen()) { |
| return new_cli; |
| } |
| std::this_thread::sleep_for(500ms); |
| } |
| } |
| |
| // read the comments in the header file |
| [[noreturn]] void HostServer::MainLoop() { |
| while (true) { |
| // this gets one input from either queue: |
| // from HAL or from all webrtc clients |
| // if no input, sleep until there is |
| auto input_ptr = input_multiplexer_.Pop(); |
| auto& input = *input_ptr; |
| const auto session_id = input.GetSessionId(); |
| const auto cmd = input.GetType(); |
| const std::string cmd_str(ToString(cmd)); |
| |
| // take input for the Finite States Machine below |
| std::string src = input.IsUserInput() ? "input" : "hal"; |
| ConfUiLog(VERBOSE) << "In Session " << GetCurrentSessionId() << ", " |
| << "in state " << GetCurrentState() << ", " |
| << "received input from " << src << " cmd =" << cmd_str |
| << " going to session " << session_id; |
| |
| if (!curr_session_) { |
| if (cmd != ConfUiCmd::kStart) { |
| ConfUiLog(VERBOSE) << ToString(cmd) << " to " << session_id |
| << " is ignored as there is no session to receive"; |
| continue; |
| } |
| // the session is created as kInit |
| curr_session_ = CreateSession(input.GetSessionId()); |
| } |
| if (cmd == ConfUiCmd::kUserTouchEvent) { |
| ConfUiSecureUserTouchMessage& touch_event = |
| static_cast<ConfUiSecureUserTouchMessage&>(input); |
| auto [x, y] = touch_event.GetLocation(); |
| const bool is_confirm = curr_session_->IsConfirm(x, y); |
| const bool is_cancel = curr_session_->IsCancel(x, y); |
| if (!is_confirm && !is_cancel) { |
| // ignore, take the next input |
| continue; |
| } |
| decltype(input_ptr) tmp_input_ptr = |
| std::make_unique<ConfUiUserSelectionMessage>( |
| GetCurrentSessionId(), |
| (is_confirm ? UserResponse::kConfirm : UserResponse::kCancel)); |
| input_ptr = WrapWithSecureFlag(*tmp_input_ptr, touch_event.IsSecure()); |
| } |
| Transition(input_ptr); |
| |
| // finalize |
| if (curr_session_ && |
| curr_session_->GetState() == MainLoopState::kAwaitCleanup) { |
| curr_session_->CleanUp(); |
| curr_session_ = nullptr; |
| } |
| } // end of the infinite while loop |
| } |
| |
| std::shared_ptr<Session> HostServer::CreateSession(const std::string& name) { |
| return std::make_shared<Session>(name, display_num_, host_mode_ctrl_, |
| screen_connector_); |
| } |
| |
| static bool IsUserAbort(ConfUiMessage& msg) { |
| if (msg.GetType() != ConfUiCmd::kUserInputEvent) { |
| return false; |
| } |
| ConfUiUserSelectionMessage& selection = |
| static_cast<ConfUiUserSelectionMessage&>(msg); |
| return (selection.GetResponse() == UserResponse::kUserAbort); |
| } |
| |
| void HostServer::Transition(std::unique_ptr<ConfUiMessage>& input_ptr) { |
| auto& input = *input_ptr; |
| const auto session_id = input.GetSessionId(); |
| const auto cmd = input.GetType(); |
| const std::string cmd_str(ToString(cmd)); |
| FsmInput fsm_input = ToFsmInput(input); |
| ConfUiLog(VERBOSE) << "Handling " << ToString(cmd); |
| if (IsUserAbort(input)) { |
| curr_session_->UserAbort(hal_cli_socket_); |
| return; |
| } |
| |
| if (cmd == ConfUiCmd::kAbort) { |
| curr_session_->Abort(); |
| return; |
| } |
| curr_session_->Transition(hal_cli_socket_, fsm_input, input); |
| } |
| |
| } // end of namespace confui |
| } // end of namespace cuttlefish |