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

525 lines
16 KiB
C++

/*
* Copyright (C) 2014-2025 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 "IpcSocket"
#include <BlockChannel.h>
#include <IpcSocket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <zconf.h>
#include <cstring>
#include <memory>
#include <mutex>
#include "SubtitleLog.h"
#define SEND_MAX_LEN 1024
// For local socket
#define USE_UN true
#if USE_UN
#define UN_BASE_PATH "/tmp/subtitle.sock."
#endif
const int eventInputOnly = EventsTracker::EVENT_INPUT | EventsTracker::EVENT_ERROR;
static std::mutex mMutex;
static double now(int clockId = CLOCK_MONOTONIC) {
struct timespec spec;
clock_gettime(clockId, &spec);
return spec.tv_sec;
}
IpcSocket::IpcSocket(bool isServer) : mIsServer(isServer), mIsValid(false), mListenFd(-1) {
SUBTITLE_LOGI("+++ (%s)", (mIsServer ? "Server" : "Client"));
}
IpcSocket::~IpcSocket() {
SUBTITLE_LOGI("-- (%s)", (mIsServer ? "Server" : "Client"));
mSelfData = nullptr;
close(mListenFd);
mListenFd = -1;
}
void IpcSocket::start(int port, void *selfData, OnThreadStart onThreadStart) {
mEventsTracker =
std::make_shared<EventsTracker>(IpcSocket::handleEvents, "IpcSocket", onThreadStart);
mSelfData = selfData;
if (mIsServer) {
mIsValid = initAsServer(port);
} else {
mIsValid = initAsClient(port);
}
SUBTITLE_LOGI("Init %s(%d) %s", (mIsServer ? "Server" : "Client"), port,
(mIsValid ? "success" : "failed"));
}
bool IpcSocket::isvalid() {
return mIsValid;
}
bool IpcSocket::initAsServer(int port) {
int flag = 1;
int sockFd = socket(USE_UN ? AF_UNIX : AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (sockFd < 0) return false;
if ((setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) < 0) {
SUBTITLE_LOGE("setsockopt failed.\n");
close(sockFd);
return false;
}
#if USE_UN
std::string unPath = std::string(UN_BASE_PATH).append(std::to_string(port));
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;
}
if (chmod(unPath.c_str(), 0775) == -1) {
SUBTITLE_LOGE("chmod fail: %m\n");
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, 10) == -1) {
SUBTITLE_LOGE("listen fail.error=%d, err:%s\n", errno, strerror(errno));
close(sockFd);
return false;
}
mListenFd = sockFd;
DataObj_t *dataObj = new DataObj_t;
dataObj->obj1 = this;
dataObj->obj2 = mSelfData;
mEventsTracker->addFd(mListenFd, eventInputOnly, dataObj);
return true;
}
bool IpcSocket::initAsClient(int port) {
#if USE_UN
std::string unPath = std::string(UN_BASE_PATH).append(std::to_string(port));
mListenFd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (mListenFd < 0) {
SUBTITLE_LOGE("%s, socket creation failed! errno=%d, err=%m", __FILE__, errno);
return false;
}
struct sockaddr_un un{};
bzero(&un, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, unPath.c_str());
size_t sockLen = offsetof(struct sockaddr_un, sun_path) + unPath.size();
int ret = ::connect(mListenFd, (struct sockaddr *)&un, (socklen_t)sockLen);
if (ret < 0 && errno != EINPROGRESS) {
SUBTITLE_LOGE("%s, connect failed immediately! errno=%d, err=%m", __FILE__, errno);
close(mListenFd);
mListenFd = -1;
return false;
}
struct pollfd pfd;
pfd.fd = mListenFd;
pfd.events = POLLOUT;
int pollRet = poll(&pfd, 1, 120);
if (pollRet <= 0) {
if (pollRet == 0) {
SUBTITLE_LOGE("%s, connect timeout after 120ms", __FILE__);
} else {
SUBTITLE_LOGE("%s, poll error! errno=%d, err=%m", __FILE__, errno);
}
close(mListenFd);
mListenFd = -1;
return false;
}
int err = 0;
socklen_t len = sizeof(err);
if (getsockopt(mListenFd, SOL_SOCKET, SO_ERROR, &err, &len) < 0 || err != 0) {
SUBTITLE_LOGE("%s, connect failed after poll! errno=%d, err=%m", __FILE__, err);
close(mListenFd);
mListenFd = -1;
return false;
}
#else
mListenFd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr{};
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (mListenFd < 0) {
SUBTITLE_LOGE("%s, create socket failed!mSockFd:%d, error=%d, err:%m", __FILE__, mListenFd,
errno);
return false;
}
int64_t startTime = now();
while (true) {
if (::connect(mListenFd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
// load dvb so need time since add close caption subtitle, add to 120ms.
// only has subtitle to connect socket
if (now() - startTime > 120000ll) {
SUBTITLE_LOGE("%s, connect socket failed!, error=%d, err:%m", __FILE__, errno);
close(mListenFd);
mListenFd = -1;
return false;
}
} else {
break;
}
}
#endif
SUBTITLE_LOGI("%s, connect socket success!mSockFd:%d\n", __FILE__, mListenFd);
DataObj_t *dataObj = new DataObj_t;
dataObj->obj1 = this;
dataObj->obj2 = mSelfData;
mEventsTracker->addFd(mListenFd, eventInputOnly, dataObj);
return true;
}
int IpcSocket::handleEvents(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 ipcSocket = static_cast<IpcSocket *>(dataObj->obj1);
if (ipcSocket == nullptr) {
close(fd);
dataObj->reset();
delete dataObj;
dataObj = nullptr;
return EventsTracker::RET_REMOVE;
}
if (events & EventsTracker::EVENT_ERROR) {
SUBTITLE_LOGE("Error for %s fd(%d)", (ipcSocket->mIsServer ? "Server" : "Client"), fd);
close(fd);
dataObj->reset();
delete dataObj;
dataObj = nullptr;
return EventsTracker::RET_REMOVE;
}
SUBTITLE_LOGI("handleEvents: %s", (ipcSocket->mIsServer ? "Server" : "Client"));
int ret;
if (ipcSocket->mIsServer) {
ret = ipcSocket->handleServerEvents(fd, events, data);
} else {
ret = ipcSocket->handleClientEvents(fd, events, dataObj->obj2);
}
if (ret == EventsTracker::RET_REMOVE) {
close(fd);
dataObj->reset();
delete dataObj;
}
return ret;
}
bool IpcSocket::processListenFd(int fd) {
int connFd;
#if USE_UN
struct sockaddr_un un{};
socklen_t length = sizeof(un);
connFd = accept(fd, (struct sockaddr *)&un, &length);
if (connFd < 0) {
SUBTITLE_LOGE("accept fd(%d) failed", fd);
close(fd);
}
#else
struct sockaddr_in client_addr{};
socklen_t length = sizeof(client_addr);
connFd = accept(fd, (struct sockaddr *)&client_addr, &length);
if (connFd < 0) {
SUBTITLE_LOGE("accept fd(%d) failed", fd);
close(fd);
return false;
}
#endif
// Set new connections to non-blocking
int flags = fcntl(connFd, F_GETFL, 0);
if (flags < 0) {
SUBTITLE_LOGE("fcntl F_GETFL failed: %m");
close(connFd);
return false;
}
if (fcntl(connFd, F_SETFL, flags | O_NONBLOCK) < 0) {
SUBTITLE_LOGE("fcntl F_SETFL O_NONBLOCK failed: %m");
close(connFd);
return false;
}
SUBTITLE_LOGI("New connection coming: fd=%d", connFd);
auto *dataObj = new DataObj_t;
dataObj->obj1 = this;
dataObj->obj2 = mSelfData;
mEventsTracker->addFd(connFd, eventInputOnly, dataObj);
return true;
}
int IpcSocket::processFdFromClient(int fd, int events, void *data) {
EventReceivedListener listener;
{
std::lock_guard<std::mutex> lk(mListenerMutex);
listener = mEventReceivedListenerFunc;
}
if (listener != nullptr) {
if (events & EventsTracker::EVENT_INPUT) {
return listener(fd, data);
}
return EventsTracker::RET_CONTINUE;
}
// No need for don't receive events' fd.
return EventsTracker::RET_REMOVE;
}
int IpcSocket::handleClientEvents(int fd, int events, void *data) {
EventReceivedListener listener;
{
std::lock_guard<std::mutex> lk(mListenerMutex);
listener = mEventReceivedListenerFunc;
}
if (listener != nullptr && (events & EventsTracker::EVENT_INPUT)) {
return listener(fd, data);
}
// Continued for client when no EventListener
return EventsTracker::RET_CONTINUE;
}
int IpcSocket::handleServerEvents(int fd, int events, void *data) {
auto *dataObj = static_cast<DataObj_t *>(data);
auto ipcSocket = static_cast<IpcSocket *>(dataObj->obj1);
if (fd == ipcSocket->mListenFd) {
SUBTITLE_LOGI("connect client coming");
bool ok = ipcSocket->processListenFd(fd);
// Do not hold self data anymore when success to
// deliver it to message fd.
dataObj->obj2 = nullptr;
if (!ok) {
return EventsTracker::RET_REMOVE;
}
} else {
return ipcSocket->processFdFromClient(fd, events, dataObj->obj2);
}
return EventsTracker::RET_CONTINUE;
}
bool IpcSocket::send(int fd, const char *data, int size) {
std::lock_guard<std::mutex> guard(mMutex);
int sendLen = 0;
int retLen = 0;
int leftLen = size;
const char *sendBuf = data;
if (fd <= 0) return false;
while (leftLen > 0) {
sendLen = (leftLen > SEND_MAX_LEN) ? SEND_MAX_LEN : leftLen;
retLen = ::send(fd, sendBuf, sendLen, MSG_NOSIGNAL | MSG_DONTWAIT);
if (retLen > 0) {
sendBuf += retLen;
leftLen -= retLen;
} else if (retLen < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
struct pollfd pfd{};
pfd.fd = fd;
pfd.events = POLLOUT;
int pollRet = poll(&pfd, 1, 120);
if (pollRet <= 0) {
SUBTITLE_LOGE("send timeout or poll error, errno=%d", errno);
return false;
}
} else if (retLen < 0 && errno == EINTR) {
continue;
} else {
SUBTITLE_LOGE("send socket failed, errno=%d, err:%m", errno);
return false;
}
}
return true;
}
bool IpcSocket::receive(int fd, char *data, int size) {
std::lock_guard<std::mutex> guard(mMutex);
int retlen = 0;
if (fd >= 0) {
retlen = ::recv(fd, data, size, 0);
if (retlen < 0) {
if (errno == EINTR)
retlen = 0;
else {
SUBTITLE_LOGE("%s, receive socket failed!", __FILE__);
return false;
}
} else if (retlen == 0) {
SUBTITLE_LOGI("%s, recv() returned 0, connection closed.", __FILE__);
return false;
}
}
return retlen > 0;
}
void IpcSocket::join() {
if (mEventsTracker != nullptr) {
mEventsTracker->join();
}
}
void IpcSocket::setEventListener(EventReceivedListener eventListener) {
std::lock_guard<std::mutex> lk(mListenerMutex);
mEventReceivedListenerFunc = eventListener;
}
void IpcSocket::stop() {
if (mEventsTracker != nullptr) {
mEventsTracker->requestExit();
}
}
std::string IpcSocket::sendForResult(int fd, const char *data, int size) {
// Lock ensures serial access and prevents mEventReceivedListenerFunc
// from being overwritten when amlsub_UpdateVideoPos is called frequently,
// avoiding callback mix-ups and blocking.
std::lock_guard<std::mutex> lock(mRpcMutex);
auto blockChannel = std::make_shared<BlockChannel<std::string>>();
EventReceivedListener tmpListener = nullptr;
{
std::lock_guard<std::mutex> lk(mListenerMutex);
if (mEventReceivedListenerFunc != nullptr) {
// Giving back after result is gotten
tmpListener = mEventReceivedListenerFunc;
}
mEventReceivedListenerFunc = [blockChannel](int fd, void * /*selfData*/) -> int {
char retBuf[1024];
if (IpcSocket::receive(fd, retBuf, sizeof(retBuf))) {
SUBTITLE_LOGI("Client received: %s", retBuf);
blockChannel->put(retBuf);
} else {
SUBTITLE_LOGE("Server broken, remove");
blockChannel->put("-1");
return EventsTracker::RET_REMOVE;
}
return EventsTracker::RET_CONTINUE;
};
}
std::string ret;
if (IpcSocket::send(fd, data, size)) {
std::string tmp;
if (blockChannel->poll(tmp, 500)) {
ret = tmp;
} else {
SUBTITLE_LOGE("timeout waiting for server response");
ret = std::string("");
}
} else {
ret = std::string("");
}
{
std::lock_guard<std::mutex> lk(mListenerMutex);
mEventReceivedListenerFunc = tmpListener;
}
return ret;
}