#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, 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__