379 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			379 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#ifdef __WIN32__
 | 
						|
#error "WIN32 NOT SUPPORTED"
 | 
						|
#else
 | 
						|
 | 
						|
#include "ismas_client.h"
 | 
						|
 | 
						|
#include <cstring>
 | 
						|
#include <cstdio>
 | 
						|
 | 
						|
#include <errno.h>
 | 
						|
#include <arpa/inet.h> // inet_addr()
 | 
						|
#include <netdb.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <netinet/in.h>
 | 
						|
#include <netinet/tcp.h>
 | 
						|
#include <string.h>
 | 
						|
#include <strings.h> // bzero()
 | 
						|
#include <sys/socket.h>
 | 
						|
#include <unistd.h> // read(), write(), close()
 | 
						|
#include <fcntl.h>
 | 
						|
 | 
						|
 | 
						|
#include <QThread>
 | 
						|
#include <QJsonDocument>
 | 
						|
#include <QJsonObject>
 | 
						|
 | 
						|
#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 <QDateTime>
 | 
						|
#include <QDebug>
 | 
						|
 | 
						|
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<QString>
 | 
						|
IsmasClient::sendRequestReceiveResponse(int port, QString const &request, bool verbose) {
 | 
						|
 | 
						|
    if (verbose) {
 | 
						|
        qInfo() << "REQUEST" << request;
 | 
						|
    }
 | 
						|
 | 
						|
    int sockfd;
 | 
						|
    int r;
 | 
						|
    errno = 0;
 | 
						|
    // socket create and verification
 | 
						|
    if ((sockfd = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) {
 | 
						|
        if (verbose) {
 | 
						|
            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) {
 | 
						|
        if (verbose) {
 | 
						|
            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);
 | 
						|
 | 
						|
    if (verbose) {
 | 
						|
        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) + ")");
 | 
						|
    // }
 | 
						|
 | 
						|
    if (verbose) {
 | 
						|
        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
 | 
						|
            if (verbose) {
 | 
						|
                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
 | 
						|
                if (verbose) {
 | 
						|
                    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__
 |