Files
2026-08-24 10:03:24 +08:00

436 lines
14 KiB
C++

/*
* Copyright (C) 2014-2019 Amlogic, Inc. All rights reserved.
*
* All information contained herein is Amlogic confidential.
*
* This software is provided to you pursuant to Software License Agreement
* (SLA) with Amlogic Inc ("Amlogic"). This software may be used
* only in accordance with the terms of this agreement.
*
* Redistribution and use in source and binary forms, with or without
* modification is strictly prohibited without prior written permission from
* Amlogic.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define LOG_TAG "SubSocketServer"
#include "SocketServer.h"
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include "../SubtitleServer.h"
#include "DataSource.h"
#include "EventsTracker.h"
#include "IpcBuffer.h"
#include "Segment.h"
#include "SubtitleLog.h"
#define USE_UN true
#if USE_UN
#define UN_BASE_PATH "/tmp/subtitlesocketserver.sock"
#endif
/**
payload is:
startFlag : 4bytes
sessionID : 4Bytes (TBD)
magic : 4bytes (for double confirm)
payload Type: defined in PayloadType_t
payload size: 4Bytes (indicate the size of payload, size is total_send_bytes - 4*5)
exclude this and above 4 items.
payload data: TO BE PARSED data
*/
/**
payload is:
startFlag : 4bytes
sessionID : 4Bytes (TBD)
magic : 4bytes (for double confirm)
payload Type: defined in PayloadType_t
payload size: 4Bytes (indicate the size of payload, size is total_send_bytes - 4*5)
exclude this and above 4 items.
payload data: TO BE PARSED data
*/
static inline void dump(const char *buf, int size) {
char str[64] = {0};
for (int i = 0; i < size; i++) {
char chars[6] = {0};
sprintf(chars, "%02x ", buf[i]);
strcat(str, chars);
if (i % 8 == 7) {
SUBTITLE_LOGI("%s", str);
str[0] = str[1] = 0;
}
}
SUBTITLE_LOGI("%s", str);
}
static std::mutex _g_inst_mutex;
SubSocketServer::SubSocketServer() : mExitRequested(false) {
SUBTITLE_LOGI_FUNC_IN();
mEventsTracker = std::make_shared<EventsTracker>(SubSocketServer::handleEvent);
}
SubSocketServer::~SubSocketServer() {
SUBTITLE_LOGI_FUNC_IN();
if (mEventsTracker != nullptr) {
mEventsTracker->requestExit();
}
mExitRequested = true;
mDispatchThread.join();
}
SubSocketServer *SubSocketServer::mInstance = nullptr;
SubSocketServer *SubSocketServer::GetInstance() {
SUBTITLE_LOGI_FUNC_IN();
std::unique_lock<std::mutex> autolock(_g_inst_mutex);
if (mInstance == nullptr) {
mInstance = new SubSocketServer();
}
return mInstance;
}
int SubSocketServer::serve() {
std::thread t = std::thread(&SubSocketServer::__threadLoop, this);
t.detach();
return 0;
}
void SubSocketServer::__threadLoop() {
while (!mExitRequested && threadLoop());
SUBTITLE_LOGI("EXIT!!!");
}
bool SubSocketServer::threadLoop() {
SUBTITLE_LOGI_FUNC_IN();
int flag = 1;
int sockFd = socket(USE_UN ? AF_UNIX : AF_INET, SOCK_STREAM, 0);
if (sockFd < 0) return true;
if ((setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) < 0) {
SUBTITLE_LOGE("setsockopt failed.\n");
close(sockFd);
return true;
}
#if USE_UN
std::string unPath = std::string(UN_BASE_PATH);
struct sockaddr_un un{};
bzero(&un, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, unPath.c_str());
// Check if the socket is active, unlink if it is not active
if (access(unPath.c_str(), F_OK) == 0) {
int testFd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (testFd >= 0) {
if (connect(testFd, (struct sockaddr *)&un,
offsetof(struct sockaddr_un, sun_path) + unPath.size()) == 0) {
SUBTITLE_LOGE("socket path %s is already in use by another process.",
unPath.c_str());
close(testFd);
close(sockFd);
return false;
}
close(testFd);
}
unlink(unPath.c_str());
}
size_t sockLen = offsetof(struct sockaddr_un, sun_path) + unPath.size();
if (::bind(sockFd, (struct sockaddr *)&un, (socklen_t)sockLen) == -1) {
SUBTITLE_LOGE("bind as UN fail. error=%d, err:%s\n", errno, strerror(errno));
close(sockFd);
return false;
}
#else
struct sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (::bind(sockFd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
SUBTITLE_LOGE("bind as INET fail. error=%d, err:%s\n", errno, strerror(errno));
close(sockFd);
return false;
}
#endif
if (::listen(sockFd, QUEUE_SIZE) == -1) {
SUBTITLE_LOGE("listen fail.error=%d, err:%s\n", errno, strerror(errno));
close(sockFd);
return true;
}
mIsServing = true;
SUBTITLE_LOGI("[startServerThread] listen success.\n");
while (!mExitRequested) {
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
int connFd = accept(sockFd, (struct sockaddr *)&client_addr, &length);
if (connFd < 0) {
close(sockFd);
return true;
}
SUBTITLE_LOGI("New connection coming: fd=%d", connFd);
auto *dataObj = new DataObj_t;
dataObj->obj1 = this;
mEventsTracker->addFd(connFd, EventsTracker::EVENT_INPUT | EventsTracker::EVENT_ERROR,
dataObj);
}
SUBTITLE_LOGI("closed.\n");
close(sockFd);
mEventsTracker->requestExit();
mIsServing = false;
return true;
}
int SubSocketServer::handleEvent(int fd, int events, void *data) {
auto *dataObj = static_cast<DataObj_t *>(data);
if (dataObj == nullptr) {
SUBTITLE_LOGE("data DataObj_t == null.");
close(fd);
return EventsTracker::RET_REMOVE;
}
auto subSocketServer = static_cast<SubSocketServer *>(dataObj->obj1);
if (subSocketServer == nullptr) {
SUBTITLE_LOGE("data SubSocketServer == null.");
if (dataObj->obj2) {
delete dataObj->obj2;
}
dataObj->reset();
close(fd);
return EventsTracker::RET_REMOVE;
}
if (events & EventsTracker::EVENT_INPUT) {
SUBTITLE_LOGI("fd= %d, events= EVENT_INPUT", fd);
int ret = subSocketServer->processData(fd, dataObj);
if (ret == EventsTracker::RET_REMOVE) {
if (dataObj->obj2 != nullptr) {
delete dataObj->obj2;
}
dataObj->reset();
close(fd);
}
return ret;
} else if (events & EventsTracker::EVENT_ERROR) {
SUBTITLE_LOGE("Error occurred for fd %d, remove.", fd);
if (dataObj->obj2 != nullptr) {
delete dataObj->obj2;
}
dataObj->reset();
close(fd);
return EventsTracker::RET_REMOVE;
}
return EventsTracker::RET_CONTINUE;
}
int SubSocketServer::processData(int fd, DataObj_t *dataObj) {
if (!dataObj) {
SUBTITLE_LOGE("processData: dataObj is null");
return EventsTracker::RET_REMOVE;
}
// 1. Precondition for data
if (dataObj->obj2 == nullptr) {
SUBTITLE_LOGI("create new handle for fd %d", fd);
IpcBuffer *buf = new (std::nothrow) IpcBuffer(512 * 1024, "socket_buffer");
if (!buf) {
SUBTITLE_LOGE("new IpcBuffer failed");
return EventsTracker::RET_REMOVE;
}
dataObj->obj2 = buf;
} else {
SUBTITLE_LOGD("handle already created for fd %d", fd);
}
auto ipcBuffer = static_cast<IpcBuffer *>(dataObj->obj2);
if (ipcBuffer->_data_header == nullptr) {
ipcBuffer->_data_header = new (std::nothrow) IpcPackageHeader{};
if (!ipcBuffer->_data_header) {
SUBTITLE_LOGE("new IpcPackageHeader failed");
return EventsTracker::RET_REMOVE;
}
}
char recvBuf[1024] = {0};
int retLen = ::recv(fd, recvBuf, sizeof(recvBuf), 0);
if (retLen <= 0) {
int sessionId = ipcBuffer->_data_header->sessionId;
SUBTITLE_LOGI("Client broken, fd=%d, sessionId=%d, remove. errno = %m", fd, sessionId);
auto subSocketServer = static_cast<SubSocketServer *>(dataObj->obj1);
if (subSocketServer) subSocketServer->onRemoteDead(sessionId < 0 ? 0 : sessionId);
return EventsTracker::RET_REMOVE;
}
if (mClients.empty()) {
SUBTITLE_LOGE("mClients is empty, do not process now.");
return EventsTracker::RET_CONTINUE;
}
// 2. Process data
ipcBuffer->write(recvBuf, retLen);
if (ipcBuffer->readableCount() < sizeof(IpcPackageHeader)) {
SUBTITLE_LOGE("No enough data for Header.");
return EventsTracker::RET_CONTINUE;
}
// Check sync word.
int sizeOfInt = sizeof(int);
char buffer[sizeof(IpcPackageHeader)] = {0};
ipcBuffer->peek(buffer, sizeOfInt);
int syncWord = peekAsSocketWord(buffer);
if (syncWord == START_FLAG) {
if (ipcBuffer->readableCount() < sizeof(IpcPackageHeader)) {
SUBTITLE_LOGE("ReadableCount too small for full header");
return EventsTracker::RET_CONTINUE;
}
ipcBuffer->read(buffer, sizeOfInt);
ipcBuffer->read(buffer + sizeOfInt, sizeof(IpcPackageHeader) - sizeOfInt);
ipcBuffer->_data_header->syncWord = syncWord;
ipcBuffer->_data_header->sessionId = peekAsSocketWord(buffer + sizeOfInt);
ipcBuffer->_data_header->magicWord = peekAsSocketWord(buffer + sizeOfInt * 2);
ipcBuffer->_data_header->dataSize = peekAsSocketWord(buffer + sizeOfInt * 3);
ipcBuffer->_data_header->pkgType = peekAsSocketWord(buffer + sizeOfInt * 4);
SUBTITLE_LOGD(
"Find header:\n"
"struct IpcPackageHeader {\n"
" int syncWord = %d;\n"
" int sessionId = %d;\n"
" int magicWord = %d;\n"
" int dataSize = %d;\n"
" int pkgType = %d;\n"
"};",
ipcBuffer->_data_header->syncWord, ipcBuffer->_data_header->sessionId,
ipcBuffer->_data_header->magicWord, ipcBuffer->_data_header->dataSize,
ipcBuffer->_data_header->pkgType);
const int TYPE_XEDC = 0x58454443; // Reverse of eTypeSubtitleExitServ
if (eTypeSubtitleExitServ == ipcBuffer->_data_header->pkgType ||
TYPE_XEDC == ipcBuffer->_data_header->pkgType) {
SUBTITLE_LOGI("exit requested!");
return EventsTracker::RET_REMOVE;
}
}
if (ipcBuffer->readableCount() < ipcBuffer->_data_header->dataSize) {
// Maybe data is coming.
SUBTITLE_LOGI("Buffer readableCount(%d) < Needed data size(%d), waiting more data.",
ipcBuffer->readableCount(), ipcBuffer->_data_header->dataSize);
return EventsTracker::RET_CONTINUE;
}
char *payloads = (char *)malloc(ipcBuffer->_data_header->dataSize + sizeOfInt);
if (payloads == nullptr) {
SUBTITLE_LOGE("Memory allocation failed for payloads.");
return EventsTracker::RET_REMOVE;
}
memcpy(payloads, &ipcBuffer->_data_header->pkgType, sizeOfInt); // fill package type
ipcBuffer->read(payloads + sizeOfInt, ipcBuffer->_data_header->dataSize);
{ // notify listener
std::lock_guard<std::mutex> guard(mLock);
DataListener *listener = mClients.front();
if (listener != nullptr) {
if (listener->onData(payloads, ipcBuffer->_data_header->dataSize + sizeOfInt) < 0) {
// if returen -1, means client exit, this request clientConnected exit!
free(payloads);
return EventsTracker::RET_REMOVE;
}
}
}
free(payloads);
ipcBuffer->reset();
return EventsTracker::RET_CONTINUE;
}
bool SubSocketServer::registClient(DataListener *client) {
std::lock_guard<std::mutex> guard(GetInstance()->mLock);
GetInstance()->mClients.push_back(client);
SUBTITLE_LOGI("registClient: %p size=%zu", client, GetInstance()->mClients.size());
return true;
}
bool SubSocketServer::unregisterClient(DataListener *client) {
// obviously, BUG here! impl later, support multi-client.
// TODO: revise the whole mClient, if we want to support multi subtitle
std::lock_guard<std::mutex> guard(GetInstance()->mLock);
std::vector<DataListener *> &vecs = GetInstance()->mClients;
if (vecs.size() > 0) {
for (auto it = vecs.cbegin(); it != vecs.cend(); it++) {
if ((*it) == client) {
vecs.erase(it);
break;
}
}
// GetInstance()->mClients.pop_back();
SUBTITLE_LOGI("unregisterClient: %p size=%zu", client, GetInstance()->mClients.size());
}
return true;
}
void SubSocketServer::onRemoteDead(int sessionId) {
SUBTITLE_LOGI("onRemoteDead, sessionId= %d", sessionId);
SubtitleServer *server = SubtitleServer::Instance();
if (server && !server->isClosed(sessionId)) {
if (server->close(sessionId) == Result::OK &&
server->closeConnection(sessionId) == Result::OK) {
SUBTITLE_LOGI("onRemoteDead, self close OK for id %d", sessionId);
} else {
SUBTITLE_LOGI("onRemoteDead, self close failed for id %d", sessionId);
}
} else {
SUBTITLE_LOGI("onRemoteDead, already closed for id %d", sessionId);
}
}