diff --git a/Check/Check.pro b/Check/Check.pro index 1683211..b2fec1f 100644 --- a/Check/Check.pro +++ b/Check/Check.pro @@ -1,6 +1,6 @@ QT += core -TARGET = check_update +TARGET = ATBUpdateCheck VERSION="1.0.0" win32 { @@ -63,9 +63,13 @@ contains( CONFIG, DesktopLinux ) { } SOURCES += \ - main.cpp + main.cpp \ + ismas_client.cpp \ + message_handler.cpp -# HEADERS += \ +HEADERS += \ + ismas_client.h \ + message_handler.h ########################################################################################## # for running program on target through QtCreator diff --git a/Check/ismas_client.cpp b/Check/ismas_client.cpp new file mode 100644 index 0000000..ae34314 --- /dev/null +++ b/Check/ismas_client.cpp @@ -0,0 +1,364 @@ +#ifdef __WIN32__ +#error "WIN32 NOT SUPPORTED" +#else + +#include "ismas_client.h" + +#include +#include + +#include +#include // inet_addr() +#include +#include +#include +#include +#include +#include +#include // bzero() +#include +#include // read(), write(), close() +#include + + +#include +#include +#include + +#if 0 +######################## +# Spec vom 27.10.2023: +# U0010 -> %-Werte +# U0001 -> 100% +# U0003 -> "FAIL" +# U0002 -> "" (OK -> WAIT state reset) +# ISMAS -> "WAIT" +######################## +# +# $1: EVENT: U0001 update finished: 100% +# U0002 reset TRG +# U0003 error +# U0010 for update process +# $2: PERCENT : "only for ISMAS: 0-100%", +# $3: RESULTCODE : "only for ISMAS", +# 0: Success +# 1: no Update nessesary +# 2: Backup failed +# 3: Package error/ Wrong package +# 4: Install Error +# $4: STEP : "running step (only for us): update_psa...", +# $5: STEP_RESULT : "error and result text", +# $6: VERSION : "opkg and conf info; what will be updated" +# +#endif + +#include +#include + +void IsmasClient::printDebugMessage(int port, + QString const &clientIP, + int clientPort, + QString const &message) { +#if 0 + Q_UNUSED(port); + Q_UNUSED(clientIP); + Q_UNUSED(clientPort); + Q_UNUSED(message); +#else + qDebug().noquote() + << "\n" + << "SEND-REQUEST-RECEIVE-RESPONSE ..." << "\n" + << "hostname ........" << "127.0.0.1" << "\n" + << "port ............" << port << "\n" + << "local address ..." << clientIP << "\n" + << "local port ......" << clientPort << "\n" + << message; +#endif +} + +void IsmasClient::printInfoMessage(int port, + QString const &clientIP, + int clientPort, + QString const &message) { +#if 0 + Q_UNUSED(port); + Q_UNUSED(clientIP); + Q_UNUSED(clientPort); + Q_UNUSED(message); +#else + qInfo().noquote() + << "\n" + << "SEND-REQUEST-RECEIVE-RESPONSE ..." << "\n" + << "hostname ........" << "127.0.0.1" << "\n" + << "port ............" << port << "\n" + << "local address ..." << clientIP << "\n" + << "local port ......" << clientPort << "\n" + << message; +#endif +} + +void IsmasClient::printErrorMessage(int port, + QString const &clientIP, + int clientPort, + QString const &message) { + qCritical().noquote() + << "\n" + << "SEND-REQUEST-RECEIVE-RESPONSE ..." << "\n" + << "hostname ........" << "127.0.0.1" << "\n" + << "port ............" << port << "\n" + << "local address ..." << clientIP << "\n" + << "local port ......" << clientPort << "\n" + << message; +} + +std::optional +IsmasClient::sendRequestReceiveResponse(int port, QString const &request) { + + qInfo() << "REQUEST" << request; + + int sockfd; + int r; + errno = 0; + // socket create and verification + if ((sockfd = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) { + qCritical().noquote() + << "\n" + << "SEND-REQUEST-RECEIVE-RESPONSE ..." << "\n" + << "SOCKET CREATION FAILED (" << strerror(errno) << ")"; + return std::nullopt; + } + + struct sockaddr_in servAddr; + bzero(&servAddr, sizeof(servAddr)); + // assign IP, PORT + servAddr.sin_family = AF_INET; + servAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + servAddr.sin_port = htons(port); + // connect the client socket to server socket + if ((r = ::connect(sockfd, (struct sockaddr *)(&servAddr), sizeof(servAddr))) != 0) { + qCritical().noquote() + << "\n" + << "SEND-REQUEST-RECEIVE-RESPONSE ..." << "\n" + << "CONNECTION WITH SERVER FAILED (" << strerror(r) << ")"; + ::close(sockfd); + return std::nullopt; + } + + struct sockaddr_in clientAddr; + bzero(&clientAddr, sizeof(clientAddr)); + socklen_t sockLen = sizeof(clientAddr); + + char clientIP[16]; + bzero(&clientIP, sizeof(clientIP)); + getsockname(sockfd, (struct sockaddr *)(&clientAddr), &sockLen); + inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP)); + unsigned int clientPort = ntohs(clientAddr.sin_port); + + printDebugMessage(port, clientIP, clientPort, QString("CONNECTED TO SERVER")); + + struct timeval tv; + tv.tv_sec = 10; /* 10 secs timeout for read and write */ + + struct linger so_linger; + so_linger.l_onoff = 1; + so_linger.l_linger = 0; + + int maxfdp1; + fd_set rset; + fd_set wset; + + setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)); + // no reliable, but does not harm, as we use select() as well + setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + int flag = 1; + setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)); + + static char buf[1024*8]; + bzero(buf, sizeof(buf)); + int const bytesToWrite = strlen(request.toStdString().c_str()); + strncpy(buf, request.toStdString().c_str(), sizeof(buf)-1); + + int loop = 0; + int bytesWritten = 0; + while (bytesWritten < bytesToWrite) { + errno = 0; + FD_ZERO(&wset); + FD_SET(sockfd, &wset); + maxfdp1 = sockfd + 1; + tv.tv_sec = 60; /* 60 secs timeout for read and write -> APISM cuts the connection after 30s */ + tv.tv_usec = 0; + + int const w = select(maxfdp1, NULL, &wset, NULL, &tv); + if (w < 0) { // error + if (errno == EINTR) { + printErrorMessage(port, clientIP, clientPort, + QString("INTERRUPTED BY SIGNAL (1) (") + strerror(errno) + ")"); + continue; + } else { + printErrorMessage(port, clientIP, clientPort, + QString("SELECT-ERROR (WRITE) %1(").arg(loop) + strerror(errno) + ")"); + ::close(sockfd); + return std::nullopt; + } + } else + if (w == 0) { // timeout + printErrorMessage(port, clientIP, clientPort, + QString("SELECT-TIMEOUT (WRITE) %1(").arg(loop) + strerror(errno) + ")"); + if (++loop < 10) { + QThread::msleep(500); + continue; + } + ::close(sockfd); + return std::nullopt; + } else + if (w > 0) { + int n = ::sendto(sockfd, buf+bytesWritten, bytesToWrite-bytesWritten, 0, NULL, 0); + if (n >= 0) { + bytesWritten += n; + } else { + if (errno == EWOULDBLOCK) { + if (++loop < 10) { + QThread::msleep(500); + continue; + } + printErrorMessage(port, clientIP, clientPort, + QString("WRITE TIMEOUT %1(").arg(loop) + strerror(errno) + ")"); + ::close(sockfd); + return std::nullopt; + } else + if (errno == EINTR) { + printErrorMessage(port, clientIP, clientPort, + QString("WRITE INTERRUPTED BY SIGNAL (1) (") + strerror(errno) + ")"); + continue; + } + } + } + } + + // DO NOT USE SHUTDOWN! APISM CAN NOT COPE WITH IT + // errno = 0; + // if (shutdown(sockfd, SHUT_WR) < 0) { + // printErrorMessage(port, clientIP, clientPort, + // QString("CANNOT CLOSE WRITING END (") + strerror(errno) + ")"); + // } + + printInfoMessage(port, clientIP, clientPort, QString("MESSAGE SENT <<<") + buf + ">>>"); + + loop = 0; + bzero(buf, sizeof(buf)); + int bytesToRead = sizeof(buf)-1; + int bytesRead = 0; + while (bytesRead < bytesToRead) { + errno = 0; + FD_ZERO(&rset); + FD_SET(sockfd, &rset); + maxfdp1 = sockfd + 1; + tv.tv_sec = 60; /* 60 secs timeout for read and write */ + tv.tv_usec = 0; + + QString const selectStart = QDateTime::currentDateTime().toString(Qt::ISODateWithMs); + + int const r = select(maxfdp1, &rset, NULL, NULL, &tv); + if (r < 0) { // error + if (errno == EINTR) { + printErrorMessage(port, clientIP, clientPort, + QString("INTERRUPTED BY SIGNAL (2) (") + strerror(errno) + ")"); + continue; + } else { + printErrorMessage(port, clientIP, clientPort, + QString("SELECT-ERROR (READ) %1(").arg(loop) + strerror(errno) + ")"); + ::close(sockfd); + return std::nullopt; + } + } else + if (r == 0) { // timeout + printErrorMessage(port, clientIP, clientPort, + QString("SELECT-TIMEOUT (READ) %1(").arg(loop) + strerror(errno) + ")"); + if (++loop < 10) { + QThread::msleep(500); + continue; + } + ::close(sockfd); + return std::nullopt; + } else + if (r > 0) { + if (FD_ISSET(sockfd, &rset)) { + int n = ::recvfrom(sockfd, buf+bytesRead, bytesToRead-bytesRead, + 0, NULL, NULL); + if (n > 0) { // + bytesRead += n; + } else + if (n == 0) { + // The return value will be 0 when the peer has performed an orderly shutdown. + printErrorMessage(port, clientIP, clientPort, + QString("PEER CLOSED CONNECTION (") + strerror(errno) + ") START AT" + + selectStart + " NOW " + QDateTime::currentDateTime().toString(Qt::ISODateWithMs)); + ::close(sockfd); + return std::nullopt; + } else + if (n < 0) { + if (errno == EWOULDBLOCK) { // check just in case + if (++loop < 10) { + QThread::msleep(500); + continue; + } + printErrorMessage(port, clientIP, clientPort, + QString("READ TIMEOUT %1(").arg(loop) + strerror(errno) + ")"); + ::close(sockfd); + return std::nullopt; + } + if (errno == EINTR) { + printErrorMessage(port, clientIP, clientPort, + QString("INTERRUPTED BY SIGNAL (2) (") + strerror(errno) + ")"); + continue; + } + } + } + } + + // printInfoMessage(port, clientIP, clientPort, QString("MESSAGE RECEIVED ") + buf); + QString response(buf); + + if (int idx = response.indexOf("{\"error\":\"ISMAS is offline\"}")) { + response = response.mid(0, idx); + } else + if (response.contains("RECORD")) { // RECORD SAVED or RECORD WRITE ABORTED + printInfoMessage(port, clientIP, clientPort, QString("IGNORED '") + response + "' RESPONSE"); + ::close(sockfd); + return std::nullopt; + } + + QJsonParseError parseError; + QJsonDocument document(QJsonDocument::fromJson(response.toUtf8(), &parseError)); + if (parseError.error == QJsonParseError::NoError) { + if (document.isObject()) { // done: received valid APISM response + printInfoMessage(port, clientIP, clientPort, + QString("VALID APISM RESPONSE .. \n") + response); + ::close(sockfd); + return response; + } else { + printInfoMessage(port, clientIP, clientPort, + QString("CORRUPTED RESPONSE ") + response); + ::close(sockfd); + return std::nullopt; + } + } else { + if (!response.contains("RECORD")) { + // maybe APISM does not send valid JSON: "RECORD SAVED" etc. + printDebugMessage(port, clientIP, clientPort, + QString("PARSE ERROR ") + response + " " + parseError.errorString()); + } + ::close(sockfd); + return std::nullopt; + } + } + return std::nullopt; +} + +char const *IsmasClient::reason[REASON::ENTRIES] = { + "TIME-TRIGGERED", "SERVICE", "DEV-TEST" +}; + +#endif // __WIN32__ diff --git a/Check/ismas_client.h b/Check/ismas_client.h new file mode 100644 index 0000000..4c38c78 --- /dev/null +++ b/Check/ismas_client.h @@ -0,0 +1,51 @@ +#ifndef ISMAS_CLIENT_H_INCLUDED +#define ISMAS_CLIENT_H_INCLUDED + +#include +#include +#include + +class IsmasClient : public QObject { + Q_OBJECT + +public: + explicit IsmasClient() = default; + + enum APISM { + DB_PORT = 7777, + DIRECT_PORT = 7778 + }; + + enum RESULT_CODE { + SUCCESS=0, + // if between 00:00 - 04:00 Wait-button state not WAIT, then we assume + // that's an automatic nightly (not-necessary) update + NO_UPDATE_NECESSARY=1, + // if APISM reports the ISMAS is not available (15x, 6s delay each) + ISMAS_NO_CONNECTION_ERROR=2, + // if not within 00:00-04:00 and WAIT-button was not in state WAIT + ISMAS_TRIGGER_ERROR=3, + }; + + enum REASON { + TIME_TRIGGERED = 0, + SERVICE, + DEV_TEST, + ENTRIES + }; + + static char const *reason[REASON::ENTRIES]; + + static std::optional + sendRequestReceiveResponse(int port, QString const &request); + + private: + static void printDebugMessage(int port, QString const &clientIP, int clientPort, + QString const &message); + static void printInfoMessage(int port, QString const &clientIP, int clientPort, + QString const &message); + static void printErrorMessage(int port, QString const &clientIP, int clientPort, + QString const &message); +}; + +#endif // ISMAS_CLIENT_H_INCLUDED diff --git a/Check/main.cpp b/Check/main.cpp index 627d253..b670f3b 100644 --- a/Check/main.cpp +++ b/Check/main.cpp @@ -9,6 +9,43 @@ #include #include +#include "commandline_parser.h" +#include "message_handler.h" + int main(int argc, char **argv) { + QByteArray const value = qgetenv("LC_ALL"); + if (value.isEmpty() || value != "C") { + qputenv("LC_ALL", "C"); + } + + + // qputenv("XDG_RUNTIME_DIR", "/var/run/user/0"); + + openlog("ATB-UPDATE_CHECK", LOG_PERROR | LOG_PID | LOG_CONS, LOG_USER); + + QCoreApplication a(argc, argv); + QCoreApplication::setApplicationName("ATBUpdateCheck"); + QCoreApplication::setApplicationVersion(APP_VERSION); + + if (!messageHandlerInstalled()) { // change internal qt-QDebug-handling + atbInstallMessageHandler(nullptr); + //atbInstallMessageHandler(atbDebugOutput); + setDebugLevel(LOG_NOTICE); + } + + QCommandLineParser parser; + QCommandLineOption ismasConnectOption("ismas-connected"); + QCommandLineOption updateRequestedOption("update-requested"); + parser.addOption(ismasConnectOption); + parser.addOption(updateRequestedOption); + parser.process(a); + + if (parser.isSet(ismasConnectOption)) { + qCritical() << parser.isSet(ismasConnectOption); + } else + if (parser.isSet(updateRequestedOption)) { + qCritical() << parser.isSet(updateRequestedOption); + } + return 0; } diff --git a/Check/message_handler.cpp b/Check/message_handler.cpp new file mode 100755 index 0000000..996bd2f --- /dev/null +++ b/Check/message_handler.cpp @@ -0,0 +1,97 @@ +#include "message_handler.h" + +#include +#include +#include +#include +#include + + +static char const *DBG_NAME[] = { "DBG ", "WARN ", "CRIT ", "FATAL", "INFO " }; +static bool installedMsgHandler = false; +static int debugLevel = LOG_NOTICE; + +int getDebugLevel() { return debugLevel; } +void setDebugLevel(int newDebugLevel) { + debugLevel = newDebugLevel; +} + +bool messageHandlerInstalled() { + return installedMsgHandler; +} + +QtMessageHandler atbInstallMessageHandler(QtMessageHandler handler) { + installedMsgHandler = (handler != 0); + static QtMessageHandler prevHandler = nullptr; + if (handler) { + prevHandler = qInstallMessageHandler(handler); + return prevHandler; + } else { + return qInstallMessageHandler(prevHandler); + } +} + +/// +/// \brief Print message according to given debug level. +/// +/// \note Install this function using qInstallMsgHandler(). +/// +/// int main(int argc, char **argv) { +/// installMsgHandler(atbDebugOutput); +/// QApplication app(argc, argv); +/// ... +/// return app.exec(); +/// } +/// +#if (QT_VERSION > QT_VERSION_CHECK(5, 0, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +void atbDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + Q_UNUSED(context); + QString const localMsg = QString(DBG_NAME[type]) + msg.toLocal8Bit(); + + switch (debugLevel) { + case LOG_DEBUG: { // debug-level message + syslog(LOG_DEBUG, "%s", localMsg.toStdString().c_str()); + } break; + case LOG_INFO: { // informational message + if (type != QtDebugMsg) { + syslog(LOG_DEBUG, "%s", localMsg.toStdString().c_str()); + } + } break; + case LOG_NOTICE: { // normal, but significant, condition + if (type != QtDebugMsg) { + syslog(LOG_DEBUG, "%s", localMsg.toStdString().c_str()); + } + } break; + case LOG_WARNING: { // warning conditions + if (type != QtInfoMsg && type != QtDebugMsg) { + syslog(LOG_DEBUG, "%s", localMsg.toStdString().c_str()); + } + } break; + case LOG_ERR: { // error conditions + if (type != QtInfoMsg && type != QtDebugMsg && type != QtWarningMsg) { + syslog(LOG_DEBUG, "%s", localMsg.toStdString().c_str()); + } + } break; + case LOG_CRIT: { // critical conditions + if (type != QtInfoMsg && type != QtDebugMsg && type != QtWarningMsg) { + syslog(LOG_DEBUG, "%s", localMsg.toStdString().c_str()); + } + } break; + case LOG_ALERT: { // action must be taken immediately + if (type != QtInfoMsg && type != QtDebugMsg && type != QtWarningMsg) { + syslog(LOG_DEBUG, "%s", localMsg.toStdString().c_str()); + } + } break; + case LOG_EMERG: { // system is unusable + if (type != QtInfoMsg && type != QtDebugMsg && type != QtWarningMsg) { + syslog(LOG_DEBUG, "%s", localMsg.toStdString().c_str()); + } + } break; + default: { + //fprintf(stderr, "%s No ErrorLevel defined! %s\n", + // datetime.toStdString().c_str(), msg.toStdString().c_str()); + } + } +} +#endif + diff --git a/Check/message_handler.h b/Check/message_handler.h new file mode 100755 index 0000000..98c4d7e --- /dev/null +++ b/Check/message_handler.h @@ -0,0 +1,23 @@ +#ifndef MESSAGE_HANDLER_H_INCLUDED +#define MESSAGE_HANDLER_H_INCLUDED + +#include +#ifdef __linux__ +#include +#endif + +int getDebugLevel(); +void setDebugLevel(int newDebugLevel); + +bool messageHandlerInstalled(); +QtMessageHandler atbInstallMessageHandler(QtMessageHandler handler); + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +// typedef void (*QtMessageHandler)(QtMsgType, const char *); +void atbDebugOutput(QtMsgType type, const char *msg); +#elif QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +// typedef void (*QtMessageHandler)(QtMsgType, const QMessageLogContext &, const QString &); +void atbDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg); +#endif + +#endif // MESSAGE_HANDLER_H_INCLUDED