///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2018- Equinor ASA // // ResInsight is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU General Public License at // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RicHoloLensRestClient.h" #include "cvfBase.h" #include "cvfTrace.h" #include #include #include // For getting time stamps for round-trip timing #if defined (__linux__) #include #include #endif #ifndef QT_NO_OPENSSL // Uncomment to enable experimental SSL support // The experimental support must be revised before shipping #define EXPERIMENTAL_SSL_SUPPORT #endif //================================================================================================== // // // //================================================================================================== //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RicHoloLensRestClient::RicHoloLensRestClient(QString serverUrl, QString sessionName, RicHoloLensRestResponseHandler* responseHandler) : m_serverUrl(serverUrl), m_sessionName(sessionName), m_responseHandler(responseHandler), m_dbgDisableCertificateVerification(false) { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::clearResponseHandler() { m_responseHandler = nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::dbgDisableCertificateVerification() { m_dbgDisableCertificateVerification = true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::createSession(const QByteArray& sessionPinCode) { const QString url = m_serverUrl + "/sessions/create/" + m_sessionName; cvf::Trace::show("createSession: POST on url: %s", url.toLatin1().constData()); QNetworkRequest request(url); //request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/x-www-form-urlencoded")); request.setRawHeader("PinCode", sessionPinCode); #ifdef EXPERIMENTAL_SSL_SUPPORT // NOTE !!! // Apparently something like this is currently needed in order to get SSL/HTTPS going // Still, can't quite figure it out since it appears to be sufficient to do this on the first request // This will have to be investigated further, SP 20180924 QSslConfiguration sslConf = request.sslConfiguration(); // Needed this one to be able to connect to sharing server sslConf.setProtocol(QSsl::AnyProtocol); if (m_dbgDisableCertificateVerification) { sslConf.setPeerVerifyMode(QSslSocket::VerifyNone); } request.setSslConfiguration(sslConf); #endif QNetworkReply* reply = m_accessManager.post(request, QByteArray()); connect(reply, SIGNAL(finished()), SLOT(slotCreateSessionFinished())); #ifdef EXPERIMENTAL_SSL_SUPPORT connect(reply, SIGNAL(sslErrors(const QList&)), SLOT(slotSslErrors(const QList&))); #endif } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::slotCreateSessionFinished() { QNetworkReply* reply = dynamic_cast(sender()); if (!reply) { return; } if (detectAndHandleErrorReply("createSession", reply)) { reply->deleteLater(); m_responseHandler->handleFailedCreateSession(); return; } const QByteArray serverData = reply->readAll(); //cvf::Trace::show(" serverResponse: %s", serverData.constData()); // Currently we get the bearer token back in the response wholesale // The format we get is typically: "Bearer " // For example: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" // Presumably the format of the response will change, but for now just strip away the starting "Bearer " string and consider the rest as the actual token m_bearerToken = serverData; m_bearerToken.replace("Bearer ", ""); reply->deleteLater(); cvf::Trace::show("createSession OK"); if (m_responseHandler) { m_responseHandler->handleSuccessfulCreateSession(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::deleteSession() { const QString url = m_serverUrl + "/sessions/delete/" + m_sessionName; cvf::Trace::show("deleteSession: DELETE on url: %s", url.toLatin1().constData()); QNetworkRequest request(url); //request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/x-www-form-urlencoded")); addBearerAuthenticationHeaderToRequest(&request); QNetworkReply* reply = m_accessManager.deleteResource(request); connect(reply, SIGNAL(finished()), SLOT(slotDeleteSessionFinished())); #ifdef EXPERIMENTAL_SSL_SUPPORT connect(reply, SIGNAL(sslErrors(const QList&)), SLOT(slotSslErrors(const QList&))); #endif } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::slotDeleteSessionFinished() { QNetworkReply* reply = dynamic_cast(sender()); if (!reply) { return; } if (detectAndHandleErrorReply("deleteSession", reply)) { reply->deleteLater(); return; } reply->deleteLater(); cvf::Trace::show("deleteSession OK"); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::sendMetaData(int metaDataSequenceNumber, const QString& jsonMetaDataString) { const QString url = m_serverUrl + "/sessions/" + m_sessionName + "/metadata"; cvf::Trace::show("sendMetaData (metaDataSequenceNumber=%d): POST on url: %s", metaDataSequenceNumber, url.toLatin1().constData()); const qint64 sendStartTimeStamp_ms = getCurrentTimeStamp_ms(); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json")); addBearerAuthenticationHeaderToRequest(&request); const QByteArray jsonByteArr = jsonMetaDataString.toLatin1(); QNetworkReply* reply = m_accessManager.post(request, jsonByteArr); reply->setProperty("holo_metaDataSequenceNumber", QVariant(metaDataSequenceNumber)); reply->setProperty("holo_sendStartTimeStamp_ms", QVariant(sendStartTimeStamp_ms)); connect(reply, SIGNAL(finished()), SLOT(slotSendMetaDataFinished())); #ifdef EXPERIMENTAL_SSL_SUPPORT connect(reply, SIGNAL(sslErrors(const QList&)), SLOT(slotSslErrors(const QList&))); #endif } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::slotSendMetaDataFinished() { QNetworkReply* reply = dynamic_cast(sender()); if (!reply) { return; } if (detectAndHandleErrorReply("sendMetaData", reply)) { reply->deleteLater(); return; } int metaDataSequenceNumber = -1; { QVariant var = reply->property("holo_metaDataSequenceNumber"); if (var.type() == QVariant::Int) { metaDataSequenceNumber = var.toInt(); } } double elapsedTime_s = -1; { QVariant var = reply->property("holo_sendStartTimeStamp_ms"); if (var.type() == QVariant::LongLong) { const qint64 startTimeStamp_ms = var.toLongLong(); elapsedTime_s = (getCurrentTimeStamp_ms() - startTimeStamp_ms)/1000.0; } } const QByteArray serverData = reply->readAll(); //cvf::Trace::show(" serverResponse: %s", serverData.constData()); reply->deleteLater(); cvf::Trace::show("sendMetaData (metaDataSequenceNumber=%d) OK, elapsedTime=%.2fs", metaDataSequenceNumber, elapsedTime_s); if (m_responseHandler) { m_responseHandler->handleSuccessfulSendMetaData(metaDataSequenceNumber, serverData); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::sendBinaryData(const QByteArray& binaryDataArr, QByteArray dbgTagString) { const QString url = m_serverUrl + "/sessions/" + m_sessionName + "/data"; cvf::Trace::show("sendBinaryData(%s): POST on url: %s", dbgTagString.constData(), url.toLatin1().constData()); const qint64 sendStartTimeStamp_ms = getCurrentTimeStamp_ms(); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); addBearerAuthenticationHeaderToRequest(&request); QNetworkReply* reply = m_accessManager.post(request, binaryDataArr); reply->setProperty("holo_sendStartTimeStamp_ms", QVariant(sendStartTimeStamp_ms)); reply->setProperty("holo_dbgTagString", QVariant(dbgTagString)); connect(reply, SIGNAL(finished()), SLOT(slotSendBinaryDataFinished())); // Debugging! connect(reply, SIGNAL(uploadProgress(qint64, qint64)), SLOT(slotDbgUploadProgress(qint64, qint64))); #ifdef EXPERIMENTAL_SSL_SUPPORT connect(reply, SIGNAL(sslErrors(const QList&)), SLOT(slotSslErrors(const QList&))); #endif } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::slotSendBinaryDataFinished() { QNetworkReply* reply = dynamic_cast(sender()); if (!reply) { return; } if (detectAndHandleErrorReply("sendBinaryData", reply)) { reply->deleteLater(); return; } double elapsedTime_s = -1; { QVariant var = reply->property("holo_sendStartTimeStamp_ms"); if (var.type() == QVariant::LongLong) { const qint64 startTimeStamp_ms = var.toLongLong(); elapsedTime_s = (getCurrentTimeStamp_ms() - startTimeStamp_ms)/1000.0; } } QByteArray dbgTagString; { QVariant var = reply->property("holo_dbgTagString"); if (var.type() == QVariant::ByteArray) { dbgTagString = var.toByteArray(); } } reply->deleteLater(); cvf::Trace::show("sendBinaryData(%s) OK, elapsedTime=%.2fs", dbgTagString.constData(), elapsedTime_s); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::slotDbgUploadProgress(qint64 bytesSent, qint64 bytesTotal) { static int sl_lastPct = -1; int pct = 0; if (bytesTotal > 0) { pct = static_cast(100*(bytesSent/static_cast(bytesTotal))); } if (pct % 10 == 0 && pct != sl_lastPct) { double elapsedTime_s = -1; QByteArray dbgTagString; QNetworkReply* reply = dynamic_cast(sender()); if (reply) { { QVariant var = reply->property("holo_sendStartTimeStamp_ms"); if (var.type() == QVariant::LongLong) { const qint64 startTimeStamp_ms = var.toLongLong(); elapsedTime_s = (getCurrentTimeStamp_ms() - startTimeStamp_ms)/1000.0; } } { QVariant var = reply->property("holo_dbgTagString"); if (var.type() == QVariant::ByteArray) { dbgTagString = var.toByteArray(); } } } cvf::Trace::show("Progress sendBinaryData(%s): %3d%%, %.2f/%.2fMB (elapsedTime=%.2fs)", dbgTagString.constData(), pct, bytesSent/(1024.0*1024.0), bytesTotal/(1024.0*1024.0), elapsedTime_s); sl_lastPct = pct; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::slotSslErrors(const QList& errors) { #ifdef EXPERIMENTAL_SSL_SUPPORT cvf::Trace::show("RicHoloLensRestClient::slotSslErrors()"); for (int i = 0; i < errors.size(); i++) { cvf::Trace::show(" %s", errors[i].errorString().toLatin1().constData()); } #endif } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicHoloLensRestClient::addBearerAuthenticationHeaderToRequest(QNetworkRequest* request) const { CVF_ASSERT(request); request->setRawHeader("Authorization", "Bearer " + m_bearerToken); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RicHoloLensRestClient::detectAndHandleErrorReply(QString operationName, QNetworkReply* reply) { CVF_ASSERT(reply); const QNetworkReply::NetworkError nwErrCode = reply->error(); const int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (nwErrCode == QNetworkReply::NoError && httpStatusCode == 200) { // No error detected return false; } QString mainErrMsg = operationName + " FAILED"; if (nwErrCode != QNetworkReply::NoError) { const QString nwErrCodeAsString = networkErrorCodeAsString(nwErrCode); const QString errText = reply->errorString(); mainErrMsg += QString(" [nwErr='%1'(%2) httpStatus=%3]: %4").arg(nwErrCodeAsString).arg(nwErrCode).arg(httpStatusCode).arg(errText); } else { mainErrMsg += QString(" [httpStatus=%1]").arg(httpStatusCode); } cvf::Trace::show(mainErrMsg.toLatin1().constData()); reply->errorString(); const QString url = reply->url().toString(); cvf::Trace::show(" url: %s", url.toLatin1().constData()); const QByteArray serverData = reply->readAll(); cvf::Trace::show(" serverResponse: %s", serverData.constData()); if (m_responseHandler) { m_responseHandler->handleError(mainErrMsg, url, serverData); } return true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RicHoloLensRestClient::networkErrorCodeAsString(QNetworkReply::NetworkError nwErr) { switch (nwErr) { case QNetworkReply::NoError: return "NoError"; case QNetworkReply::ConnectionRefusedError: return "ConnectionRefusedError"; case QNetworkReply::RemoteHostClosedError: return "RemoteHostClosedError"; case QNetworkReply::HostNotFoundError: return "HostNotFoundError"; case QNetworkReply::TimeoutError: return "TimeoutError"; case QNetworkReply::OperationCanceledError: return "OperationCanceledError"; case QNetworkReply::SslHandshakeFailedError: return "SslHandshakeFailedError"; //case QNetworkReply::TemporaryNetworkFailureError: return "TemporaryNetworkFailureError"; case QNetworkReply::UnknownNetworkError: return "UnknownNetworkError"; case QNetworkReply::ProxyConnectionRefusedError: return "ProxyConnectionRefusedError"; case QNetworkReply::ProxyConnectionClosedError: return "ProxyConnectionClosedError"; case QNetworkReply::ProxyNotFoundError: return "ProxyNotFoundError"; case QNetworkReply::ProxyTimeoutError: return "ProxyTimeoutError"; case QNetworkReply::ProxyAuthenticationRequiredError: return "ProxyAuthenticationRequiredError"; case QNetworkReply::UnknownProxyError: return "UnknownProxyError"; case QNetworkReply::ContentAccessDenied: return "ContentAccessDenied"; case QNetworkReply::ContentOperationNotPermittedError: return "ContentOperationNotPermittedError"; case QNetworkReply::ContentNotFoundError: return "ContentNotFoundError"; case QNetworkReply::AuthenticationRequiredError: return "AuthenticationRequiredError"; case QNetworkReply::ContentReSendError: return "ContentReSendError"; case QNetworkReply::UnknownContentError: return "UnknownContentError"; case QNetworkReply::ProtocolUnknownError: return "ProtocolUnknownError"; case QNetworkReply::ProtocolInvalidOperationError: return "ProtocolInvalidOperationError"; case QNetworkReply::ProtocolFailure: return "ProtocolFailure"; }; return "UnknownErrorCode"; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- qint64 RicHoloLensRestClient::getCurrentTimeStamp_ms() { #if QT_VERSION >= 0x040700 const qint64 timeStamp_ms = QDateTime::currentMSecsSinceEpoch(); return timeStamp_ms; #elif defined(__linux__) struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { return static_cast(ts.tv_sec*1000 + ts.tv_nsec/1000000); } #else return 0; #endif }