diff --git a/Linphone/core/call/CallCore.cpp b/Linphone/core/call/CallCore.cpp index 319b7fef..d8d5fe56 100644 --- a/Linphone/core/call/CallCore.cpp +++ b/Linphone/core/call/CallCore.cpp @@ -20,7 +20,6 @@ #include "CallCore.hpp" #include "core/App.hpp" -#include "model/object/VariantObject.hpp" #include "model/tool/ToolModel.hpp" #include "tool/Utils.hpp" #include "tool/thread/SafeConnection.hpp" @@ -51,11 +50,11 @@ CallCore::CallCore(const std::shared_ptr &call) : QObject(nullpt mPeerAddress = Utils::coreStringToAppString(mCallModel->getRemoteAddress()->asString()); mStatus = LinphoneEnums::fromLinphone(call->getCallLog()->getStatus()); mTransferState = LinphoneEnums::fromLinphone(call->getTransferState()); - auto encryption = LinphoneEnums::fromLinphone(call->getCurrentParams()->getMediaEncryption()); + mEncryption = LinphoneEnums::fromLinphone(call->getParams()->getMediaEncryption()); auto tokenVerified = mCallModel->getAuthenticationTokenVerified(); - mPeerSecured = (encryption == LinphoneEnums::MediaEncryption::Zrtp && tokenVerified) || - encryption == LinphoneEnums::MediaEncryption::Srtp || - encryption == LinphoneEnums::MediaEncryption::Dtls; + mIsSecured = (mEncryption == LinphoneEnums::MediaEncryption::Zrtp && tokenVerified) || + mEncryption == LinphoneEnums::MediaEncryption::Srtp || + mEncryption == LinphoneEnums::MediaEncryption::Dtls; mPaused = mState == LinphoneEnums::CallState::Pausing || mState == LinphoneEnums::CallState::Paused || mState == LinphoneEnums::CallState::PausedByRemote; mRecording = call->getParams() && call->getParams()->isRecording(); @@ -70,75 +69,82 @@ CallCore::~CallCore() { } void CallCore::setSelf(QSharedPointer me) { - mAccountModelConnection = QSharedPointer>( + mCallModelConnection = QSharedPointer>( new SafeConnection(me, mCallModel), &QObject::deleteLater); - mAccountModelConnection->makeConnectToCore(&CallCore::lSetMicrophoneMuted, [this](bool isMuted) { - mAccountModelConnection->invokeToModel([this, isMuted]() { mCallModel->setMicrophoneMuted(isMuted); }); + mCallModelConnection->makeConnectToCore(&CallCore::lSetMicrophoneMuted, [this](bool isMuted) { + mCallModelConnection->invokeToModel([this, isMuted]() { mCallModel->setMicrophoneMuted(isMuted); }); }); - mAccountModelConnection->makeConnectToModel(&CallModel::microphoneMutedChanged, [this](bool isMuted) { - mAccountModelConnection->invokeToCore([this, isMuted]() { setMicrophoneMuted(isMuted); }); + mCallModelConnection->makeConnectToModel(&CallModel::microphoneMutedChanged, [this](bool isMuted) { + mCallModelConnection->invokeToCore([this, isMuted]() { setMicrophoneMuted(isMuted); }); }); - mAccountModelConnection->makeConnectToModel(&CallModel::remoteVideoEnabledChanged, [this](bool enabled) { - mAccountModelConnection->invokeToCore([this, enabled]() { setRemoteVideoEnabled(enabled); }); + mCallModelConnection->makeConnectToModel(&CallModel::remoteVideoEnabledChanged, [this](bool enabled) { + mCallModelConnection->invokeToCore([this, enabled]() { setRemoteVideoEnabled(enabled); }); }); - mAccountModelConnection->makeConnectToCore(&CallCore::lSetSpeakerMuted, [this](bool isMuted) { - mAccountModelConnection->invokeToModel([this, isMuted]() { mCallModel->setSpeakerMuted(isMuted); }); + mCallModelConnection->makeConnectToCore(&CallCore::lSetSpeakerMuted, [this](bool isMuted) { + mCallModelConnection->invokeToModel([this, isMuted]() { mCallModel->setSpeakerMuted(isMuted); }); }); - mAccountModelConnection->makeConnectToModel(&CallModel::speakerMutedChanged, [this](bool isMuted) { - mAccountModelConnection->invokeToCore([this, isMuted]() { setSpeakerMuted(isMuted); }); + mCallModelConnection->makeConnectToModel(&CallModel::speakerMutedChanged, [this](bool isMuted) { + mCallModelConnection->invokeToCore([this, isMuted]() { setSpeakerMuted(isMuted); }); }); - mAccountModelConnection->makeConnectToCore(&CallCore::lSetCameraEnabled, [this](bool enabled) { - mAccountModelConnection->invokeToModel([this, enabled]() { mCallModel->setCameraEnabled(enabled); }); + mCallModelConnection->makeConnectToCore(&CallCore::lSetCameraEnabled, [this](bool enabled) { + mCallModelConnection->invokeToModel([this, enabled]() { mCallModel->setCameraEnabled(enabled); }); }); - mAccountModelConnection->makeConnectToCore(&CallCore::lStartRecording, [this]() { - mAccountModelConnection->invokeToModel([this]() { mCallModel->startRecording(); }); + mCallModelConnection->makeConnectToCore(&CallCore::lStartRecording, [this]() { + mCallModelConnection->invokeToModel([this]() { mCallModel->startRecording(); }); }); - mAccountModelConnection->makeConnectToCore(&CallCore::lStopRecording, [this]() { - mAccountModelConnection->invokeToModel([this]() { mCallModel->stopRecording(); }); + mCallModelConnection->makeConnectToCore(&CallCore::lStopRecording, [this]() { + mCallModelConnection->invokeToModel([this]() { mCallModel->stopRecording(); }); }); - mAccountModelConnection->makeConnectToModel(&CallModel::recordingChanged, [this](bool recording) { - mAccountModelConnection->invokeToCore([this, recording]() { setRecording(recording); }); + mCallModelConnection->makeConnectToModel(&CallModel::recordingChanged, [this](bool recording) { + mCallModelConnection->invokeToCore([this, recording]() { setRecording(recording); }); }); - mAccountModelConnection->makeConnectToModel( + mCallModelConnection->makeConnectToCore(&CallCore::lVerifyAuthenticationToken, [this](bool verified) { + mCallModelConnection->invokeToModel( + [this, verified]() { mCallModel->setAuthenticationTokenVerified(verified); }); + }); + mCallModelConnection->makeConnectToModel(&CallModel::authenticationTokenVerifiedChanged, [this](bool verified) { + mCallModelConnection->invokeToCore([this, verified]() { setIsSecured(verified); }); + }); + mCallModelConnection->makeConnectToModel( &CallModel::remoteRecording, [this](const std::shared_ptr &call, bool recording) { - mAccountModelConnection->invokeToCore([this, recording]() { setRemoteRecording(recording); }); + mCallModelConnection->invokeToCore([this, recording]() { setRemoteRecording(recording); }); }); - mAccountModelConnection->makeConnectToModel(&CallModel::cameraEnabledChanged, [this](bool enabled) { - mAccountModelConnection->invokeToCore([this, enabled]() { setCameraEnabled(enabled); }); + mCallModelConnection->makeConnectToModel(&CallModel::cameraEnabledChanged, [this](bool enabled) { + mCallModelConnection->invokeToCore([this, enabled]() { setCameraEnabled(enabled); }); }); - mAccountModelConnection->makeConnectToModel(&CallModel::durationChanged, [this](int duration) { - mAccountModelConnection->invokeToCore([this, duration]() { setDuration(duration); }); + mCallModelConnection->makeConnectToModel(&CallModel::durationChanged, [this](int duration) { + mCallModelConnection->invokeToCore([this, duration]() { setDuration(duration); }); }); - mAccountModelConnection->makeConnectToModel( + mCallModelConnection->makeConnectToModel( &CallModel::stateChanged, [this](linphone::Call::State state, const std::string &message) { - mAccountModelConnection->invokeToCore([this, state, message]() { + mCallModelConnection->invokeToCore([this, state, message]() { setState(LinphoneEnums::fromLinphone(state), Utils::coreStringToAppString(message)); }); }); - mAccountModelConnection->makeConnectToModel(&CallModel::statusChanged, [this](linphone::Call::Status status) { - mAccountModelConnection->invokeToCore([this, status]() { setStatus(LinphoneEnums::fromLinphone(status)); }); + mCallModelConnection->makeConnectToModel(&CallModel::statusChanged, [this](linphone::Call::Status status) { + mCallModelConnection->invokeToCore([this, status]() { setStatus(LinphoneEnums::fromLinphone(status)); }); }); - mAccountModelConnection->makeConnectToModel(&CallModel::stateChanged, - [this](linphone::Call::State state, const std::string &message) { - mAccountModelConnection->invokeToCore([this, state]() { - setRecordable(state == linphone::Call::State::StreamsRunning); - }); - }); - mAccountModelConnection->makeConnectToCore(&CallCore::lSetPaused, [this](bool paused) { - mAccountModelConnection->invokeToModel([this, paused]() { mCallModel->setPaused(paused); }); + mCallModelConnection->makeConnectToModel(&CallModel::stateChanged, + [this](linphone::Call::State state, const std::string &message) { + mCallModelConnection->invokeToCore([this, state]() { + setRecordable(state == linphone::Call::State::StreamsRunning); + }); + }); + mCallModelConnection->makeConnectToCore(&CallCore::lSetPaused, [this](bool paused) { + mCallModelConnection->invokeToModel([this, paused]() { mCallModel->setPaused(paused); }); }); - mAccountModelConnection->makeConnectToModel(&CallModel::pausedChanged, [this](bool paused) { - mAccountModelConnection->invokeToCore([this, paused]() { setPaused(paused); }); + mCallModelConnection->makeConnectToModel(&CallModel::pausedChanged, [this](bool paused) { + mCallModelConnection->invokeToCore([this, paused]() { setPaused(paused); }); }); - mAccountModelConnection->makeConnectToCore(&CallCore::lTransferCall, [this](const QString &address) { - mAccountModelConnection->invokeToModel( + mCallModelConnection->makeConnectToCore(&CallCore::lTransferCall, [this](const QString &address) { + mCallModelConnection->invokeToModel( [this, address]() { mCallModel->transferTo(ToolModel::interpretUrl(address)); }); }); - mAccountModelConnection->makeConnectToModel( + mCallModelConnection->makeConnectToModel( &CallModel::transferStateChanged, [this](const std::shared_ptr &call, linphone::Call::State state) { - mAccountModelConnection->invokeToCore([this, state]() { + mCallModelConnection->invokeToCore([this, state]() { QString message; if (state == linphone::Call::State::Error) { message = "L'appel n'a pas pu être transféré."; @@ -146,27 +152,35 @@ void CallCore::setSelf(QSharedPointer me) { setTransferState(LinphoneEnums::fromLinphone(state), message); }); }); - mAccountModelConnection->makeConnectToModel( + mCallModelConnection->makeConnectToModel( &CallModel::encryptionChanged, [this](const std::shared_ptr &call, bool on, const std::string &authenticationToken) { auto encryption = LinphoneEnums::fromLinphone(call->getCurrentParams()->getMediaEncryption()); auto tokenVerified = mCallModel->getAuthenticationTokenVerified(); - mAccountModelConnection->invokeToCore([this, call, encryption, tokenVerified]() { - setPeerSecured((encryption == LinphoneEnums::MediaEncryption::Zrtp && tokenVerified) || - encryption == LinphoneEnums::MediaEncryption::Srtp || - encryption == LinphoneEnums::MediaEncryption::Dtls); + auto token = Utils::coreStringToAppString(mCallModel->getAuthenticationToken()); + mCallModelConnection->invokeToCore([this, call, encryption, tokenVerified, token]() { + auto localToken = + mDir == LinphoneEnums::CallDir::Incoming ? token.left(2).toUpper() : token.right(2).toUpper(); + auto remoteToken = + mDir == LinphoneEnums::CallDir::Outgoing ? token.left(2).toUpper() : token.right(2).toUpper(); + setLocalSas(localToken); + setRemoteSas(remoteToken); + setEncryption(encryption); + setIsSecured((encryption == LinphoneEnums::MediaEncryption::Zrtp && tokenVerified) || + encryption == LinphoneEnums::MediaEncryption::Srtp || + encryption == LinphoneEnums::MediaEncryption::Dtls); }); }); - mAccountModelConnection->makeConnectToCore(&CallCore::lAccept, [this](bool withVideo) { - mAccountModelConnection->invokeToModel([this, withVideo]() { mCallModel->accept(withVideo); }); + mCallModelConnection->makeConnectToCore(&CallCore::lAccept, [this](bool withVideo) { + mCallModelConnection->invokeToModel([this, withVideo]() { mCallModel->accept(withVideo); }); }); - mAccountModelConnection->makeConnectToCore( - &CallCore::lDecline, [this]() { mAccountModelConnection->invokeToModel([this]() { mCallModel->decline(); }); }); - mAccountModelConnection->makeConnectToCore(&CallCore::lTerminate, [this]() { - mAccountModelConnection->invokeToModel([this]() { mCallModel->terminate(); }); + mCallModelConnection->makeConnectToCore( + &CallCore::lDecline, [this]() { mCallModelConnection->invokeToModel([this]() { mCallModel->decline(); }); }); + mCallModelConnection->makeConnectToCore(&CallCore::lTerminate, [this]() { + mCallModelConnection->invokeToModel([this]() { mCallModel->terminate(); }); }); - mAccountModelConnection->makeConnectToCore(&CallCore::lTerminateAllCalls, [this]() { - mAccountModelConnection->invokeToModel([this]() { mCallModel->terminateAllCalls(); }); + mCallModelConnection->makeConnectToCore(&CallCore::lTerminateAllCalls, [this]() { + mCallModelConnection->invokeToModel([this]() { mCallModel->terminateAllCalls(); }); }); } @@ -276,13 +290,47 @@ void CallCore::setPaused(bool paused) { } } -bool CallCore::getPeerSecured() const { - return mPeerSecured; +bool CallCore::isSecured() const { + return mIsSecured; } -void CallCore::setPeerSecured(bool secured) { - if (mPeerSecured != secured) { - mPeerSecured = secured; - emit peerSecuredChanged(); + +void CallCore::setIsSecured(bool secured) { + if (mIsSecured != secured) { + mIsSecured = secured; + emit securityUpdated(); + } +} + +QString CallCore::getLocalSas() { + return mLocalSas; +} + +QString CallCore::getRemoteSas() { + return mRemoteSas; +} + +void CallCore::setLocalSas(const QString &sas) { + if (mLocalSas != sas) { + mLocalSas = sas; + emit localSasChanged(); + } +} + +void CallCore::setRemoteSas(const QString &sas) { + if (mRemoteSas != sas) { + mRemoteSas = sas; + emit remoteSasChanged(); + } +} + +LinphoneEnums::MediaEncryption CallCore::getEncryption() const { + return mEncryption; +} + +void CallCore::setEncryption(LinphoneEnums::MediaEncryption encryption) { + if (mEncryption != encryption) { + mEncryption = encryption; + emit securityUpdated(); } } diff --git a/Linphone/core/call/CallCore.hpp b/Linphone/core/call/CallCore.hpp index 360a0ec2..97bd4dc8 100644 --- a/Linphone/core/call/CallCore.hpp +++ b/Linphone/core/call/CallCore.hpp @@ -42,7 +42,10 @@ class CallCore : public QObject, public AbstractObject { Q_PROPERTY(bool cameraEnabled READ getCameraEnabled WRITE lSetCameraEnabled NOTIFY cameraEnabledChanged) Q_PROPERTY(bool paused READ getPaused WRITE lSetPaused NOTIFY pausedChanged) Q_PROPERTY(QString peerAddress READ getPeerAddress CONSTANT) - Q_PROPERTY(bool peerSecured READ getPeerSecured WRITE setPeerSecured NOTIFY peerSecuredChanged) + Q_PROPERTY(bool isSecured READ isSecured NOTIFY securityUpdated) + Q_PROPERTY(LinphoneEnums::MediaEncryption encryption READ getEncryption NOTIFY securityUpdated) + Q_PROPERTY(QString localSas READ getLocalSas WRITE setLocalSas MEMBER mLocalSas NOTIFY localSasChanged) + Q_PROPERTY(QString remoteSas WRITE setRemoteSas MEMBER mRemoteSas NOTIFY remoteSasChanged) Q_PROPERTY( bool remoteVideoEnabled READ getRemoteVideoEnabled WRITE setRemoteVideoEnabled NOTIFY remoteVideoEnabledChanged) Q_PROPERTY(bool recording READ getRecording WRITE setRecording NOTIFY recordingChanged) @@ -86,8 +89,16 @@ public: bool getPaused() const; void setPaused(bool paused); - bool getPeerSecured() const; - void setPeerSecured(bool secured); + bool isSecured() const; + void setIsSecured(bool secured); + + QString getLocalSas(); + void setLocalSas(const QString &sas); + QString getRemoteSas(); + void setRemoteSas(const QString &sas); + + LinphoneEnums::MediaEncryption getEncryption() const; + void setEncryption(LinphoneEnums::MediaEncryption encryption); bool getRemoteVideoEnabled() const; void setRemoteVideoEnabled(bool enabled); @@ -118,7 +129,9 @@ signals: void cameraEnabledChanged(); void pausedChanged(); void transferStateChanged(); - void peerSecuredChanged(); + void securityUpdated(); + void localSasChanged(); + void remoteSasChanged(); void remoteVideoEnabledChanged(bool remoteVideoEnabled); void recordingChanged(); void remoteRecordingChanged(); @@ -136,6 +149,7 @@ signals: void lTransferCall(const QString &dest); void lStartRecording(); void lStopRecording(); + void lVerifyAuthenticationToken(bool verified); /* TODO Q_INVOKABLE void acceptWithVideo(); @@ -161,9 +175,10 @@ private: LinphoneEnums::CallState mState; LinphoneEnums::CallState mTransferState; LinphoneEnums::CallDir mDir; + LinphoneEnums::MediaEncryption mEncryption; QString mLastErrorMessage; QString mPeerAddress; - bool mPeerSecured; + bool mIsSecured; int mDuration = 0; bool mSpeakerMuted; bool mMicrophoneMuted; @@ -173,7 +188,9 @@ private: bool mRecording = false; bool mRemoteRecording = false; bool mRecordable = false; - QSharedPointer> mAccountModelConnection; + QString mLocalSas; + QString mRemoteSas; + QSharedPointer> mCallModelConnection; DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/data/CMakeLists.txt b/Linphone/data/CMakeLists.txt index 24c451a7..fe3b7801 100644 --- a/Linphone/data/CMakeLists.txt +++ b/Linphone/data/CMakeLists.txt @@ -66,6 +66,7 @@ list(APPEND _LINPHONEAPP_RC_FILES data/assistant/use-app-sip-account.rc "data/image/heart.svg" "data/image/heart-fill.svg" "data/image/record-fill.svg" + "data/image/media_encryption_zrtp_pq.svg" data/shaders/roundEffect.vert.qsb data/shaders/roundEffect.frag.qsb diff --git a/Linphone/model/call/CallModel.cpp b/Linphone/model/call/CallModel.cpp index 06fcf1b9..f2a1de45 100644 --- a/Linphone/model/call/CallModel.cpp +++ b/Linphone/model/call/CallModel.cpp @@ -136,6 +136,7 @@ void CallModel::stopRecording() { } void CallModel::setRecordFile(const std::string &path) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); auto core = CoreModel::getInstance()->getCore(); auto params = core->createCallParams(mMonitor); params->setRecordFile(path); @@ -143,6 +144,7 @@ void CallModel::setRecordFile(const std::string &path) { } std::string CallModel::getRecordFile() const { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return mMonitor->getParams()->getRecordFile(); } @@ -151,11 +153,23 @@ std::shared_ptr CallModel::getRemoteAddress() { return mMonitor->getRemoteAddress(); } -bool CallModel::getAuthenticationTokenVerified() { +bool CallModel::getAuthenticationTokenVerified() const { mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); return mMonitor->getAuthenticationTokenVerified(); } +void CallModel::setAuthenticationTokenVerified(bool verified) { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + mMonitor->setAuthenticationTokenVerified(verified); + emit authenticationTokenVerifiedChanged(verified); +} + +std::string CallModel::getAuthenticationToken() const { + mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); + auto token = mMonitor->getAuthenticationToken(); + return token; +} + void CallModel::onDtmfReceived(const std::shared_ptr &call, int dtmf) { emit dtmfReceived(call, dtmf); } diff --git a/Linphone/model/call/CallModel.hpp b/Linphone/model/call/CallModel.hpp index bc7f290f..64a29708 100644 --- a/Linphone/model/call/CallModel.hpp +++ b/Linphone/model/call/CallModel.hpp @@ -53,7 +53,9 @@ public: std::string getRecordFile() const; std::shared_ptr getRemoteAddress(); - bool getAuthenticationTokenVerified(); + bool getAuthenticationTokenVerified() const; + void setAuthenticationTokenVerified(bool verified); + std::string getAuthenticationToken() const; signals: void microphoneMutedChanged(bool isMuted); @@ -63,6 +65,7 @@ signals: void pausedChanged(bool paused); void remoteVideoEnabledChanged(bool remoteVideoEnabled); void recordingChanged(bool recording); + void authenticationTokenVerifiedChanged(bool verified); private: QTimer mDurationTimer; diff --git a/Linphone/model/tool/ToolModel.cpp b/Linphone/model/tool/ToolModel.cpp index 4fd012cc..cae2efee 100644 --- a/Linphone/model/tool/ToolModel.cpp +++ b/Linphone/model/tool/ToolModel.cpp @@ -75,7 +75,8 @@ QString ToolModel::getDisplayName(QString address) { QSharedPointer ToolModel::createCall(const QString &sipAddress, const QString &prepareTransfertAddress, - const QHash &headers) { + const QHash &headers, + linphone::MediaEncryption mediaEncryption) { bool waitRegistrationForCall = true; // getSettingsModel()->getWaitRegistrationForCall() std::shared_ptr core = CoreModel::getInstance()->getCore(); @@ -88,6 +89,7 @@ QSharedPointer ToolModel::createCall(const QString &sipAddress, std::shared_ptr params = core->createCallParams(nullptr); params->enableVideo(false); + params->setMediaEncryption(mediaEncryption); if (Utils::coreStringToAppString(params->getRecordFile()).isEmpty()) { params->setRecordFile( diff --git a/Linphone/model/tool/ToolModel.hpp b/Linphone/model/tool/ToolModel.hpp index 5436acef..515f5705 100644 --- a/Linphone/model/tool/ToolModel.hpp +++ b/Linphone/model/tool/ToolModel.hpp @@ -41,7 +41,8 @@ public: static QSharedPointer createCall(const QString &sipAddress, const QString &prepareTransfertAddress = "", - const QHash &headers = {}); + const QHash &headers = {}, + linphone::MediaEncryption = linphone::MediaEncryption::None); private: DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/tool/LinphoneEnums.hpp b/Linphone/tool/LinphoneEnums.hpp index 8c861fbe..1a6a2cdc 100644 --- a/Linphone/tool/LinphoneEnums.hpp +++ b/Linphone/tool/LinphoneEnums.hpp @@ -312,7 +312,6 @@ Q_DECLARE_METATYPE(LinphoneEnums::ConferenceInfoState) Q_DECLARE_METATYPE(LinphoneEnums::ConferenceSchedulerState) Q_DECLARE_METATYPE(LinphoneEnums::EventLogType) Q_DECLARE_METATYPE(LinphoneEnums::FriendCapability) -Q_DECLARE_METATYPE(LinphoneEnums::MediaEncryption) Q_DECLARE_METATYPE(LinphoneEnums::ParticipantDeviceState) Q_DECLARE_METATYPE(LinphoneEnums::RecorderState) Q_DECLARE_METATYPE(LinphoneEnums::TunnelMode) diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index 733fc8c3..f2564b92 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -28,6 +28,7 @@ #include "tool/providers/AvatarProvider.hpp" #include #include +#include // ============================================================================= @@ -260,4 +261,30 @@ QString Utils::generateSavedFilename(const QString &from, const QString &to) { .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm-ss")) .arg(escape(from)) .arg(escape(to)); +} + +QStringList Utils::generateSecurityLettersArray(int arraySize, int correctIndex, QString correctCode) { + QStringList vec; + const QString possibleCharacters(tr("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")); + const int n = 2; + for (int i = 0; i < arraySize; ++i) { + QString randomString; + if (i == correctIndex) randomString = correctCode; + else { + do { + randomString.clear(); + for (int j = 0; j < n; ++j) { + int index = rand() % possibleCharacters.length(); + QChar nextChar = possibleCharacters.at(index); + randomString.append(nextChar); + } + } while (vec.contains(randomString) || randomString == correctCode); + } + vec.append(randomString); + } + return vec; +} + +int Utils::getRandomIndex(int size) { + return QRandomGenerator::global()->bounded(size); } \ No newline at end of file diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index a11866b9..8e662e2a 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -69,7 +69,9 @@ public: Q_INVOKABLE static QString formatElapsedTime(int seconds, bool dotsSeparator = true); // Return the elapsed time formated Q_INVOKABLE static QString formatDate(const QDateTime &date, bool includeTime = true); // Return the date formated - Q_INVOKABLE static QString formatDateElapsedTime(const QDateTime &date); // Return the date formated + Q_INVOKABLE static QString formatDateElapsedTime(const QDateTime &date); + Q_INVOKABLE static QStringList generateSecurityLettersArray(int arraySize, int correctIndex, QString correctCode); + Q_INVOKABLE static int getRandomIndex(int size); static QString generateSavedFilename(const QString &from, const QString &to); static inline QString coreStringToAppString(const std::string &str) { diff --git a/Linphone/view/App/CallsWindow.qml b/Linphone/view/App/CallsWindow.qml index a1ec8046..92b9f417 100644 --- a/Linphone/view/App/CallsWindow.qml +++ b/Linphone/view/App/CallsWindow.qml @@ -18,6 +18,15 @@ Window { Connections { target: call.core onRemoteVideoEnabledChanged: console.log("remote video enabled", call.core.remoteVideoEnabled) + onSecurityUpdated: { + if (call.core.isSecured) { + zrtpValidation.close() + } + else if(call.core.encryption === LinphoneEnums.MediaEncryption.Zrtp) { + zrtpValidation.open() + // mainWindow.attachVirtualWindow(Utils.buildLinphoneDialogUri('ZrtpTokenAuthenticationDialog'), {call:callModel}) + } + } } onCallChanged: { @@ -29,7 +38,12 @@ Window { property var callState: call.core.state onCallStateChanged: { console.log("State:", callState) - if (callState === LinphoneEnums.CallState.Error || callState === LinphoneEnums.CallState.End) { + if (callState === LinphoneEnums.CallState.Connected) { + if(!call.core.isSecured && call.core.encryption === LinphoneEnums.MediaEncryption.Zrtp) { + zrtpValidation.open() + } + } + else if (callState === LinphoneEnums.CallState.Error || callState === LinphoneEnums.CallState.End) { endCall(call) } } @@ -122,6 +136,10 @@ Window { colorizationColor: disabledIcon && bottomButton.checked ? DefaultStyle.main2_0 : DefaultStyle.grey_0 } } + ZrtpTokenAuthenticationDialog { + id: zrtpValidation + call: mainWindow.call + } Popup { id: waitingPopup visible: mainWindow.call.core.transferState === LinphoneEnums.CallState.OutgoingInit @@ -259,8 +277,7 @@ Window { bottomPadding: 8 * DefaultStyle.dp leftPadding: 10 * DefaultStyle.dp rightPadding: 10 * DefaultStyle.dp - visible: mainWindow.call.core.peerSecured - onVisibleChanged: console.log("peer secured", mainWindow.call.core.peerSecured) + visible: mainWindow.call.core.isSecured background: Rectangle { anchors.fill: parent border.color: DefaultStyle.info_500_main @@ -483,8 +500,6 @@ Window { Control.StackView.onActivated: rightPanelTitle.text = qsTr("Liste d'appel") // width: callList.width // height: callList.height - onHeightChanged: console.log("control height changed", height) - // padding: 15 * DefaultStyle.dp topPadding: 15 * DefaultStyle.dp bottomPadding: 15 * DefaultStyle.dp diff --git a/Linphone/view/App/Main.qml b/Linphone/view/App/Main.qml index 1c0e9bba..d0ef6cd3 100644 --- a/Linphone/view/App/Main.qml +++ b/Linphone/view/App/Main.qml @@ -99,4 +99,4 @@ Window { // StackView.onActivated: connectionSecured(0) // TODO : connect to cpp part when ready } } -} \ No newline at end of file +} diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index 25a26da1..6ab1b82b 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -45,6 +45,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/TextInput.qml view/Item/ToolTip.qml view/Item/VerticalTabBar.qml + view/Item/ZrtpTokenAuthenticationDialog.qml view/Item/Form/LoginForm.qml diff --git a/Linphone/view/Item/Button.qml b/Linphone/view/Item/Button.qml index c46e6cbd..c88eabc2 100644 --- a/Linphone/view/Item/Button.qml +++ b/Linphone/view/Item/Button.qml @@ -6,9 +6,12 @@ import Linphone Control.Button { id: mainItem property int capitalization + property color color: DefaultStyle.main1_500_main + property color pressedColor: DefaultStyle.main1_500_main_darker property bool inversedColors: false property int textSize: 18 * DefaultStyle.dp property int textWeight: 600 * DefaultStyle.dp + property bool underline: false property bool shadowEnabled: false hoverEnabled: true @@ -26,10 +29,10 @@ Control.Button { ? DefaultStyle.grey_100 : DefaultStyle.grey_0 : mainItem.pressed - ? DefaultStyle.main1_500_main_darker - : DefaultStyle.main1_500_main + ? mainItem.pressedColor + : mainItem.color radius: 48 * DefaultStyle.dp - border.color: inversedColors ? DefaultStyle.main1_500_main : DefaultStyle.grey_0 + border.color: inversedColors ? mainItem.color : DefaultStyle.grey_0 MouseArea { anchors.fill: parent @@ -49,16 +52,18 @@ Control.Button { } contentItem: Text { + id: contentText horizontalAlignment: Text.AlignHCenter anchors.centerIn: parent wrapMode: Text.WordWrap text: mainItem.text - color: inversedColors ? DefaultStyle.main1_500_main : DefaultStyle.grey_0 + color: inversedColors ? mainItem.color : DefaultStyle.grey_0 font { pixelSize: mainItem.textSize weight: mainItem.textWeight family: DefaultStyle.defaultFont capitalization: mainItem.capitalization + underline: mainItem.underline } } } diff --git a/Linphone/view/Item/Dialog.qml b/Linphone/view/Item/Dialog.qml index de03a7e0..76d8db88 100644 --- a/Linphone/view/Item/Dialog.qml +++ b/Linphone/view/Item/Dialog.qml @@ -12,9 +12,11 @@ Popup { rightPadding: 10 * DefaultStyle.dp leftPadding: 10 * DefaultStyle.dp topPadding: 10 * DefaultStyle.dp - bottomPadding: 10 * DefaultStyle.dp + buttonsLayout.height + bottomPadding: 10 * DefaultStyle.dp property int radius: 16 * DefaultStyle.dp property color underlineColor: DefaultStyle.main1_500_main + property alias buttons: buttonsLayout.data + property alias content: contentLayout.data property string text signal accepted() signal rejected() @@ -24,14 +26,17 @@ Popup { Rectangle { visible: mainItem.underlineColor != undefined width: mainItem.width - height: mainItem.height + 2 * DefaultStyle.dp + x: backgroundItem.x + y: backgroundItem.y + height: backgroundItem.height + 2 * DefaultStyle.dp color: mainItem.underlineColor radius: mainItem.radius } - Rectangle{ + Rectangle { id: backgroundItem + anchors.fill: parent width: mainItem.width - height: mainItem.height + height: mainItem.implicitHeight radius: mainItem.radius color: DefaultStyle.grey_0 border.color: DefaultStyle.grey_0 @@ -44,13 +49,39 @@ Popup { shadowBlur: 1.0 shadowOpacity: 0.1 } + } + + contentItem: ColumnLayout { + spacing: 20 * DefaultStyle.dp + ColumnLayout { + id: contentLayout + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter + } + Text { + id: defaultText + visible: text.length != 0 + width: parent.width + Layout.preferredWidth: 278 * DefaultStyle.dp + text: mainItem.text + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + } + RowLayout { id: buttonsLayout - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - Layout.alignment: Qt.AlignHCenter - Layout.bottomMargin: 10 * DefaultStyle.dp + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + spacing: 10 * DefaultStyle.dp + + // Default buttons only visible if no other children + // have been set Button { + visible: mainItem.buttons.length === 2 text: qsTr("Oui") onClicked: { mainItem.accepted() @@ -58,6 +89,7 @@ Popup { } } Button { + visible: mainItem.buttons.length === 2 text: qsTr("Non") onClicked: { mainItem.rejected() @@ -66,16 +98,4 @@ Popup { } } } - - contentItem: Text { - width: parent.width - Layout.preferredWidth: 278 * DefaultStyle.dp - text: mainItem.text - font { - pixelSize: 14 * DefaultStyle.dp - weight: 400 * DefaultStyle.dp - } - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter - } } \ No newline at end of file diff --git a/Linphone/view/Item/ZrtpTokenAuthenticationDialog.qml b/Linphone/view/Item/ZrtpTokenAuthenticationDialog.qml new file mode 100644 index 00000000..ca19527d --- /dev/null +++ b/Linphone/view/Item/ZrtpTokenAuthenticationDialog.qml @@ -0,0 +1,155 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Effects + +import Linphone +import UtilsCpp 1.0 + +// ============================================================================= +Dialog { + id: mainItem + + property var call + + width: 436 * DefaultStyle.dp + height: 549 * DefaultStyle.dp + + rightPadding: 15 * DefaultStyle.dp + leftPadding: 15 * DefaultStyle.dp + topPadding: 40 * DefaultStyle.dp + bottomPadding: 40 * DefaultStyle.dp + + onCallChanged: if(!call) close() + + Connections { + target: call.core + onStatusChanged: if (status === CallModel.CallStatusEnded) close() + } + + buttons: ColumnLayout { + spacing: 15 * DefaultStyle.dp + Button { + Layout.alignment: Qt.AlignHCenter + background: Item{} + contentItem: Text { + text: qsTr("Skip") + font { + pixelSize: 13 * DefaultStyle.dp + weight: 600 * DefaultStyle.dp + underline: true + } + } + onClicked: { + if(mainItem.call) mainItem.call.core.lVerifyAuthenticationToken(false) + mainItem.close() + } + } + Button { + text: qsTr("Letters doesn't match") + color: DefaultStyle.danger_500main + inversedColors: true + Layout.alignment: Qt.AlignHCenter + width: 330 * DefaultStyle.dp + onClicked: { + if(mainItem.call) mainItem.call.core.lVerifyAuthenticationToken(false) + mainItem.close() + } + } + } + + content: ColumnLayout { + spacing: 32 * DefaultStyle.dp + Layout.alignment: Qt.AlignHCenter + ColumnLayout { + spacing: 10 * DefaultStyle.dp + Text { + Layout.preferredWidth: 330 * DefaultStyle.dp + Layout.alignment: Qt.AlignHCenter + + text: qsTr("Vérifier l'appareil") + horizontalAlignment: Text.AlignLeft + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + + Text { + Layout.preferredWidth: 330 * DefaultStyle.dp + Layout.alignment: Qt.AlignHCenter + + horizontalAlignment: Text.AlignLeft + //: 'To raise the security level, you can check the following codes with your correspondent.' : Explanation to do a security check. + text: qsTr("Dites %1 et cliquez sur les lettres votre interlocuteur vous dit :".arg(mainItem.call && mainItem.call.core.localSas || "")) + + wrapMode: Text.WordWrap + font.pixelSize: 14 * DefaultStyle.dp + } + } + + GridLayout { + id: securityGridView + // Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + rows: 2 + columns: 2 + rowSpacing: 32 * DefaultStyle.dp + columnSpacing: 32 * DefaultStyle.dp + property var correctIndex + property var modelList + Connections { + target: mainItem.call.core + // this connection is needed to get the remoteSas when available + // due to the asynchronous connection between core and ui + onRemoteSasChanged: { + securityGridView.correctIndex = UtilsCpp.getRandomIndex(4) + securityGridView.modelList = UtilsCpp.generateSecurityLettersArray(4, securityGridView.correctIndex, mainItem.call.core.remoteSas) + } + } + Repeater { + model: securityGridView.modelList + Item { + // implicitWidth: 70 * DefaultStyle.dp + // implicitHeight: 70 * DefaultStyle.dp + width: 70 * DefaultStyle.dp + height: 70 * DefaultStyle.dp + Rectangle { + id: code + anchors.fill: parent + color: DefaultStyle.grey_0 + radius: 71 * DefaultStyle.dp + Text { + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: modelData + font { + pixelSize: 32 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + MouseArea { + anchors.fill: parent + onClicked: { + console.log("correct", index == securityGridView.correctIndex, index) + if (index == securityGridView.correctIndex) { + if(mainItem.call) mainItem.call.core.lVerifyAuthenticationToken(true) + } else { + if(mainItem.call) mainItem.call.core.lVerifyAuthenticationToken(false) + mainItem.close() + } + } + } + } + MultiEffect { + source: code + anchors.fill: code + shadowEnabled: true + shadowOpacity: 0.1 + shadowBlur: 1.0 + } + } + } + } + } +} diff --git a/Linphone/view/Page/Main/CallPage.qml b/Linphone/view/Page/Main/CallPage.qml index 9076caae..f6ae0805 100644 --- a/Linphone/view/Page/Main/CallPage.qml +++ b/Linphone/view/Page/Main/CallPage.qml @@ -23,7 +23,6 @@ AbstractMainPage { listStackView.push(newCallItem) } - Dialog { id: deleteHistoryPopup width: 278 * DefaultStyle.dp diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index 0dab48b7..571347f2 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -66,4 +66,5 @@ QtObject { property string heart: "image://internal/heart.svg" property string heartFill: "image://internal/heart-fill.svg" property string recordFill: "image://internal/record-fill.svg" + property string mediaEncryptionZrtpPq: "image://internal/media_encryption_zrtp_pq.svg" } \ No newline at end of file