/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(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(data); if (dataObj == nullptr) { SUBTITLE_LOGE("data DataObj_t == null."); close(fd); return EventsTracker::RET_REMOVE; } auto ipcSocket = static_cast(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 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 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(data); auto ipcSocket = static_cast(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 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 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 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 lock(mRpcMutex); auto blockChannel = std::make_shared>(); EventReceivedListener tmpListener = nullptr; { std::lock_guard 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 lk(mListenerMutex); mEventReceivedListenerFunc = tmpListener; } return ret; }