blob: 68bf3aa2051bb08e8a8b3479b385de70ebec4d92 [file] [log] [blame] [edit]
/*
* 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 <functional>
#include <memory>
#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 {
namespace {
template <typename Derived, typename Base>
std::unique_ptr<Derived> DowncastTo(std::unique_ptr<Base>&& base) {
Base* tmp = base.release();
Derived* derived = static_cast<Derived*>(tmp);
return std::unique_ptr<Derived>(derived);
}
} // namespace
/**
* 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(
std::unique_ptr<ConfUiMessage>&& base_msg, const bool secure) {
switch (base_msg->GetType()) {
case ConfUiCmd::kUserInputEvent: {
auto as_selection =
DowncastTo<ConfUiUserSelectionMessage>(std::move(base_msg));
return ToSecureSelectionMessage(std::move(as_selection), secure);
}
case ConfUiCmd::kUserTouchEvent: {
auto as_touch = DowncastTo<ConfUiUserTouchMessage>(std::move(base_msg));
return ToSecureTouchMessage(std::move(as_touch), secure);
}
default:
return nullptr;
}
}
HostServer::HostServer(HostModeCtrl& host_mode_ctrl,
ConfUiRenderer& host_renderer,
const PipeConnectionPair& fd_pair)
: display_num_(0),
host_renderer_{host_renderer},
host_mode_ctrl_(host_mode_ctrl),
from_guest_fifo_fd_(fd_pair.from_guest_),
to_guest_fifo_fd_(fd_pair.to_guest_) {
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));
}
bool HostServer::IsVirtioConsoleOpen() const {
return from_guest_fifo_fd_->IsOpen() && to_guest_fifo_fd_->IsOpen();
}
bool HostServer::CheckVirtioConsole() {
if (IsVirtioConsoleOpen()) return true;
ConfUiLog(FATAL) << "Virtio console is not open";
return false;
}
void HostServer::Start() {
if (!CheckVirtioConsole()) {
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) << "host service started.";
return;
}
void HostServer::HalCmdFetcherLoop() {
while (true) {
if (!CheckVirtioConsole()) {
return;
}
auto msg = RecvConfUiMsg(from_guest_fifo_fd_);
if (!msg) {
ConfUiLog(ERROR) << "Error in RecvConfUiMsg from HAL";
// TODO(kwstephenkim): error handling
// either file is not open, or ill-formatted message
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(std::move(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));
}
}
/**
* Send inputs generated not by auto-tester but by the human users
*
* Send such inputs into the command queue consumed by the state machine
* in the main loop/current session.
*/
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(std::move(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);
SendUserSelection(input);
}
void HostServer::UserAbortEvent() {
if (!curr_session_) {
return;
}
std::unique_ptr<ConfUiMessage> input =
std::make_unique<ConfUiUserSelectionMessage>(GetCurrentSessionId(),
UserResponse::kUserAbort);
SendUserSelection(input);
}
// 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);
ConfUiLog(INFO) << "Touch at [" << x << ", " << y << "] was "
<< (is_cancel ? "CANCEL"
: (is_confirm ? "CONFIRM" : "INVALID"));
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(std::move(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_renderer_,
host_mode_ctrl_);
}
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(to_guest_fifo_fd_);
return;
}
if (cmd == ConfUiCmd::kAbort) {
curr_session_->Abort();
return;
}
curr_session_->Transition(to_guest_fifo_fd_, fsm_input, input);
}
} // end of namespace confui
} // end of namespace cuttlefish