From c04dcc2e7d834218ef2d4194331e383402495ae1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 20:07:22 +0200 Subject: Adding upstream version 2:20.4+dfsg. Signed-off-by: Daniel Baumann --- xbmc/network/TCPServer.cpp | 756 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 756 insertions(+) create mode 100644 xbmc/network/TCPServer.cpp (limited to 'xbmc/network/TCPServer.cpp') diff --git a/xbmc/network/TCPServer.cpp b/xbmc/network/TCPServer.cpp new file mode 100644 index 0000000..cd055c4 --- /dev/null +++ b/xbmc/network/TCPServer.cpp @@ -0,0 +1,756 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "TCPServer.h" + +#include "ServiceBroker.h" +#include "interfaces/AnnouncementManager.h" +#include "interfaces/json-rpc/JSONRPC.h" +#include "network/Network.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "websocket/WebSocketManager.h" + +#include +#include +#include + +#include +#include +#include + +using namespace std::chrono_literals; + +#if defined(TARGET_WINDOWS) || defined(HAVE_LIBBLUETOOTH) +static const char bt_service_name[] = "XBMC JSON-RPC"; +static const char bt_service_desc[] = "Interface for XBMC remote control over bluetooth"; +static const char bt_service_prov[] = "XBMC JSON-RPC Provider"; +static const uint32_t bt_service_guid[] = {0x65AE4CC0, 0x775D11E0, 0xBE16CE28, 0x4824019B}; +#endif + +#if defined(TARGET_WINDOWS) +#include "platform/win32/CharsetConverter.h" +#endif + +#ifdef HAVE_LIBBLUETOOTH +#include +#include +#include +#include + + /* The defines BDADDR_ANY and BDADDR_LOCAL are broken so use our own structs */ +static const bdaddr_t bt_bdaddr_any = {{0, 0, 0, 0, 0, 0}}; +static const bdaddr_t bt_bdaddr_local = {{0, 0, 0, 0xff, 0xff, 0xff}}; + +#endif + +using namespace JSONRPC; + +#define RECEIVEBUFFER 4096 + +namespace +{ +constexpr size_t maxBufferLength = 64 * 1024; +} + +CTCPServer *CTCPServer::ServerInstance = NULL; + +bool CTCPServer::StartServer(int port, bool nonlocal) +{ + StopServer(true); + + ServerInstance = new CTCPServer(port, nonlocal); + if (ServerInstance->Initialize()) + { + ServerInstance->Create(false); + return true; + } + else + return false; +} + +void CTCPServer::StopServer(bool bWait) +{ + if (ServerInstance) + { + ServerInstance->StopThread(bWait); + if (bWait) + { + delete ServerInstance; + ServerInstance = NULL; + } + } +} + +bool CTCPServer::IsRunning() +{ + if (ServerInstance == NULL) + return false; + + return ((CThread*)ServerInstance)->IsRunning(); +} + +CTCPServer::CTCPServer(int port, bool nonlocal) : CThread("TCPServer") +{ + m_port = port; + m_nonlocal = nonlocal; + m_sdpd = NULL; +} + +void CTCPServer::Process() +{ + m_bStop = false; + + while (!m_bStop) + { + SOCKET max_fd = 0; + fd_set rfds; + struct timeval to = {1, 0}; + FD_ZERO(&rfds); + + for (auto& it : m_servers) + { + FD_SET(it, &rfds); + if ((intptr_t)it > (intptr_t)max_fd) + max_fd = it; + } + + for (unsigned int i = 0; i < m_connections.size(); i++) + { + FD_SET(m_connections[i]->m_socket, &rfds); + if ((intptr_t)m_connections[i]->m_socket > (intptr_t)max_fd) + max_fd = m_connections[i]->m_socket; + } + + int res = select((intptr_t)max_fd+1, &rfds, NULL, NULL, &to); + if (res < 0) + { + CLog::Log(LOGERROR, "JSONRPC Server: Select failed"); + CThread::Sleep(1000ms); + Initialize(); + } + else if (res > 0) + { + for (int i = m_connections.size() - 1; i >= 0; i--) + { + int socket = m_connections[i]->m_socket; + if (FD_ISSET(socket, &rfds)) + { + char buffer[RECEIVEBUFFER] = {}; + int nread = 0; + nread = recv(socket, (char*)&buffer, RECEIVEBUFFER, 0); + bool close = false; + if (nread > 0) + { + std::string response; + if (m_connections[i]->IsNew()) + { + CWebSocket *websocket = CWebSocketManager::Handle(buffer, nread, response); + + if (!response.empty()) + m_connections[i]->Send(response.c_str(), response.size()); + + if (websocket != NULL) + { + // Replace the CTCPClient with a CWebSocketClient + CWebSocketClient *websocketClient = new CWebSocketClient(websocket, *(m_connections[i])); + delete m_connections[i]; + m_connections.erase(m_connections.begin() + i); + m_connections.insert(m_connections.begin() + i, websocketClient); + } + } + + if (response.size() <= 0) + m_connections[i]->PushBuffer(this, buffer, nread); + + close = m_connections[i]->Closing(); + } + else + close = true; + + if (close) + { + CLog::Log(LOGINFO, "JSONRPC Server: Disconnection detected"); + m_connections[i]->Disconnect(); + delete m_connections[i]; + m_connections.erase(m_connections.begin() + i); + } + } + } + + for (auto& it : m_servers) + { + if (FD_ISSET(it, &rfds)) + { + CLog::Log(LOGDEBUG, "JSONRPC Server: New connection detected"); + CTCPClient *newconnection = new CTCPClient(); + newconnection->m_socket = + accept(it, (sockaddr*)&newconnection->m_cliaddr, &newconnection->m_addrlen); + + if (newconnection->m_socket == INVALID_SOCKET) + { + CLog::Log(LOGERROR, "JSONRPC Server: Accept of new connection failed: {}", errno); + if (EBADF == errno) + { + CThread::Sleep(1000ms); + Initialize(); + break; + } + } + else + { + CLog::Log(LOGINFO, "JSONRPC Server: New connection added"); + m_connections.push_back(newconnection); + } + } + } + } + } + + Deinitialize(); +} + +bool CTCPServer::PrepareDownload(const char *path, CVariant &details, std::string &protocol) +{ + return false; +} + +bool CTCPServer::Download(const char *path, CVariant &result) +{ + return false; +} + +int CTCPServer::GetCapabilities() +{ + return Response | Announcing; +} + +void CTCPServer::Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + if (m_connections.empty()) + return; + + std::string str = IJSONRPCAnnouncer::AnnouncementToJSONRPC(flag, sender, message, data, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_jsonOutputCompact); + + for (unsigned int i = 0; i < m_connections.size(); i++) + { + { + std::unique_lock lock(m_connections[i]->m_critSection); + if ((m_connections[i]->GetAnnouncementFlags() & flag) == 0) + continue; + } + + m_connections[i]->Send(str.c_str(), str.size()); + } +} + +bool CTCPServer::Initialize() +{ + Deinitialize(); + + bool started = false; + + started |= InitializeBlue(); + started |= InitializeTCP(); + + if (started) + { + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); + CLog::Log(LOGINFO, "JSONRPC Server: Successfully initialized"); + return true; + } + return false; +} + +#ifdef TARGET_WINDOWS_STORE +bool CTCPServer::InitializeBlue() +{ + CLog::Log(LOGDEBUG, "{} is not implemented", __FUNCTION__); + return true; // need to fake it for now +} + +#else +bool CTCPServer::InitializeBlue() +{ + if (!m_nonlocal) + return false; + +#ifdef TARGET_WINDOWS + + SOCKET fd = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM); + if (fd == INVALID_SOCKET) + { + CLog::Log(LOGINFO, "JSONRPC Server: Unable to get bluetooth socket"); + return false; + } + SOCKADDR_BTH sa = {}; + sa.addressFamily = AF_BTH; + sa.port = BT_PORT_ANY; + + if (bind(fd, (SOCKADDR*)&sa, sizeof(sa)) < 0) + { + CLog::Log(LOGINFO, "JSONRPC Server: Unable to bind to bluetooth socket"); + closesocket(fd); + return false; + } + + ULONG optval = TRUE; + if (setsockopt(fd, SOL_RFCOMM, SO_BTH_AUTHENTICATE, (const char*)&optval, sizeof(optval)) == SOCKET_ERROR) + { + CLog::Log(LOGERROR, "JSONRPC Server: Failed to force authentication for bluetooth socket"); + closesocket(fd); + return false; + } + + int len = sizeof(sa); + if (getsockname(fd, (SOCKADDR*)&sa, &len) < 0) + CLog::Log(LOGERROR, "JSONRPC Server: Failed to get bluetooth port"); + + if (listen(fd, 10) < 0) + { + CLog::Log(LOGERROR, "JSONRPC Server: Failed to listen to bluetooth port"); + closesocket(fd); + return false; + } + + m_servers.push_back(fd); + + CSADDR_INFO addrinfo; + addrinfo.iProtocol = BTHPROTO_RFCOMM; + addrinfo.iSocketType = SOCK_STREAM; + addrinfo.LocalAddr.lpSockaddr = (SOCKADDR*)&sa; + addrinfo.LocalAddr.iSockaddrLength = sizeof(sa); + addrinfo.RemoteAddr.lpSockaddr = (SOCKADDR*)&sa; + addrinfo.RemoteAddr.iSockaddrLength = sizeof(sa); + + using KODI::PLATFORM::WINDOWS::ToW; + + WSAQUERYSET service = {}; + service.dwSize = sizeof(service); + service.lpszServiceInstanceName = const_cast(ToW(bt_service_name).c_str()); + service.lpServiceClassId = (LPGUID)&bt_service_guid; + service.lpszComment = const_cast(ToW(bt_service_desc).c_str()); + service.dwNameSpace = NS_BTH; + service.lpNSProviderId = NULL; /* RFCOMM? */ + service.lpcsaBuffer = &addrinfo; + service.dwNumberOfCsAddrs = 1; + + if (WSASetService(&service, RNRSERVICE_REGISTER, 0) == SOCKET_ERROR) + CLog::Log(LOGERROR, "JSONRPC Server: failed to register bluetooth service error {}", + WSAGetLastError()); + + return true; +#endif + +#ifdef HAVE_LIBBLUETOOTH + + SOCKET fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (fd == INVALID_SOCKET) + { + CLog::Log(LOGINFO, "JSONRPC Server: Unable to get bluetooth socket"); + return false; + } + struct sockaddr_rc sa = {}; + sa.rc_family = AF_BLUETOOTH; + sa.rc_bdaddr = bt_bdaddr_any; + sa.rc_channel = 0; + + if (bind(fd, (struct sockaddr*)&sa, sizeof(sa)) < 0) + { + CLog::Log(LOGINFO, "JSONRPC Server: Unable to bind to bluetooth socket"); + closesocket(fd); + return false; + } + + socklen_t len = sizeof(sa); + if (getsockname(fd, (struct sockaddr*)&sa, &len) < 0) + CLog::Log(LOGERROR, "JSONRPC Server: Failed to get bluetooth port"); + + if (listen(fd, 10) < 0) + { + CLog::Log(LOGERROR, "JSONRPC Server: Failed to listen to bluetooth port {}", sa.rc_channel); + closesocket(fd); + return false; + } + + uint8_t rfcomm_channel = sa.rc_channel; + + uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid; + sdp_list_t *l2cap_list = 0, + *rfcomm_list = 0, + *root_list = 0, + *proto_list = 0, + *access_proto_list = 0, + *service_class = 0; + + sdp_data_t *channel = 0; + + sdp_record_t *record = sdp_record_alloc(); + + // set the general service ID + sdp_uuid128_create(&svc_uuid, &bt_service_guid); + sdp_set_service_id(record, svc_uuid); + + // make the service record publicly browseable + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root_list = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root_list); + + // set l2cap information + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + l2cap_list = sdp_list_append(0, &l2cap_uuid); + proto_list = sdp_list_append(0, l2cap_list); + + // set rfcomm information + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel); + rfcomm_list = sdp_list_append(0, &rfcomm_uuid); + sdp_list_append(rfcomm_list, channel); + sdp_list_append(proto_list, rfcomm_list); + + // attach protocol information to service record + access_proto_list = sdp_list_append(0, proto_list); + sdp_set_access_protos(record, access_proto_list); + + // set the name, provider, and description + sdp_set_info_attr(record, bt_service_name, bt_service_prov, bt_service_desc); + + // set the Service class ID + service_class = sdp_list_append(0, &svc_uuid); + sdp_set_service_classes(record, service_class); + + // cleanup + sdp_data_free(channel); + sdp_list_free(l2cap_list, 0); + sdp_list_free(rfcomm_list, 0); + sdp_list_free(root_list, 0); + sdp_list_free(access_proto_list, 0); + sdp_list_free(service_class, 0); + + // connect to the local SDP server, register the service record + sdp_session_t *session = sdp_connect(&bt_bdaddr_any, &bt_bdaddr_local, SDP_RETRY_IF_BUSY); + if (session == NULL) + { + CLog::Log(LOGERROR, "JSONRPC Server: Failed to connect to sdpd"); + closesocket(fd); + sdp_record_free(record); + return false; + } + + if (sdp_record_register(session, record, 0) < 0) + { + CLog::Log(LOGERROR, "JSONRPC Server: Failed to register record with error {}", errno); + closesocket(fd); + sdp_close(session); + sdp_record_free(record); + return false; + } + + m_sdpd = session; + m_servers.push_back(fd); + + return true; +#endif + return false; +} +#endif + +bool CTCPServer::InitializeTCP() +{ + Deinitialize(); + + std::vector sockets = CreateTCPServerSocket(m_port, !m_nonlocal, 10, "JSONRPC"); + if (sockets.empty()) + return false; + + m_servers.insert(m_servers.end(), sockets.begin(), sockets.end()); + return true; +} + +void CTCPServer::Deinitialize() +{ + for (unsigned int i = 0; i < m_connections.size(); i++) + { + m_connections[i]->Disconnect(); + delete m_connections[i]; + } + + m_connections.clear(); + + for (unsigned int i = 0; i < m_servers.size(); i++) + closesocket(m_servers[i]); + + m_servers.clear(); + +#ifdef HAVE_LIBBLUETOOTH + if (m_sdpd) + sdp_close((sdp_session_t*)m_sdpd); + m_sdpd = NULL; +#endif + + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); +} + +CTCPServer::CTCPClient::CTCPClient() +{ + m_new = true; + m_announcementflags = ANNOUNCEMENT::ANNOUNCE_ALL; + m_socket = INVALID_SOCKET; + m_beginBrackets = 0; + m_endBrackets = 0; + m_beginChar = 0; + m_endChar = 0; + + m_addrlen = sizeof(m_cliaddr); +} + +CTCPServer::CTCPClient::CTCPClient(const CTCPClient& client) +{ + Copy(client); +} + +CTCPServer::CTCPClient& CTCPServer::CTCPClient::operator=(const CTCPClient& client) +{ + Copy(client); + return *this; +} + +int CTCPServer::CTCPClient::GetPermissionFlags() +{ + return OPERATION_PERMISSION_ALL; +} + +int CTCPServer::CTCPClient::GetAnnouncementFlags() +{ + return m_announcementflags; +} + +bool CTCPServer::CTCPClient::SetAnnouncementFlags(int flags) +{ + m_announcementflags = flags; + return true; +} + +void CTCPServer::CTCPClient::Send(const char *data, unsigned int size) +{ + unsigned int sent = 0; + do + { + std::unique_lock lock(m_critSection); + sent += send(m_socket, data + sent, size - sent, 0); + } while (sent < size); +} + +void CTCPServer::CTCPClient::PushBuffer(CTCPServer *host, const char *buffer, int length) +{ + m_new = false; + bool inObject = false; + bool inString = false; + bool escapeNext = false; + + for (int i = 0; i < length; i++) + { + char c = buffer[i]; + + if (m_beginChar == 0 && c == '{') + { + m_beginChar = '{'; + m_endChar = '}'; + } + else if (m_beginChar == 0 && c == '[') + { + m_beginChar = '['; + m_endChar = ']'; + } + + if (m_beginChar != 0) + { + m_buffer.push_back(c); + if (inObject) + { + if (!inString) + { + if (c == '"') + inString = true; + } + else + { + if (escapeNext) + { + escapeNext = false; + } + else + { + if (c == '\\') + escapeNext = true; + else if (c == '"') + inString = false; + } + } + } + if (!inString) + { + if (c == m_beginChar) + { + m_beginBrackets++; + inObject = true; + } + else if (c == m_endChar) + { + m_endBrackets++; + if (m_beginBrackets == m_endBrackets) + inObject = false; + } + } + if (m_beginBrackets > 0 && m_endBrackets > 0 && m_beginBrackets == m_endBrackets) + { + std::string line = CJSONRPC::MethodCall(m_buffer, host, this); + Send(line.c_str(), line.size()); + m_beginChar = m_beginBrackets = m_endBrackets = 0; + m_buffer.clear(); + } + } + } +} + +void CTCPServer::CTCPClient::Disconnect() +{ + if (m_socket > 0) + { + std::unique_lock lock(m_critSection); + shutdown(m_socket, SHUT_RDWR); + closesocket(m_socket); + m_socket = INVALID_SOCKET; + } +} + +void CTCPServer::CTCPClient::Copy(const CTCPClient& client) +{ + m_new = client.m_new; + m_socket = client.m_socket; + m_cliaddr = client.m_cliaddr; + m_addrlen = client.m_addrlen; + m_announcementflags = client.m_announcementflags; + m_beginBrackets = client.m_beginBrackets; + m_endBrackets = client.m_endBrackets; + m_beginChar = client.m_beginChar; + m_endChar = client.m_endChar; + m_buffer = client.m_buffer; +} + +CTCPServer::CWebSocketClient::CWebSocketClient(CWebSocket *websocket) +{ + m_websocket = websocket; + m_buffer.reserve(maxBufferLength); +} + +CTCPServer::CWebSocketClient::CWebSocketClient(const CWebSocketClient& client) + : CTCPServer::CTCPClient(client) +{ + *this = client; + m_buffer.reserve(maxBufferLength); +} + +CTCPServer::CWebSocketClient::CWebSocketClient(CWebSocket *websocket, const CTCPClient& client) +{ + Copy(client); + + m_websocket = websocket; + m_buffer.reserve(maxBufferLength); +} + +CTCPServer::CWebSocketClient::~CWebSocketClient() +{ + delete m_websocket; +} + +CTCPServer::CWebSocketClient& CTCPServer::CWebSocketClient::operator=(const CWebSocketClient& client) +{ + Copy(client); + + m_websocket = client.m_websocket; + m_buffer = client.m_buffer; + + return *this; +} + +void CTCPServer::CWebSocketClient::Send(const char *data, unsigned int size) +{ + const CWebSocketMessage *msg = m_websocket->Send(WebSocketTextFrame, data, size); + if (msg == NULL || !msg->IsComplete()) + return; + + std::vector frames = msg->GetFrames(); + for (unsigned int index = 0; index < frames.size(); index++) + CTCPClient::Send(frames.at(index)->GetFrameData(), (unsigned int)frames.at(index)->GetFrameLength()); +} + +void CTCPServer::CWebSocketClient::PushBuffer(CTCPServer *host, const char *buffer, int length) +{ + bool send; + const CWebSocketMessage *msg = NULL; + + if (m_buffer.size() + length > maxBufferLength) + { + CLog::Log(LOGINFO, "WebSocket: client buffer size {} exceeded", maxBufferLength); + return Disconnect(); + } + + m_buffer.append(buffer, length); + + const char* buf = m_buffer.data(); + size_t len = m_buffer.size(); + + do + { + if ((msg = m_websocket->Handle(buf, len, send)) != NULL && msg->IsComplete()) + { + std::vector frames = msg->GetFrames(); + if (send) + { + for (unsigned int index = 0; index < frames.size(); index++) + Send(frames.at(index)->GetFrameData(), (unsigned int)frames.at(index)->GetFrameLength()); + } + else + { + for (unsigned int index = 0; index < frames.size(); index++) + CTCPClient::PushBuffer(host, frames.at(index)->GetApplicationData(), (int)frames.at(index)->GetLength()); + } + + delete msg; + } + } + while (len > 0 && msg != NULL); + + if (len < m_buffer.size()) + m_buffer = m_buffer.substr(m_buffer.size() - len); + + if (m_websocket->GetState() == WebSocketStateClosed) + Disconnect(); +} + +void CTCPServer::CWebSocketClient::Disconnect() +{ + if (m_socket > 0) + { + if (m_websocket->GetState() != WebSocketStateClosed && m_websocket->GetState() != WebSocketStateNotConnected) + { + const CWebSocketFrame *closeFrame = m_websocket->Close(); + if (closeFrame) + Send(closeFrame->GetFrameData(), (unsigned int)closeFrame->GetFrameLength()); + } + + if (m_websocket->GetState() == WebSocketStateClosed) + CTCPClient::Disconnect(); + } +} -- cgit v1.2.3