/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include "lm_log_server_proxy.hpp"
namespace ams::lm::srv {
namespace {
constexpr inline const char PortName[] = "iywys@$LogManager";
constexpr inline const int HtcsSessionCountMax = 2;
constexpr inline const TimeSpan PollingInterval = TimeSpan::FromSeconds(1);
constinit u8 g_htcs_heap_buffer[2_KB];
constexpr inline const int InvalidHtcsSocket = -1;
constexpr ALWAYS_INLINE bool IsValidHtcsSocket(int socket) {
return socket >= 0;
}
static_assert(!IsValidHtcsSocket(InvalidHtcsSocket));
bool IsHtcEnabled() {
u8 enable_htc = 0;
settings::fwdbg::GetSettingsItemValue(&enable_htc, sizeof(enable_htc), "atmosphere", "enable_htc");
return enable_htc != 0;
}
}
LogServerProxy::LogServerProxy() : m_cv_connected(), m_stop_event(os::EventClearMode_ManualClear), m_connection_mutex(), m_observer_mutex(), m_server_socket(InvalidHtcsSocket), m_client_socket(InvalidHtcsSocket), m_connection_observer(nullptr) {
/* ... */
}
void LogServerProxy::Start() {
/* Create thread. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), [](void *_this) { static_cast(_this)->LoopAuto(); }, this, m_thread_stack, sizeof(m_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(lm, HtcsConnection)));
/* Set thread name pointer. */
os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(lm, HtcsConnection));
/* Clear stop event. */
m_stop_event.Clear();
/* Start thread. */
os::StartThread(std::addressof(m_thread));
}
void LogServerProxy::Stop() {
/* Signal to connection thread to stop. */
m_stop_event.Signal();
/* Close client socket. */
if (const int client_socket = m_client_socket; client_socket >= 0) {
htcs::Close(client_socket);
}
/* Close server socket. */
if (const int server_socket = m_server_socket; server_socket >= 0) {
htcs::Close(server_socket);
}
/* Wait for the connection thread to exit. */
os::WaitThread(std::addressof(m_thread));
os::DestroyThread(std::addressof(m_thread));
}
bool LogServerProxy::IsConnected() {
/* Return whether there's a valid client socket. */
return IsValidHtcsSocket(m_client_socket);
}
void LogServerProxy::SetConnectionObserver(ConnectionObserver observer) {
/* Acquire exclusive access to observer data. */
std::scoped_lock lk(m_observer_mutex);
/* Set the observer. */
m_connection_observer = observer;
}
bool LogServerProxy::Send(const u8 *data, size_t size) {
/* Send as much data as we can, until it's all send. */
size_t offset = 0;
while (this->IsConnected() && offset < size) {
/* Try to send the remaining data. */
if (const auto result = htcs::Send(m_client_socket, data + offset, size - offset, 0); result >= 0) {
/* Advance. */
offset += static_cast(result);
} else {
/* We failed to send data, shutdown the socket. */
htcs::Shutdown(m_client_socket, htcs::HTCS_SHUT_RDWR);
/* Wait a second, before returning to the caller. */
os::SleepThread(TimeSpan::FromSeconds(1));
return false;
}
}
/* Return whether we sent all the data. */
AMS_ASSERT(offset <= size);
return offset == size;
}
void LogServerProxy::LoopAuto() {
/* If we're not using htcs, there's nothing to do. */
if (!IsHtcEnabled()) {
return;
}
/* Check that we have enough working memory. */
const auto working_memory_size = htcs::GetWorkingMemorySize(HtcsSessionCountMax);
AMS_ABORT_UNLESS(working_memory_size <= sizeof(g_htcs_heap_buffer));
/* Initialize htcs for the duration that we loop. */
htcs::InitializeForDisableDisconnectionEmulation(g_htcs_heap_buffer, working_memory_size);
ON_SCOPE_EXIT { htcs::Finalize(); };
/* Setup socket address. */
htcs::SockAddrHtcs server_address;
server_address.family = htcs::HTCS_AF_HTCS;
server_address.peer_name = htcs::GetPeerNameAny();
std::strcpy(server_address.port_name.name, PortName);
/* Manage htcs connections until a stop is requested. */
do {
/* Create a server socket. */
const auto server_socket = htcs::Socket();
if (!IsValidHtcsSocket(server_socket)) {
continue;
}
m_server_socket = server_socket;
/* Ensure we cleanup the server socket when done. */
ON_SCOPE_EXIT {
htcs::Close(server_socket);
m_server_socket = InvalidHtcsSocket;
};
/* Bind to the server socket. */
if (htcs::Bind(server_socket, std::addressof(server_address)) != 0) {
continue;
}
/* Listen on the server socket. */
if (htcs::Listen(server_socket, 0) != 0) {
continue;
}
/* Loop on clients, until we're asked to stop. */
while (!m_stop_event.TryWait()) {
/* Accept a client socket. */
const auto client_socket = htcs::Accept(server_socket, nullptr);
if (!IsValidHtcsSocket(client_socket)) {
break;
}
m_client_socket = client_socket;
/* Note that we're connected. */
this->InvokeConnectionObserver(true);
this->SignalConnection();
/* Ensure we cleanup the client socket when done. */
ON_SCOPE_EXIT {
htcs::Close(client_socket);
m_client_socket = InvalidHtcsSocket;
this->InvokeConnectionObserver(false);
};
/* Receive data (and do nothing with it), so long as we're connected. */
u8 v;
while (htcs::Recv(client_socket, std::addressof(v), sizeof(v), 0) == sizeof(v)) { /* ... */ }
}
} while (!m_stop_event.TimedWait(PollingInterval));
}
void LogServerProxy::SignalConnection() {
/* Acquire exclusive access to observer data. */
std::scoped_lock lk(m_connection_mutex);
/* Broadcast to our connected cv. */
m_cv_connected.Broadcast();
}
void LogServerProxy::InvokeConnectionObserver(bool connected) {
/* Acquire exclusive access to observer data. */
std::scoped_lock lk(m_observer_mutex);
/* If we have an observer, observe the connection state. */
if (m_connection_observer) {
m_connection_observer(connected);
}
}
void StartLogServerProxy() {
LogServerProxy::GetInstance().Start();
}
void StopLogServerProxy() {
LogServerProxy::GetInstance().Stop();
}
}