From af0c8135afce32d9e06cc2446d9c675693d2c5fb Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 18 Nov 2018 14:18:45 +0900 Subject: BaseJob::rawDataSample() A new recommended (and localisable) way of getting a piece of raw response to display next to error messages as "details". BaseJob::rawData() returns exactly the trimmed piece of data, no "truncated" suffix there anymore. --- lib/connection.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index df9fb112..6bda932a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -65,10 +65,6 @@ HashT erase_if(HashT& hashMap, Pred pred) return removals; } -#ifndef TRIM_RAW_DATA -#define TRIM_RAW_DATA 65535 -#endif - class Connection::Private { public: @@ -228,8 +224,7 @@ void Connection::doConnectToServer(const QString& user, const QString& password, }); connect(loginJob, &BaseJob::failure, this, [this, loginJob] { - emit loginError(loginJob->errorString(), - loginJob->rawData(TRIM_RAW_DATA)); + emit loginError(loginJob->errorString(), loginJob->rawDataSample()); }); } @@ -306,7 +301,7 @@ void Connection::sync(int timeout) connect( job, &SyncJob::retryScheduled, this, [this,job] (int retriesTaken, int nextInMilliseconds) { - emit networkError(job->errorString(), job->rawData(TRIM_RAW_DATA), + emit networkError(job->errorString(), job->rawDataSample(), retriesTaken, nextInMilliseconds); }); connect( job, &SyncJob::failure, this, [this, job] { @@ -315,10 +310,10 @@ void Connection::sync(int timeout) { qCWarning(SYNCJOB) << "Sync job failed with ContentAccessError - login expired?"; - emit loginError(job->errorString(), job->rawData(TRIM_RAW_DATA)); + emit loginError(job->errorString(), job->rawDataSample()); } else - emit syncError(job->errorString(), job->rawData(TRIM_RAW_DATA)); + emit syncError(job->errorString(), job->rawDataSample()); }); } -- cgit v1.2.3 From dc3d6bd3b46ae7a9e8d9b9f62e50db982ef2b004 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 20 Nov 2018 13:24:40 +0900 Subject: Make SyncData more self-contained and prepare for cache splitting SyncData now resides in its own pair of files and is capable to load either from file or from JSON. There is also (yet untested) capability to load rooms from files if a file name stands is the value for a given room id. This allows to store the master cache file separately from cache files for each room, massively easing the problem of bulky accounts that can overflow the poor capacity of Qt's JSON engine. --- lib/connection.cpp | 50 +++++++++++++------------------------------------- 1 file changed, 13 insertions(+), 37 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 6bda932a..8a451a79 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -1059,9 +1058,6 @@ void Connection::setHomeserver(const QUrl& url) emit homeserverChanged(homeserver()); } -static constexpr int CACHE_VERSION_MAJOR = 8; -static constexpr int CACHE_VERSION_MINOR = 0; - void Connection::saveState(const QUrl &toFile) const { if (!d->cacheState) @@ -1091,6 +1087,8 @@ void Connection::saveState(const QUrl &toFile) const QJsonObject inviteRooms; for (const auto* i : roomMap()) // Pass on rooms in Leave state { + // TODO: instead of adding the room JSON add a file name and save + // the JSON to that file. if (i->joinState() == JoinState::Invite) inviteRooms.insert(i->id(), i->toJson()); else @@ -1123,8 +1121,8 @@ void Connection::saveState(const QUrl &toFile) const } QJsonObject versionObj; - versionObj.insert("major", CACHE_VERSION_MAJOR); - versionObj.insert("minor", CACHE_VERSION_MINOR); + versionObj.insert("major", SyncData::cacheVersion().first); + versionObj.insert("minor", SyncData::cacheVersion().second); rootObj.insert("cache_version", versionObj); QJsonDocument json { rootObj }; @@ -1142,42 +1140,20 @@ void Connection::loadState(const QUrl &fromFile) return; QElapsedTimer et; et.start(); - QFile file { - fromFile.isEmpty() ? stateCachePath() : fromFile.toLocalFile() - }; - if (!file.exists()) - { - qCDebug(MAIN) << "No state cache file found"; - return; - } - if(!file.open(QFile::ReadOnly)) - { - qCWarning(MAIN) << "file " << file.fileName() << "failed to open for read"; - return; - } - QByteArray data = file.readAll(); - auto jsonDoc = d->cacheToBinary ? QJsonDocument::fromBinaryData(data) : - QJsonDocument::fromJson(data); - if (jsonDoc.isNull()) - { - qCWarning(MAIN) << "Cache file broken, discarding"; + SyncData sync { + fromFile.isEmpty() ? stateCachePath() : fromFile.toLocalFile() }; + if (sync.nextBatch().isEmpty()) // No token means no cache by definition return; - } - auto actualCacheVersionMajor = - jsonDoc.object() - .value("cache_version").toObject() - .value("major").toInt(); - if (actualCacheVersionMajor < CACHE_VERSION_MAJOR) + + if (!sync.unresolvedRooms().isEmpty()) { - qCWarning(MAIN) - << "Major version of the cache file is" << actualCacheVersionMajor - << "but" << CACHE_VERSION_MAJOR << "required; discarding the cache"; + qCWarning(MAIN) << "State cache incomplete, discarding"; return; } - - SyncData sync; - sync.parseJson(jsonDoc); + // TODO: to handle load failures, instead of the above block: + // 1. Do initial sync on failed rooms without saving the nextBatch token + // 2. Do the sync across all rooms as normal onSyncSuccess(std::move(sync)); qCDebug(PROFILER) << "*** Cached state for" << userId() << "loaded in" << et; } -- cgit v1.2.3 From 5fb74ca3d253b658fe77aaeb6a106cf6c0a9e7f0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 22 Nov 2018 16:51:49 +0900 Subject: Save state cache per-room Closes #257. --- lib/connection.cpp | 67 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 30 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 8a451a79..099d6a4e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1058,41 +1058,55 @@ void Connection::setHomeserver(const QUrl& url) emit homeserverChanged(homeserver()); } -void Connection::saveState(const QUrl &toFile) const +void Connection::saveRoomState(Room* r) const { + Q_ASSERT(r); if (!d->cacheState) return; - QElapsedTimer et; et.start(); + QFile outRoomFile { stateCachePath() % SyncData::fileNameForRoom(r->id()) }; + if (outRoomFile.open(QFile::WriteOnly)) + { + QJsonDocument json { r->toJson() }; + auto data = d->cacheToBinary ? json.toBinaryData() + : json.toJson(QJsonDocument::Compact); + outRoomFile.write(data.data(), data.size()); + } else { + qCWarning(MAIN) << "Error opening" << outRoomFile.fileName() + << ":" << outRoomFile.errorString(); + } +} - QFileInfo stateFile { - toFile.isEmpty() ? stateCachePath() : toFile.toLocalFile() - }; - if (!stateFile.dir().exists()) - stateFile.dir().mkpath("."); +void Connection::saveState() const +{ + if (!d->cacheState) + return; - QFile outfile { stateFile.absoluteFilePath() }; - if (!outfile.open(QFile::WriteOnly)) + QElapsedTimer et; et.start(); + + QFile outFile { stateCachePath() % "state.json" }; + if (!outFile.open(QFile::WriteOnly)) { - qCWarning(MAIN) << "Error opening" << stateFile.absoluteFilePath() - << ":" << outfile.errorString(); + qCWarning(MAIN) << "Error opening" << outFile.fileName() + << ":" << outFile.errorString(); qCWarning(MAIN) << "Caching the rooms state disabled"; d->cacheState = false; return; } - QJsonObject rootObj; + QJsonObject rootObj { + { QStringLiteral("cache_version"), QJsonObject { + { QStringLiteral("major"), SyncData::cacheVersion().first }, + { QStringLiteral("minor"), SyncData::cacheVersion().second } + }}}; { QJsonObject rooms; QJsonObject inviteRooms; for (const auto* i : roomMap()) // Pass on rooms in Leave state { - // TODO: instead of adding the room JSON add a file name and save - // the JSON to that file. - if (i->joinState() == JoinState::Invite) - inviteRooms.insert(i->id(), i->toJson()); - else - rooms.insert(i->id(), i->toJson()); + auto& targetArray = i->joinState() == JoinState::Invite + ? inviteRooms : rooms; + targetArray.insert(i->id(), QJsonObject()); QElapsedTimer et1; et1.start(); QCoreApplication::processEvents(); if (et1.elapsed() > 1) @@ -1120,29 +1134,23 @@ void Connection::saveState(const QUrl &toFile) const QJsonObject {{ QStringLiteral("events"), accountDataEvents }}); } - QJsonObject versionObj; - versionObj.insert("major", SyncData::cacheVersion().first); - versionObj.insert("minor", SyncData::cacheVersion().second); - rootObj.insert("cache_version", versionObj); - QJsonDocument json { rootObj }; auto data = d->cacheToBinary ? json.toBinaryData() : json.toJson(QJsonDocument::Compact); qCDebug(PROFILER) << "Cache for" << userId() << "generated in" << et; - outfile.write(data.data(), data.size()); - qCDebug(MAIN) << "State cache saved to" << outfile.fileName(); + outFile.write(data.data(), data.size()); + qCDebug(MAIN) << "State cache saved to" << outFile.fileName(); } -void Connection::loadState(const QUrl &fromFile) +void Connection::loadState() { if (!d->cacheState) return; QElapsedTimer et; et.start(); - SyncData sync { - fromFile.isEmpty() ? stateCachePath() : fromFile.toLocalFile() }; + SyncData sync { stateCachePath() % "state.json" }; if (sync.nextBatch().isEmpty()) // No token means no cache by definition return; @@ -1162,8 +1170,7 @@ QString Connection::stateCachePath() const { auto safeUserId = userId(); safeUserId.replace(':', '_'); - return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) - % '/' % safeUserId % "_state.json"; + return cacheLocation(safeUserId); } bool Connection::cacheState() const -- cgit v1.2.3 From 53f3fe79ef91bb5ba318b61b3a073c12409abc72 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 22 Nov 2018 21:06:36 +0900 Subject: Connection: Log when a room state cache is written --- lib/connection.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 099d6a4e..7feeb075 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1071,6 +1071,7 @@ void Connection::saveRoomState(Room* r) const auto data = d->cacheToBinary ? json.toBinaryData() : json.toJson(QJsonDocument::Compact); outRoomFile.write(data.data(), data.size()); + qCDebug(MAIN) << "Room state cache saved to" << outRoomFile.fileName(); } else { qCWarning(MAIN) << "Error opening" << outRoomFile.fileName() << ":" << outRoomFile.errorString(); -- cgit v1.2.3 From 64799eaf667840c7f81d80810508d948f64f97d6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 23 Nov 2018 15:38:06 +0900 Subject: Connection::saveState: use null instead of an empty object for a room placeholder Otherwise placeholder objects are confused with normal room JSON objects when loading from the cache. Closes #257 (again). --- lib/connection.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 7feeb075..53835a80 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1104,15 +1104,8 @@ void Connection::saveState() const QJsonObject rooms; QJsonObject inviteRooms; for (const auto* i : roomMap()) // Pass on rooms in Leave state - { - auto& targetArray = i->joinState() == JoinState::Invite - ? inviteRooms : rooms; - targetArray.insert(i->id(), QJsonObject()); - QElapsedTimer et1; et1.start(); - QCoreApplication::processEvents(); - if (et1.elapsed() > 1) - qCDebug(PROFILER) << "processEvents() borrowed" << et1; - } + (i->joinState() == JoinState::Invite ? inviteRooms : rooms) + .insert(i->id(), QJsonValue::Null); QJsonObject roomObj; if (!rooms.isEmpty()) -- cgit v1.2.3 From 49ad563550ba9d2d03fc7a519ccb857a6d08791c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 23 Nov 2018 15:38:59 +0900 Subject: Room/Connection: don't save the just loaded room cache --- lib/connection.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 53835a80..9372acd5 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -316,7 +316,7 @@ void Connection::sync(int timeout) }); } -void Connection::onSyncSuccess(SyncData &&data) { +void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { d->data->setLastEvent(data.nextBatch()); for (auto&& roomData: data.takeRoomData()) { @@ -337,7 +337,7 @@ void Connection::onSyncSuccess(SyncData &&data) { } if ( auto* r = provideRoom(roomData.roomId, roomData.joinState) ) { - r->updateData(std::move(roomData)); + r->updateData(std::move(roomData), fromCache); if (d->firstTimeRooms.removeOne(r)) emit loadedRoomState(r); } @@ -1156,7 +1156,7 @@ void Connection::loadState() // TODO: to handle load failures, instead of the above block: // 1. Do initial sync on failed rooms without saving the nextBatch token // 2. Do the sync across all rooms as normal - onSyncSuccess(std::move(sync)); + onSyncSuccess(std::move(sync), true); qCDebug(PROFILER) << "*** Cached state for" << userId() << "loaded in" << et; } -- cgit v1.2.3 From c665883be52016be51f5b0a902e43885b024a8ac Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 6 Dec 2018 20:57:37 +0900 Subject: Connection: Avoid Omittable<>::operator bool It was accidentally (and incorrectly) used in tags sorting code; will be dropped from Omittable<> in a later commit. --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 9372acd5..26c33767 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -825,7 +825,7 @@ QHash> Connection::tagsToRooms() const for (auto it = result.begin(); it != result.end(); ++it) std::sort(it->begin(), it->end(), [t=it.key()] (Room* r1, Room* r2) { - return r1->tags().value(t).order < r2->tags().value(t).order; + return r1->tags().value(t) < r2->tags().value(t); }); return result; } -- cgit v1.2.3 From 1ff8a0c26fc2738a085ca0302f0471ffa95a567e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 14 Nov 2018 07:16:05 +0900 Subject: Connection: support members lazy-loading This should cover the Connection-related part of #253. Connection gained lazyLoading/setLazyLoading accessors and the respective Q_PROPERTY. When lazy loading is on, sync() adds lazy_load_members: true to its filter. --- lib/connection.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 26c33767..52609370 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -94,6 +94,7 @@ class Connection::Private bool cacheState = true; bool cacheToBinary = SettingsGroup("libqmatrixclient") .value("cache_type").toString() != "json"; + bool lazyLoading; void connectWithToken(const QString& user, const QString& accessToken, const QString& deviceId); @@ -287,11 +288,11 @@ void Connection::sync(int timeout) if (d->syncJob) return; - // Raw string: http://en.cppreference.com/w/cpp/language/string_literal - const auto filter = - QStringLiteral(R"({"room": { "timeline": { "limit": 100 } } })"); + Filter filter; + filter.room->timeline->limit = 100; + filter.room->state->lazyLoadMembers = d->lazyLoading; auto job = d->syncJob = callApi(BackgroundRequest, - d->data->lastEvent(), filter, timeout); + d->data->lastEvent(), filter, timeout); connect( job, &SyncJob::success, this, [this, job] { onSyncSuccess(job->takeData()); d->syncJob = nullptr; @@ -1181,6 +1182,20 @@ void Connection::setCacheState(bool newValue) } } +bool QMatrixClient::Connection::lazyLoading() const +{ + return d->lazyLoading; +} + +void QMatrixClient::Connection::setLazyLoading(bool newValue) +{ + if (d->lazyLoading != newValue) + { + d->lazyLoading = newValue; + emit lazyLoadingChanged(); + } +} + void Connection::getTurnServers() { auto job = callApi(); -- cgit v1.2.3 From 9272d21ce6e5439444794e6da58e08421e8973db Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 8 Dec 2018 15:37:16 +0900 Subject: Room summaries --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 52609370..28156d11 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -562,7 +562,7 @@ void Connection::doInDirectChat(User* u, { Q_ASSERT(r->id() == roomId); // A direct chat with yourself should only involve yourself :) - if (userId == d->userId && r->memberCount() > 1) + if (userId == d->userId && r->totalMemberCount() > 1) continue; qCDebug(MAIN) << "Requested direct chat with" << userId << "is already available as" << r->id(); -- cgit v1.2.3 From c6720cc8bb8d45ab4d2b7390f076d50cb59cb8d3 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 11 Dec 2018 12:23:33 +0900 Subject: Expose Connection::nextBatchToken() --- lib/connection.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 28156d11..76e61ed1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -412,6 +412,11 @@ void Connection::stopSync() } } +QString Connection::nextBatchToken() const +{ + return d->data->lastEvent(); +} + PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const { return callApi(room->id(), "m.read", event->id()); -- cgit v1.2.3 From cb5f0f61e74cdc4ee64530cd73af0d080538bc1e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 13 Dec 2018 10:17:02 +0900 Subject: Connection: initialize lazyLoading member variable --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 76e61ed1..a16bc753 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -94,7 +94,7 @@ class Connection::Private bool cacheState = true; bool cacheToBinary = SettingsGroup("libqmatrixclient") .value("cache_type").toString() != "json"; - bool lazyLoading; + bool lazyLoading = false; void connectWithToken(const QString& user, const QString& accessToken, const QString& deviceId); -- cgit v1.2.3 From 3b88c2b537b6cb98dcd0f2066d39e426b5cc52da Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 26 Dec 2018 19:34:15 +0900 Subject: Connection::upload*: autodetect content type if not supplied --- lib/connection.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index a16bc753..c17cbffc 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include using namespace QMatrixClient; @@ -466,13 +467,21 @@ MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, } UploadContentJob* Connection::uploadContent(QIODevice* contentSource, - const QString& filename, const QString& contentType) const + const QString& filename, const QString& overrideContentType) const { + auto contentType = overrideContentType; + if (contentType.isEmpty()) + { + contentType = + QMimeDatabase().mimeTypeForFileNameAndData(filename, contentSource) + .name(); + contentSource->open(QIODevice::ReadOnly); + } return callApi(contentSource, filename, contentType); } UploadContentJob* Connection::uploadFile(const QString& fileName, - const QString& contentType) + const QString& overrideContentType) { auto sourceFile = new QFile(fileName); if (!sourceFile->open(QIODevice::ReadOnly)) @@ -482,7 +491,7 @@ UploadContentJob* Connection::uploadFile(const QString& fileName, return nullptr; } return uploadContent(sourceFile, QFileInfo(*sourceFile).fileName(), - contentType); + overrideContentType); } GetContentJob* Connection::getContent(const QString& mediaId) const -- cgit v1.2.3 From 8dc2a3273ac3a5bb518fa987b25e3df15106c4d2 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 13 Jan 2019 13:34:31 +0900 Subject: Connection::provideRoom: allow omitting join state --- lib/connection.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index c17cbffc..18fa91e7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -981,11 +981,12 @@ const ConnectionData* Connection::connectionData() const return d->data.get(); } -Room* Connection::provideRoom(const QString& id, JoinState joinState) +Room* Connection::provideRoom(const QString& id, Omittable joinState) { // TODO: This whole function is a strong case for a RoomManager class. Q_ASSERT_X(!id.isEmpty(), __FUNCTION__, "Empty room id"); + // If joinState.omitted(), all joinState == comparisons below are false. const auto roomKey = qMakePair(id, joinState == JoinState::Invite); auto* room = d->roomMap.value(roomKey, nullptr); if (room) @@ -995,10 +996,19 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) // and emit a signal. For Invite and Join, there's no such problem. if (room->joinState() == joinState && joinState != JoinState::Leave) return room; + } else if (joinState.omitted()) + { + // No Join and Leave, maybe Invite? + room = d->roomMap.value({id, true}, nullptr); + if (room) + return room; + // No Invite either, setup a new room object below } - else + + if (!room) { - room = roomFactory()(this, id, joinState); + room = roomFactory()(this, id, + joinState.omitted() ? JoinState::Join : joinState.value()); if (!room) { qCCritical(MAIN) << "Failed to create a room" << id; @@ -1010,6 +1020,9 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) this, &Connection::aboutToDeleteRoom); emit newRoom(room); } + if (joinState.omitted()) + return room; + if (joinState == JoinState::Invite) { // prev is either Leave or nullptr @@ -1018,7 +1031,7 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) } else { - room->setJoinState(joinState); + room->setJoinState(joinState.value()); // Preempt the Invite room (if any) with a room in Join/Leave state. auto* prevInvite = d->roomMap.take({id, true}); if (joinState == JoinState::Join) -- cgit v1.2.3 From a9bdc89f66ba283859fd9ca7383a7256198174ed Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 13 Jan 2019 13:36:57 +0900 Subject: Connection: fix/workaround glitches on joining/leaving Closes #273, in particular. --- lib/connection.cpp | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 18fa91e7..c582cf94 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -84,6 +84,7 @@ class Connection::Private QHash, Room*> roomMap; QVector roomIdsToForget; QVector firstTimeRooms; + QVector pendingStateRoomIds; QMap userMap; DirectChatsMap directChats; DirectChatUsersMap directChatUsers; @@ -339,6 +340,7 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { } if ( auto* r = provideRoom(roomData.roomId, roomData.joinState) ) { + d->pendingStateRoomIds.removeOne(roomData.roomId); r->updateData(std::move(roomData), fromCache); if (d->firstTimeRooms.removeOne(r)) emit loadedRoomState(r); @@ -427,14 +429,32 @@ JoinRoomJob* Connection::joinRoom(const QString& roomAlias, const QStringList& serverNames) { auto job = callApi(roomAlias, serverNames); + // Upon completion, ensure a room object in Join state is created but only + // if it's not already there due to a sync completing earlier. connect(job, &JoinRoomJob::success, - this, [this, job] { provideRoom(job->roomId(), JoinState::Join); }); + this, [this, job] { provideRoom(job->roomId()); }); return job; } -void Connection::leaveRoom(Room* room) +LeaveRoomJob* Connection::leaveRoom(Room* room) { - callApi(room->id()); + const auto& roomId = room->id(); + const auto job = callApi(roomId); + if (room->joinState() == JoinState::Invite) + { + // Workaround matrix-org/synapse#2181 - if the room is in invite state + // the invite may have been cancelled but Synapse didn't send it in + // `/sync`. See also #273 for the discussion in the library context. + d->pendingStateRoomIds.push_back(roomId); + connect(job, &LeaveRoomJob::success, this, [this,roomId] { + if (d->pendingStateRoomIds.removeOne(roomId)) + { + qCDebug(MAIN) << "Forcing the room to Leave status"; + provideRoom(roomId, JoinState::Leave); + } + }); + } + return job; } inline auto splitMediaId(const QString& mediaId) -- cgit v1.2.3 From cc7d034fa67196ad4950d3785aff64e4c5765855 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Wed, 30 Jan 2019 19:30:07 +0300 Subject: Connection: infinite sync loop logic by default --- lib/connection.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index c582cf94..982145f7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -133,6 +133,8 @@ Connection::Connection(const QUrl& server, QObject* parent) , d(std::make_unique(std::make_unique(server))) { d->q = this; // All d initialization should occur before this line + // sync loop: + connect(this, &Connection::syncDone, this, &Connection::getNewEvents); } Connection::Connection(QObject* parent) @@ -250,7 +252,7 @@ void Connection::Private::connectWithToken(const QString& user, << "by user" << userId << "from device" << deviceId; emit q->stateChanged(); emit q->connected(); - + q->sync(); // initial sync after connection } void Connection::checkAndConnect(const QString& userId, @@ -406,6 +408,15 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { } } +void Connection::getNewEvents() +{ + // Borrowed the logic from Quiark's code in Tensor + // to cache not too aggressively and not on the first sync. + if (++_saveStateCounter % 17 == 2) + saveState(); + sync(30*1000); +} + void Connection::stopSync() { if (d->syncJob) -- cgit v1.2.3 From 22dd5f1e8988b03a691487cdad164a82a36e7f8c Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sat, 2 Feb 2019 23:51:20 +0300 Subject: Connection: separated sync loop logic with delay control Signed-off-by: Alexey Andreyev --- lib/connection.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 8 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 982145f7..e7f9e4b2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -45,6 +45,7 @@ #include #include #include +#include using namespace QMatrixClient; @@ -133,8 +134,6 @@ Connection::Connection(const QUrl& server, QObject* parent) , d(std::make_unique(std::make_unique(server))) { d->q = this; // All d initialization should occur before this line - // sync loop: - connect(this, &Connection::syncDone, this, &Connection::getNewEvents); } Connection::Connection(QObject* parent) @@ -232,6 +231,11 @@ void Connection::doConnectToServer(const QString& user, const QString& password, }); } +void Connection::syncLoopIteration() +{ + sync(_syncLoopTimeout); +} + void Connection::connectWithToken(const QString& userId, const QString& accessToken, const QString& deviceId) @@ -252,7 +256,6 @@ void Connection::Private::connectWithToken(const QString& user, << "by user" << userId << "from device" << deviceId; emit q->stateChanged(); emit q->connected(); - q->sync(); // initial sync after connection } void Connection::checkAndConnect(const QString& userId, @@ -321,6 +324,15 @@ void Connection::sync(int timeout) }); } +void Connection::syncLoop(int timeout) +{ + _syncLoopTimeout = timeout; + connect(this, &Connection::syncDone, this, &Connection::getNewEventsOnSyncDone); + connect(this, &Connection::syncError, this, &Connection::getNewEventsOnSyncError); + _syncLoopElapsedTimer.start(); + sync(_syncLoopTimeout); // initial sync to start the loop +} + void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { d->data->setLastEvent(data.nextBatch()); for (auto&& roomData: data.takeRoomData()) @@ -410,11 +422,33 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { void Connection::getNewEvents() { - // Borrowed the logic from Quiark's code in Tensor - // to cache not too aggressively and not on the first sync. - if (++_saveStateCounter % 17 == 2) - saveState(); - sync(30*1000); + int delay = minSyncLoopDelayMs() - _syncLoopElapsedTimer.restart(); + if (delay<0) { + delay = 0; + } + QTimer::singleShot(delay, this, &Connection::syncLoopIteration); +} + +void Connection::getNewEventsOnSyncDone() +{ + if (_prevSyncLoopIterationDone) { + _syncLoopAttemptNumber++; + } else { + _syncLoopAttemptNumber = 0; + } + emit syncAttemptNumberChanged(_syncLoopAttemptNumber); + getNewEvents(); +} + +void Connection::getNewEventsOnSyncError() +{ + if (_prevSyncLoopIterationDone) { + _syncLoopAttemptNumber = 0; + } else { + _syncLoopAttemptNumber++; + } + emit syncAttemptNumberChanged(_syncLoopAttemptNumber); + getNewEvents(); } void Connection::stopSync() @@ -436,6 +470,15 @@ PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const return callApi(room->id(), "m.read", event->id()); } +void Connection::setMinSyncDelayMs(qint64 minSyncDelayMs) +{ + if (_minSyncLoopDelayMs == minSyncDelayMs) + return; + + _minSyncLoopDelayMs = minSyncDelayMs; + emit minSyncDelayMsChanged(_minSyncLoopDelayMs); +} + JoinRoomJob* Connection::joinRoom(const QString& roomAlias, const QStringList& serverNames) { @@ -1100,6 +1143,16 @@ user_factory_t Connection::userFactory() return _userFactory; } +qint64 Connection::minSyncLoopDelayMs() const +{ + return _minSyncLoopDelayMs; +} + +uint Connection::syncLoopAttemptNumber() const +{ + return _syncLoopAttemptNumber; +} + room_factory_t Connection::_roomFactory = defaultRoomFactory<>(); user_factory_t Connection::_userFactory = defaultUserFactory<>(); -- cgit v1.2.3 From bf6cd3d29052f9e79fee29c6a6646d95271a196a Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Mon, 4 Feb 2019 11:58:43 +0300 Subject: Connection: simplified sync loop logic without delays Signed-off-by: Alexey Andreyev --- lib/connection.cpp | 58 +++--------------------------------------------------- 1 file changed, 3 insertions(+), 55 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index e7f9e4b2..a9a8bba3 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -45,7 +45,6 @@ #include #include #include -#include using namespace QMatrixClient; @@ -256,6 +255,7 @@ void Connection::Private::connectWithToken(const QString& user, << "by user" << userId << "from device" << deviceId; emit q->stateChanged(); emit q->connected(); + } void Connection::checkAndConnect(const QString& userId, @@ -327,10 +327,8 @@ void Connection::sync(int timeout) void Connection::syncLoop(int timeout) { _syncLoopTimeout = timeout; - connect(this, &Connection::syncDone, this, &Connection::getNewEventsOnSyncDone); - connect(this, &Connection::syncError, this, &Connection::getNewEventsOnSyncError); - _syncLoopElapsedTimer.start(); - sync(_syncLoopTimeout); // initial sync to start the loop + connect(this, &Connection::syncDone, this, &Connection::syncLoopIteration); + syncLoopIteration(); // initial sync to start the loop } void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { @@ -420,37 +418,6 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { } } -void Connection::getNewEvents() -{ - int delay = minSyncLoopDelayMs() - _syncLoopElapsedTimer.restart(); - if (delay<0) { - delay = 0; - } - QTimer::singleShot(delay, this, &Connection::syncLoopIteration); -} - -void Connection::getNewEventsOnSyncDone() -{ - if (_prevSyncLoopIterationDone) { - _syncLoopAttemptNumber++; - } else { - _syncLoopAttemptNumber = 0; - } - emit syncAttemptNumberChanged(_syncLoopAttemptNumber); - getNewEvents(); -} - -void Connection::getNewEventsOnSyncError() -{ - if (_prevSyncLoopIterationDone) { - _syncLoopAttemptNumber = 0; - } else { - _syncLoopAttemptNumber++; - } - emit syncAttemptNumberChanged(_syncLoopAttemptNumber); - getNewEvents(); -} - void Connection::stopSync() { if (d->syncJob) @@ -470,15 +437,6 @@ PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const return callApi(room->id(), "m.read", event->id()); } -void Connection::setMinSyncDelayMs(qint64 minSyncDelayMs) -{ - if (_minSyncLoopDelayMs == minSyncDelayMs) - return; - - _minSyncLoopDelayMs = minSyncDelayMs; - emit minSyncDelayMsChanged(_minSyncLoopDelayMs); -} - JoinRoomJob* Connection::joinRoom(const QString& roomAlias, const QStringList& serverNames) { @@ -1143,16 +1101,6 @@ user_factory_t Connection::userFactory() return _userFactory; } -qint64 Connection::minSyncLoopDelayMs() const -{ - return _minSyncLoopDelayMs; -} - -uint Connection::syncLoopAttemptNumber() const -{ - return _syncLoopAttemptNumber; -} - room_factory_t Connection::_roomFactory = defaultRoomFactory<>(); user_factory_t Connection::_userFactory = defaultUserFactory<>(); -- cgit v1.2.3 From 73c836239bfa35713ad76d5e205ce2f2dceffffd Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sun, 10 Feb 2019 15:08:08 +0300 Subject: Connection: move syncLoopTimeout to Connection::Private Signed-off-by: Alexey Andreyev --- lib/connection.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index a9a8bba3..63b0a31d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -90,6 +90,7 @@ class Connection::Private DirectChatUsersMap directChatUsers; std::unordered_map accountData; QString userId; + int syncLoopTimeout = -1; SyncJob* syncJob = nullptr; @@ -232,7 +233,7 @@ void Connection::doConnectToServer(const QString& user, const QString& password, void Connection::syncLoopIteration() { - sync(_syncLoopTimeout); + sync(d->syncLoopTimeout); } void Connection::connectWithToken(const QString& userId, @@ -326,7 +327,7 @@ void Connection::sync(int timeout) void Connection::syncLoop(int timeout) { - _syncLoopTimeout = timeout; + d->syncLoopTimeout = timeout; connect(this, &Connection::syncDone, this, &Connection::syncLoopIteration); syncLoopIteration(); // initial sync to start the loop } -- cgit v1.2.3 From e9ace5cbe8a930a8aa3cc81df1a4f73d51c5fa90 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 5 Feb 2019 19:14:55 +0900 Subject: Connection::createRoom: support passing a room version On the path to address #233. --- lib/connection.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index c582cf94..88fb547f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -537,7 +537,8 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, CreateRoomJob* Connection::createRoom(RoomVisibility visibility, const QString& alias, const QString& name, const QString& topic, - QStringList invites, const QString& presetName, bool isDirect, + QStringList invites, const QString& presetName, + const QString& roomVersion, bool isDirect, const QVector& initialState, const QVector& invite3pids, const QJsonObject& creationContent) @@ -546,7 +547,7 @@ CreateRoomJob* Connection::createRoom(RoomVisibility visibility, auto job = callApi( visibility == PublishRoom ? QStringLiteral("public") : QStringLiteral("private"), - alias, name, topic, invites, invite3pids, QString(/*TODO: #233*/), + alias, name, topic, invites, invite3pids, roomVersion, creationContent, initialState, presetName, isDirect); connect(job, &BaseJob::success, this, [this,job] { emit createdRoom(provideRoom(job->roomId(), JoinState::Join)); @@ -648,7 +649,7 @@ CreateRoomJob* Connection::createDirectChat(const QString& userId, const QString& topic, const QString& name) { return createRoom(UnpublishRoom, "", name, topic, {userId}, - "trusted_private_chat", true); + "trusted_private_chat", {}, true); } ForgetRoomJob* Connection::forgetRoom(const QString& id) -- cgit v1.2.3 From e12fc32b94c3840249676b2e0656c174846f1c6e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 12 Feb 2019 22:11:23 +0900 Subject: Connection: load supported room versions A part of #236. --- lib/connection.cpp | 58 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 2a2d4822..6d1763ee 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -24,6 +24,7 @@ #include "room.h" #include "settings.h" #include "csapi/login.h" +#include "csapi/capabilities.h" #include "csapi/logout.h" #include "csapi/receipts.h" #include "csapi/leaving.h" @@ -92,6 +93,9 @@ class Connection::Private QString userId; int syncLoopTimeout = -1; + GetCapabilitiesJob* capabilitiesJob = nullptr; + GetCapabilitiesJob::Capabilities capabilities; + SyncJob* syncJob = nullptr; bool cacheState = true; @@ -244,6 +248,29 @@ void Connection::connectWithToken(const QString& userId, [=] { d->connectWithToken(userId, accessToken, deviceId); }); } +void Connection::reloadCapabilities() +{ + d->capabilitiesJob = callApi(BackgroundRequest); + connect(d->capabilitiesJob, &BaseJob::finished, this, [this] { + if (d->capabilitiesJob->error() == BaseJob::Success) + d->capabilities = d->capabilitiesJob->capabilities(); + else if (d->capabilitiesJob->error() == BaseJob::IncorrectRequestError) + qCDebug(MAIN) << "Server doesn't support /capabilities"; + + if (d->capabilities.roomVersions.omitted()) + { + qCWarning(MAIN) << "Pinning supported room version to 1"; + d->capabilities.roomVersions = { "1", {{ "1", "stable" }} }; + } else { + qCDebug(MAIN) << "Room versions:" + << defaultRoomVersion() << "is default, full list:" + << availableRoomVersions(); + } + Q_ASSERT(!d->capabilities.roomVersions.omitted()); + emit capabilitiesLoaded(); + }); +} + void Connection::Private::connectWithToken(const QString& user, const QString& accessToken, const QString& deviceId) @@ -256,7 +283,7 @@ void Connection::Private::connectWithToken(const QString& user, << "by user" << userId << "from device" << deviceId; emit q->stateChanged(); emit q->connected(); - + q->reloadCapabilities(); } void Connection::checkAndConnect(const QString& userId, @@ -1259,9 +1286,30 @@ void QMatrixClient::Connection::setLazyLoading(bool newValue) void Connection::getTurnServers() { - auto job = callApi(); - connect( job, &GetTurnServerJob::success, [=] { - emit turnServersChanged(job->data()); - }); + auto job = callApi(); + connect(job, &GetTurnServerJob::success, + this, [=] { emit turnServersChanged(job->data()); }); +} + +QString Connection::defaultRoomVersion() const +{ + Q_ASSERT(!d->capabilities.roomVersions.omitted()); + return d->capabilities.roomVersions->defaultVersion; +} +QStringList Connection::stableRoomVersions() const +{ + Q_ASSERT(!d->capabilities.roomVersions.omitted()); + QStringList l; + const auto& allVersions = d->capabilities.roomVersions->available; + for (auto it = allVersions.begin(); it != allVersions.end(); ++it) + if (it.value() == "stable") + l.push_back(it.key()); + return l; +} + +const QHash& Connection::availableRoomVersions() const +{ + Q_ASSERT(!d->capabilities.roomVersions.omitted()); + return d->capabilities.roomVersions->available; } -- cgit v1.2.3 From ac7d2ad8b0942cc465c0d340f159cb0b343008ab Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 15 Feb 2019 12:29:02 +0900 Subject: Room::checkVersion() and Room::unstableVersion() Initial (sans power levels checking) implementation of the check that room should be upgraded. Closes most of #236. --- lib/connection.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 6d1763ee..22fa2f15 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -268,6 +268,9 @@ void Connection::reloadCapabilities() } Q_ASSERT(!d->capabilities.roomVersions.omitted()); emit capabilitiesLoaded(); + for (auto* r: d->roomMap) + if (r->joinState() == JoinState::Join && r->successorId().isEmpty()) + r->checkVersion(); }); } @@ -383,8 +386,14 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { d->pendingStateRoomIds.removeOne(roomData.roomId); r->updateData(std::move(roomData), fromCache); if (d->firstTimeRooms.removeOne(r)) + { emit loadedRoomState(r); + if (!d->capabilities.roomVersions.omitted()) + r->checkVersion(); + // Otherwise, the version will be checked in reloadCapabilities() + } } + // Let UI update itself after updating each room QCoreApplication::processEvents(); } for (auto&& accountEvent: data.takeAccountData()) -- cgit v1.2.3 From ac5daf2ed495a932aba23606f5b3d0dca5aaf676 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 17 Feb 2019 17:41:47 +0900 Subject: Connection: loadingCapabilities(); sort availableRoomVersions --- lib/connection.cpp | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 22fa2f15..4c0fe6b8 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -274,6 +274,13 @@ void Connection::reloadCapabilities() }); } +bool Connection::loadingCapabilities() const +{ + // (Ab)use the fact that room versions cannot be omitted after + // the capabilities have been loaded (see reloadCapabilities() above). + return d->capabilities.roomVersions.omitted(); +} + void Connection::Private::connectWithToken(const QString& user, const QString& accessToken, const QString& deviceId) @@ -1300,6 +1307,9 @@ void Connection::getTurnServers() this, [=] { emit turnServersChanged(job->data()); }); } +const QString Connection::SupportedRoomVersion::StableTag = + QStringLiteral("stable"); + QString Connection::defaultRoomVersion() const { Q_ASSERT(!d->capabilities.roomVersions.omitted()); @@ -1312,13 +1322,34 @@ QStringList Connection::stableRoomVersions() const QStringList l; const auto& allVersions = d->capabilities.roomVersions->available; for (auto it = allVersions.begin(); it != allVersions.end(); ++it) - if (it.value() == "stable") + if (it.value() == SupportedRoomVersion::StableTag) l.push_back(it.key()); return l; } -const QHash& Connection::availableRoomVersions() const +inline bool roomVersionLess(const Connection::SupportedRoomVersion& v1, + const Connection::SupportedRoomVersion& v2) +{ + bool ok1 = false, ok2 = false; + const auto vNum1 = v1.id.toFloat(&ok1); + const auto vNum2 = v2.id.toFloat(&ok2); + return ok1 && ok2 ? vNum1 < vNum2 : v1.id < v2.id; +} + +QVector Connection::availableRoomVersions() const { Q_ASSERT(!d->capabilities.roomVersions.omitted()); - return d->capabilities.roomVersions->available; + QVector result; + result.reserve(d->capabilities.roomVersions->available.size()); + for (auto it = d->capabilities.roomVersions->available.begin(); + it != d->capabilities.roomVersions->available.end(); ++it) + result.push_back({ it.key(), it.value() }); + // Put stable versions over unstable; within each group, + // sort numeric versions as numbers, the rest as strings. + const auto mid = std::partition(result.begin(), result.end(), + std::mem_fn(&SupportedRoomVersion::isStable)); + std::sort(result.begin(), mid, roomVersionLess); + std::sort(mid, result.end(), roomVersionLess); + + return result; } -- cgit v1.2.3 From 297216e95c0802248110403f1b8fdcd5eb02fae6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 1 Feb 2019 07:30:09 +0900 Subject: Room::setAliases, Connection: roomByAlias, updateRoomAliases --- lib/connection.cpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4c0fe6b8..998282d3 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -83,6 +83,8 @@ class Connection::Private // separately so we should, e.g., keep objects for Invite and // Leave state of the same room. QHash, Room*> roomMap; + // Mapping from aliases to room ids, as per the last sync + QHash roomAliasMap; QVector roomIdsToForget; QVector firstTimeRooms; QVector pendingStateRoomIds; @@ -802,6 +804,41 @@ Room* Connection::room(const QString& roomId, JoinStates states) const return nullptr; } +Room* Connection::roomByAlias(const QString& roomAlias, JoinStates states) const +{ + const auto id = d->roomAliasMap.value(roomAlias); + if (!id.isEmpty()) + return room(id, states); + qCWarning(MAIN) << "Room for alias" << roomAlias + << "is not found under account" << userId(); + return nullptr; +} + +void Connection::updateRoomAliases(const QString& roomId, + const QStringList& previousRoomAliases, + const QStringList& roomAliases) +{ + for (const auto& a: previousRoomAliases) + if (d->roomAliasMap.remove(a) == 0) + qCWarning(MAIN) << "Alias" << a << "is not found (already deleted?)"; + + for (const auto& a: roomAliases) + { + auto& mappedId = d->roomAliasMap[a]; + if (!mappedId.isEmpty()) + { + if (mappedId == roomId) + qCDebug(MAIN) << "Alias" << a << "is already mapped to room" + << roomId; + else + qCWarning(MAIN) << "Alias" << a + << "will be force-remapped from room" + << mappedId << "to" << roomId; + } + mappedId = roomId; + } +} + Room* Connection::invitation(const QString& roomId) const { return d->roomMap.value({roomId, true}, nullptr); -- cgit v1.2.3 From 46cb751f73ca4234d5600e0c76e7f93c74278ef5 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 27 Feb 2019 15:28:39 +0900 Subject: Connection::stopSync: undo the sync loop --- lib/connection.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 998282d3..26b40c03 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -466,7 +466,10 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { void Connection::stopSync() { - if (d->syncJob) + // If there's a sync loop, break it + disconnect(this, &Connection::syncDone, + this, &Connection::syncLoopIteration); + if (d->syncJob) // If there's an ongoing sync job, stop it too { d->syncJob->abandon(); d->syncJob = nullptr; -- cgit v1.2.3 From aacc4bcb4a487871daae6717f77605aaba444341 Mon Sep 17 00:00:00 2001 From: Marc Deop Date: Sat, 2 Mar 2019 12:26:57 +0100 Subject: style: apply .clang-format to all .cpp and .h files --- lib/connection.cpp | 826 ++++++++++++++++++++++++----------------------------- 1 file changed, 377 insertions(+), 449 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 26b40c03..2b1cc1b9 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -13,39 +13,39 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "connection.h" #include "connectiondata.h" -#include "user.h" -#include "events/directchatevent.h" -#include "events/eventloader.h" -#include "room.h" -#include "settings.h" -#include "csapi/login.h" +#include "csapi/account-data.h" #include "csapi/capabilities.h" +#include "csapi/joining.h" +#include "csapi/leaving.h" +#include "csapi/login.h" #include "csapi/logout.h" #include "csapi/receipts.h" -#include "csapi/leaving.h" -#include "csapi/account-data.h" -#include "csapi/joining.h" -#include "csapi/to_device.h" #include "csapi/room_send.h" -#include "jobs/syncjob.h" -#include "jobs/mediathumbnailjob.h" -#include "jobs/downloadfilejob.h" +#include "csapi/to_device.h" #include "csapi/voip.h" +#include "events/directchatevent.h" +#include "events/eventloader.h" +#include "jobs/downloadfilejob.h" +#include "jobs/mediathumbnailjob.h" +#include "jobs/syncjob.h" +#include "room.h" +#include "settings.h" +#include "user.h" -#include -#include +#include #include -#include -#include #include -#include +#include #include -#include +#include +#include +#include +#include using namespace QMatrixClient; @@ -54,10 +54,8 @@ template HashT erase_if(HashT& hashMap, Pred pred) { HashT removals; - for (auto it = hashMap.begin(); it != hashMap.end();) - { - if (pred(it)) - { + for (auto it = hashMap.begin(); it != hashMap.end();) { + if (pred(it)) { removals.insert(it.key(), it.value()); it = hashMap.erase(it); } else @@ -69,82 +67,81 @@ HashT erase_if(HashT& hashMap, Pred pred) class Connection::Private { public: - explicit Private(std::unique_ptr&& connection) - : data(move(connection)) - { } - Q_DISABLE_COPY(Private) - Private(Private&&) = delete; - Private operator=(Private&&) = delete; - - Connection* q = nullptr; - std::unique_ptr data; - // A complex key below is a pair of room name and whether its - // state is Invited. The spec mandates to keep Invited room state - // separately so we should, e.g., keep objects for Invite and - // Leave state of the same room. - QHash, Room*> roomMap; - // Mapping from aliases to room ids, as per the last sync - QHash roomAliasMap; - QVector roomIdsToForget; - QVector firstTimeRooms; - QVector pendingStateRoomIds; - QMap userMap; - DirectChatsMap directChats; - DirectChatUsersMap directChatUsers; - std::unordered_map accountData; - QString userId; - int syncLoopTimeout = -1; - - GetCapabilitiesJob* capabilitiesJob = nullptr; - GetCapabilitiesJob::Capabilities capabilities; - - SyncJob* syncJob = nullptr; - - bool cacheState = true; - bool cacheToBinary = SettingsGroup("libqmatrixclient") - .value("cache_type").toString() != "json"; - bool lazyLoading = false; - - void connectWithToken(const QString& user, const QString& accessToken, - const QString& deviceId); - void broadcastDirectChatUpdates(const DirectChatsMap& additions, - const DirectChatsMap& removals); - - template - EventT* unpackAccountData() const - { - const auto& eventIt = accountData.find(EventT::matrixTypeId()); - return eventIt == accountData.end() - ? nullptr : weakPtrCast(eventIt->second); - } + explicit Private(std::unique_ptr&& connection) + : data(move(connection)) + { + } + Q_DISABLE_COPY(Private) + Private(Private&&) = delete; + Private operator=(Private&&) = delete; + + Connection* q = nullptr; + std::unique_ptr data; + // A complex key below is a pair of room name and whether its + // state is Invited. The spec mandates to keep Invited room state + // separately so we should, e.g., keep objects for Invite and + // Leave state of the same room. + QHash, Room*> roomMap; + // Mapping from aliases to room ids, as per the last sync + QHash roomAliasMap; + QVector roomIdsToForget; + QVector firstTimeRooms; + QVector pendingStateRoomIds; + QMap userMap; + DirectChatsMap directChats; + DirectChatUsersMap directChatUsers; + std::unordered_map accountData; + QString userId; + int syncLoopTimeout = -1; + + GetCapabilitiesJob* capabilitiesJob = nullptr; + GetCapabilitiesJob::Capabilities capabilities; + + SyncJob* syncJob = nullptr; + + bool cacheState = true; + bool cacheToBinary = + SettingsGroup("libqmatrixclient").value("cache_type").toString() + != "json"; + bool lazyLoading = false; + + void connectWithToken(const QString& user, const QString& accessToken, + const QString& deviceId); + void broadcastDirectChatUpdates(const DirectChatsMap& additions, + const DirectChatsMap& removals); + + template EventT* unpackAccountData() const + { + const auto& eventIt = accountData.find(EventT::matrixTypeId()); + return eventIt == accountData.end() + ? nullptr + : weakPtrCast(eventIt->second); + } - void packAndSendAccountData(EventPtr&& event) - { - const auto eventType = event->matrixType(); - q->callApi(userId, eventType, - event->contentJson()); - accountData[eventType] = std::move(event); - emit q->accountDataChanged(eventType); - } + void packAndSendAccountData(EventPtr&& event) + { + const auto eventType = event->matrixType(); + q->callApi(userId, eventType, event->contentJson()); + accountData[eventType] = std::move(event); + emit q->accountDataChanged(eventType); + } - template - void packAndSendAccountData(ContentT&& content) - { - packAndSendAccountData( - makeEvent(std::forward(content))); - } + template + void packAndSendAccountData(ContentT&& content) + { + packAndSendAccountData( + makeEvent(std::forward(content))); + } }; Connection::Connection(const QUrl& server, QObject* parent) - : QObject(parent) - , d(std::make_unique(std::make_unique(server))) + : QObject(parent), + d(std::make_unique(std::make_unique(server))) { d->q = this; // All d initialization should occur before this line } -Connection::Connection(QObject* parent) - : Connection({}, parent) -{ } +Connection::Connection(QObject* parent) : Connection({}, parent) {} Connection::~Connection() { @@ -159,19 +156,20 @@ void Connection::resolveServer(const QString& mxidOrDomain) // Try to parse as an FQID; if there's no @ part, assume it's a domain name. QRegularExpression parser( - "^(@.+?:)?" // Optional username (allow everything for compatibility) - "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address - "(:\\d{1,5})?$", // Optional port - QRegularExpression::UseUnicodePropertiesOption); // Because asian digits + "^(@.+?:)?" // Optional username (allow everything for + // compatibility) + "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 + // address + "(:\\d{1,5})?$", // Optional port + QRegularExpression::UseUnicodePropertiesOption); // Because asian + // digits auto match = parser.match(mxidOrDomain); QUrl maybeBaseUrl = QUrl::fromUserInput(match.captured(2)); maybeBaseUrl.setScheme("https"); // Instead of the Qt-default "http" - if (!match.hasMatch() || !maybeBaseUrl.isValid()) - { - emit resolveError( - tr("%1 is not a valid homeserver address") - .arg(maybeBaseUrl.toString())); + if (!match.hasMatch() || !maybeBaseUrl.isValid()) { + emit resolveError(tr("%1 is not a valid homeserver address") + .arg(maybeBaseUrl.toString())); return; } @@ -188,16 +186,15 @@ void Connection::resolveServer(const QString& mxidOrDomain) dns->setType(QDnsLookup::SRV); dns->setName("_matrix._tcp." + domain); - connect(dns, &QDnsLookup::finished, [this,dns,maybeBaseUrl]() { + connect(dns, &QDnsLookup::finished, [this, dns, maybeBaseUrl]() { QUrl baseUrl { maybeBaseUrl }; - if (dns->error() == QDnsLookup::NoError && - dns->serviceRecords().isEmpty()) - { + if (dns->error() == QDnsLookup::NoError + && dns->serviceRecords().isEmpty()) { auto record = dns->serviceRecords().front(); baseUrl.setHost(record.target()); baseUrl.setPort(record.port()); - qCDebug(MAIN) << "SRV record for" << maybeBaseUrl.host() - << "is" << baseUrl.authority(); + qCDebug(MAIN) << "SRV record for" << maybeBaseUrl.host() << "is" + << baseUrl.authority(); } else { qCDebug(MAIN) << baseUrl.host() << "doesn't have SRV record" << dns->name() << "- using the hostname as is"; @@ -213,41 +210,37 @@ void Connection::connectToServer(const QString& user, const QString& password, const QString& initialDeviceName, const QString& deviceId) { - checkAndConnect(user, - [=] { - doConnectToServer(user, password, initialDeviceName, deviceId); - }); + checkAndConnect(user, [=] { + doConnectToServer(user, password, initialDeviceName, deviceId); + }); } void Connection::doConnectToServer(const QString& user, const QString& password, const QString& initialDeviceName, const QString& deviceId) { - auto loginJob = callApi(QStringLiteral("m.login.password"), + auto loginJob = callApi( + QStringLiteral("m.login.password"), UserIdentifier { QStringLiteral("m.id.user"), - {{ QStringLiteral("user"), user }} }, + { { QStringLiteral("user"), user } } }, password, /*token*/ "", deviceId, initialDeviceName); - connect(loginJob, &BaseJob::success, this, - [this, loginJob] { - d->connectWithToken(loginJob->userId(), loginJob->accessToken(), - loginJob->deviceId()); - }); - connect(loginJob, &BaseJob::failure, this, - [this, loginJob] { - emit loginError(loginJob->errorString(), loginJob->rawDataSample()); - }); + connect(loginJob, &BaseJob::success, this, [this, loginJob] { + d->connectWithToken(loginJob->userId(), loginJob->accessToken(), + loginJob->deviceId()); + }); + connect(loginJob, &BaseJob::failure, this, [this, loginJob] { + emit loginError(loginJob->errorString(), loginJob->rawDataSample()); + }); } -void Connection::syncLoopIteration() -{ - sync(d->syncLoopTimeout); -} +void Connection::syncLoopIteration() { sync(d->syncLoopTimeout); } void Connection::connectWithToken(const QString& userId, const QString& accessToken, const QString& deviceId) { - checkAndConnect(userId, - [=] { d->connectWithToken(userId, accessToken, deviceId); }); + checkAndConnect(userId, [=] { + d->connectWithToken(userId, accessToken, deviceId); + }); } void Connection::reloadCapabilities() @@ -259,18 +252,17 @@ void Connection::reloadCapabilities() else if (d->capabilitiesJob->error() == BaseJob::IncorrectRequestError) qCDebug(MAIN) << "Server doesn't support /capabilities"; - if (d->capabilities.roomVersions.omitted()) - { + if (d->capabilities.roomVersions.omitted()) { qCWarning(MAIN) << "Pinning supported room version to 1"; - d->capabilities.roomVersions = { "1", {{ "1", "stable" }} }; + d->capabilities.roomVersions = { "1", { { "1", "stable" } } }; } else { - qCDebug(MAIN) << "Room versions:" - << defaultRoomVersion() << "is default, full list:" - << availableRoomVersions(); + qCDebug(MAIN) << "Room versions:" << defaultRoomVersion() + << "is default, full list:" + << availableRoomVersions(); } Q_ASSERT(!d->capabilities.roomVersions.omitted()); emit capabilitiesLoaded(); - for (auto* r: d->roomMap) + for (auto* r : d->roomMap) if (r->joinState() == JoinState::Join && r->successorId().isEmpty()) r->checkVersion(); }); @@ -301,28 +293,26 @@ void Connection::Private::connectWithToken(const QString& user, void Connection::checkAndConnect(const QString& userId, std::function connectFn) { - if (d->data->baseUrl().isValid()) - { + if (d->data->baseUrl().isValid()) { connectFn(); return; } // Not good to go, try to fix the homeserver URL. - if (userId.startsWith('@') && userId.indexOf(':') != -1) - { - connectSingleShot(this, &Connection::homeserverChanged, this, connectFn); + if (userId.startsWith('@') && userId.indexOf(':') != -1) { + connectSingleShot(this, &Connection::homeserverChanged, this, + connectFn); // NB: doResolveServer can emit resolveError, so this is a part of // checkAndConnect function contract. resolveServer(userId); } else - emit resolveError( - tr("%1 is an invalid homeserver URL") - .arg(d->data->baseUrl().toString())); + emit resolveError(tr("%1 is an invalid homeserver URL") + .arg(d->data->baseUrl().toString())); } void Connection::logout() { auto job = callApi(); - connect( job, &LogoutJob::success, this, [this] { + connect(job, &LogoutJob::success, this, [this] { stopSync(); d->data->setToken({}); emit stateChanged(); @@ -338,28 +328,25 @@ void Connection::sync(int timeout) Filter filter; filter.room->timeline->limit = 100; filter.room->state->lazyLoadMembers = d->lazyLoading; - auto job = d->syncJob = callApi(BackgroundRequest, - d->data->lastEvent(), filter, timeout); - connect( job, &SyncJob::success, this, [this, job] { + auto job = d->syncJob = callApi( + BackgroundRequest, d->data->lastEvent(), filter, timeout); + connect(job, &SyncJob::success, this, [this, job] { onSyncSuccess(job->takeData()); d->syncJob = nullptr; emit syncDone(); }); - connect( job, &SyncJob::retryScheduled, this, - [this,job] (int retriesTaken, int nextInMilliseconds) - { - emit networkError(job->errorString(), job->rawDataSample(), - retriesTaken, nextInMilliseconds); - }); - connect( job, &SyncJob::failure, this, [this, job] { + connect(job, &SyncJob::retryScheduled, this, + [this, job](int retriesTaken, int nextInMilliseconds) { + emit networkError(job->errorString(), job->rawDataSample(), + retriesTaken, nextInMilliseconds); + }); + connect(job, &SyncJob::failure, this, [this, job] { d->syncJob = nullptr; - if (job->error() == BaseJob::ContentAccessError) - { - qCWarning(SYNCJOB) - << "Sync job failed with ContentAccessError - login expired?"; + if (job->error() == BaseJob::ContentAccessError) { + qCWarning(SYNCJOB) << "Sync job failed with ContentAccessError - " + "login expired?"; emit loginError(job->errorString(), job->rawDataSample()); - } - else + } else emit syncError(job->errorString(), job->rawDataSample()); }); } @@ -371,65 +358,59 @@ void Connection::syncLoop(int timeout) syncLoopIteration(); // initial sync to start the loop } -void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { +void Connection::onSyncSuccess(SyncData&& data, bool fromCache) +{ d->data->setLastEvent(data.nextBatch()); - for (auto&& roomData: data.takeRoomData()) - { + for (auto&& roomData : data.takeRoomData()) { const auto forgetIdx = d->roomIdsToForget.indexOf(roomData.roomId); - if (forgetIdx != -1) - { + if (forgetIdx != -1) { d->roomIdsToForget.removeAt(forgetIdx); - if (roomData.joinState == JoinState::Leave) - { - qDebug(MAIN) << "Room" << roomData.roomId - << "has been forgotten, ignoring /sync response for it"; + if (roomData.joinState == JoinState::Leave) { + qDebug(MAIN) + << "Room" << roomData.roomId + << "has been forgotten, ignoring /sync response for it"; continue; } qWarning(MAIN) << "Room" << roomData.roomId - << "has just been forgotten but /sync returned it in" - << toCString(roomData.joinState) - << "state - suspiciously fast turnaround"; + << "has just been forgotten but /sync returned it in" + << toCString(roomData.joinState) + << "state - suspiciously fast turnaround"; } - if ( auto* r = provideRoom(roomData.roomId, roomData.joinState) ) - { + if (auto* r = provideRoom(roomData.roomId, roomData.joinState)) { d->pendingStateRoomIds.removeOne(roomData.roomId); r->updateData(std::move(roomData), fromCache); - if (d->firstTimeRooms.removeOne(r)) - { + if (d->firstTimeRooms.removeOne(r)) { emit loadedRoomState(r); if (!d->capabilities.roomVersions.omitted()) r->checkVersion(); - // Otherwise, the version will be checked in reloadCapabilities() + // Otherwise, the version will be checked in + // reloadCapabilities() } } // Let UI update itself after updating each room QCoreApplication::processEvents(); } - for (auto&& accountEvent: data.takeAccountData()) - { - if (is(*accountEvent)) - { + for (auto&& accountEvent : data.takeAccountData()) { + if (is(*accountEvent)) { const auto usersToDCs = ptrCast(move(accountEvent)) - ->usersToDirectChats(); + ->usersToDirectChats(); DirectChatsMap removals = - erase_if(d->directChats, [&usersToDCs] (auto it) { - return !usersToDCs.contains(it.key()->id(), it.value()); - }); - erase_if(d->directChatUsers, [&usersToDCs] (auto it) { + erase_if(d->directChats, [&usersToDCs](auto it) { + return !usersToDCs.contains(it.key()->id(), it.value()); + }); + erase_if(d->directChatUsers, [&usersToDCs](auto it) { return !usersToDCs.contains(it.value()->id(), it.key()); }); if (MAIN().isDebugEnabled()) for (auto it = removals.begin(); it != removals.end(); ++it) - qCDebug(MAIN) << it.value() - << "is no more a direct chat with" << it.key()->id(); + qCDebug(MAIN) + << it.value() << "is no more a direct chat with" + << it.key()->id(); DirectChatsMap additions; - for (auto it = usersToDCs.begin(); it != usersToDCs.end(); ++it) - { - if (auto* u = user(it.key())) - { - if (!d->directChats.contains(u, it.value())) - { + for (auto it = usersToDCs.begin(); it != usersToDCs.end(); ++it) { + if (auto* u = user(it.key())) { + if (!d->directChats.contains(u, it.value())) { Q_ASSERT(!d->directChatUsers.contains(it.value(), u)); additions.insert(u, it.value()); d->directChats.insert(u, it.value()); @@ -448,14 +429,13 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { } if (is(*accountEvent)) qCDebug(MAIN) << "Users ignored by" << d->userId << "updated:" - << QStringList::fromSet(ignoredUsers()).join(','); + << QStringList::fromSet(ignoredUsers()).join(','); auto& currentData = d->accountData[accountEvent->matrixType()]; // A polymorphic event-specific comparison might be a bit more // efficient; maaybe do it another day - if (!currentData || - currentData->contentJson() != accountEvent->contentJson()) - { + if (!currentData + || currentData->contentJson() != accountEvent->contentJson()) { currentData = std::move(accountEvent); qCDebug(MAIN) << "Updated account data of type" << currentData->matrixType(); @@ -467,8 +447,8 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { void Connection::stopSync() { // If there's a sync loop, break it - disconnect(this, &Connection::syncDone, - this, &Connection::syncLoopIteration); + disconnect(this, &Connection::syncDone, this, + &Connection::syncLoopIteration); if (d->syncJob) // If there's an ongoing sync job, stop it too { d->syncJob->abandon(); @@ -476,10 +456,7 @@ void Connection::stopSync() } } -QString Connection::nextBatchToken() const -{ - return d->data->lastEvent(); -} +QString Connection::nextBatchToken() const { return d->data->lastEvent(); } PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const { @@ -492,8 +469,8 @@ JoinRoomJob* Connection::joinRoom(const QString& roomAlias, auto job = callApi(roomAlias, serverNames); // Upon completion, ensure a room object in Join state is created but only // if it's not already there due to a sync completing earlier. - connect(job, &JoinRoomJob::success, - this, [this, job] { provideRoom(job->roomId()); }); + connect(job, &JoinRoomJob::success, this, + [this, job] { provideRoom(job->roomId()); }); return job; } @@ -501,15 +478,13 @@ LeaveRoomJob* Connection::leaveRoom(Room* room) { const auto& roomId = room->id(); const auto job = callApi(roomId); - if (room->joinState() == JoinState::Invite) - { + if (room->joinState() == JoinState::Invite) { // Workaround matrix-org/synapse#2181 - if the room is in invite state // the invite may have been cancelled but Synapse didn't send it in // `/sync`. See also #273 for the discussion in the library context. d->pendingStateRoomIds.push_back(roomId); - connect(job, &LeaveRoomJob::success, this, [this,roomId] { - if (d->pendingStateRoomIds.removeOne(roomId)) - { + connect(job, &LeaveRoomJob::success, this, [this, roomId] { + if (d->pendingStateRoomIds.removeOne(roomId)) { qCDebug(MAIN) << "Forcing the room to Leave status"; provideRoom(roomId, JoinState::Leave); } @@ -522,40 +497,44 @@ inline auto splitMediaId(const QString& mediaId) { auto idParts = mediaId.split('/'); Q_ASSERT_X(idParts.size() == 2, __FUNCTION__, - ("'" + mediaId + - "' doesn't look like 'serverName/localMediaId'").toLatin1()); + ("'" + mediaId + "' doesn't look like 'serverName/localMediaId'") + .toLatin1()); return idParts; } MediaThumbnailJob* Connection::getThumbnail(const QString& mediaId, - QSize requestedSize, RunningPolicy policy) const + QSize requestedSize, + RunningPolicy policy) const { auto idParts = splitMediaId(mediaId); - return callApi(policy, - idParts.front(), idParts.back(), requestedSize); + return callApi(policy, idParts.front(), idParts.back(), + requestedSize); } MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, - QSize requestedSize, RunningPolicy policy) const + QSize requestedSize, + RunningPolicy policy) const { return getThumbnail(url.authority() + url.path(), requestedSize, policy); } -MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, - int requestedWidth, int requestedHeight, RunningPolicy policy) const +MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, int requestedWidth, + int requestedHeight, + RunningPolicy policy) const { return getThumbnail(url, QSize(requestedWidth, requestedHeight), policy); } -UploadContentJob* Connection::uploadContent(QIODevice* contentSource, - const QString& filename, const QString& overrideContentType) const +UploadContentJob* +Connection::uploadContent(QIODevice* contentSource, const QString& filename, + const QString& overrideContentType) const { auto contentType = overrideContentType; - if (contentType.isEmpty()) - { + if (contentType.isEmpty()) { contentType = - QMimeDatabase().mimeTypeForFileNameAndData(filename, contentSource) - .name(); + QMimeDatabase() + .mimeTypeForFileNameAndData(filename, contentSource) + .name(); contentSource->open(QIODevice::ReadOnly); } return callApi(contentSource, filename, contentType); @@ -565,8 +544,7 @@ UploadContentJob* Connection::uploadFile(const QString& fileName, const QString& overrideContentType) { auto sourceFile = new QFile(fileName); - if (!sourceFile->open(QIODevice::ReadOnly)) - { + if (!sourceFile->open(QIODevice::ReadOnly)) { qCWarning(MAIN) << "Couldn't open" << sourceFile->fileName() << "for reading"; return nullptr; @@ -596,13 +574,14 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, return job; } -CreateRoomJob* Connection::createRoom(RoomVisibility visibility, - const QString& alias, const QString& name, const QString& topic, - QStringList invites, const QString& presetName, - const QString& roomVersion, bool isDirect, - const QVector& initialState, - const QVector& invite3pids, - const QJsonObject& creationContent) +CreateRoomJob* +Connection::createRoom(RoomVisibility visibility, const QString& alias, + const QString& name, const QString& topic, + QStringList invites, const QString& presetName, + const QString& roomVersion, bool isDirect, + const QVector& initialState, + const QVector& invite3pids, + const QJsonObject& creationContent) { invites.removeOne(d->userId); // The creator is by definition in the room auto job = callApi( @@ -610,7 +589,7 @@ CreateRoomJob* Connection::createRoom(RoomVisibility visibility, : QStringLiteral("private"), alias, name, topic, invites, invite3pids, roomVersion, creationContent, initialState, presetName, isDirect); - connect(job, &BaseJob::success, this, [this,job] { + connect(job, &BaseJob::success, this, [this, job] { emit createdRoom(provideRoom(job->roomId(), JoinState::Join)); }); return job; @@ -621,14 +600,14 @@ void Connection::requestDirectChat(const QString& userId) if (auto* u = user(userId)) requestDirectChat(u); else - qCCritical(MAIN) - << "Connection::requestDirectChat: Couldn't get a user object for" - << userId; + qCCritical(MAIN) << "Connection::requestDirectChat: Couldn't get a " + "user object for" + << userId; } void Connection::requestDirectChat(User* u) { - doInDirectChat(u, [this] (Room* r) { emit directChatAvailable(r); }); + doInDirectChat(u, [this](Room* r) { emit directChatAvailable(r); }); } void Connection::doInDirectChat(const QString& userId, @@ -638,8 +617,8 @@ void Connection::doInDirectChat(const QString& userId, doInDirectChat(u, operation); else qCCritical(MAIN) - << "Connection::doInDirectChat: Couldn't get a user object for" - << userId; + << "Connection::doInDirectChat: Couldn't get a user object for" + << userId; } void Connection::doInDirectChat(User* u, @@ -651,11 +630,9 @@ void Connection::doInDirectChat(User* u, // (left/forgotten) ones along the way. DirectChatsMap removals; for (auto it = d->directChats.find(u); - it != d->directChats.end() && it.key() == u; ++it) - { + it != d->directChats.end() && it.key() == u; ++it) { const auto& roomId = *it; - if (auto r = room(roomId, JoinState::Join)) - { + if (auto r = room(roomId, JoinState::Join)) { Q_ASSERT(r->id() == roomId); // A direct chat with yourself should only involve yourself :) if (userId == d->userId && r->totalMemberCount() > 1) @@ -665,15 +642,16 @@ void Connection::doInDirectChat(User* u, operation(r); return; } - if (auto ir = invitation(roomId)) - { + if (auto ir = invitation(roomId)) { Q_ASSERT(ir->id() == roomId); auto j = joinRoom(ir->id()); - connect(j, &BaseJob::success, this, [this,roomId,userId,operation] { - qCDebug(MAIN) << "Joined the already invited direct chat with" - << userId << "as" << roomId; - operation(room(roomId, JoinState::Join)); - }); + connect(j, &BaseJob::success, this, + [this, roomId, userId, operation] { + qCDebug(MAIN) + << "Joined the already invited direct chat with" + << userId << "as" << roomId; + operation(room(roomId, JoinState::Join)); + }); return; } // Avoid reusing previously left chats but don't remove them @@ -686,10 +664,8 @@ void Connection::doInDirectChat(User* u, // Postpone actual deletion until we finish iterating d->directChats. removals.insert(it.key(), it.value()); } - if (!removals.isEmpty()) - { - for (auto it = removals.cbegin(); it != removals.cend(); ++it) - { + if (!removals.isEmpty()) { + for (auto it = removals.cbegin(); it != removals.cend(); ++it) { d->directChats.remove(it.key(), it.value()); d->directChatUsers.remove(it.value(), const_cast(it.key())); // FIXME @@ -698,18 +674,18 @@ void Connection::doInDirectChat(User* u, } auto j = createDirectChat(userId); - connect(j, &BaseJob::success, this, [this,j,userId,operation] { - qCDebug(MAIN) << "Direct chat with" << userId - << "has been created as" << j->roomId(); + connect(j, &BaseJob::success, this, [this, j, userId, operation] { + qCDebug(MAIN) << "Direct chat with" << userId << "has been created as" + << j->roomId(); operation(room(j->roomId(), JoinState::Join)); }); - } CreateRoomJob* Connection::createDirectChat(const QString& userId, - const QString& topic, const QString& name) + const QString& topic, + const QString& name) { - return createRoom(UnpublishRoom, "", name, topic, {userId}, + return createRoom(UnpublishRoom, "", name, topic, { userId }, "trusted_private_chat", {}, true); } @@ -723,11 +699,10 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) // a ForgetRoomJob is created in advance and can be returned in a probably // not-yet-started state (it will start once /leave completes). auto forgetJob = new ForgetRoomJob(id); - auto room = d->roomMap.value({id, false}); + auto room = d->roomMap.value({ id, false }); if (!room) - room = d->roomMap.value({id, true}); - if (room && room->joinState() != JoinState::Leave) - { + room = d->roomMap.value({ id, true }); + if (room && room->joinState() != JoinState::Leave) { auto leaveJob = room->leaveRoom(); connect(leaveJob, &BaseJob::success, this, [this, forgetJob, room] { forgetJob->start(connectionData()); @@ -737,18 +712,14 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) d->roomIdsToForget.push_back(room->id()); }); connect(leaveJob, &BaseJob::failure, forgetJob, &BaseJob::abandon); - } - else + } else forgetJob->start(connectionData()); - connect(forgetJob, &BaseJob::success, this, [this, id] - { + connect(forgetJob, &BaseJob::success, this, [this, id] { // Delete whatever instances of the room are still in the map. - for (auto f: {false, true}) - if (auto r = d->roomMap.take({ id, f })) - { - qCDebug(MAIN) << "Room" << r->objectName() - << "in state" << toCString(r->joinState()) - << "will be deleted"; + for (auto f : { false, true }) + if (auto r = d->roomMap.take({ id, f })) { + qCDebug(MAIN) << "Room" << r->objectName() << "in state" + << toCString(r->joinState()) << "will be deleted"; emit r->beforeDestruction(r); r->deleteLater(); } @@ -756,52 +727,52 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) return forgetJob; } -SendToDeviceJob* Connection::sendToDevices(const QString& eventType, - const UsersToDevicesToEvents& eventsMap) const +SendToDeviceJob* +Connection::sendToDevices(const QString& eventType, + const UsersToDevicesToEvents& eventsMap) const { QHash> json; json.reserve(int(eventsMap.size())); - std::for_each(eventsMap.begin(), eventsMap.end(), - [&json] (const auto& userTodevicesToEvents) { - auto& jsonUser = json[userTodevicesToEvents.first]; - const auto& devicesToEvents = userTodevicesToEvents.second; - std::for_each(devicesToEvents.begin(), devicesToEvents.end(), - [&jsonUser] (const auto& deviceToEvents) { - jsonUser.insert(deviceToEvents.first, - deviceToEvents.second.contentJson()); - }); - }); - return callApi(BackgroundRequest, - eventType, generateTxnId(), json); + std::for_each( + eventsMap.begin(), eventsMap.end(), + [&json](const auto& userTodevicesToEvents) { + auto& jsonUser = json[userTodevicesToEvents.first]; + const auto& devicesToEvents = userTodevicesToEvents.second; + std::for_each(devicesToEvents.begin(), devicesToEvents.end(), + [&jsonUser](const auto& deviceToEvents) { + jsonUser.insert( + deviceToEvents.first, + deviceToEvents.second.contentJson()); + }); + }); + return callApi(BackgroundRequest, eventType, + generateTxnId(), json); } SendMessageJob* Connection::sendMessage(const QString& roomId, const RoomEvent& event) const { - const auto txnId = event.transactionId().isEmpty() - ? generateTxnId() : event.transactionId(); - return callApi(roomId, event.matrixType(), - txnId, event.contentJson()); + const auto txnId = event.transactionId().isEmpty() ? generateTxnId() + : event.transactionId(); + return callApi(roomId, event.matrixType(), txnId, + event.contentJson()); } -QUrl Connection::homeserver() const -{ - return d->data->baseUrl(); -} +QUrl Connection::homeserver() const { return d->data->baseUrl(); } Room* Connection::room(const QString& roomId, JoinStates states) const { - Room* room = d->roomMap.value({roomId, false}, nullptr); - if (states.testFlag(JoinState::Join) && - room && room->joinState() == JoinState::Join) + Room* room = d->roomMap.value({ roomId, false }, nullptr); + if (states.testFlag(JoinState::Join) && room + && room->joinState() == JoinState::Join) return room; if (states.testFlag(JoinState::Invite)) if (Room* invRoom = invitation(roomId)) return invRoom; - if (states.testFlag(JoinState::Leave) && - room && room->joinState() == JoinState::Leave) + if (states.testFlag(JoinState::Leave) && room + && room->joinState() == JoinState::Leave) return room; return nullptr; @@ -821,22 +792,21 @@ void Connection::updateRoomAliases(const QString& roomId, const QStringList& previousRoomAliases, const QStringList& roomAliases) { - for (const auto& a: previousRoomAliases) + for (const auto& a : previousRoomAliases) if (d->roomAliasMap.remove(a) == 0) - qCWarning(MAIN) << "Alias" << a << "is not found (already deleted?)"; + qCWarning(MAIN) + << "Alias" << a << "is not found (already deleted?)"; - for (const auto& a: roomAliases) - { + for (const auto& a : roomAliases) { auto& mappedId = d->roomAliasMap[a]; - if (!mappedId.isEmpty()) - { + if (!mappedId.isEmpty()) { if (mappedId == roomId) qCDebug(MAIN) << "Alias" << a << "is already mapped to room" << roomId; else - qCWarning(MAIN) << "Alias" << a - << "will be force-remapped from room" - << mappedId << "to" << roomId; + qCWarning(MAIN) + << "Alias" << a << "will be force-remapped from room" + << mappedId << "to" << roomId; } mappedId = roomId; } @@ -844,19 +814,18 @@ void Connection::updateRoomAliases(const QString& roomId, Room* Connection::invitation(const QString& roomId) const { - return d->roomMap.value({roomId, true}, nullptr); + return d->roomMap.value({ roomId, true }, nullptr); } User* Connection::user(const QString& userId) { if (userId.isEmpty()) return nullptr; - if (!userId.startsWith('@') || !userId.contains(':')) - { + if (!userId.startsWith('@') || !userId.contains(':')) { qCCritical(MAIN) << "Malformed userId:" << userId; return nullptr; } - if( d->userMap.contains(userId) ) + if (d->userMap.contains(userId)) return d->userMap.value(userId); auto* user = userFactory()(this, userId); d->userMap.insert(userId, user); @@ -869,47 +838,29 @@ const User* Connection::user() const return d->userMap.value(d->userId, nullptr); } -User* Connection::user() -{ - return user(d->userId); -} +User* Connection::user() { return user(d->userId); } -QString Connection::userId() const -{ - return d->userId; -} +QString Connection::userId() const { return d->userId; } -QString Connection::deviceId() const -{ - return d->data->deviceId(); -} +QString Connection::deviceId() const { return d->data->deviceId(); } -QString Connection::token() const -{ - return accessToken(); -} +QString Connection::token() const { return accessToken(); } -QByteArray Connection::accessToken() const -{ - return d->data->accessToken(); -} +QByteArray Connection::accessToken() const { return d->data->accessToken(); } -SyncJob* Connection::syncJob() const -{ - return d->syncJob; -} +SyncJob* Connection::syncJob() const { return d->syncJob; } int Connection::millisToReconnect() const { return d->syncJob ? d->syncJob->millisToRetry() : 0; } -QHash< QPair, Room* > Connection::roomMap() const +QHash, Room*> Connection::roomMap() const { - // Copy-on-write-and-remove-elements is faster than copying elements one by one. - QHash< QPair, Room* > roomMap = d->roomMap; - for (auto it = roomMap.begin(); it != roomMap.end(); ) - { + // Copy-on-write-and-remove-elements is faster than copying elements one by + // one. + QHash, Room*> roomMap = d->roomMap; + for (auto it = roomMap.begin(); it != roomMap.end();) { if (it.value()->joinState() == JoinState::Leave) it = roomMap.erase(it); else @@ -949,24 +900,22 @@ void Connection::setAccountData(const QString& type, const QJsonObject& content) QHash> Connection::tagsToRooms() const { QHash> result; - for (auto* r: qAsConst(d->roomMap)) - { - for (const auto& tagName: r->tagNames()) + for (auto* r : qAsConst(d->roomMap)) { + for (const auto& tagName : r->tagNames()) result[tagName].push_back(r); } for (auto it = result.begin(); it != result.end(); ++it) - std::sort(it->begin(), it->end(), - [t=it.key()] (Room* r1, Room* r2) { - return r1->tags().value(t) < r2->tags().value(t); - }); + std::sort(it->begin(), it->end(), [t = it.key()](Room* r1, Room* r2) { + return r1->tags().value(t) < r2->tags().value(t); + }); return result; } QStringList Connection::tagNames() const { - QStringList tags ({FavouriteTag}); - for (auto* r: qAsConst(d->roomMap)) - for (const auto& tag: r->tagNames()) + QStringList tags({ FavouriteTag }); + for (auto* r : qAsConst(d->roomMap)) + for (const auto& tag : r->tagNames()) if (tag != LowPriorityTag && !tags.contains(tag)) tags.push_back(tag); tags.push_back(LowPriorityTag); @@ -976,8 +925,9 @@ QStringList Connection::tagNames() const QVector Connection::roomsWithTag(const QString& tagName) const { QVector rooms; - std::copy_if(d->roomMap.begin(), d->roomMap.end(), std::back_inserter(rooms), - [&tagName] (Room* r) { return r->tags().contains(tagName); }); + std::copy_if(d->roomMap.begin(), d->roomMap.end(), + std::back_inserter(rooms), + [&tagName](Room* r) { return r->tags().contains(tagName); }); return rooms; } @@ -989,8 +939,7 @@ Connection::DirectChatsMap Connection::directChats() const QJsonObject toJson(const Connection::DirectChatsMap& directChats) { QJsonObject json; - for (auto it = directChats.begin(); it != directChats.end();) - { + for (auto it = directChats.begin(); it != directChats.end();) { QJsonArray roomIds; const auto* user = it.key(); for (; it != directChats.end() && it.key() == user; ++it) @@ -1000,8 +949,8 @@ QJsonObject toJson(const Connection::DirectChatsMap& directChats) return json; } -void Connection::Private::broadcastDirectChatUpdates(const DirectChatsMap& additions, - const DirectChatsMap& removals) +void Connection::Private::broadcastDirectChatUpdates( + const DirectChatsMap& additions, const DirectChatsMap& removals) { q->callApi(userId, QStringLiteral("m.direct"), toJson(directChats)); @@ -1023,19 +972,19 @@ void Connection::addToDirectChats(const Room* room, User* user) void Connection::removeFromDirectChats(const QString& roomId, User* user) { Q_ASSERT(!roomId.isEmpty()); - if ((user != nullptr && !d->directChats.contains(user, roomId)) || - d->directChats.key(roomId) == nullptr) + if ((user != nullptr && !d->directChats.contains(user, roomId)) + || d->directChats.key(roomId) == nullptr) return; DirectChatsMap removals; - if (user != nullptr) - { + if (user != nullptr) { removals.insert(user, roomId); d->directChats.remove(user, roomId); d->directChatUsers.remove(roomId, user); } else { - removals = erase_if(d->directChats, - [&roomId] (auto it) { return it.value() == roomId; }); + removals = erase_if(d->directChats, [&roomId](auto it) { + return it.value() == roomId; + }); d->directChatUsers.remove(roomId); } d->broadcastDirectChatUpdates({}, removals); @@ -1068,11 +1017,10 @@ void Connection::addToIgnoredUsers(const User* user) Q_ASSERT(user != nullptr); auto ignoreList = ignoredUsers(); - if (!ignoreList.contains(user->id())) - { + if (!ignoreList.contains(user->id())) { ignoreList.insert(user->id()); d->packAndSendAccountData(ignoreList); - emit ignoredUsersListChanged({{ user->id() }}, {}); + emit ignoredUsersListChanged({ { user->id() } }, {}); } } @@ -1081,17 +1029,13 @@ void Connection::removeFromIgnoredUsers(const User* user) Q_ASSERT(user != nullptr); auto ignoreList = ignoredUsers(); - if (ignoreList.remove(user->id()) != 0) - { + if (ignoreList.remove(user->id()) != 0) { d->packAndSendAccountData(ignoreList); - emit ignoredUsersListChanged({}, {{ user->id() }}); + emit ignoredUsersListChanged({}, { { user->id() } }); } } -QMap Connection::users() const -{ - return d->userMap; -} +QMap Connection::users() const { return d->userMap; } const ConnectionData* Connection::connectionData() const { @@ -1106,58 +1050,52 @@ Room* Connection::provideRoom(const QString& id, Omittable joinState) // If joinState.omitted(), all joinState == comparisons below are false. const auto roomKey = qMakePair(id, joinState == JoinState::Invite); auto* room = d->roomMap.value(roomKey, nullptr); - if (room) - { + if (room) { // Leave is a special case because in transition (5a) (see the .h file) // joinState == room->joinState but we still have to preempt the Invite // and emit a signal. For Invite and Join, there's no such problem. if (room->joinState() == joinState && joinState != JoinState::Leave) return room; - } else if (joinState.omitted()) - { + } else if (joinState.omitted()) { // No Join and Leave, maybe Invite? - room = d->roomMap.value({id, true}, nullptr); + room = d->roomMap.value({ id, true }, nullptr); if (room) return room; // No Invite either, setup a new room object below } - if (!room) - { + if (!room) { room = roomFactory()(this, id, - joinState.omitted() ? JoinState::Join : joinState.value()); - if (!room) - { + joinState.omitted() ? JoinState::Join + : joinState.value()); + if (!room) { qCCritical(MAIN) << "Failed to create a room" << id; return nullptr; } d->roomMap.insert(roomKey, room); d->firstTimeRooms.push_back(room); - connect(room, &Room::beforeDestruction, - this, &Connection::aboutToDeleteRoom); + connect(room, &Room::beforeDestruction, this, + &Connection::aboutToDeleteRoom); emit newRoom(room); } if (joinState.omitted()) return room; - if (joinState == JoinState::Invite) - { + if (joinState == JoinState::Invite) { // prev is either Leave or nullptr - auto* prev = d->roomMap.value({id, false}, nullptr); + auto* prev = d->roomMap.value({ id, false }, nullptr); emit invitedRoom(room, prev); - } - else - { + } else { room->setJoinState(joinState.value()); // Preempt the Invite room (if any) with a room in Join/Leave state. - auto* prevInvite = d->roomMap.take({id, true}); + auto* prevInvite = d->roomMap.take({ id, true }); if (joinState == JoinState::Join) emit joinedRoom(room, prevInvite); else if (joinState == JoinState::Leave) emit leftRoom(room, prevInvite); - if (prevInvite) - { - qCDebug(MAIN) << "Deleting Invite state for room" << prevInvite->id(); + if (prevInvite) { + qCDebug(MAIN) << "Deleting Invite state for room" + << prevInvite->id(); emit prevInvite->beforeDestruction(prevInvite); prevInvite->deleteLater(); } @@ -1176,15 +1114,9 @@ void Connection::setUserFactory(user_factory_t f) _userFactory = std::move(f); } -room_factory_t Connection::roomFactory() -{ - return _roomFactory; -} +room_factory_t Connection::roomFactory() { return _roomFactory; } -user_factory_t Connection::userFactory() -{ - return _userFactory; -} +user_factory_t Connection::userFactory() { return _userFactory; } room_factory_t Connection::_roomFactory = defaultRoomFactory<>(); user_factory_t Connection::_userFactory = defaultUserFactory<>(); @@ -1210,16 +1142,15 @@ void Connection::saveRoomState(Room* r) const return; QFile outRoomFile { stateCachePath() % SyncData::fileNameForRoom(r->id()) }; - if (outRoomFile.open(QFile::WriteOnly)) - { + if (outRoomFile.open(QFile::WriteOnly)) { QJsonDocument json { r->toJson() }; auto data = d->cacheToBinary ? json.toBinaryData() : json.toJson(QJsonDocument::Compact); outRoomFile.write(data.data(), data.size()); qCDebug(MAIN) << "Room state cache saved to" << outRoomFile.fileName(); } else { - qCWarning(MAIN) << "Error opening" << outRoomFile.fileName() - << ":" << outRoomFile.errorString(); + qCWarning(MAIN) << "Error opening" << outRoomFile.fileName() << ":" + << outRoomFile.errorString(); } } @@ -1228,29 +1159,31 @@ void Connection::saveState() const if (!d->cacheState) return; - QElapsedTimer et; et.start(); + QElapsedTimer et; + et.start(); QFile outFile { stateCachePath() % "state.json" }; - if (!outFile.open(QFile::WriteOnly)) - { - qCWarning(MAIN) << "Error opening" << outFile.fileName() - << ":" << outFile.errorString(); + if (!outFile.open(QFile::WriteOnly)) { + qCWarning(MAIN) << "Error opening" << outFile.fileName() << ":" + << outFile.errorString(); qCWarning(MAIN) << "Caching the rooms state disabled"; d->cacheState = false; return; } QJsonObject rootObj { - { QStringLiteral("cache_version"), QJsonObject { - { QStringLiteral("major"), SyncData::cacheVersion().first }, - { QStringLiteral("minor"), SyncData::cacheVersion().second } - }}}; + { QStringLiteral("cache_version"), + QJsonObject { + { QStringLiteral("major"), SyncData::cacheVersion().first }, + { QStringLiteral("minor"), + SyncData::cacheVersion().second } } } + }; { QJsonObject rooms; QJsonObject inviteRooms; for (const auto* i : roomMap()) // Pass on rooms in Leave state (i->joinState() == JoinState::Invite ? inviteRooms : rooms) - .insert(i->id(), QJsonValue::Null); + .insert(i->id(), QJsonValue::Null); QJsonObject roomObj; if (!rooms.isEmpty()) @@ -1262,20 +1195,20 @@ void Connection::saveState() const rootObj.insert("rooms", roomObj); } { - QJsonArray accountDataEvents { - basicEventJson(QStringLiteral("m.direct"), toJson(d->directChats)) - }; - for (const auto &e : d->accountData) + QJsonArray accountDataEvents { basicEventJson( + QStringLiteral("m.direct"), toJson(d->directChats)) }; + for (const auto& e : d->accountData) accountDataEvents.append( - basicEventJson(e.first, e.second->contentJson())); + basicEventJson(e.first, e.second->contentJson())); rootObj.insert("account_data", - QJsonObject {{ QStringLiteral("events"), accountDataEvents }}); + QJsonObject { { QStringLiteral("events"), + accountDataEvents } }); } QJsonDocument json { rootObj }; - auto data = d->cacheToBinary ? json.toBinaryData() : - json.toJson(QJsonDocument::Compact); + auto data = d->cacheToBinary ? json.toBinaryData() + : json.toJson(QJsonDocument::Compact); qCDebug(PROFILER) << "Cache for" << userId() << "generated in" << et; outFile.write(data.data(), data.size()); @@ -1287,14 +1220,14 @@ void Connection::loadState() if (!d->cacheState) return; - QElapsedTimer et; et.start(); + QElapsedTimer et; + et.start(); SyncData sync { stateCachePath() % "state.json" }; if (sync.nextBatch().isEmpty()) // No token means no cache by definition return; - if (!sync.unresolvedRooms().isEmpty()) - { + if (!sync.unresolvedRooms().isEmpty()) { qCWarning(MAIN) << "State cache incomplete, discarding"; return; } @@ -1302,7 +1235,8 @@ void Connection::loadState() // 1. Do initial sync on failed rooms without saving the nextBatch token // 2. Do the sync across all rooms as normal onSyncSuccess(std::move(sync), true); - qCDebug(PROFILER) << "*** Cached state for" << userId() << "loaded in" << et; + qCDebug(PROFILER) << "*** Cached state for" << userId() << "loaded in" + << et; } QString Connection::stateCachePath() const @@ -1312,29 +1246,21 @@ QString Connection::stateCachePath() const return cacheLocation(safeUserId); } -bool Connection::cacheState() const -{ - return d->cacheState; -} +bool Connection::cacheState() const { return d->cacheState; } void Connection::setCacheState(bool newValue) { - if (d->cacheState != newValue) - { + if (d->cacheState != newValue) { d->cacheState = newValue; emit cacheStateChanged(); } } -bool QMatrixClient::Connection::lazyLoading() const -{ - return d->lazyLoading; -} +bool QMatrixClient::Connection::lazyLoading() const { return d->lazyLoading; } void QMatrixClient::Connection::setLazyLoading(bool newValue) { - if (d->lazyLoading != newValue) - { + if (d->lazyLoading != newValue) { d->lazyLoading = newValue; emit lazyLoadingChanged(); } @@ -1343,8 +1269,8 @@ void QMatrixClient::Connection::setLazyLoading(bool newValue) void Connection::getTurnServers() { auto job = callApi(); - connect(job, &GetTurnServerJob::success, - this, [=] { emit turnServersChanged(job->data()); }); + connect(job, &GetTurnServerJob::success, this, + [=] { emit turnServersChanged(job->data()); }); } const QString Connection::SupportedRoomVersion::StableTag = @@ -1376,7 +1302,8 @@ inline bool roomVersionLess(const Connection::SupportedRoomVersion& v1, return ok1 && ok2 ? vNum1 < vNum2 : v1.id < v2.id; } -QVector Connection::availableRoomVersions() const +QVector +Connection::availableRoomVersions() const { Q_ASSERT(!d->capabilities.roomVersions.omitted()); QVector result; @@ -1386,8 +1313,9 @@ QVector Connection::availableRoomVersions() co result.push_back({ it.key(), it.value() }); // Put stable versions over unstable; within each group, // sort numeric versions as numbers, the rest as strings. - const auto mid = std::partition(result.begin(), result.end(), - std::mem_fn(&SupportedRoomVersion::isStable)); + const auto mid = + std::partition(result.begin(), result.end(), + std::mem_fn(&SupportedRoomVersion::isStable)); std::sort(result.begin(), mid, roomVersionLess); std::sort(mid, result.end(), roomVersionLess); -- cgit v1.2.3 From 8c685b4ae5b47e55a55f23e16ccbda0132cb60c5 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 10 Mar 2019 16:59:55 +0900 Subject: Room::checkVersion(): be tolerant to already upgraded rooms --- lib/connection.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 26b40c03..59aca025 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -271,8 +271,7 @@ void Connection::reloadCapabilities() Q_ASSERT(!d->capabilities.roomVersions.omitted()); emit capabilitiesLoaded(); for (auto* r: d->roomMap) - if (r->joinState() == JoinState::Join && r->successorId().isEmpty()) - r->checkVersion(); + r->checkVersion(); }); } -- cgit v1.2.3 From fe3141f7bc3d2c92dcfe8ee5b3eb8d3ca6f0ab17 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 27 Mar 2019 19:06:34 +0900 Subject: Connection::domain() --- lib/connection.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 59aca025..07c24c92 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -788,6 +788,11 @@ QUrl Connection::homeserver() const return d->data->baseUrl(); } +QString Connection::domain() const +{ + return d->userId.section(':', 1); +} + Room* Connection::room(const QString& roomId, JoinStates states) const { Room* room = d->roomMap.value({roomId, false}, nullptr); -- cgit v1.2.3 From e2b30ad0dc6f5a369cbd1dce3f9c92ef5d801dae Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 29 Mar 2019 13:30:03 +0900 Subject: Connection: make sure to mark rooms supposed to be direct chats as such Closes #305. Relies on correct tracking of Invite membership from the previous commit. --- lib/connection.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 07c24c92..c09de979 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -609,8 +609,17 @@ CreateRoomJob* Connection::createRoom(RoomVisibility visibility, : QStringLiteral("private"), alias, name, topic, invites, invite3pids, roomVersion, creationContent, initialState, presetName, isDirect); - connect(job, &BaseJob::success, this, [this,job] { - emit createdRoom(provideRoom(job->roomId(), JoinState::Join)); + connect(job, &BaseJob::success, this, [this,job,invites,isDirect] { + auto* room = provideRoom(job->roomId(), JoinState::Join); + if (!room) + { + Q_ASSERT_X(room, "Connection::createRoom", "Failed to create a room"); + return; + } + emit createdRoom(room); + if (isDirect) + for (const auto& i: invites) + addToDirectChats(room, user(i)); }); return job; } @@ -1161,6 +1170,9 @@ Room* Connection::provideRoom(const QString& id, Omittable joinState) emit leftRoom(room, prevInvite); if (prevInvite) { + const auto dcUsers = prevInvite->directChatUsers(); + for (auto* u: dcUsers) + addToDirectChats(room, u); qCDebug(MAIN) << "Deleting Invite state for room" << prevInvite->id(); emit prevInvite->beforeDestruction(prevInvite); prevInvite->deleteLater(); -- cgit v1.2.3 From 432e7fd7107d8260e0016a1adcd8d94263dc1044 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 4 Apr 2019 21:27:38 +0900 Subject: Clean up on clang-tidy/clazy analysis --- lib/connection.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index c09de979..5ed72616 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -717,8 +717,8 @@ void Connection::doInDirectChat(User* u, CreateRoomJob* Connection::createDirectChat(const QString& userId, const QString& topic, const QString& name) { - return createRoom(UnpublishRoom, "", name, topic, {userId}, - "trusted_private_chat", {}, true); + return createRoom(UnpublishRoom, {}, name, topic, {userId}, + QStringLiteral("trusted_private_chat"), {}, true); } ForgetRoomJob* Connection::forgetRoom(const QString& id) @@ -964,7 +964,8 @@ QHash> Connection::tagsToRooms() const QHash> result; for (auto* r: qAsConst(d->roomMap)) { - for (const auto& tagName: r->tagNames()) + const auto& tagNames = r->tagNames(); + for (const auto& tagName: tagNames) result[tagName].push_back(r); } for (auto it = result.begin(); it != result.end(); ++it) @@ -979,9 +980,12 @@ QStringList Connection::tagNames() const { QStringList tags ({FavouriteTag}); for (auto* r: qAsConst(d->roomMap)) - for (const auto& tag: r->tagNames()) + { + const auto& tagNames = r->tagNames(); + for (const auto& tag: tagNames) if (tag != LowPriorityTag && !tags.contains(tag)) tags.push_back(tag); + } tags.push_back(LowPriorityTag); return tags; } @@ -1264,18 +1268,19 @@ void Connection::saveState() const { QJsonObject rooms; QJsonObject inviteRooms; - for (const auto* i : roomMap()) // Pass on rooms in Leave state + const auto& rs = roomMap(); // Pass on rooms in Leave state + for (const auto* i : rs) (i->joinState() == JoinState::Invite ? inviteRooms : rooms) .insert(i->id(), QJsonValue::Null); QJsonObject roomObj; if (!rooms.isEmpty()) - roomObj.insert("join", rooms); + roomObj.insert(QStringLiteral("join"), rooms); if (!inviteRooms.isEmpty()) - roomObj.insert("invite", inviteRooms); + roomObj.insert(QStringLiteral("invite"), inviteRooms); - rootObj.insert("next_batch", d->data->lastEvent()); - rootObj.insert("rooms", roomObj); + rootObj.insert(QStringLiteral("next_batch"), d->data->lastEvent()); + rootObj.insert(QStringLiteral("rooms"), roomObj); } { QJsonArray accountDataEvents { @@ -1285,7 +1290,7 @@ void Connection::saveState() const accountDataEvents.append( basicEventJson(e.first, e.second->contentJson())); - rootObj.insert("account_data", + rootObj.insert(QStringLiteral("account_data"), QJsonObject {{ QStringLiteral("events"), accountDataEvents }}); } -- cgit v1.2.3 From 1103c1fa00d99c4e090f76fd07a384032f79f0c1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 16 Apr 2019 16:22:54 +0900 Subject: Connection::logout: ignore ContentAccessError Closes #316. --- lib/connection.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 5ed72616..fa358e17 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -321,11 +321,14 @@ void Connection::checkAndConnect(const QString& userId, void Connection::logout() { auto job = callApi(); - connect( job, &LogoutJob::success, this, [this] { - stopSync(); - d->data->setToken({}); - emit stateChanged(); - emit loggedOut(); + connect( job, &LogoutJob::finished, this, [job,this] { + if (job->status().good() || job->error() == BaseJob::ContentAccessError) + { + stopSync(); + d->data->setToken({}); + emit stateChanged(); + emit loggedOut(); + } }); } -- cgit v1.2.3 From 0378e830c40a1d7302a8108d460940b6cd15201e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 3 May 2019 22:56:42 +0900 Subject: Connection::stateCacheDir Same as stateCachePath but returns QDir. --- lib/connection.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index fa358e17..d75d8e56 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -133,6 +133,10 @@ class Connection::Private packAndSendAccountData( makeEvent(std::forward(content))); } + QString topLevelStatePath() const + { + return q->stateCacheDir().filePath("state.json"); + } }; Connection::Connection(const QUrl& server, QObject* parent) @@ -1232,7 +1236,8 @@ void Connection::saveRoomState(Room* r) const if (!d->cacheState) return; - QFile outRoomFile { stateCachePath() % SyncData::fileNameForRoom(r->id()) }; + QFile outRoomFile { + stateCacheDir().filePath(SyncData::fileNameForRoom(r->id())) }; if (outRoomFile.open(QFile::WriteOnly)) { QJsonDocument json { r->toJson() }; @@ -1253,7 +1258,7 @@ void Connection::saveState() const QElapsedTimer et; et.start(); - QFile outFile { stateCachePath() % "state.json" }; + QFile outFile { d->topLevelStatePath() }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(MAIN) << "Error opening" << outFile.fileName() @@ -1313,7 +1318,7 @@ void Connection::loadState() QElapsedTimer et; et.start(); - SyncData sync { stateCachePath() % "state.json" }; + SyncData sync { d->topLevelStatePath() }; if (sync.nextBatch().isEmpty()) // No token means no cache by definition return; @@ -1330,6 +1335,11 @@ void Connection::loadState() } QString Connection::stateCachePath() const +{ + return stateCacheDir().path() % '/'; +} + +QDir Connection::stateCacheDir() const { auto safeUserId = userId(); safeUserId.replace(':', '_'); -- cgit v1.2.3 From 1033f3b4249b8b0224ba725b220338e6c9621084 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 18 May 2019 21:50:08 +0900 Subject: Connection::onSyncSuccess(): fix using after move() Also rewrite the account data piece with visit(). --- lib/connection.cpp | 116 +++++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 56 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d75d8e56..cd0d96f7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -411,63 +411,67 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { // Let UI update itself after updating each room QCoreApplication::processEvents(); } - for (auto&& accountEvent: data.takeAccountData()) - { - if (is(*accountEvent)) - { - const auto usersToDCs = ptrCast(move(accountEvent)) - ->usersToDirectChats(); - DirectChatsMap removals = - erase_if(d->directChats, [&usersToDCs] (auto it) { - return !usersToDCs.contains(it.key()->id(), it.value()); - }); - erase_if(d->directChatUsers, [&usersToDCs] (auto it) { - return !usersToDCs.contains(it.value()->id(), it.key()); - }); - if (MAIN().isDebugEnabled()) - for (auto it = removals.begin(); it != removals.end(); ++it) - qCDebug(MAIN) << it.value() - << "is no more a direct chat with" << it.key()->id(); - - DirectChatsMap additions; - for (auto it = usersToDCs.begin(); it != usersToDCs.end(); ++it) - { - if (auto* u = user(it.key())) - { - if (!d->directChats.contains(u, it.value())) - { - Q_ASSERT(!d->directChatUsers.contains(it.value(), u)); - additions.insert(u, it.value()); - d->directChats.insert(u, it.value()); - d->directChatUsers.insert(it.value(), u); - qCDebug(MAIN) << "Marked room" << it.value() + // After running this loop, the account data events not saved in + // d->accountData (see the end of the loop body) are auto-cleaned away + for (auto& eventPtr: data.takeAccountData()) + visit(*eventPtr, + [this](const DirectChatEvent& dce) { + const auto& usersToDCs = dce.usersToDirectChats(); + DirectChatsMap removals = + erase_if(d->directChats, [&usersToDCs](auto it) { + return !usersToDCs.contains(it.key()->id(), + it.value()); + }); + erase_if(d->directChatUsers, [&usersToDCs](auto it) { + return !usersToDCs.contains(it.value()->id(), it.key()); + }); + if (MAIN().isDebugEnabled()) + for (auto it = removals.begin(); it != removals.end(); + ++it) + qCDebug(MAIN) << it.value() + << "is no more a direct chat with" + << it.key()->id(); + + DirectChatsMap additions; + for (auto it = usersToDCs.begin(); it != usersToDCs.end(); + ++it) { + if (auto* u = user(it.key())) { + if (!d->directChats.contains(u, it.value())) { + Q_ASSERT(!d->directChatUsers.contains(it.value(), + u)); + additions.insert(u, it.value()); + d->directChats.insert(u, it.value()); + d->directChatUsers.insert(it.value(), u); + qCDebug(MAIN) + << "Marked room" << it.value() << "as a direct chat with" << u->id(); - } - } else - qCWarning(MAIN) - << "Couldn't get a user object for" << it.key(); - } - if (!additions.isEmpty() || !removals.isEmpty()) - emit directChatsListChanged(additions, removals); - - continue; - } - if (is(*accountEvent)) - qCDebug(MAIN) << "Users ignored by" << d->userId << "updated:" - << QStringList::fromSet(ignoredUsers()).join(','); - - auto& currentData = d->accountData[accountEvent->matrixType()]; - // A polymorphic event-specific comparison might be a bit more - // efficient; maaybe do it another day - if (!currentData || - currentData->contentJson() != accountEvent->contentJson()) - { - currentData = std::move(accountEvent); - qCDebug(MAIN) << "Updated account data of type" - << currentData->matrixType(); - emit accountDataChanged(currentData->matrixType()); - } - } + } + } else + qCWarning(MAIN) << "Couldn't get a user object for" + << it.key(); + } + if (!additions.isEmpty() || !removals.isEmpty()) + emit directChatsListChanged(additions, removals); + }, + // catch-all, passing eventPtr for a possible take-over + [this, &eventPtr](const Event& accountEvent) { + if (is(accountEvent)) + qCDebug(MAIN) + << "Users ignored by" << d->userId << "updated:" + << QStringList::fromSet(ignoredUsers()).join(','); + + auto& currentData = d->accountData[accountEvent.matrixType()]; + // A polymorphic event-specific comparison might be a bit more + // efficient; maaybe do it another day + if (!currentData + || currentData->contentJson() + != accountEvent.contentJson()) { + currentData = std::move(eventPtr); + qCDebug(MAIN) << "Updated account data of type" + << currentData->matrixType(); + emit accountDataChanged(currentData->matrixType()); + } + }); } void Connection::stopSync() -- cgit v1.2.3 From 626b2711a49e581babafd179ce362a1c88db8b85 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 19 May 2019 08:54:00 +0900 Subject: Connection: use QScopedPointer instead of unique_ptr While theoretically less robust (no equivalent of make_unique), QScopedPointer is navigable in Qt Creator debug views, unlike unique_ptr. Of course this will eventually be fixed; but given that inability to create an owning pointer object means sure abnormal termination of our code shortly afterwards, having make_unique in this particular case doesn't help in any way at all; so unique_ptr has zero advantages over QScopedPointer in this setting. --- lib/connection.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index cd0d96f7..e3da3c16 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -141,7 +141,7 @@ class Connection::Private Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent) - , d(std::make_unique(std::make_unique(server))) + , d(new Private(std::make_unique(server))) { d->q = this; // All d initialization should occur before this line } @@ -294,6 +294,7 @@ void Connection::Private::connectWithToken(const QString& user, q->user(); // Creates a User object for the local user data->setToken(accessToken.toLatin1()); data->setDeviceId(deviceId); + q->setObjectName(userId % '/' % deviceId); qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << userId << "from device" << deviceId; emit q->stateChanged(); -- cgit v1.2.3 From 4ae7fe50e5aae28dee3d207b0ca02207c46bb781 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 19 May 2019 15:35:52 +0900 Subject: Improve wording in a comment --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index e3da3c16..98cd1ef1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -80,8 +80,8 @@ class Connection::Private std::unique_ptr data; // A complex key below is a pair of room name and whether its // state is Invited. The spec mandates to keep Invited room state - // separately so we should, e.g., keep objects for Invite and - // Leave state of the same room. + // separately; specifically, we should keep objects for Invite and + // Leave state of the same room if the two happen to co-exist. QHash, Room*> roomMap; // Mapping from aliases to room ids, as per the last sync QHash roomAliasMap; -- cgit v1.2.3 From 89e0cec42fe9538cbd170a9dc3fcf6dd18b5ac02 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 20 May 2019 07:46:03 +0900 Subject: Connection: Fix a race condition in direct chats handling upon initial sync Closes #323. --- lib/connection.cpp | 129 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 54 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 98cd1ef1..a7eae30f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -73,8 +73,7 @@ class Connection::Private : data(move(connection)) { } Q_DISABLE_COPY(Private) - Private(Private&&) = delete; - Private operator=(Private&&) = delete; + DISABLE_MOVE(Private) Connection* q = nullptr; std::unique_ptr data; @@ -91,6 +90,10 @@ class Connection::Private QMap userMap; DirectChatsMap directChats; DirectChatUsersMap directChatUsers; + // The below two variables track local changes between sync completions. + // See also: https://github.com/QMatrixClient/libqmatrixclient/wiki/Handling-direct-chat-events + DirectChatsMap dcLocalAdditions; + DirectChatsMap dcLocalRemovals; std::unordered_map accountData; QString userId; int syncLoopTimeout = -1; @@ -107,8 +110,6 @@ class Connection::Private void connectWithToken(const QString& user, const QString& accessToken, const QString& deviceId); - void broadcastDirectChatUpdates(const DirectChatsMap& additions, - const DirectChatsMap& removals); template EventT* unpackAccountData() const @@ -378,6 +379,20 @@ void Connection::syncLoop(int timeout) syncLoopIteration(); // initial sync to start the loop } +QJsonObject toJson(const Connection::DirectChatsMap& directChats) +{ + QJsonObject json; + for (auto it = directChats.begin(); it != directChats.end();) + { + QJsonArray roomIds; + const auto* user = it.key(); + for (; it != directChats.end() && it.key() == user; ++it) + roomIds.append(*it); + json.insert(user->id(), roomIds); + } + return json; +} + void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { d->data->setLastEvent(data.nextBatch()); for (auto&& roomData: data.takeRoomData()) @@ -414,33 +429,43 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { } // After running this loop, the account data events not saved in // d->accountData (see the end of the loop body) are auto-cleaned away - for (auto& eventPtr: data.takeAccountData()) + for (auto& eventPtr : data.takeAccountData()) + { visit(*eventPtr, [this](const DirectChatEvent& dce) { + // See https://github.com/QMatrixClient/libqmatrixclient/wiki/Handling-direct-chat-events const auto& usersToDCs = dce.usersToDirectChats(); - DirectChatsMap removals = - erase_if(d->directChats, [&usersToDCs](auto it) { - return !usersToDCs.contains(it.key()->id(), - it.value()); - }); - erase_if(d->directChatUsers, [&usersToDCs](auto it) { - return !usersToDCs.contains(it.value()->id(), it.key()); + DirectChatsMap remoteRemovals = + erase_if(d->directChats, [&usersToDCs, this](auto it) { + return !(usersToDCs.contains(it.key()->id(), it.value()) + || d->dcLocalAdditions.contains(it.key(), + it.value())); + }); + erase_if(d->directChatUsers, [&remoteRemovals](auto it) { + return remoteRemovals.contains(it.value(), it.key()); + }); + // Remove from dcLocalRemovals what the server already has. + erase_if(d->dcLocalRemovals, [&remoteRemovals](auto it) { + return remoteRemovals.contains(it.key(), it.value()); }); if (MAIN().isDebugEnabled()) - for (auto it = removals.begin(); it != removals.end(); - ++it) - qCDebug(MAIN) << it.value() - << "is no more a direct chat with" - << it.key()->id(); - - DirectChatsMap additions; + for (auto it = remoteRemovals.begin(); + it != remoteRemovals.end(); ++it) { + qCDebug(MAIN) + << it.value() << "is no more a direct chat with" + << it.key()->id(); + } + + DirectChatsMap remoteAdditions; for (auto it = usersToDCs.begin(); it != usersToDCs.end(); ++it) { if (auto* u = user(it.key())) { - if (!d->directChats.contains(u, it.value())) { - Q_ASSERT(!d->directChatUsers.contains(it.value(), - u)); - additions.insert(u, it.value()); + if (!d->directChats.contains(u, it.value()) + && !d->dcLocalRemovals.contains(u, it.value())) + { + Q_ASSERT( + !d->directChatUsers.contains(it.value(), u)); + remoteAdditions.insert(u, it.value()); d->directChats.insert(u, it.value()); d->directChatUsers.insert(it.value(), u); qCDebug(MAIN) @@ -451,8 +476,12 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { qCWarning(MAIN) << "Couldn't get a user object for" << it.key(); } - if (!additions.isEmpty() || !removals.isEmpty()) - emit directChatsListChanged(additions, removals); + // Remove from dcLocalAdditions what the server already has. + erase_if(d->dcLocalAdditions, [&remoteAdditions](auto it) { + return remoteAdditions.contains(it.key(), it.value()); + }); + if (!remoteAdditions.isEmpty() || !remoteRemovals.isEmpty()) + emit directChatsListChanged(remoteAdditions, remoteRemovals); }, // catch-all, passing eventPtr for a possible take-over [this, &eventPtr](const Event& accountEvent) { @@ -473,6 +502,16 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { emit accountDataChanged(currentData->matrixType()); } }); + } + if (!d->dcLocalAdditions.isEmpty() || !d->dcLocalRemovals.isEmpty()) { + qDebug(MAIN) << "Sending updated direct chats to the server:" + << d->dcLocalRemovals.size() << "removal(s)," + << d->dcLocalAdditions.size() << "addition(s)"; + callApi(d->userId, QStringLiteral("m.direct"), + toJson(d->directChats)); + d->dcLocalAdditions.clear(); + d->dcLocalRemovals.clear(); + } } void Connection::stopSync() @@ -667,8 +706,8 @@ void Connection::doInDirectChat(User* u, { Q_ASSERT(u); const auto& userId = u->id(); - // There can be more than one DC; find the first valid, and delete invalid - // (left/forgotten) ones along the way. + // There can be more than one DC; find the first valid (existing and + // not left), and delete inexistent (forgotten?) ones along the way. DirectChatsMap removals; for (auto it = d->directChats.find(u); it != d->directChats.end() && it.key() == u; ++it) @@ -705,6 +744,8 @@ void Connection::doInDirectChat(User* u, << roomId << "is not valid and will be discarded"; // Postpone actual deletion until we finish iterating d->directChats. removals.insert(it.key(), it.value()); + // Add to the list of updates to send to the server upon the next sync. + d->dcLocalRemovals.insert(it.key(), it.value()); } if (!removals.isEmpty()) { @@ -714,7 +755,7 @@ void Connection::doInDirectChat(User* u, d->directChatUsers.remove(it.value(), const_cast(it.key())); // FIXME } - d->broadcastDirectChatUpdates({}, removals); + emit directChatsListChanged({}, removals); } auto j = createDirectChat(userId); @@ -1015,28 +1056,6 @@ Connection::DirectChatsMap Connection::directChats() const return d->directChats; } -QJsonObject toJson(const Connection::DirectChatsMap& directChats) -{ - QJsonObject json; - for (auto it = directChats.begin(); it != directChats.end();) - { - QJsonArray roomIds; - const auto* user = it.key(); - for (; it != directChats.end() && it.key() == user; ++it) - roomIds.append(*it); - json.insert(user->id(), roomIds); - } - return json; -} - -void Connection::Private::broadcastDirectChatUpdates(const DirectChatsMap& additions, - const DirectChatsMap& removals) -{ - q->callApi(userId, QStringLiteral("m.direct"), - toJson(directChats)); - emit q->directChatsListChanged(additions, removals); -} - void Connection::addToDirectChats(const Room* room, User* user) { Q_ASSERT(room != nullptr && user != nullptr); @@ -1045,8 +1064,8 @@ void Connection::addToDirectChats(const Room* room, User* user) Q_ASSERT(!d->directChatUsers.contains(room->id(), user)); d->directChats.insert(user, room->id()); d->directChatUsers.insert(room->id(), user); - DirectChatsMap additions { { user, room->id() } }; - d->broadcastDirectChatUpdates(additions, {}); + d->dcLocalAdditions.insert(user, room->id()); + emit directChatsListChanged({ { user, room->id() } }, {}); } void Connection::removeFromDirectChats(const QString& roomId, User* user) @@ -1059,15 +1078,17 @@ void Connection::removeFromDirectChats(const QString& roomId, User* user) DirectChatsMap removals; if (user != nullptr) { - removals.insert(user, roomId); d->directChats.remove(user, roomId); d->directChatUsers.remove(roomId, user); + removals.insert(user, roomId); + d->dcLocalRemovals.insert(user, roomId); } else { removals = erase_if(d->directChats, [&roomId] (auto it) { return it.value() == roomId; }); d->directChatUsers.remove(roomId); + d->dcLocalRemovals += removals; } - d->broadcastDirectChatUpdates({}, removals); + emit directChatsListChanged({}, removals); } bool Connection::isDirectChat(const QString& roomId) const -- cgit v1.2.3 From 26c51455901a6c0112531d86d61d65d31a70dfaa Mon Sep 17 00:00:00 2001 From: Ville Ranki Date: Thu, 4 Apr 2019 11:45:41 +0300 Subject: Ignore some errors on leaving rooms, add new error enum. Fixes #307 --- lib/connection.cpp | 56 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 17 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index a7eae30f..5bf89815 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -110,6 +110,7 @@ class Connection::Private void connectWithToken(const QString& user, const QString& accessToken, const QString& deviceId); + void removeRoom(const QString& roomId); template EventT* unpackAccountData() const @@ -790,29 +791,36 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) if (room && room->joinState() != JoinState::Leave) { auto leaveJob = room->leaveRoom(); - connect(leaveJob, &BaseJob::success, this, [this, forgetJob, room] { - forgetJob->start(connectionData()); - // If the matching /sync response hasn't arrived yet, mark the room - // for explicit deletion - if (room->joinState() != JoinState::Leave) - d->roomIdsToForget.push_back(room->id()); + connect(leaveJob, &BaseJob::result, this, [this, leaveJob, forgetJob, room] { + // After leave, continue if there is no error or the room id is not found (IncorrectRequestError) + if(!leaveJob->error() || leaveJob->error() == BaseJob::StatusCode::UnknownObjectError) { + forgetJob->start(connectionData()); + // If the matching /sync response hasn't arrived yet, mark the room + // for explicit deletion + if (room->joinState() != JoinState::Leave) + d->roomIdsToForget.push_back(room->id()); + } else { + qCWarning(MAIN) << "Error leaving room " + << room->name() << ":" + << leaveJob->errorString(); + forgetJob->abandon(); + } }); connect(leaveJob, &BaseJob::failure, forgetJob, &BaseJob::abandon); } else forgetJob->start(connectionData()); - connect(forgetJob, &BaseJob::success, this, [this, id] + connect(forgetJob, &BaseJob::result, this, [this, id, forgetJob] { - // Delete whatever instances of the room are still in the map. - for (auto f: {false, true}) - if (auto r = d->roomMap.take({ id, f })) - { - qCDebug(MAIN) << "Room" << r->objectName() - << "in state" << toCString(r->joinState()) - << "will be deleted"; - emit r->beforeDestruction(r); - r->deleteLater(); - } + // Leave room in case of success, or room not known by server + if(!forgetJob->error() || forgetJob->error() == BaseJob::StatusCode::IncorrectRequestError || forgetJob->error() == BaseJob::StatusCode::UnknownObjectError) { + // Delete the room from roomMap + d->removeRoom(id); + } else { + qCWarning(MAIN) << "Error forgetting room " + << id << ":" + << forgetJob->errorString(); + } }); return forgetJob; } @@ -1056,6 +1064,20 @@ Connection::DirectChatsMap Connection::directChats() const return d->directChats; } +// Removes room with given id from roomMap +void Connection::Private::removeRoom(const QString& roomId) +{ + for (auto f: {false, true}) + if (auto r = roomMap.take({ roomId, f })) + { + qCDebug(MAIN) << "Room" << r->objectName() + << "in state" << toCString(r->joinState()) + << "will be deleted"; + emit r->beforeDestruction(r); + r->deleteLater(); + } +} + void Connection::addToDirectChats(const Room* room, User* user) { Q_ASSERT(room != nullptr && user != nullptr); -- cgit v1.2.3 From 7591ec34cee15a58611408a996bdb1b92b6ffb98 Mon Sep 17 00:00:00 2001 From: Ville Ranki Date: Tue, 11 Jun 2019 11:58:57 +0300 Subject: Remove unnecessary error checks in lib/connection.cpp Co-Authored-By: Kitsune Ral --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 5bf89815..27211a77 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -813,7 +813,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) connect(forgetJob, &BaseJob::result, this, [this, id, forgetJob] { // Leave room in case of success, or room not known by server - if(!forgetJob->error() || forgetJob->error() == BaseJob::StatusCode::IncorrectRequestError || forgetJob->error() == BaseJob::StatusCode::UnknownObjectError) { + if(!forgetJob->error() || forgetJob->error() == BaseJob::UnknownObjectError) { // Delete the room from roomMap d->removeRoom(id); } else { -- cgit v1.2.3 From a19e12544d174588bb99d1d9d5b2576f0ea1e037 Mon Sep 17 00:00:00 2001 From: Ville Ranki Date: Tue, 11 Jun 2019 11:59:40 +0300 Subject: Comment change as requested Co-Authored-By: Kitsune Ral --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 27211a77..af2aa2ab 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -792,7 +792,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) { auto leaveJob = room->leaveRoom(); connect(leaveJob, &BaseJob::result, this, [this, leaveJob, forgetJob, room] { - // After leave, continue if there is no error or the room id is not found (IncorrectRequestError) + // After leave, continue if there is no error or the room id is not found if(!leaveJob->error() || leaveJob->error() == BaseJob::StatusCode::UnknownObjectError) { forgetJob->start(connectionData()); // If the matching /sync response hasn't arrived yet, mark the room -- cgit v1.2.3 From 12478cf7330727083103d22f76de92c4aa476f5b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 1 Jul 2019 16:10:35 +0900 Subject: Handle M_UNKNOWN as The Spec says; factor out BaseJob::parseError() --- lib/connection.cpp | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index af2aa2ab..4c068b8f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -791,21 +791,23 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) if (room && room->joinState() != JoinState::Leave) { auto leaveJob = room->leaveRoom(); - connect(leaveJob, &BaseJob::result, this, [this, leaveJob, forgetJob, room] { - // After leave, continue if there is no error or the room id is not found - if(!leaveJob->error() || leaveJob->error() == BaseJob::StatusCode::UnknownObjectError) { - forgetJob->start(connectionData()); - // If the matching /sync response hasn't arrived yet, mark the room - // for explicit deletion - if (room->joinState() != JoinState::Leave) - d->roomIdsToForget.push_back(room->id()); - } else { - qCWarning(MAIN) << "Error leaving room " - << room->name() << ":" - << leaveJob->errorString(); - forgetJob->abandon(); - } - }); + connect(leaveJob, &BaseJob::result, this, + [this, leaveJob, forgetJob, room] { + if (leaveJob->error() == BaseJob::Success + || leaveJob->error() == BaseJob::NotFoundError) + { + forgetJob->start(connectionData()); + // If the matching /sync response hasn't arrived yet, + // mark the room for explicit deletion + if (room->joinState() != JoinState::Leave) + d->roomIdsToForget.push_back(room->id()); + } else { + qCWarning(MAIN).nospace() + << "Error leaving room " << room->objectName() + << ": " << leaveJob->errorString(); + forgetJob->abandon(); + } + }); connect(leaveJob, &BaseJob::failure, forgetJob, &BaseJob::abandon); } else @@ -813,14 +815,12 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) connect(forgetJob, &BaseJob::result, this, [this, id, forgetJob] { // Leave room in case of success, or room not known by server - if(!forgetJob->error() || forgetJob->error() == BaseJob::UnknownObjectError) { - // Delete the room from roomMap - d->removeRoom(id); - } else { - qCWarning(MAIN) << "Error forgetting room " - << id << ":" - << forgetJob->errorString(); - } + if(forgetJob->error() == BaseJob::Success + || forgetJob->error() == BaseJob::NotFoundError) + d->removeRoom(id); // Delete the room from roomMap + else + qCWarning(MAIN).nospace() << "Error forgetting room " << id << ": " + << forgetJob->errorString(); }); return forgetJob; } -- cgit v1.2.3 From 6a6857b9d4dbf22402f2871494bdd06cdccdf366 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 3 Jul 2019 23:28:09 +0900 Subject: Room/Connection: make room aliases work properly Closes #301. --- lib/connection.cpp | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4c068b8f..783e12c0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -82,8 +82,9 @@ class Connection::Private // separately; specifically, we should keep objects for Invite and // Leave state of the same room if the two happen to co-exist. QHash, Room*> roomMap; - // Mapping from aliases to room ids, as per the last sync - QHash roomAliasMap; + /// Mapping from serverparts to alias/room id mappings, + /// as of the last sync + QHash> roomAliasMap; QVector roomIdsToForget; QVector firstTimeRooms; QVector pendingStateRoomIds; @@ -158,20 +159,31 @@ Connection::~Connection() stopSync(); } -void Connection::resolveServer(const QString& mxidOrDomain) +static const auto ServerPartRegEx = QStringLiteral( + "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address + "(?::(\\d{1,5}))?" // Optional port +); + +QString serverPart(const QString& mxId) { - // At this point we may have something as complex as - // @username:[IPv6:address]:port, or as simple as a plain domain name. + static auto re = "^[@!#$+].+?:(" // Localpart and colon + % ServerPartRegEx % ")$"; + static QRegularExpression parser(re, + QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits + return parser.match(mxId).captured(1); +} - // Try to parse as an FQID; if there's no @ part, assume it's a domain name. - QRegularExpression parser( +void Connection::resolveServer(const QString& mxidOrDomain) +{ + // mxIdOrDomain may be something as complex as + // @username:[IPv6:address]:port, or as simple as a plain serverpart. + static QRegularExpression parser( "^(@.+?:)?" // Optional username (allow everything for compatibility) - "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address - "(:\\d{1,5})?$", // Optional port - QRegularExpression::UseUnicodePropertiesOption); // Because asian digits + % ServerPartRegEx % '$', + QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits auto match = parser.match(mxidOrDomain); - QUrl maybeBaseUrl = QUrl::fromUserInput(match.captured(2)); + auto maybeBaseUrl = QUrl::fromUserInput(match.captured(2)); maybeBaseUrl.setScheme("https"); // Instead of the Qt-default "http" if (!match.hasMatch() || !maybeBaseUrl.isValid()) { @@ -883,33 +895,36 @@ Room* Connection::room(const QString& roomId, JoinStates states) const Room* Connection::roomByAlias(const QString& roomAlias, JoinStates states) const { - const auto id = d->roomAliasMap.value(roomAlias); + const auto id = + d->roomAliasMap.value(serverPart(roomAlias)).value(roomAlias); if (!id.isEmpty()) return room(id, states); + qCWarning(MAIN) << "Room for alias" << roomAlias << "is not found under account" << userId(); return nullptr; } void Connection::updateRoomAliases(const QString& roomId, + const QString& aliasServer, const QStringList& previousRoomAliases, const QStringList& roomAliases) { + auto& aliasMap = d->roomAliasMap[aliasServer]; // Allocate if necessary for (const auto& a: previousRoomAliases) - if (d->roomAliasMap.remove(a) == 0) + if (aliasMap.remove(a) == 0) qCWarning(MAIN) << "Alias" << a << "is not found (already deleted?)"; for (const auto& a: roomAliases) { - auto& mappedId = d->roomAliasMap[a]; + auto& mappedId = aliasMap[a]; if (!mappedId.isEmpty()) { if (mappedId == roomId) - qCDebug(MAIN) << "Alias" << a << "is already mapped to room" + qCDebug(MAIN) << "Alias" << a << "is already mapped to" << roomId; else - qCWarning(MAIN) << "Alias" << a - << "will be force-remapped from room" + qCWarning(MAIN) << "Alias" << a << "will be force-remapped from" << mappedId << "to" << roomId; } mappedId = roomId; -- cgit v1.2.3 From fe82533860a327fbbaa0c980188ccd56d8463b1c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 4 Jul 2019 13:17:36 +0900 Subject: Connection::token() is no more Use Connection::accessToken() instead. --- lib/connection.cpp | 5 ----- 1 file changed, 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 783e12c0..cd02f6d7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -973,11 +973,6 @@ QString Connection::deviceId() const return d->data->deviceId(); } -QString Connection::token() const -{ - return accessToken(); -} - QByteArray Connection::accessToken() const { return d->data->accessToken(); -- cgit v1.2.3 From 69ba71cca36b4a90328d169c845195f39c041a3a Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sat, 22 Jun 2019 19:57:18 +0300 Subject: Add EncryptionManager class. Add AccountSettings::encryptionAccountPickle logic. --- lib/connection.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index a7eae30f..d7c3d78f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -23,6 +23,7 @@ #include "events/eventloader.h" #include "room.h" #include "settings.h" +#include "encryptionmanager.h" #include "csapi/login.h" #include "csapi/capabilities.h" #include "csapi/logout.h" @@ -101,6 +102,8 @@ class Connection::Private GetCapabilitiesJob* capabilitiesJob = nullptr; GetCapabilitiesJob::Capabilities capabilities; + QScopedPointer encryptionManager; + SyncJob* syncJob = nullptr; bool cacheState = true; @@ -235,6 +238,13 @@ void Connection::doConnectToServer(const QString& user, const QString& password, [this, loginJob] { d->connectWithToken(loginJob->userId(), loginJob->accessToken(), loginJob->deviceId()); + + AccountSettings accountSettings(loginJob->userId()); + d->encryptionManager.reset(new EncryptionManager(accountSettings.encryptionAccountPickle())); + + d->encryptionManager->uploadIdentityKeys(this); + d->encryptionManager->uploadOneTimeKeys(this); + }); connect(loginJob, &BaseJob::failure, this, [this, loginJob] { -- cgit v1.2.3 From a9984d39e5186e2cfd105cf2ffb4148d9a8573a2 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Thu, 4 Jul 2019 21:52:02 +0800 Subject: Connection.cpp: Add .well-known parsing in resolveServer() --- lib/connection.cpp | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index cd02f6d7..4391a1b2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -32,6 +32,7 @@ #include "csapi/joining.h" #include "csapi/to_device.h" #include "csapi/room_send.h" +#include "csapi/wellknown.h" #include "jobs/syncjob.h" #include "jobs/mediathumbnailjob.h" #include "jobs/downloadfilejob.h" @@ -194,37 +195,24 @@ void Connection::resolveServer(const QString& mxidOrDomain) } setHomeserver(maybeBaseUrl); - emit resolved(); - return; - // FIXME, #178: The below code is incorrect and is no more executed. The - // correct server resolution should be done from .well-known/matrix/client auto domain = maybeBaseUrl.host(); qCDebug(MAIN) << "Finding the server" << domain; - // Check if the Matrix server has a dedicated service record. - auto* dns = new QDnsLookup(); - dns->setType(QDnsLookup::SRV); - dns->setName("_matrix._tcp." + domain); - - connect(dns, &QDnsLookup::finished, [this,dns,maybeBaseUrl]() { - QUrl baseUrl { maybeBaseUrl }; - if (dns->error() == QDnsLookup::NoError && - dns->serviceRecords().isEmpty()) - { - auto record = dns->serviceRecords().front(); - baseUrl.setHost(record.target()); - baseUrl.setPort(record.port()); - qCDebug(MAIN) << "SRV record for" << maybeBaseUrl.host() - << "is" << baseUrl.authority(); + + auto job = callApi(); + connect(job, &BaseJob::finished, [this, job, maybeBaseUrl] { + QUrl baseUrl(job->data().homeserver.baseUrl); + if (!baseUrl.isValid() || job->status() != BaseJob::Success) { + baseUrl = maybeBaseUrl; + qCDebug(MAIN) << maybeBaseUrl.host() << "doesn't have .well-known record" << "- using the hostname as is"; } else { - qCDebug(MAIN) << baseUrl.host() << "doesn't have SRV record" - << dns->name() << "- using the hostname as is"; + qCDebug(MAIN) << ".well-known for" << maybeBaseUrl.host() << "is" << baseUrl.authority(); } + setHomeserver(baseUrl); emit resolved(); - dns->deleteLater(); + job->deleteLater(); }); - dns->lookup(); } void Connection::connectToServer(const QString& user, const QString& password, -- cgit v1.2.3 From 5d9d56a4fe9f3178c8bc9b3839ec608659c0c371 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Fri, 5 Jul 2019 10:15:54 +0800 Subject: Clean up .well-known logic --- lib/connection.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4391a1b2..98555104 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -203,13 +203,12 @@ void Connection::resolveServer(const QString& mxidOrDomain) connect(job, &BaseJob::finished, [this, job, maybeBaseUrl] { QUrl baseUrl(job->data().homeserver.baseUrl); if (!baseUrl.isValid() || job->status() != BaseJob::Success) { - baseUrl = maybeBaseUrl; - qCDebug(MAIN) << maybeBaseUrl.host() << "doesn't have .well-known record" << "- using the hostname as is"; + qCDebug(MAIN) << maybeBaseUrl.host() << "doesn't have valid .well-known file" << "- using the hostname as is"; } else { qCDebug(MAIN) << ".well-known for" << maybeBaseUrl.host() << "is" << baseUrl.authority(); + setHomeserver(baseUrl); } - setHomeserver(baseUrl); emit resolved(); job->deleteLater(); }); -- cgit v1.2.3 From 281e3235d3d4618afd9f01049b8a2acbe1c8475c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Jul 2019 11:41:08 +0900 Subject: Connection::serverPart: replace auto with QString because of QStringBuilder See https://github.com/KDE/clazy/blob/master/docs/checks/README-auto-unexpected-qstringbuilder.md Closes #613. --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index cd02f6d7..5d377173 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -166,7 +166,7 @@ static const auto ServerPartRegEx = QStringLiteral( QString serverPart(const QString& mxId) { - static auto re = "^[@!#$+].+?:(" // Localpart and colon + static QString re = "^[@!#$+].+?:(" // Localpart and colon % ServerPartRegEx % ")$"; static QRegularExpression parser(re, QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits -- cgit v1.2.3 From 0bfb1c1c69c02c7936cb018ead496616322a1cf7 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sun, 7 Jul 2019 21:53:22 +0300 Subject: E2EE: add new account generation logic --- lib/connection.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 20fb367c..ff066def 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -254,6 +254,9 @@ void Connection::doConnectToServer(const QString& user, const QString& password, AccountSettings accountSettings(loginJob->userId()); d->encryptionManager.reset(new EncryptionManager(accountSettings.encryptionAccountPickle())); + if (accountSettings.encryptionAccountPickle().isEmpty()) { + accountSettings.setEncryptionAccountPickle(d->encryptionManager->olmAccountPickle()); + } d->encryptionManager->uploadIdentityKeys(this); d->encryptionManager->uploadOneTimeKeys(this); -- cgit v1.2.3 From 651478c1681ba6f93e22c20328a048dbbc263ffe Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 8 Jul 2019 21:31:40 +0900 Subject: Move serverPart() to the public API Also: Connection::resolveServer() now only accepts MXIDs, not domains. --- lib/connection.cpp | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index ff066def..6d06c603 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -162,33 +162,11 @@ Connection::~Connection() stopSync(); } -static const auto ServerPartRegEx = QStringLiteral( - "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address - "(?::(\\d{1,5}))?" // Optional port -); - -QString serverPart(const QString& mxId) +void Connection::resolveServer(const QString& mxid) { - static QString re = "^[@!#$+].+?:(" // Localpart and colon - % ServerPartRegEx % ")$"; - static QRegularExpression parser(re, - QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits - return parser.match(mxId).captured(1); -} - -void Connection::resolveServer(const QString& mxidOrDomain) -{ - // mxIdOrDomain may be something as complex as - // @username:[IPv6:address]:port, or as simple as a plain serverpart. - static QRegularExpression parser( - "^(@.+?:)?" // Optional username (allow everything for compatibility) - % ServerPartRegEx % '$', - QRegularExpression::UseUnicodePropertiesOption); // Because Asian digits - auto match = parser.match(mxidOrDomain); - - auto maybeBaseUrl = QUrl::fromUserInput(match.captured(2)); + auto maybeBaseUrl = QUrl::fromUserInput(serverPart(mxid)); maybeBaseUrl.setScheme("https"); // Instead of the Qt-default "http" - if (!match.hasMatch() || !maybeBaseUrl.isValid()) + if (maybeBaseUrl.isEmpty() || !maybeBaseUrl.isValid()) { emit resolveError( tr("%1 is not a valid homeserver address") -- cgit v1.2.3 From f40d511917e7ac1e44106b24bfa3cdf93bd6f664 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Tue, 9 Jul 2019 20:46:49 +0800 Subject: Add more checks according to the spec --- lib/connection.cpp | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 98555104..511a0411 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -33,6 +33,7 @@ #include "csapi/to_device.h" #include "csapi/room_send.h" #include "csapi/wellknown.h" +#include "csapi/versions.h" #include "jobs/syncjob.h" #include "jobs/mediathumbnailjob.h" #include "jobs/downloadfilejob.h" @@ -199,18 +200,40 @@ void Connection::resolveServer(const QString& mxidOrDomain) auto domain = maybeBaseUrl.host(); qCDebug(MAIN) << "Finding the server" << domain; - auto job = callApi(); - connect(job, &BaseJob::finished, [this, job, maybeBaseUrl] { - QUrl baseUrl(job->data().homeserver.baseUrl); - if (!baseUrl.isValid() || job->status() != BaseJob::Success) { - qCDebug(MAIN) << maybeBaseUrl.host() << "doesn't have valid .well-known file" << "- using the hostname as is"; + auto getWellKnownJob = callApi(); + connect(getWellKnownJob, &BaseJob::finished, [this, getWellKnownJob, maybeBaseUrl] { + if (getWellKnownJob->status() == BaseJob::NotFoundError) { + qCDebug(MAIN) << "No .well-known file, IGNORE"; + } else if (getWellKnownJob->status() != BaseJob::Success) { + qCDebug(MAIN) << "Fetching .well-known file failed, FAIL_PROMPT"; + emit resolveError(tr("Fetching .well-known file failed")); + return; + } else if (getWellKnownJob->data().homeserver.baseUrl.isEmpty()) { + qCDebug(MAIN) << "base_url not provided, FAIL_PROMPT"; + emit resolveError(tr("base_url not provided")); + return; + } else if (!QUrl(getWellKnownJob->data().homeserver.baseUrl).isValid()) { + qCDebug(MAIN) << "base_url invalid, FAIL_ERROR"; + emit resolveError(tr("base_url invalid")); + return; } else { + QUrl baseUrl(getWellKnownJob->data().homeserver.baseUrl); + qCDebug(MAIN) << ".well-known for" << maybeBaseUrl.host() << "is" << baseUrl.authority(); setHomeserver(baseUrl); } - emit resolved(); - job->deleteLater(); + auto getVersionsJob = callApi(); + + connect(getVersionsJob, &BaseJob::finished, [this, getVersionsJob] { + if (getVersionsJob->status() == BaseJob::Success) { + qCDebug(MAIN) << "homeserver url is valid"; + emit resolved(); + } else { + qCDebug(MAIN) << "homeserver url invalid"; + emit resolveError(tr("homeserver url invalid")); + } + }); }); } -- cgit v1.2.3 From 74caea2669b8f76ca76507bc40321fdcd23dc522 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 12 Jul 2019 18:02:27 +0900 Subject: Minor polish --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 79411dce..1bd2e32e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -200,7 +200,7 @@ void Connection::resolveServer(const QString& mxid) } else { QUrl baseUrl(getWellKnownJob->data().homeserver.baseUrl); - qCDebug(MAIN) << ".well-known for" << maybeBaseUrl.host() << "is" << baseUrl.authority(); + qCDebug(MAIN) << ".well-known for" << maybeBaseUrl.host() << "is" << baseUrl.toString(); setHomeserver(baseUrl); } -- cgit v1.2.3 From 2737dc00334ad3a56c1b311435dbe84453ee389e Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sun, 14 Jul 2019 03:54:19 +0300 Subject: E2EE: introduce EncryptedEvent --- lib/connection.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1bd2e32e..b9ab5147 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -979,6 +979,11 @@ QByteArray Connection::accessToken() const return d->data->accessToken(); } +QtOlm::Account* Connection::olmAccount() const +{ + return d->encryptionManager->account(); +} + SyncJob* Connection::syncJob() const { return d->syncJob; -- cgit v1.2.3 From c05ade838f0fce81f2bbe80a3295618a8a26ff52 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 2 Aug 2019 19:59:40 +0900 Subject: Apply the new brace wrapping to source files --- lib/connection.cpp | 79 ++++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 41 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 6ebe05dc..6cd6ad0b 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -70,8 +70,7 @@ HashT erase_if(HashT& hashMap, Pred pred) return removals; } -class Connection::Private -{ +class Connection::Private { public: explicit Private(std::unique_ptr&& connection) : data(move(connection)) @@ -151,15 +150,12 @@ public: }; Connection::Connection(const QUrl& server, QObject* parent) - : QObject(parent) - , d(new Private(std::make_unique(server))) + : QObject(parent), d(new Private(std::make_unique(server))) { d->q = this; // All d initialization should occur before this line } -Connection::Connection(QObject* parent) - : Connection({}, parent) -{} +Connection::Connection(QObject* parent) : Connection({}, parent) {} Connection::~Connection() { @@ -183,45 +179,47 @@ void Connection::resolveServer(const QString& mxid) qCDebug(MAIN) << "Finding the server" << domain; auto getWellKnownJob = callApi(); - connect(getWellKnownJob, &BaseJob::finished, - [this, getWellKnownJob, maybeBaseUrl] { - if (getWellKnownJob->status() == BaseJob::NotFoundError) { - qCDebug(MAIN) << "No .well-known file, IGNORE"; - } else if (getWellKnownJob->status() != BaseJob::Success) { + connect( + getWellKnownJob, &BaseJob::finished, + [this, getWellKnownJob, maybeBaseUrl] { + if (getWellKnownJob->status() == BaseJob::NotFoundError) + qCDebug(MAIN) << "No .well-known file, IGNORE"; + else { + if (getWellKnownJob->status() != BaseJob::Success) { qCDebug(MAIN) << "Fetching .well-known file failed, FAIL_PROMPT"; emit resolveError(tr("Fetching .well-known file failed")); return; - } else if (getWellKnownJob->data().homeserver.baseUrl.isEmpty()) { + } + QUrl baseUrl(getWellKnownJob->data().homeserver.baseUrl); + if (baseUrl.isEmpty()) { qCDebug(MAIN) << "base_url not provided, FAIL_PROMPT"; emit resolveError(tr("base_url not provided")); return; - } else if (!QUrl(getWellKnownJob->data().homeserver.baseUrl) - .isValid()) { + } + if (!baseUrl.isValid()) { qCDebug(MAIN) << "base_url invalid, FAIL_ERROR"; emit resolveError(tr("base_url invalid")); return; - } else { - QUrl baseUrl(getWellKnownJob->data().homeserver.baseUrl); - - qCDebug(MAIN) << ".well-known for" << maybeBaseUrl.host() - << "is" << baseUrl.toString(); - setHomeserver(baseUrl); } - auto getVersionsJob = callApi(); - - connect(getVersionsJob, &BaseJob::finished, - [this, getVersionsJob] { - if (getVersionsJob->status() == BaseJob::Success) { - qCDebug(MAIN) << "homeserver url is valid"; - emit resolved(); - } else { - qCDebug(MAIN) << "homeserver url invalid"; - emit resolveError(tr("homeserver url invalid")); - } - }); + qCDebug(MAIN) << ".well-known for" << maybeBaseUrl.host() + << "is" << baseUrl.toString(); + setHomeserver(baseUrl); + } + + auto getVersionsJob = callApi(); + + connect(getVersionsJob, &BaseJob::finished, [this, getVersionsJob] { + if (getVersionsJob->status() == BaseJob::Success) { + qCDebug(MAIN) << "homeserver url is valid"; + emit resolved(); + } else { + qCDebug(MAIN) << "homeserver url invalid"; + emit resolveError(tr("homeserver url invalid")); + } }); + }); } void Connection::connectToServer(const QString& user, const QString& password, @@ -372,8 +370,8 @@ void Connection::sync(int timeout) connect(job, &SyncJob::failure, this, [this, job] { d->syncJob = nullptr; if (job->error() == BaseJob::ContentAccessError) { - qCWarning(SYNCJOB) - << "Sync job failed with ContentAccessError - login expired?"; + qCWarning(SYNCJOB) << "Sync job failed with ContentAccessError - " + "login expired?"; emit loginError(job->errorString(), job->rawDataSample()); } else emit syncError(job->errorString(), job->rawDataSample()); @@ -437,7 +435,6 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) visit( *eventPtr, [this](const DirectChatEvent& dce) { - // See // https://github.com/QMatrixClient/libqmatrixclient/wiki/Handling-direct-chat-events const auto& usersToDCs = dce.usersToDirectChats(); DirectChatsMap remoteRemovals = @@ -492,8 +489,8 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) << QStringList::fromSet(ignoredUsers()).join(','); auto& currentData = d->accountData[accountEvent.matrixType()]; - // A polymorphic event-specific comparison might be a bit more - // efficient; maaybe do it another day + // A polymorphic event-specific comparison might be a bit + // more efficient; maaybe do it another day if (!currentData || currentData->contentJson() != accountEvent.contentJson()) { currentData = std::move(eventPtr); @@ -678,9 +675,9 @@ void Connection::requestDirectChat(const QString& userId) if (auto* u = user(userId)) requestDirectChat(u); else - qCCritical(MAIN) - << "Connection::requestDirectChat: Couldn't get a user object for" - << userId; + qCCritical(MAIN) << "Connection::requestDirectChat: Couldn't get a " + "user object for" + << userId; } void Connection::requestDirectChat(User* u) -- cgit v1.2.3 From 7a5b359b8823646ce97cbaf05c251cb04c291466 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 7 Jul 2019 18:16:30 +0900 Subject: Rename zero-impact strings --- lib/connection.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 6cd6ad0b..58d2e01a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -95,8 +95,7 @@ public: DirectChatsMap directChats; DirectChatUsersMap directChatUsers; // The below two variables track local changes between sync completions. - // See also: - // https://github.com/QMatrixClient/libqmatrixclient/wiki/Handling-direct-chat-events + // See https://github.com/quotient-im/libQuotient/wiki/Handling-direct-chat-events DirectChatsMap dcLocalAdditions; DirectChatsMap dcLocalRemovals; std::unordered_map accountData; @@ -435,7 +434,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) visit( *eventPtr, [this](const DirectChatEvent& dce) { - // https://github.com/QMatrixClient/libqmatrixclient/wiki/Handling-direct-chat-events + // https://github.com/quotient-im/libQuotient/wiki/Handling-direct-chat-events const auto& usersToDCs = dce.usersToDirectChats(); DirectChatsMap remoteRemovals = erase_if(d->directChats, [&usersToDCs, this](auto it) { -- cgit v1.2.3 From 27ca32a1e5a56e09b9cc1d94224d2831004dcf3d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 7 Jul 2019 19:32:34 +0900 Subject: Namespace: QMatrixClient -> Quotient (with back comp alias) --- lib/connection.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 58d2e01a..22db8e3d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -53,7 +53,7 @@ #include #include -using namespace QMatrixClient; +using namespace Quotient; // This is very much Qt-specific; STL iterators don't have key() and value() template @@ -1349,9 +1349,9 @@ void Connection::setCacheState(bool newValue) } } -bool QMatrixClient::Connection::lazyLoading() const { return d->lazyLoading; } +bool Connection::lazyLoading() const { return d->lazyLoading; } -void QMatrixClient::Connection::setLazyLoading(bool newValue) +void Connection::setLazyLoading(bool newValue) { if (d->lazyLoading != newValue) { d->lazyLoading = newValue; -- cgit v1.2.3 From 64d096d3f8cdf93216ab138ea0378ae60a19b7d2 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 11 Aug 2019 12:33:52 +0900 Subject: Connection: check for cache_type setting in libQuotient group The fallback to libQMatrixClient still remains. --- lib/connection.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 22db8e3d..56613857 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -111,7 +111,8 @@ public: bool cacheState = true; bool cacheToBinary = - SettingsGroup("libqmatrixclient").value("cache_type").toString() + SettingsGroup("libQuotient").get("cache_type", + SettingsGroup("libQMatrixClient").get("cache_type")) != "json"; bool lazyLoading = false; -- cgit v1.2.3 From 8629748c5c609882486d25d989becfe0ae2352ed Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 20 Aug 2019 17:59:27 +0900 Subject: Store userId in ConnectionData instead of Connection To collect all connection-identifying information in a single place. --- lib/connection.cpp | 87 +++++++++++++++++++++++++----------------------------- 1 file changed, 41 insertions(+), 46 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 56613857..c9623729 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -99,7 +99,6 @@ public: DirectChatsMap dcLocalAdditions; DirectChatsMap dcLocalRemovals; std::unordered_map accountData; - QString userId; int syncLoopTimeout = -1; GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -116,7 +115,7 @@ public: != "json"; bool lazyLoading = false; - void connectWithToken(const QString& user, const QString& accessToken, + void connectWithToken(const QString& userId, const QString& accessToken, const QString& deviceId); void removeRoom(const QString& roomId); @@ -132,7 +131,8 @@ public: void packAndSendAccountData(EventPtr&& event) { const auto eventType = event->matrixType(); - q->callApi(userId, eventType, event->contentJson()); + q->callApi(data->userId(), eventType, + event->contentJson()); accountData[eventType] = std::move(event); emit q->accountDataChanged(eventType); } @@ -159,7 +159,7 @@ Connection::Connection(QObject* parent) : Connection({}, parent) {} Connection::~Connection() { - qCDebug(MAIN) << "deconstructing connection object for" << d->userId; + qCDebug(MAIN) << "deconstructing connection object for" << userId(); stopSync(); } @@ -234,11 +234,11 @@ void Connection::doConnectToServer(const QString& user, const QString& password, const QString& initialDeviceName, const QString& deviceId) { - auto loginJob = callApi( - QStringLiteral("m.login.password"), - UserIdentifier { QStringLiteral("m.id.user"), - { { QStringLiteral("user"), user } } }, - password, /*token*/ "", deviceId, initialDeviceName); + auto loginJob = + callApi(QStringLiteral("m.login.password"), + UserIdentifier { QStringLiteral("m.id.user"), + { { QStringLiteral("user"), user } } }, + password, /*token*/ "", deviceId, initialDeviceName); connect(loginJob, &BaseJob::success, this, [this, loginJob] { d->connectWithToken(loginJob->userId(), loginJob->accessToken(), loginJob->deviceId()); @@ -299,11 +299,11 @@ bool Connection::loadingCapabilities() const return d->capabilities.roomVersions.omitted(); } -void Connection::Private::connectWithToken(const QString& user, +void Connection::Private::connectWithToken(const QString& userId, const QString& accessToken, const QString& deviceId) { - userId = user; + data->setUserId(userId); q->user(); // Creates a User object for the local user data->setToken(accessToken.toLatin1()); data->setDeviceId(deviceId); @@ -354,9 +354,9 @@ void Connection::sync(int timeout) Filter filter; filter.room->timeline->limit = 100; filter.room->state->lazyLoadMembers = d->lazyLoading; - auto job = d->syncJob = callApi(BackgroundRequest, - d->data->lastEvent(), filter, - timeout); + auto job = d->syncJob = + callApi(BackgroundRequest, d->data->lastEvent(), filter, + timeout); connect(job, &SyncJob::success, this, [this, job] { onSyncSuccess(job->takeData()); d->syncJob = nullptr; @@ -485,7 +485,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) [this, &eventPtr](const Event& accountEvent) { if (is(accountEvent)) qCDebug(MAIN) - << "Users ignored by" << d->userId << "updated:" + << "Users ignored by" << userId() << "updated:" << QStringList::fromSet(ignoredUsers()).join(','); auto& currentData = d->accountData[accountEvent.matrixType()]; @@ -504,7 +504,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) qDebug(MAIN) << "Sending updated direct chats to the server:" << d->dcLocalRemovals.size() << "removal(s)," << d->dcLocalAdditions.size() << "addition(s)"; - callApi(d->userId, QStringLiteral("m.direct"), + callApi(userId(), QStringLiteral("m.direct"), toJson(d->directChats)); d->dcLocalAdditions.clear(); d->dcLocalRemovals.clear(); @@ -634,8 +634,8 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, { auto mediaId = url.authority() + url.path(); auto idParts = splitMediaId(mediaId); - auto* job = callApi(idParts.front(), idParts.back(), - localFilename); + auto* job = + callApi(idParts.front(), idParts.back(), localFilename); return job; } @@ -648,7 +648,7 @@ Connection::createRoom(RoomVisibility visibility, const QString& alias, const QVector& invite3pids, const QJsonObject& creationContent) { - invites.removeOne(d->userId); // The creator is by definition in the room + invites.removeOne(userId()); // The creator is by definition in the room auto job = callApi(visibility == PublishRoom ? QStringLiteral("public") : QStringLiteral("private"), @@ -672,12 +672,7 @@ Connection::createRoom(RoomVisibility visibility, const QString& alias, void Connection::requestDirectChat(const QString& userId) { - if (auto* u = user(userId)) - requestDirectChat(u); - else - qCCritical(MAIN) << "Connection::requestDirectChat: Couldn't get a " - "user object for" - << userId; + doInDirectChat(userId, [this](Room* r) { emit directChatAvailable(r); }); } void Connection::requestDirectChat(User* u) @@ -700,7 +695,7 @@ void Connection::doInDirectChat(User* u, const std::function& operation) { Q_ASSERT(u); - const auto& userId = u->id(); + const auto& otherUserId = u->id(); // There can be more than one DC; find the first valid (existing and // not left), and delete inexistent (forgotten?) ones along the way. DirectChatsMap removals; @@ -710,9 +705,9 @@ void Connection::doInDirectChat(User* u, if (auto r = room(roomId, JoinState::Join)) { Q_ASSERT(r->id() == roomId); // A direct chat with yourself should only involve yourself :) - if (userId == d->userId && r->totalMemberCount() > 1) + if (otherUserId == userId() && r->totalMemberCount() > 1) continue; - qCDebug(MAIN) << "Requested direct chat with" << userId + qCDebug(MAIN) << "Requested direct chat with" << otherUserId << "is already available as" << r->id(); operation(r); return; @@ -721,10 +716,10 @@ void Connection::doInDirectChat(User* u, Q_ASSERT(ir->id() == roomId); auto j = joinRoom(ir->id()); connect(j, &BaseJob::success, this, - [this, roomId, userId, operation] { + [this, roomId, otherUserId, operation] { qCDebug(MAIN) << "Joined the already invited direct chat with" - << userId << "as" << roomId; + << otherUserId << "as" << roomId; operation(room(roomId, JoinState::Join)); }); return; @@ -734,7 +729,7 @@ void Connection::doInDirectChat(User* u, if (room(roomId, JoinState::Leave)) continue; - qCWarning(MAIN) << "Direct chat with" << userId << "known as room" + qCWarning(MAIN) << "Direct chat with" << otherUserId << "known as room" << roomId << "is not valid and will be discarded"; // Postpone actual deletion until we finish iterating d->directChats. removals.insert(it.key(), it.value()); @@ -750,9 +745,9 @@ void Connection::doInDirectChat(User* u, emit directChatsListChanged({}, removals); } - auto j = createDirectChat(userId); - connect(j, &BaseJob::success, this, [this, j, userId, operation] { - qCDebug(MAIN) << "Direct chat with" << userId << "has been created as" + auto j = createDirectChat(otherUserId); + connect(j, &BaseJob::success, this, [this, j, otherUserId, operation] { + qCDebug(MAIN) << "Direct chat with" << otherUserId << "has been created as" << j->roomId(); operation(room(j->roomId(), JoinState::Join)); }); @@ -845,7 +840,7 @@ SendMessageJob* Connection::sendMessage(const QString& roomId, QUrl Connection::homeserver() const { return d->data->baseUrl(); } -QString Connection::domain() const { return d->userId.section(':', 1); } +QString Connection::domain() const { return userId().section(':', 1); } Room* Connection::room(const QString& roomId, JoinStates states) const { @@ -905,30 +900,30 @@ Room* Connection::invitation(const QString& roomId) const return d->roomMap.value({ roomId, true }, nullptr); } -User* Connection::user(const QString& userId) +User* Connection::user(const QString& uId) { - if (userId.isEmpty()) + if (uId.isEmpty()) return nullptr; - if (!userId.startsWith('@') || !userId.contains(':')) { - qCCritical(MAIN) << "Malformed userId:" << userId; + if (!uId.startsWith('@') || !uId.contains(':')) { + qCCritical(MAIN) << "Malformed userId:" << uId; return nullptr; } - if (d->userMap.contains(userId)) - return d->userMap.value(userId); - auto* user = userFactory()(this, userId); - d->userMap.insert(userId, user); + if (d->userMap.contains(uId)) + return d->userMap.value(uId); + auto* user = userFactory()(this, uId); + d->userMap.insert(uId, user); emit newUser(user); return user; } const User* Connection::user() const { - return d->userMap.value(d->userId, nullptr); + return d->userMap.value(userId(), nullptr); } -User* Connection::user() { return user(d->userId); } +User* Connection::user() { return user(userId()); } -QString Connection::userId() const { return d->userId; } +QString Connection::userId() const { return d->data->userId(); } QString Connection::deviceId() const { return d->data->deviceId(); } -- cgit v1.2.3 From 92614294a89c1f450b82c2b6e35614cf124dc344 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 20 Aug 2019 18:10:46 +0900 Subject: Connection::run() Finally, clients can pre-create job objects and then separately submit them for execution on a given connection - previously such separation was a privilege of Connection (others had to use Connection::callApi<>, which invoked jobs right away). --- lib/connection.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index c9623729..5f90ed55 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -780,7 +780,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) [this, leaveJob, forgetJob, room] { if (leaveJob->error() == BaseJob::Success || leaveJob->error() == BaseJob::NotFoundError) { - forgetJob->start(connectionData()); + run(forgetJob); // If the matching /sync response hasn't arrived yet, // mark the room for explicit deletion if (room->joinState() != JoinState::Leave) @@ -794,7 +794,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) }); connect(leaveJob, &BaseJob::failure, forgetJob, &BaseJob::abandon); } else - forgetJob->start(connectionData()); + run(forgetJob); connect(forgetJob, &BaseJob::result, this, [this, id, forgetJob] { // Leave room in case of success, or room not known by server if (forgetJob->error() == BaseJob::Success @@ -1355,6 +1355,13 @@ void Connection::setLazyLoading(bool newValue) } } +void Connection::run(BaseJob* job, RunningPolicy runningPolicy) const +{ + connect(job, &BaseJob::failure, this, &Connection::requestFailed); + job->start(d->data.get(), runningPolicy & BackgroundRequest); + d->data->submit(job); +} + void Connection::getTurnServers() { auto job = callApi(); -- cgit v1.2.3 From 59c4996a602e9eeae4e3bfc0210ff15f957df38f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 20 Aug 2019 20:09:09 +0900 Subject: BaseJob/ConnectionData: connection-wide rate-limiting As before, completely transparent for clients, driven by 529 errors from the server (but cases of rate limiting are signalled by BaseJob::rateLimited). That brings changes to BaseJob API: timeouts now use int64_t and also can be handled in std::chrono terms; aboutToStart() -> aboutToSendRequest(); started() -> sentRequest(). Closes #292. --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 5f90ed55..5ebdcf6c 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1358,7 +1358,7 @@ void Connection::setLazyLoading(bool newValue) void Connection::run(BaseJob* job, RunningPolicy runningPolicy) const { connect(job, &BaseJob::failure, this, &Connection::requestFailed); - job->start(d->data.get(), runningPolicy & BackgroundRequest); + job->prepare(d->data.get(), runningPolicy & BackgroundRequest); d->data->submit(job); } -- cgit v1.2.3 From 944653463fe4134c82d85e2d01e2bc0fa43fd727 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 12 Sep 2019 11:12:37 +0900 Subject: Introduce HashQ<> and UnorderedMap<> Invading into std:: is frowned upon, even though legitimate from the C++ standard perspective. Given that it's possible to pass a hash object to unordered_map, it only takes an alias for std::unordered_map to avoid having to specialize std::hash. And besides, a generic compatibility bridge between qHash and std::hash has been long needed. std::hash in converters.h remains for now; it will be dropped separately when the API files get regenerated to use UnorderedMap. --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 5ebdcf6c..c3e46356 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -98,7 +98,7 @@ public: // See https://github.com/quotient-im/libQuotient/wiki/Handling-direct-chat-events DirectChatsMap dcLocalAdditions; DirectChatsMap dcLocalRemovals; - std::unordered_map accountData; + UnorderedMap accountData; int syncLoopTimeout = -1; GetCapabilitiesJob* capabilitiesJob = nullptr; -- cgit v1.2.3 From 508f4362b7b5e1863153cca6fe6756adedfa33ee Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 29 Sep 2019 18:20:54 +0900 Subject: Connection: allRooms(), rooms(), roomsCount(); deprecate roomMap() Closes #354. --- lib/connection.cpp | 49 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 10 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index c3e46356..2c5bf574 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -955,6 +955,33 @@ QHash, Room*> Connection::roomMap() const return roomMap; } +QVector Connection::allRooms() const +{ + QVector result; + result.resize(d->roomMap.size()); + std::copy(d->roomMap.cbegin(), d->roomMap.cend(), result.begin()); + return result; +} + +QVector Connection::rooms(JoinStates joinStates) const +{ + QVector result; + for (auto* r: qAsConst(d->roomMap)) + if (joinStates.testFlag(r->joinState())) + result.push_back(r); + return result; +} + +int Connection::roomsCount(JoinStates joinStates) const +{ + // Using int to maintain compatibility with QML + // (consider also that QHash<>::size() returns int anyway). + return int(std::count_if(d->roomMap.begin(), d->roomMap.end(), + [joinStates](Room* r) { + return joinStates.testFlag(r->joinState()); + })); +} + bool Connection::hasAccountData(const QString& type) const { return d->accountData.find(type) != d->accountData.cend(); @@ -1262,18 +1289,20 @@ void Connection::saveState() const { QStringLiteral("minor"), SyncData::cacheVersion().second } } } }; { - QJsonObject rooms; - QJsonObject inviteRooms; - const auto& rs = roomMap(); // Pass on rooms in Leave state - for (const auto* i : rs) - (i->joinState() == JoinState::Invite ? inviteRooms : rooms) - .insert(i->id(), QJsonValue::Null); + QJsonObject roomsJson; + QJsonObject inviteRoomsJson; + for (const auto* r: qAsConst(d->roomMap)) { + if (r->joinState() == JoinState::Leave) + continue; + (r->joinState() == JoinState::Invite ? inviteRoomsJson : roomsJson) + .insert(r->id(), QJsonValue::Null); + } QJsonObject roomObj; - if (!rooms.isEmpty()) - roomObj.insert(QStringLiteral("join"), rooms); - if (!inviteRooms.isEmpty()) - roomObj.insert(QStringLiteral("invite"), inviteRooms); + if (!roomsJson.isEmpty()) + roomObj.insert(QStringLiteral("join"), roomsJson); + if (!inviteRoomsJson.isEmpty()) + roomObj.insert(QStringLiteral("invite"), inviteRoomsJson); rootObj.insert(QStringLiteral("next_batch"), d->data->lastEvent()); rootObj.insert(QStringLiteral("rooms"), roomObj); -- cgit v1.2.3 From 0a82e68f86a3b4716ebada39c041a6c8673d2999 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 17 Oct 2019 09:59:37 +0900 Subject: Connection::joinRoom: make sure the room object is created early enough All direct slots connected to finished() will run before success() is even emitted; so create the room object in the earliest slot connected to finished(), rather than to success(). --- lib/connection.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 2c5bf574..25f1c3f6 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -534,10 +534,14 @@ JoinRoomJob* Connection::joinRoom(const QString& roomAlias, const QStringList& serverNames) { auto job = callApi(roomAlias, serverNames); - // Upon completion, ensure a room object in Join state is created but only - // if it's not already there due to a sync completing earlier. - connect(job, &JoinRoomJob::success, this, - [this, job] { provideRoom(job->roomId()); }); + // Upon completion, ensure a room object in Join state is created + // (or it might already be there due to a sync completing earlier). + // finished() is used here instead of success() to overtake clients + // that may add their own slots to finished(). + connect(job, &BaseJob::finished, this, [this, job] { + if (job->status().good()) + provideRoom(job->roomId()); + }); return job; } -- cgit v1.2.3 From 8b9207d5a04386957d8eab8dd251421eaaa7c0d2 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 20 Oct 2019 19:13:56 +0900 Subject: Qualify types in signals and Q_INVOKABLEs Because https://doc.qt.io/qt-5/moc.html#limitations . For direct connections that doesn't matter but it very much does for queued ones. Along with this DirectChatsMap and IgnoredUsersList have been moved from Connection:: to Quotient::. --- lib/connection.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 25f1c3f6..af85d066 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -385,7 +385,7 @@ void Connection::syncLoop(int timeout) syncLoopIteration(); // initial sync to start the loop } -QJsonObject toJson(const Connection::DirectChatsMap& directChats) +QJsonObject toJson(const DirectChatsMap& directChats) { QJsonObject json; for (auto it = directChats.begin(); it != directChats.end();) { @@ -1050,7 +1050,7 @@ QVector Connection::roomsWithTag(const QString& tagName) const return rooms; } -Connection::DirectChatsMap Connection::directChats() const +DirectChatsMap Connection::directChats() const { return d->directChats; } @@ -1117,7 +1117,7 @@ bool Connection::isIgnored(const User* user) const return ignoredUsers().contains(user->id()); } -Connection::IgnoredUsersList Connection::ignoredUsers() const +IgnoredUsersList Connection::ignoredUsers() const { const auto* event = d->unpackAccountData(); return event ? event->ignored_users() : IgnoredUsersList(); -- cgit v1.2.3 From 693beec0005bdd98732ad8b4ad760f9de4a9faee Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 15 Oct 2019 19:33:21 +0900 Subject: Connection: make syncLoop() reentrant ...in the sense that you can call it twice and expect the second invocation to be gracefully ignored rather than two loops conflicting with each other. --- lib/connection.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index af85d066..a7b1bee9 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -99,6 +99,7 @@ public: DirectChatsMap dcLocalAdditions; DirectChatsMap dcLocalRemovals; UnorderedMap accountData; + QMetaObject::Connection syncLoopConnection {}; int syncLoopTimeout = -1; GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -380,9 +381,20 @@ void Connection::sync(int timeout) void Connection::syncLoop(int timeout) { - d->syncLoopTimeout = timeout; - connect(this, &Connection::syncDone, this, &Connection::syncLoopIteration); - syncLoopIteration(); // initial sync to start the loop + if (d->syncLoopConnection && d->syncLoopTimeout == timeout) { + qCInfo(MAIN) << "Attempt to run sync loop but there's one already " + "running; nothing will be done"; + return; + } + std::swap(d->syncLoopTimeout, timeout); + if (d->syncLoopConnection) { + qCWarning(MAIN) << "Overriding timeout of the running sync loop from" + << timeout << "to" << d->syncLoopTimeout; + } else { + d->syncLoopConnection = connect(this, &Connection::syncDone, + this, &Connection::syncLoopIteration); + syncLoopIteration(); // initial sync to start the loop + } } QJsonObject toJson(const DirectChatsMap& directChats) @@ -514,8 +526,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) void Connection::stopSync() { // If there's a sync loop, break it - disconnect(this, &Connection::syncDone, this, - &Connection::syncLoopIteration); + disconnect(d->syncLoopConnection); if (d->syncJob) // If there's an ongoing sync job, stop it too { d->syncJob->abandon(); -- cgit v1.2.3 From ff77965cb92e94061345e4e955f1d7289de2597a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 21 Oct 2019 15:58:12 +0900 Subject: Connection: record sync timeout; suspend sync before logout This is mostly internal but clients may see fewer spurious sync failures upon logging out. --- lib/connection.cpp | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index a7b1bee9..3d617733 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -100,7 +100,7 @@ public: DirectChatsMap dcLocalRemovals; UnorderedMap accountData; QMetaObject::Connection syncLoopConnection {}; - int syncLoopTimeout = -1; + int syncTimeout = -1; GetCapabilitiesJob* capabilitiesJob = nullptr; GetCapabilitiesJob::Capabilities capabilities; @@ -260,8 +260,6 @@ void Connection::doConnectToServer(const QString& user, const QString& password, }); } -void Connection::syncLoopIteration() { sync(d->syncLoopTimeout); } - void Connection::connectWithToken(const QString& userId, const QString& accessToken, const QString& deviceId) @@ -336,22 +334,34 @@ void Connection::checkAndConnect(const QString& userId, void Connection::logout() { - auto job = callApi(); - connect(job, &LogoutJob::finished, this, [job, this] { + // If there's an ongoing sync job, stop it but don't break the sync loop yet + const auto syncWasRunning = bool(d->syncJob); + if (syncWasRunning) + { + d->syncJob->abandon(); + d->syncJob = nullptr; + } + const auto* job = callApi(); + connect(job, &LogoutJob::finished, this, [this, job, syncWasRunning] { if (job->status().good() || job->error() == BaseJob::ContentAccessError) { - stopSync(); + if (d->syncLoopConnection) + disconnect(d->syncLoopConnection); d->data->setToken({}); emit stateChanged(); emit loggedOut(); - } + } else if (syncWasRunning) + syncLoopIteration(); // Resume sync loop (or a single sync) }); } void Connection::sync(int timeout) { - if (d->syncJob) + if (d->syncJob) { + qCInfo(MAIN) << d->syncJob << "is already running"; return; + } + d->syncTimeout = timeout; Filter filter; filter.room->timeline->limit = 100; filter.room->state->lazyLoadMembers = d->lazyLoading; @@ -381,22 +391,25 @@ void Connection::sync(int timeout) void Connection::syncLoop(int timeout) { - if (d->syncLoopConnection && d->syncLoopTimeout == timeout) { + if (d->syncLoopConnection && d->syncTimeout == timeout) { qCInfo(MAIN) << "Attempt to run sync loop but there's one already " "running; nothing will be done"; return; } - std::swap(d->syncLoopTimeout, timeout); + std::swap(d->syncTimeout, timeout); if (d->syncLoopConnection) { - qCWarning(MAIN) << "Overriding timeout of the running sync loop from" - << timeout << "to" << d->syncLoopTimeout; + qCInfo(MAIN) << "Timeout for next syncs changed from" + << timeout << "to" << d->syncTimeout; } else { d->syncLoopConnection = connect(this, &Connection::syncDone, - this, &Connection::syncLoopIteration); + this, &Connection::syncLoopIteration, + Qt::QueuedConnection); syncLoopIteration(); // initial sync to start the loop } } +void Connection::syncLoopIteration() { sync(d->syncTimeout); } + QJsonObject toJson(const DirectChatsMap& directChats) { QJsonObject json; -- cgit v1.2.3 From 60bb1cf942ad0815dcf42cbfe8acd1e076d848cf Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 29 Oct 2019 22:04:40 +0900 Subject: Derive Omittable<> from std::optional<> That breaks API all over the place but: 1. The fixes are trivial. 2. More of std:: is used instead of home-baking the same stuff. --- lib/connection.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 3d617733..47c643b0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -277,16 +277,16 @@ void Connection::reloadCapabilities() else if (d->capabilitiesJob->error() == BaseJob::IncorrectRequestError) qCDebug(MAIN) << "Server doesn't support /capabilities"; - if (d->capabilities.roomVersions.omitted()) { + if (!d->capabilities.roomVersions) { qCWarning(MAIN) << "Pinning supported room version to 1"; - d->capabilities.roomVersions = { "1", { { "1", "stable" } } }; + d->capabilities.roomVersions.emplace({ "1", { { "1", "stable" } } }); } else { qCDebug(MAIN) << "Room versions:" << defaultRoomVersion() << "is default, full list:" << availableRoomVersions(); } - Q_ASSERT(!d->capabilities.roomVersions.omitted()); + Q_ASSERT(d->capabilities.roomVersions.has_value()); emit capabilitiesLoaded(); - for (auto* r : d->roomMap) + for (auto* r : qAsConst(d->roomMap)) r->checkVersion(); }); } @@ -295,7 +295,7 @@ bool Connection::loadingCapabilities() const { // (Ab)use the fact that room versions cannot be omitted after // the capabilities have been loaded (see reloadCapabilities() above). - return d->capabilities.roomVersions.omitted(); + return !d->capabilities.roomVersions; } void Connection::Private::connectWithToken(const QString& userId, @@ -363,8 +363,8 @@ void Connection::sync(int timeout) d->syncTimeout = timeout; Filter filter; - filter.room->timeline->limit = 100; - filter.room->state->lazyLoadMembers = d->lazyLoading; + filter.room.edit().timeline.edit().limit.emplace(100); + filter.room.edit().state.edit().lazyLoadMembers.emplace(d->lazyLoading); auto job = d->syncJob = callApi(BackgroundRequest, d->data->lastEvent(), filter, timeout); @@ -446,7 +446,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) r->updateData(std::move(roomData), fromCache); if (d->firstTimeRooms.removeOne(r)) { emit loadedRoomState(r); - if (!d->capabilities.roomVersions.omitted()) + if (d->capabilities.roomVersions) r->checkVersion(); // Otherwise, the version will be checked in reloadCapabilities() } @@ -1191,7 +1191,7 @@ Room* Connection::provideRoom(const QString& id, Omittable joinState) // and emit a signal. For Invite and Join, there's no such problem. if (room->joinState() == joinState && joinState != JoinState::Leave) return room; - } else if (joinState.omitted()) { + } else if (!joinState) { // No Join and Leave, maybe Invite? room = d->roomMap.value({ id, true }, nullptr); if (room) @@ -1200,9 +1200,7 @@ Room* Connection::provideRoom(const QString& id, Omittable joinState) } if (!room) { - room = roomFactory()(this, id, - joinState.omitted() ? JoinState::Join - : joinState.value()); + room = roomFactory()(this, id, joinState.value_or(JoinState::Join)); if (!room) { qCCritical(MAIN) << "Failed to create a room" << id; return nullptr; @@ -1213,7 +1211,7 @@ Room* Connection::provideRoom(const QString& id, Omittable joinState) &Connection::aboutToDeleteRoom); emit newRoom(room); } - if (joinState.omitted()) + if (!joinState) return room; if (joinState == JoinState::Invite) { @@ -1431,13 +1429,13 @@ const QString Connection::SupportedRoomVersion::StableTag = QString Connection::defaultRoomVersion() const { - Q_ASSERT(!d->capabilities.roomVersions.omitted()); + Q_ASSERT(d->capabilities.roomVersions.has_value()); return d->capabilities.roomVersions->defaultVersion; } QStringList Connection::stableRoomVersions() const { - Q_ASSERT(!d->capabilities.roomVersions.omitted()); + Q_ASSERT(d->capabilities.roomVersions.has_value()); QStringList l; const auto& allVersions = d->capabilities.roomVersions->available; for (auto it = allVersions.begin(); it != allVersions.end(); ++it) @@ -1457,7 +1455,7 @@ inline bool roomVersionLess(const Connection::SupportedRoomVersion& v1, QVector Connection::availableRoomVersions() const { - Q_ASSERT(!d->capabilities.roomVersions.omitted()); + Q_ASSERT(d->capabilities.roomVersions.has_value()); QVector result; result.reserve(d->capabilities.roomVersions->available.size()); for (auto it = d->capabilities.roomVersions->available.begin(); -- cgit v1.2.3 From edbbc2bc77599ead0e14bc08cdddda10d1c5f305 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 31 Oct 2019 12:48:49 +0900 Subject: Omittable: get rid of value() Xcode 10 doesn't have it, and value() is not quite fitting mostly-exceptionless Quotient anyway. --- lib/connection.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 47c643b0..998b45d1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1182,7 +1182,7 @@ Room* Connection::provideRoom(const QString& id, Omittable joinState) // TODO: This whole function is a strong case for a RoomManager class. Q_ASSERT_X(!id.isEmpty(), __FUNCTION__, "Empty room id"); - // If joinState.omitted(), all joinState == comparisons below are false. + // If joinState is empty, all joinState == comparisons below are false. const auto roomKey = qMakePair(id, joinState == JoinState::Invite); auto* room = d->roomMap.value(roomKey, nullptr); if (room) { @@ -1214,17 +1214,17 @@ Room* Connection::provideRoom(const QString& id, Omittable joinState) if (!joinState) return room; - if (joinState == JoinState::Invite) { + if (*joinState == JoinState::Invite) { // prev is either Leave or nullptr auto* prev = d->roomMap.value({ id, false }, nullptr); emit invitedRoom(room, prev); } else { - room->setJoinState(joinState.value()); + room->setJoinState(*joinState); // Preempt the Invite room (if any) with a room in Join/Leave state. auto* prevInvite = d->roomMap.take({ id, true }); - if (joinState == JoinState::Join) + if (*joinState == JoinState::Join) emit joinedRoom(room, prevInvite); - else if (joinState == JoinState::Leave) + else if (*joinState == JoinState::Leave) emit leftRoom(room, prevInvite); if (prevInvite) { const auto dcUsers = prevInvite->directChatUsers(); -- cgit v1.2.3 From ae5c2aa9acce500e2ad94c6781843c02310dbab3 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 1 Nov 2019 20:47:07 +0900 Subject: Omittable: Add direct-list-initialising operator=; document See the change in connection.cpp for the example of usage. Also: removed static_asserts: the first one is provided by std::optional, and the second one is only relevant to edit(). --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 998b45d1..5ee7e84e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -279,7 +279,7 @@ void Connection::reloadCapabilities() if (!d->capabilities.roomVersions) { qCWarning(MAIN) << "Pinning supported room version to 1"; - d->capabilities.roomVersions.emplace({ "1", { { "1", "stable" } } }); + d->capabilities.roomVersions = { "1", { { "1", "stable" } } }; } else { qCDebug(MAIN) << "Room versions:" << defaultRoomVersion() << "is default, full list:" << availableRoomVersions(); -- cgit v1.2.3 From 5937127b73a82fc86f6546397373ce9dbaf4e560 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 19 Nov 2019 17:57:41 +0900 Subject: BaseJob: Don't send accessToken if not needed; send again on 401 The first part closes #358; the second part is a workaround for non-standard cases when endpoints without security by the spec turn out to be secured (in particular, the case of authenticating media servers). --- lib/connection.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 5ee7e84e..c830557c 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -343,7 +343,8 @@ void Connection::logout() } const auto* job = callApi(); connect(job, &LogoutJob::finished, this, [this, job, syncWasRunning] { - if (job->status().good() || job->error() == BaseJob::ContentAccessError) { + if (job->status().good() || job->error() == BaseJob::Unauthorised + || job->error() == BaseJob::ContentAccessError) { if (d->syncLoopConnection) disconnect(d->syncLoopConnection); d->data->setToken({}); @@ -380,9 +381,9 @@ void Connection::sync(int timeout) }); connect(job, &SyncJob::failure, this, [this, job] { d->syncJob = nullptr; - if (job->error() == BaseJob::ContentAccessError) { - qCWarning(SYNCJOB) << "Sync job failed with ContentAccessError - " - "login expired?"; + if (job->error() == BaseJob::Unauthorised) { + qCWarning(SYNCJOB) + << "Sync job failed with Unauthorised - login expired?"; emit loginError(job->errorString(), job->rawDataSample()); } else emit syncError(job->errorString(), job->rawDataSample()); -- cgit v1.2.3 From d438c08e11d17dc9c005806a6036e1aeb769c54b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 12 Dec 2019 19:39:24 +0300 Subject: Connection::uploadFile/Content(): refactoring around QIODevice::open() No more "The file is already open" log messages. --- lib/connection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index c830557c..999f4382 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -624,12 +624,17 @@ UploadContentJob* Connection::uploadContent(QIODevice* contentSource, const QString& filename, const QString& overrideContentType) const { + Q_ASSERT(contentSource != nullptr); auto contentType = overrideContentType; if (contentType.isEmpty()) { contentType = QMimeDatabase() .mimeTypeForFileNameAndData(filename, contentSource) .name(); - contentSource->open(QIODevice::ReadOnly); + if (!contentSource->open(QIODevice::ReadOnly)) { + qCWarning(MAIN) << "Couldn't open content source" << filename + << "for reading:" << contentSource->errorString(); + return nullptr; + } } return callApi(contentSource, filename, contentType); } @@ -638,11 +643,6 @@ UploadContentJob* Connection::uploadFile(const QString& fileName, const QString& overrideContentType) { auto sourceFile = new QFile(fileName); - if (!sourceFile->open(QIODevice::ReadOnly)) { - qCWarning(MAIN) << "Couldn't open" << sourceFile->fileName() - << "for reading"; - return nullptr; - } return uploadContent(sourceFile, QFileInfo(*sourceFile).fileName(), overrideContentType); } -- cgit v1.2.3 From f6505a541fc0a2e02f9c79496488121a3e46fb01 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 12 Dec 2019 19:47:07 +0300 Subject: BaseJob: prepare() -> initiate() + refactoring around it * BaseJob::initiate() now calls ConnectionData::submit() without relying on Connection to do that * ConnectionData::submit() is now the only site where a job enters Pending state * No more shortcuts to BaseJob::sendRequest(), even retries are sent through the ConnectionData submission queue * Additional validation in BaseJob::initiate() that the request data device is actually open (because QtNetwork API officially requires that, even if you can get away passing a closed QBuffer to it) --- lib/connection.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 999f4382..c13568f1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1414,8 +1414,7 @@ void Connection::setLazyLoading(bool newValue) void Connection::run(BaseJob* job, RunningPolicy runningPolicy) const { connect(job, &BaseJob::failure, this, &Connection::requestFailed); - job->prepare(d->data.get(), runningPolicy & BackgroundRequest); - d->data->submit(job); + job->initiate(d->data.get(), runningPolicy & BackgroundRequest); } void Connection::getTurnServers() -- cgit v1.2.3 From 92264831077874511341b2dabae255649f741f54 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 23 Dec 2019 21:48:16 +0300 Subject: Connection::forgetRoom: slightly simplify code --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index c13568f1..5bddbb83 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -804,7 +804,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) if (!room) room = d->roomMap.value({ id, true }); if (room && room->joinState() != JoinState::Leave) { - auto leaveJob = room->leaveRoom(); + auto leaveJob = leaveRoom(room); connect(leaveJob, &BaseJob::result, this, [this, leaveJob, forgetJob, room] { if (leaveJob->error() == BaseJob::Success -- cgit v1.2.3 From 2de9cb11b4157eebacc3906df79a8d6d4fb43651 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Wed, 14 Aug 2019 12:43:22 +0300 Subject: E2EE: Fix EncryptionManager initialization place Signed-off-by: Alexey Andreev --- lib/connection.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 5bddbb83..75c459d5 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -243,15 +243,6 @@ void Connection::doConnectToServer(const QString& user, const QString& password, connect(loginJob, &BaseJob::success, this, [this, loginJob] { d->connectWithToken(loginJob->userId(), loginJob->accessToken(), loginJob->deviceId()); - - AccountSettings accountSettings(loginJob->userId()); - d->encryptionManager.reset( - new EncryptionManager(accountSettings.encryptionAccountPickle())); - if (accountSettings.encryptionAccountPickle().isEmpty()) { - accountSettings.setEncryptionAccountPickle( - d->encryptionManager->olmAccountPickle()); - } - d->encryptionManager->uploadIdentityKeys(this); d->encryptionManager->uploadOneTimeKeys(this); }); @@ -309,6 +300,13 @@ void Connection::Private::connectWithToken(const QString& userId, q->setObjectName(userId % '/' % deviceId); qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << userId << "from device" << deviceId; + AccountSettings accountSettings(userId); + encryptionManager.reset( + new EncryptionManager(accountSettings.encryptionAccountPickle())); + if (accountSettings.encryptionAccountPickle().isEmpty()) { + accountSettings.setEncryptionAccountPickle( + encryptionManager->olmAccountPickle()); + } emit q->stateChanged(); emit q->connected(); q->reloadCapabilities(); -- cgit v1.2.3 From 715a0c0aa094eedbe24516e19a7b37dde71ba994 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Fri, 23 Aug 2019 17:14:09 +0300 Subject: E2EE: add connection session decrypt, handle to-device and device_one_time_keys_count Signed-off-by: Alexey Andreev --- lib/connection.cpp | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 75c459d5..98c8a4bc 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -43,6 +43,8 @@ #include "jobs/mediathumbnailjob.h" #include "jobs/syncjob.h" +#include "account.h" // QtOlm + #include #include #include @@ -148,6 +150,65 @@ public: { return q->stateCacheDir().filePath("state.json"); } + + RoomEventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) + { + if (encryptedEvent.algorithm() != OlmV1Curve25519AesSha2AlgoKey) + { + return {}; + } + QString identityKey = + encryptionManager->account()->curve25519IdentityKey(); + QJsonObject personalCipherObject = + encryptedEvent.ciphertext(identityKey); + if (personalCipherObject.isEmpty()) { + qCDebug(E2EE) << "Encrypted event is not for the current device"; + return {}; + } + QString decrypted = encryptionManager->sessionDecryptMessage( + personalCipherObject, encryptedEvent.senderKey().toLatin1()); + if (decrypted.isEmpty()) { + qCDebug(E2EE) << "Problem with new session from senderKey:" + << encryptedEvent.senderKey() + << encryptionManager->account()->oneTimeKeys(); + return {}; + } + + RoomEventPtr decryptedEvent = makeEvent( + QJsonDocument::fromJson(decrypted.toUtf8()).object()); + + if (decryptedEvent->senderId() != encryptedEvent.senderId()) { + qCDebug(E2EE) << "Found user" << decryptedEvent->senderId() + << "instead of sender" << encryptedEvent.senderId() + << "in Olm plaintext"; + return {}; + } + + // TODO: keys to constants + QJsonObject decryptedEventObject = decryptedEvent->fullJson(); + QString recipient = + decryptedEventObject.value("recipient"_ls).toString(); + if (recipient != data->userId()) { + qCDebug(E2EE) << "Found user" << recipient << "instead of us" + << data->userId() << "in Olm plaintext"; + return {}; + } + QString ourKey = decryptedEventObject.value("recipient_keys"_ls) + .toObject() + .value(Ed25519Key) + .toString(); + if (ourKey + != QString::fromUtf8( + encryptionManager->account()->ed25519IdentityKey())) { + qCDebug(E2EE) << "Found key" << ourKey + << "instead of ours own ed25519 key" + << encryptionManager->account()->ed25519IdentityKey() + << "in Olm plaintext"; + return {}; + } + + return decryptedEvent; + } }; Connection::Connection(const QUrl& server, QObject* parent) @@ -533,6 +594,57 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->dcLocalAdditions.clear(); d->dcLocalRemovals.clear(); } + // handling m.room_key to-device encrypted event + for (auto&& toDeviceEvent : data.takeToDeviceEvents()) { + if (toDeviceEvent->type() == EncryptedEvent::typeId()) { + event_ptr_tt encryptedEvent = + makeEvent(toDeviceEvent->fullJson()); + if (encryptedEvent->algorithm() != OlmV1Curve25519AesSha2AlgoKey) { + qCDebug(E2EE) + << "Encrypted event" << encryptedEvent->id() << "algorithm" + << encryptedEvent->algorithm() << "is not supported"; + return; + } + + // TODO: full maintaining of the device keys + // with device_lists sync extention and /keys/query + qCDebug(E2EE) << "Getting device keys for the m.room_key sender:" + << encryptedEvent->senderId(); + // d->encryptionManager->updateDeviceKeys(); + + RoomEventPtr decryptedEvent = + d->sessionDecryptMessage(*encryptedEvent.get()); + // since we are waiting for the RoomKeyEvent: + event_ptr_tt roomKeyEvent = + makeEvent(decryptedEvent->fullJson()); + if (!roomKeyEvent) { + qCDebug(E2EE) << "Failed to decrypt olm event from user" + << encryptedEvent->senderId(); + return; + } + Room* detectedRoom = room(roomKeyEvent->roomId()); + if (!detectedRoom) { + qCDebug(E2EE) + << "Encrypted event room id" << encryptedEvent->roomId() + << "is not found at the connection"; + return; + } + detectedRoom->handleRoomKeyEvent(roomKeyEvent.get(), + encryptedEvent->senderKey()); + } + } + // handling device_one_time_keys_count + auto deviceOneTimeKeysCount = data.deviceOneTimeKeysCount(); + if (!d->encryptionManager) + { + qCDebug(E2EE) << "Encryption manager is not there yet"; + return; + } + if (!deviceOneTimeKeysCount.isEmpty()) + { + d->encryptionManager->updateOneTimeKeyCounts(this, + deviceOneTimeKeysCount); + } } void Connection::stopSync() -- cgit v1.2.3 From 42bca67768d6f174ecf125563ab28ef79b994e23 Mon Sep 17 00:00:00 2001 From: Ram Nad Date: Sun, 8 Mar 2020 02:43:23 +0530 Subject: fixing msc2432 --- lib/connection.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 5bddbb83..f3d31d2d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -87,7 +87,7 @@ public: QHash, Room*> roomMap; /// Mapping from serverparts to alias/room id mappings, /// as of the last sync - QHash> roomAliasMap; + QHash roomAliasMap; QVector roomIdsToForget; QVector firstTimeRooms; QVector pendingStateRoomIds; @@ -891,7 +891,7 @@ Room* Connection::room(const QString& roomId, JoinStates states) const Room* Connection::roomByAlias(const QString& roomAlias, JoinStates states) const { - const auto id = d->roomAliasMap.value(serverPart(roomAlias)).value(roomAlias); + const auto id = d->roomAliasMap.value(roomAlias); if (!id.isEmpty()) return room(id, states); @@ -901,17 +901,15 @@ Room* Connection::roomByAlias(const QString& roomAlias, JoinStates states) const } void Connection::updateRoomAliases(const QString& roomId, - const QString& aliasServer, const QStringList& previousRoomAliases, const QStringList& roomAliases) { - auto& aliasMap = d->roomAliasMap[aliasServer]; // Allocate if necessary for (const auto& a : previousRoomAliases) - if (aliasMap.remove(a) == 0) + if (d->roomAliasMap.remove(a) == 0) qCWarning(MAIN) << "Alias" << a << "is not found (already deleted?)"; for (const auto& a : roomAliases) { - auto& mappedId = aliasMap[a]; + auto& mappedId = d->roomAliasMap[a]; if (!mappedId.isEmpty()) { if (mappedId == roomId) qCDebug(MAIN) -- cgit v1.2.3 From 56d9a0addaabf2cec78e1c82a9846997a3669736 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 25 Feb 2020 20:06:19 +0300 Subject: E2EE: Make building E2EE optional. Contributes to #369 Signed-off-by: Alexey Andreev --- lib/connection.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 98c8a4bc..6ad24fba 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -19,7 +19,9 @@ #include "connection.h" #include "connectiondata.h" +#ifdef Quotient_E2EE_ENABLED #include "encryptionmanager.h" +#endif // Quotient_E2EE_ENABLED #include "room.h" #include "settings.h" #include "user.h" @@ -43,7 +45,9 @@ #include "jobs/mediathumbnailjob.h" #include "jobs/syncjob.h" +#ifdef Quotient_E2EE_ENABLED #include "account.h" // QtOlm +#endif // Quotient_E2EE_ENABLED #include #include @@ -107,7 +111,9 @@ public: GetCapabilitiesJob* capabilitiesJob = nullptr; GetCapabilitiesJob::Capabilities capabilities; +#ifdef Quotient_E2EE_ENABLED QScopedPointer encryptionManager; +#endif // Quotient_E2EE_ENABLED SyncJob* syncJob = nullptr; @@ -153,6 +159,10 @@ public: RoomEventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { +#ifndef Quotient_E2EE_ENABLED + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; + return {}; +#else // Quotient_E2EE_ENABLED if (encryptedEvent.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { return {}; @@ -208,6 +218,7 @@ public: } return decryptedEvent; +#endif // Quotient_E2EE_ENABLED } }; @@ -304,8 +315,12 @@ void Connection::doConnectToServer(const QString& user, const QString& password, connect(loginJob, &BaseJob::success, this, [this, loginJob] { d->connectWithToken(loginJob->userId(), loginJob->accessToken(), loginJob->deviceId()); +#ifndef Quotient_E2EE_ENABLED + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; +#else // Quotient_E2EE_ENABLED d->encryptionManager->uploadIdentityKeys(this); d->encryptionManager->uploadOneTimeKeys(this); +#endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, this, [this, loginJob] { emit loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -362,12 +377,16 @@ void Connection::Private::connectWithToken(const QString& userId, qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << userId << "from device" << deviceId; AccountSettings accountSettings(userId); +#ifndef Quotient_E2EE_ENABLED + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; +#else // Quotient_E2EE_ENABLED encryptionManager.reset( new EncryptionManager(accountSettings.encryptionAccountPickle())); if (accountSettings.encryptionAccountPickle().isEmpty()) { accountSettings.setEncryptionAccountPickle( encryptionManager->olmAccountPickle()); } +#endif // Quotient_E2EE_ENABLED emit q->stateChanged(); emit q->connected(); q->reloadCapabilities(); @@ -594,6 +613,9 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->dcLocalAdditions.clear(); d->dcLocalRemovals.clear(); } +#ifndef Quotient_E2EE_ENABLED + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; +#else // Quotient_E2EE_ENABLED // handling m.room_key to-device encrypted event for (auto&& toDeviceEvent : data.takeToDeviceEvents()) { if (toDeviceEvent->type() == EncryptedEvent::typeId()) { @@ -645,6 +667,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->encryptionManager->updateOneTimeKeyCounts(this, deviceOneTimeKeysCount); } +#endif // Quotient_E2EE_ENABLED } void Connection::stopSync() @@ -1068,10 +1091,12 @@ QString Connection::deviceId() const { return d->data->deviceId(); } QByteArray Connection::accessToken() const { return d->data->accessToken(); } +#ifdef Quotient_E2EE_ENABLED QtOlm::Account* Connection::olmAccount() const { return d->encryptionManager->account(); } +#endif // Quotient_E2EE_ENABLED SyncJob* Connection::syncJob() const { return d->syncJob; } -- cgit v1.2.3 From 4102144290312ebed7d4af69dd640835275a9675 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 13 Mar 2020 18:39:59 +0100 Subject: Connection: support getting the list of login flows The flows themselves are not facilitated in any way (yet). --- lib/connection.cpp | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 6ad24fba..1989050e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -30,7 +30,6 @@ #include "csapi/capabilities.h" #include "csapi/joining.h" #include "csapi/leaving.h" -#include "csapi/login.h" #include "csapi/logout.h" #include "csapi/receipts.h" #include "csapi/room_send.h" @@ -111,6 +110,8 @@ public: GetCapabilitiesJob* capabilitiesJob = nullptr; GetCapabilitiesJob::Capabilities capabilities; + QVector loginFlows; + #ifdef Quotient_E2EE_ENABLED QScopedPointer encryptionManager; #endif // Quotient_E2EE_ENABLED @@ -1004,6 +1005,21 @@ QUrl Connection::homeserver() const { return d->data->baseUrl(); } QString Connection::domain() const { return userId().section(':', 1); } +QVector Connection::loginFlows() const +{ + return d->loginFlows; +} + +bool Connection::supportsPasswordAuth() const +{ + return d->loginFlows.contains(LoginFlows::Password); +} + +bool Connection::supportsSso() const +{ + return d->loginFlows.contains(LoginFlows::SSO); +} + Room* Connection::room(const QString& roomId, JoinStates states) const { Room* room = d->roomMap.value({ roomId, false }, nullptr); @@ -1400,11 +1416,21 @@ QByteArray Connection::generateTxnId() const void Connection::setHomeserver(const QUrl& url) { - if (homeserver() == url) - return; + if (homeserver() != url) { + d->data->setBaseUrl(url); + d->loginFlows.clear(); + emit homeserverChanged(homeserver()); + } - d->data->setBaseUrl(url); - emit homeserverChanged(homeserver()); + // Whenever a homeserver is updated, retrieve available login flows from it + auto* j = callApi(BackgroundRequest); + connect(j, &BaseJob::finished, this, [this, j] { + if (j->status().good()) + d->loginFlows = j->flows(); + else + d->loginFlows.clear(); + emit loginFlowsChanged(); + }); } void Connection::saveRoomState(Room* r) const -- cgit v1.2.3 From a0008f808859ee491bf0a1bc683748a42c5e209a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 14 Mar 2020 20:28:51 +0100 Subject: Connection: minor cleanup --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1989050e..b0ab5bfe 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -317,7 +317,7 @@ void Connection::doConnectToServer(const QString& user, const QString& password, d->connectWithToken(loginJob->userId(), loginJob->accessToken(), loginJob->deviceId()); #ifndef Quotient_E2EE_ENABLED - qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED d->encryptionManager->uploadIdentityKeys(this); d->encryptionManager->uploadOneTimeKeys(this); @@ -377,10 +377,10 @@ void Connection::Private::connectWithToken(const QString& userId, q->setObjectName(userId % '/' % deviceId); qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << userId << "from device" << deviceId; - AccountSettings accountSettings(userId); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED + AccountSettings accountSettings(userId); encryptionManager.reset( new EncryptionManager(accountSettings.encryptionAccountPickle())); if (accountSettings.encryptionAccountPickle().isEmpty()) { -- cgit v1.2.3 From d02a510a586db1a89476cd283ea24a281e9bb6af Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 15 Mar 2020 14:53:27 +0100 Subject: Connection: loginWithToken(); connectWithToken() -> assumeIdentity() --- lib/connection.cpp | 88 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 33 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b0ab5bfe..4b4d371a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -125,8 +125,10 @@ public: != "json"; bool lazyLoading = false; - void connectWithToken(const QString& userId, const QString& accessToken, - const QString& deviceId); + template + void loginToServer(LoginArgTs&&... loginArgs); + void assumeIdentity(const QString& userId, const QString& accessToken, + const QString& deviceId); void removeRoom(const QString& roomId); template @@ -296,44 +298,44 @@ void Connection::resolveServer(const QString& mxid) }); } -void Connection::connectToServer(const QString& user, const QString& password, +inline UserIdentifier makeUserIdentifier(const QString& id) +{ + return { QStringLiteral("m.id.user"), { { QStringLiteral("user"), id } } }; +} + +inline UserIdentifier make3rdPartyIdentifier(const QString& medium, + const QString& address) +{ + return { QStringLiteral("m.id.thirdparty"), + { { QStringLiteral("medium"), medium }, + { QStringLiteral("address"), address } } }; +} + +void Connection::connectToServer(const QString& userId, const QString& password, const QString& initialDeviceName, const QString& deviceId) { - checkAndConnect(user, [=] { - doConnectToServer(user, password, initialDeviceName, deviceId); + checkAndConnect(userId, [=] { + d->loginToServer(LoginFlows::Password.type, makeUserIdentifier(userId), + password, /*token*/ "", deviceId, initialDeviceName); }); } -void Connection::doConnectToServer(const QString& user, const QString& password, - const QString& initialDeviceName, - const QString& deviceId) + +void Connection::loginWithToken(const QByteArray& loginToken, + const QString& initialDeviceName, + const QString& deviceId) { - auto loginJob = - callApi(QStringLiteral("m.login.password"), - UserIdentifier { QStringLiteral("m.id.user"), - { { QStringLiteral("user"), user } } }, - password, /*token*/ "", deviceId, initialDeviceName); - connect(loginJob, &BaseJob::success, this, [this, loginJob] { - d->connectWithToken(loginJob->userId(), loginJob->accessToken(), - loginJob->deviceId()); -#ifndef Quotient_E2EE_ENABLED - qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; -#else // Quotient_E2EE_ENABLED - d->encryptionManager->uploadIdentityKeys(this); - d->encryptionManager->uploadOneTimeKeys(this); -#endif // Quotient_E2EE_ENABLED - }); - connect(loginJob, &BaseJob::failure, this, [this, loginJob] { - emit loginError(loginJob->errorString(), loginJob->rawDataSample()); - }); + d->loginToServer(LoginFlows::Token.type, + makeUserIdentifier(/*user is encoded in loginToken*/ {}), + /*password*/ "", loginToken, deviceId, initialDeviceName); } -void Connection::connectWithToken(const QString& userId, - const QString& accessToken, - const QString& deviceId) +void Connection::assumeIdentity(const QString& userId, + const QString& accessToken, + const QString& deviceId) { checkAndConnect(userId, - [=] { d->connectWithToken(userId, accessToken, deviceId); }); + [=] { d->assumeIdentity(userId, accessToken, deviceId); }); } void Connection::reloadCapabilities() @@ -366,9 +368,29 @@ bool Connection::loadingCapabilities() const return !d->capabilities.roomVersions; } -void Connection::Private::connectWithToken(const QString& userId, - const QString& accessToken, - const QString& deviceId) +template +void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) +{ + auto loginJob = + q->callApi(std::forward(loginArgs)...); + connect(loginJob, &BaseJob::success, q, [this, loginJob] { + assumeIdentity(loginJob->userId(), loginJob->accessToken(), + loginJob->deviceId()); +#ifndef Quotient_E2EE_ENABLED + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; +#else // Quotient_E2EE_ENABLED + encryptionManager->uploadIdentityKeys(this); + encryptionManager->uploadOneTimeKeys(this); +#endif // Quotient_E2EE_ENABLED + }); + connect(loginJob, &BaseJob::failure, q, [this, loginJob] { + emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); + }); +} + +void Connection::Private::assumeIdentity(const QString& userId, + const QString& accessToken, + const QString& deviceId) { data->setUserId(userId); q->user(); // Creates a User object for the local user -- cgit v1.2.3 From ab3d0263b770e30de673c63740a5c26bcbf33e58 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 18 Mar 2020 09:51:27 +0100 Subject: SsoSession and Connection::prepareForSso() The response in the web browser is quite barebone, just enough to give feedback that things are alright. Closes #386. Closes #388. --- lib/connection.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4b4d371a..0e6b1c84 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -321,6 +321,12 @@ void Connection::connectToServer(const QString& userId, const QString& password, }); } +SsoSession* Connection::prepareForSso(const QString& initialDeviceName, + const QString& deviceId) +{ + return new SsoSession(this, initialDeviceName, deviceId); +} + void Connection::loginWithToken(const QByteArray& loginToken, const QString& initialDeviceName, const QString& deviceId) -- cgit v1.2.3 From 89573ca2a1c19d79c1417e64613f1a0fa696837c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 24 Mar 2020 13:02:24 +0100 Subject: Connection: fix a deprecation warning --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 7400c82d..ca866429 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -619,7 +619,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) if (is(accountEvent)) qCDebug(MAIN) << "Users ignored by" << userId() << "updated:" - << QStringList::fromSet(ignoredUsers()).join(','); + << QStringList(ignoredUsers().values()).join(','); auto& currentData = d->accountData[accountEvent.matrixType()]; // A polymorphic event-specific comparison might be a bit -- cgit v1.2.3 From ec4110c63443e29c78fdf0f72af08f5395ec48f7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 26 Mar 2020 13:25:09 +0100 Subject: Refactoring around Connection::onSyncSuccess() The method grew large and a bit unwieldy over the years. --- lib/connection.cpp | 195 ++++++++++++++++++++++++++++------------------------- 1 file changed, 105 insertions(+), 90 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index ca866429..98515617 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -131,6 +131,11 @@ public: const QString& deviceId); void removeRoom(const QString& roomId); + void consumeRoomData(SyncDataList&& roomDataList, bool fromCache); + void consumeAccountData(Events&& accountDataEvents); + void consumePresenceData(Events&& presenceData); + void consumeToDeviceEvents(Events&& toDeviceEvents); + template EventT* unpackAccountData() const { @@ -534,10 +539,32 @@ QJsonObject toJson(const DirectChatsMap& directChats) void Connection::onSyncSuccess(SyncData&& data, bool fromCache) { d->data->setLastEvent(data.nextBatch()); - for (auto&& roomData : data.takeRoomData()) { - const auto forgetIdx = d->roomIdsToForget.indexOf(roomData.roomId); + d->consumeRoomData(data.takeRoomData(), fromCache); + d->consumeAccountData(data.takeAccountData()); + d->consumePresenceData(data.takePresenceData()); + d->consumeToDeviceEvents(data.takeToDeviceEvents()); +#ifdef Quotient_E2EE_ENABLED + // handling device_one_time_keys_count + if (!d->encryptionManager) + { + qCDebug(E2EE) << "Encryption manager is not there yet, updating " + "one-time key counts will be skipped"; + return; + } + if (const auto deviceOneTimeKeysCount = data.deviceOneTimeKeysCount(); + !deviceOneTimeKeysCount.isEmpty()) + d->encryptionManager->updateOneTimeKeyCounts(this, + deviceOneTimeKeysCount); +#endif // Quotient_E2EE_ENABLED +} + +void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, + bool fromCache) +{ + for (auto&& roomData: roomDataList) { + const auto forgetIdx = roomIdsToForget.indexOf(roomData.roomId); if (forgetIdx != -1) { - d->roomIdsToForget.removeAt(forgetIdx); + roomIdsToForget.removeAt(forgetIdx); if (roomData.joinState == JoinState::Leave) { qDebug(MAIN) << "Room" << roomData.roomId @@ -549,12 +576,12 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) << toCString(roomData.joinState) << "state - suspiciously fast turnaround"; } - if (auto* r = provideRoom(roomData.roomId, roomData.joinState)) { - d->pendingStateRoomIds.removeOne(roomData.roomId); + if (auto* r = q->provideRoom(roomData.roomId, roomData.joinState)) { + pendingStateRoomIds.removeOne(roomData.roomId); r->updateData(std::move(roomData), fromCache); - if (d->firstTimeRooms.removeOne(r)) { - emit loadedRoomState(r); - if (d->capabilities.roomVersions) + if (firstTimeRooms.removeOne(r)) { + emit q->loadedRoomState(r); + if (capabilities.roomVersions) r->checkVersion(); // Otherwise, the version will be checked in reloadCapabilities() } @@ -562,25 +589,28 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) // Let UI update itself after updating each room QCoreApplication::processEvents(); } +} + +void Connection::Private::consumeAccountData(Events&& accountDataEvents) +{ // After running this loop, the account data events not saved in - // d->accountData (see the end of the loop body) are auto-cleaned away - for (auto& eventPtr : data.takeAccountData()) { - visit( - *eventPtr, + // accountData (see the end of the loop body) are auto-cleaned away + for (auto&& eventPtr: accountDataEvents) { + visit(*eventPtr, [this](const DirectChatEvent& dce) { // https://github.com/quotient-im/libQuotient/wiki/Handling-direct-chat-events const auto& usersToDCs = dce.usersToDirectChats(); DirectChatsMap remoteRemovals = - erase_if(d->directChats, [&usersToDCs, this](auto it) { - return !(usersToDCs.contains(it.key()->id(), it.value()) - || d->dcLocalAdditions.contains(it.key(), - it.value())); + erase_if(directChats, [&usersToDCs, this](auto it) { + return !( + usersToDCs.contains(it.key()->id(), it.value()) + || dcLocalAdditions.contains(it.key(), it.value())); }); - erase_if(d->directChatUsers, [&remoteRemovals](auto it) { + erase_if(directChatUsers, [&remoteRemovals](auto it) { return remoteRemovals.contains(it.value(), it.key()); }); // Remove from dcLocalRemovals what the server already has. - erase_if(d->dcLocalRemovals, [&remoteRemovals](auto it) { + erase_if(dcLocalRemovals, [&remoteRemovals](auto it) { return remoteRemovals.contains(it.key(), it.value()); }); if (MAIN().isDebugEnabled()) @@ -593,13 +623,13 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) DirectChatsMap remoteAdditions; for (auto it = usersToDCs.begin(); it != usersToDCs.end(); ++it) { - if (auto* u = user(it.key())) { - if (!d->directChats.contains(u, it.value()) - && !d->dcLocalRemovals.contains(u, it.value())) { - Q_ASSERT(!d->directChatUsers.contains(it.value(), u)); + if (auto* u = q->user(it.key())) { + if (!directChats.contains(u, it.value()) + && !dcLocalRemovals.contains(u, it.value())) { + Q_ASSERT(!directChatUsers.contains(it.value(), u)); remoteAdditions.insert(u, it.value()); - d->directChats.insert(u, it.value()); - d->directChatUsers.insert(it.value(), u); + directChats.insert(u, it.value()); + directChatUsers.insert(it.value(), u); qCDebug(MAIN) << "Marked room" << it.value() << "as a direct chat with" << u->id(); } @@ -608,20 +638,21 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) << "Couldn't get a user object for" << it.key(); } // Remove from dcLocalAdditions what the server already has. - erase_if(d->dcLocalAdditions, [&remoteAdditions](auto it) { + erase_if(dcLocalAdditions, [&remoteAdditions](auto it) { return remoteAdditions.contains(it.key(), it.value()); }); if (!remoteAdditions.isEmpty() || !remoteRemovals.isEmpty()) - emit directChatsListChanged(remoteAdditions, remoteRemovals); + emit q->directChatsListChanged(remoteAdditions, + remoteRemovals); }, // catch-all, passing eventPtr for a possible take-over [this, &eventPtr](const Event& accountEvent) { if (is(accountEvent)) qCDebug(MAIN) - << "Users ignored by" << userId() << "updated:" - << QStringList(ignoredUsers().values()).join(','); + << "Users ignored by" << data->userId() << "updated:" + << QStringList(q->ignoredUsers().values()).join(','); - auto& currentData = d->accountData[accountEvent.matrixType()]; + auto& currentData = accountData[accountEvent.matrixType()]; // A polymorphic event-specific comparison might be a bit // more efficient; maaybe do it another day if (!currentData @@ -629,74 +660,58 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) currentData = std::move(eventPtr); qCDebug(MAIN) << "Updated account data of type" << currentData->matrixType(); - emit accountDataChanged(currentData->matrixType()); + emit q->accountDataChanged(currentData->matrixType()); } }); } - if (!d->dcLocalAdditions.isEmpty() || !d->dcLocalRemovals.isEmpty()) { + if (!dcLocalAdditions.isEmpty() || !dcLocalRemovals.isEmpty()) { qDebug(MAIN) << "Sending updated direct chats to the server:" - << d->dcLocalRemovals.size() << "removal(s)," - << d->dcLocalAdditions.size() << "addition(s)"; - callApi(userId(), QStringLiteral("m.direct"), - toJson(d->directChats)); - d->dcLocalAdditions.clear(); - d->dcLocalRemovals.clear(); + << dcLocalRemovals.size() << "removal(s)," + << dcLocalAdditions.size() << "addition(s)"; + q->callApi(data->userId(), QStringLiteral("m.direct"), + toJson(directChats)); + dcLocalAdditions.clear(); + dcLocalRemovals.clear(); } -#ifndef Quotient_E2EE_ENABLED - qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; -#else // Quotient_E2EE_ENABLED - // handling m.room_key to-device encrypted event - for (auto&& toDeviceEvent : data.takeToDeviceEvents()) { - if (toDeviceEvent->type() == EncryptedEvent::typeId()) { - event_ptr_tt encryptedEvent = - makeEvent(toDeviceEvent->fullJson()); - if (encryptedEvent->algorithm() != OlmV1Curve25519AesSha2AlgoKey) { - qCDebug(E2EE) - << "Encrypted event" << encryptedEvent->id() << "algorithm" - << encryptedEvent->algorithm() << "is not supported"; - return; - } +} - // TODO: full maintaining of the device keys - // with device_lists sync extention and /keys/query - qCDebug(E2EE) << "Getting device keys for the m.room_key sender:" - << encryptedEvent->senderId(); - // d->encryptionManager->updateDeviceKeys(); - - RoomEventPtr decryptedEvent = - d->sessionDecryptMessage(*encryptedEvent.get()); - // since we are waiting for the RoomKeyEvent: - event_ptr_tt roomKeyEvent = - makeEvent(decryptedEvent->fullJson()); - if (!roomKeyEvent) { - qCDebug(E2EE) << "Failed to decrypt olm event from user" - << encryptedEvent->senderId(); - return; - } - Room* detectedRoom = room(roomKeyEvent->roomId()); - if (!detectedRoom) { - qCDebug(E2EE) - << "Encrypted event room id" << encryptedEvent->roomId() - << "is not found at the connection"; - return; - } - detectedRoom->handleRoomKeyEvent(roomKeyEvent.get(), - encryptedEvent->senderKey()); +void Connection::Private::consumePresenceData(Events&& presenceData) +{ + // To be implemented +} + +void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) +{ +#ifdef Quotient_E2EE_ENABLED + // handling m.room_key to-device encrypted event + visitEach(toDeviceEvents, [this](const EncryptedEvent& ee) { + if (ee.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { + qCDebug(E2EE) << "Encrypted event" << ee.id() << "algorithm" + << ee.algorithm() << "is not supported"; + return; } - } - // handling device_one_time_keys_count - auto deviceOneTimeKeysCount = data.deviceOneTimeKeysCount(); - if (!d->encryptionManager) - { - qCDebug(E2EE) << "Encryption manager is not there yet"; - return; - } - if (!deviceOneTimeKeysCount.isEmpty()) - { - d->encryptionManager->updateOneTimeKeyCounts(this, - deviceOneTimeKeysCount); - } -#endif // Quotient_E2EE_ENABLED + + // TODO: full maintaining of the device keys + // with device_lists sync extention and /keys/query + qCDebug(E2EE) << "Getting device keys for the m.room_key sender:" + << ee.senderId(); + // encryptionManager->updateDeviceKeys(); + + visit(*sessionDecryptMessage(ee), + [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { + if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) + detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); + else + qCDebug(E2EE) + << "Encrypted event room id" << roomKeyEvent.roomId() + << "is not found at the connection" << q->objectName(); + }, + [](const Event& evt) { + qCDebug(E2EE) << "Skipping encrypted to_device event, type" + << evt.matrixType(); + }); + }); +#endif } void Connection::stopSync() -- cgit v1.2.3 From a81590c9e3b26f76d02d498802a44719c116fa13 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 27 Mar 2020 11:25:59 +0100 Subject: Fix FTBFS with Quotient_ENABLE_E2EE --- lib/connection.cpp | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 98515617..021ff5dd 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -165,25 +165,24 @@ public: return q->stateCacheDir().filePath("state.json"); } - RoomEventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) + EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return {}; #else // Quotient_E2EE_ENABLED if (encryptedEvent.algorithm() != OlmV1Curve25519AesSha2AlgoKey) - { return {}; - } - QString identityKey = + + const auto identityKey = encryptionManager->account()->curve25519IdentityKey(); - QJsonObject personalCipherObject = + const auto personalCipherObject = encryptedEvent.ciphertext(identityKey); if (personalCipherObject.isEmpty()) { qCDebug(E2EE) << "Encrypted event is not for the current device"; return {}; } - QString decrypted = encryptionManager->sessionDecryptMessage( + const auto decrypted = encryptionManager->sessionDecryptMessage( personalCipherObject, encryptedEvent.senderKey().toLatin1()); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" @@ -192,29 +191,29 @@ public: return {}; } - RoomEventPtr decryptedEvent = makeEvent( - QJsonDocument::fromJson(decrypted.toUtf8()).object()); + auto&& decryptedEvent = + fromJson(QJsonDocument::fromJson(decrypted.toUtf8())); - if (decryptedEvent->senderId() != encryptedEvent.senderId()) { - qCDebug(E2EE) << "Found user" << decryptedEvent->senderId() + if (auto sender = decryptedEvent->fullJson()["sender"_ls].toString(); + sender != encryptedEvent.senderId()) { + qCWarning(E2EE) << "Found user" << sender << "instead of sender" << encryptedEvent.senderId() << "in Olm plaintext"; return {}; } // TODO: keys to constants - QJsonObject decryptedEventObject = decryptedEvent->fullJson(); - QString recipient = + const auto decryptedEventObject = decryptedEvent->fullJson(); + const auto recipient = decryptedEventObject.value("recipient"_ls).toString(); if (recipient != data->userId()) { qCDebug(E2EE) << "Found user" << recipient << "instead of us" << data->userId() << "in Olm plaintext"; return {}; } - QString ourKey = decryptedEventObject.value("recipient_keys"_ls) - .toObject() - .value(Ed25519Key) - .toString(); + const auto ourKey = + decryptedEventObject.value("recipient_keys"_ls).toObject() + .value(Ed25519Key).toString(); if (ourKey != QString::fromUtf8( encryptionManager->account()->ed25519IdentityKey())) { @@ -225,7 +224,7 @@ public: return {}; } - return decryptedEvent; + return std::move(decryptedEvent); #endif // Quotient_E2EE_ENABLED } }; @@ -390,8 +389,8 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED - encryptionManager->uploadIdentityKeys(this); - encryptionManager->uploadOneTimeKeys(this); + encryptionManager->uploadIdentityKeys(q); + encryptionManager->uploadOneTimeKeys(q); #endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { -- cgit v1.2.3 From 0a7008bfd345afb083d8ac8c0c6cf21f879aedc6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 30 Mar 2020 19:16:11 +0200 Subject: Connection::resolveServer(): refactor Also: use 4-arg connect() to make sure lambdas are disconnected if the connection is gone. --- lib/connection.cpp | 49 +++++++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 26 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 021ff5dd..6babcf27 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -253,51 +253,48 @@ void Connection::resolveServer(const QString& mxid) return; } - setHomeserver(maybeBaseUrl); - auto domain = maybeBaseUrl.host(); qCDebug(MAIN) << "Finding the server" << domain; + d->data->setBaseUrl(maybeBaseUrl); // Just enough to check .well-known file auto getWellKnownJob = callApi(); - connect( - getWellKnownJob, &BaseJob::finished, + connect(getWellKnownJob, &BaseJob::finished, this, [this, getWellKnownJob, maybeBaseUrl] { - if (getWellKnownJob->status() == BaseJob::NotFoundError) - qCDebug(MAIN) << "No .well-known file, IGNORE"; - else { + if (getWellKnownJob->status() != BaseJob::NotFoundError) { if (getWellKnownJob->status() != BaseJob::Success) { - qCDebug(MAIN) + qCWarning(MAIN) << "Fetching .well-known file failed, FAIL_PROMPT"; - emit resolveError(tr("Fetching .well-known file failed")); + emit resolveError(tr("Failed resolving the homeserver")); return; } - QUrl baseUrl(getWellKnownJob->data().homeserver.baseUrl); + QUrl baseUrl { getWellKnownJob->data().homeserver.baseUrl }; if (baseUrl.isEmpty()) { - qCDebug(MAIN) << "base_url not provided, FAIL_PROMPT"; - emit resolveError(tr("base_url not provided")); + qCWarning(MAIN) << "base_url not provided, FAIL_PROMPT"; + emit resolveError( + tr("The homeserver base URL is not provided")); return; } if (!baseUrl.isValid()) { - qCDebug(MAIN) << "base_url invalid, FAIL_ERROR"; - emit resolveError(tr("base_url invalid")); + qCWarning(MAIN) << "base_url invalid, FAIL_ERROR"; + emit resolveError(tr("The homeserver base URL is invalid")); return; } - - qCDebug(MAIN) << ".well-known for" << maybeBaseUrl.host() - << "is" << baseUrl.toString(); + qCInfo(MAIN) << ".well-known URL for" << maybeBaseUrl.host() + << "is" << baseUrl.authority(); setHomeserver(baseUrl); + } else { + qCInfo(MAIN) << "No .well-known file, using" << maybeBaseUrl + << "for base URL"; + setHomeserver(maybeBaseUrl); } auto getVersionsJob = callApi(); - - connect(getVersionsJob, &BaseJob::finished, [this, getVersionsJob] { - if (getVersionsJob->status() == BaseJob::Success) { - qCDebug(MAIN) << "homeserver url is valid"; - emit resolved(); - } else { - qCDebug(MAIN) << "homeserver url invalid"; - emit resolveError(tr("homeserver url invalid")); - } + connect(getVersionsJob, &BaseJob::success, this, + &Connection::resolved); + connect(getVersionsJob, &BaseJob::failure, this, [this] { + qCWarning(MAIN) << "Homeserver base URL invalid"; + emit resolveError(tr("The homeserver base URL " + "doesn't seem to work")); }); }); } -- cgit v1.2.3 From 86b29318385edf07ecbaca975e5ab90f11304d1a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 30 Mar 2020 21:57:40 +0200 Subject: Reparent all jobs to their connections (Mostly) fixes #397. Fixes #398. If there's a need to reparent the job to some other object, it should now be done after callApi()/run() call (not that there were many touch points before that moment, anyway). Collateral damage: job-starting methods are no more const (but that didn't belong them anyway, too). --- lib/connection.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 6babcf27..1c63adeb 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -723,7 +723,7 @@ void Connection::stopSync() QString Connection::nextBatchToken() const { return d->data->lastEvent(); } -PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const +PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) { return callApi(room->id(), "m.read", event->id()); } @@ -773,7 +773,7 @@ inline auto splitMediaId(const QString& mediaId) MediaThumbnailJob* Connection::getThumbnail(const QString& mediaId, QSize requestedSize, - RunningPolicy policy) const + RunningPolicy policy) { auto idParts = splitMediaId(mediaId); return callApi(policy, idParts.front(), idParts.back(), @@ -781,21 +781,21 @@ MediaThumbnailJob* Connection::getThumbnail(const QString& mediaId, } MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, QSize requestedSize, - RunningPolicy policy) const + RunningPolicy policy) { return getThumbnail(url.authority() + url.path(), requestedSize, policy); } MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, int requestedWidth, int requestedHeight, - RunningPolicy policy) const + RunningPolicy policy) { return getThumbnail(url, QSize(requestedWidth, requestedHeight), policy); } UploadContentJob* Connection::uploadContent(QIODevice* contentSource, const QString& filename, - const QString& overrideContentType) const + const QString& overrideContentType) { Q_ASSERT(contentSource != nullptr); auto contentType = overrideContentType; @@ -820,19 +820,19 @@ UploadContentJob* Connection::uploadFile(const QString& fileName, overrideContentType); } -GetContentJob* Connection::getContent(const QString& mediaId) const +GetContentJob* Connection::getContent(const QString& mediaId) { auto idParts = splitMediaId(mediaId); return callApi(idParts.front(), idParts.back()); } -GetContentJob* Connection::getContent(const QUrl& url) const +GetContentJob* Connection::getContent(const QUrl& url) { return getContent(url.authority() + url.path()); } DownloadFileJob* Connection::downloadFile(const QUrl& url, - const QString& localFilename) const + const QString& localFilename) { auto mediaId = url.authority() + url.path(); auto idParts = splitMediaId(mediaId); @@ -1011,7 +1011,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) SendToDeviceJob* Connection::sendToDevices(const QString& eventType, - const UsersToDevicesToEvents& eventsMap) const + const UsersToDevicesToEvents& eventsMap) { QHash> json; json.reserve(int(eventsMap.size())); @@ -1032,7 +1032,7 @@ Connection::sendToDevices(const QString& eventType, } SendMessageJob* Connection::sendMessage(const QString& roomId, - const RoomEvent& event) const + const RoomEvent& event) { const auto txnId = event.transactionId().isEmpty() ? generateTxnId() : event.transactionId(); @@ -1609,8 +1609,9 @@ void Connection::setLazyLoading(bool newValue) } } -void Connection::run(BaseJob* job, RunningPolicy runningPolicy) const +void Connection::run(BaseJob* job, RunningPolicy runningPolicy) { + job->setParent(this); // Protects from #397, #398 connect(job, &BaseJob::failure, this, &Connection::requestFailed); job->initiate(d->data.get(), runningPolicy & BackgroundRequest); } -- cgit v1.2.3 From 4088ab4572c5b7cde603aeb1a89bc4515833beaf Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 14 Apr 2020 14:21:26 +0200 Subject: BaseJob::makeRequestUrl(): even more tolerance to slash separators The code is really defensive now, making sure there's exactly one slash between the base path and the endpoint. It's still very conservative about the path composition otherwise (no normalisation etc.). --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1c63adeb..41ecee67 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -280,7 +280,7 @@ void Connection::resolveServer(const QString& mxid) return; } qCInfo(MAIN) << ".well-known URL for" << maybeBaseUrl.host() - << "is" << baseUrl.authority(); + << "is" << baseUrl.toString(); setHomeserver(baseUrl); } else { qCInfo(MAIN) << "No .well-known file, using" << maybeBaseUrl -- cgit v1.2.3 From 7ecc82079f85a7fa29f122fd2fd78401af0ee799 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 15 Apr 2020 09:27:41 +0200 Subject: Connection: connectToServer -> loginWithPassword connectToServer() is left for compatibility but deprecated. --- lib/connection.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 41ecee67..229a6564 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -312,9 +312,10 @@ inline UserIdentifier make3rdPartyIdentifier(const QString& medium, { QStringLiteral("address"), address } } }; } -void Connection::connectToServer(const QString& userId, const QString& password, - const QString& initialDeviceName, - const QString& deviceId) +void Connection::loginWithPassword(const QString& userId, + const QString& password, + const QString& initialDeviceName, + const QString& deviceId) { checkAndConnect(userId, [=] { d->loginToServer(LoginFlows::Password.type, makeUserIdentifier(userId), -- cgit v1.2.3 From b19a7c8b9786ad506ab208da4f0f5e76ff7daa74 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 15 Apr 2020 11:02:03 +0200 Subject: Connection: cleanup and reformat --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 229a6564..db61986a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -334,8 +334,8 @@ void Connection::loginWithToken(const QByteArray& loginToken, const QString& deviceId) { d->loginToServer(LoginFlows::Token.type, - makeUserIdentifier(/*user is encoded in loginToken*/ {}), - /*password*/ "", loginToken, deviceId, initialDeviceName); + none /*user is encoded in loginToken*/, "" /*password*/, + loginToken, deviceId, initialDeviceName); } void Connection::assumeIdentity(const QString& userId, -- cgit v1.2.3 From 753c281f4eb055d684e63472468fd091f2166526 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 15 Apr 2020 13:05:16 +0200 Subject: Connection: track resolving and login flows jobs; isUsable() This is to prevent the jobs from several resolveServer() / setHomeserver() invocations running in parallel. --- lib/connection.cpp | 98 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 43 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index db61986a..9f4f7082 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -116,6 +116,9 @@ public: QScopedPointer encryptionManager; #endif // Quotient_E2EE_ENABLED + QPointer resolverJob = nullptr; + QPointer loginFlowsJob = nullptr; + SyncJob* syncJob = nullptr; bool cacheState = true; @@ -245,6 +248,9 @@ Connection::~Connection() void Connection::resolveServer(const QString& mxid) { + if (isJobRunning(d->resolverJob)) + d->resolverJob->abandon(); + auto maybeBaseUrl = QUrl::fromUserInput(serverPart(mxid)); maybeBaseUrl.setScheme("https"); // Instead of the Qt-default "http" if (maybeBaseUrl.isEmpty() || !maybeBaseUrl.isValid()) { @@ -257,46 +263,43 @@ void Connection::resolveServer(const QString& mxid) qCDebug(MAIN) << "Finding the server" << domain; d->data->setBaseUrl(maybeBaseUrl); // Just enough to check .well-known file - auto getWellKnownJob = callApi(); - connect(getWellKnownJob, &BaseJob::finished, this, - [this, getWellKnownJob, maybeBaseUrl] { - if (getWellKnownJob->status() != BaseJob::NotFoundError) { - if (getWellKnownJob->status() != BaseJob::Success) { - qCWarning(MAIN) - << "Fetching .well-known file failed, FAIL_PROMPT"; - emit resolveError(tr("Failed resolving the homeserver")); - return; - } - QUrl baseUrl { getWellKnownJob->data().homeserver.baseUrl }; - if (baseUrl.isEmpty()) { - qCWarning(MAIN) << "base_url not provided, FAIL_PROMPT"; - emit resolveError( - tr("The homeserver base URL is not provided")); - return; - } - if (!baseUrl.isValid()) { - qCWarning(MAIN) << "base_url invalid, FAIL_ERROR"; - emit resolveError(tr("The homeserver base URL is invalid")); - return; - } - qCInfo(MAIN) << ".well-known URL for" << maybeBaseUrl.host() - << "is" << baseUrl.toString(); - setHomeserver(baseUrl); - } else { - qCInfo(MAIN) << "No .well-known file, using" << maybeBaseUrl - << "for base URL"; - setHomeserver(maybeBaseUrl); + d->resolverJob = callApi(); + connect(d->resolverJob, &BaseJob::finished, this, [this, maybeBaseUrl] { + if (d->resolverJob->status() != BaseJob::NotFoundError) { + if (d->resolverJob->status() != BaseJob::Success) { + qCWarning(MAIN) + << "Fetching .well-known file failed, FAIL_PROMPT"; + emit resolveError(tr("Failed resolving the homeserver")); + return; } - - auto getVersionsJob = callApi(); - connect(getVersionsJob, &BaseJob::success, this, - &Connection::resolved); - connect(getVersionsJob, &BaseJob::failure, this, [this] { - qCWarning(MAIN) << "Homeserver base URL invalid"; - emit resolveError(tr("The homeserver base URL " - "doesn't seem to work")); - }); + QUrl baseUrl { d->resolverJob->data().homeserver.baseUrl }; + if (baseUrl.isEmpty()) { + qCWarning(MAIN) << "base_url not provided, FAIL_PROMPT"; + emit resolveError( + tr("The homeserver base URL is not provided")); + return; + } + if (!baseUrl.isValid()) { + qCWarning(MAIN) << "base_url invalid, FAIL_ERROR"; + emit resolveError(tr("The homeserver base URL is invalid")); + return; + } + qCInfo(MAIN) << ".well-known URL for" << maybeBaseUrl.host() << "is" + << baseUrl.toString(); + setHomeserver(baseUrl); + } else { + qCInfo(MAIN) << "No .well-known file, using" << maybeBaseUrl + << "for base URL"; + setHomeserver(maybeBaseUrl); + } + connect(d->loginFlowsJob, &BaseJob::success, this, + &Connection::resolved); + connect(d->loginFlowsJob, &BaseJob::failure, this, [this] { + qCWarning(MAIN) << "Homeserver base URL sanity check failed"; + emit resolveError( + tr("The homeserver base URL doesn't seem to work")); }); + }); } inline UserIdentifier makeUserIdentifier(const QString& id) @@ -1045,6 +1048,8 @@ QUrl Connection::homeserver() const { return d->data->baseUrl(); } QString Connection::domain() const { return userId().section(':', 1); } +bool Connection::isUsable() const { return !loginFlows().isEmpty(); } + QVector Connection::loginFlows() const { return d->loginFlows; @@ -1454,17 +1459,24 @@ QByteArray Connection::generateTxnId() const void Connection::setHomeserver(const QUrl& url) { + if (isJobRunning(d->resolverJob)) + d->resolverJob->abandon(); + d->resolverJob = nullptr; + if (isJobRunning(d->loginFlowsJob)) + d->loginFlowsJob->abandon(); + d->loginFlowsJob = nullptr; + d->loginFlows.clear(); + if (homeserver() != url) { d->data->setBaseUrl(url); - d->loginFlows.clear(); emit homeserverChanged(homeserver()); } // Whenever a homeserver is updated, retrieve available login flows from it - auto* j = callApi(BackgroundRequest); - connect(j, &BaseJob::finished, this, [this, j] { - if (j->status().good()) - d->loginFlows = j->flows(); + d->loginFlowsJob = callApi(BackgroundRequest); + connect(d->loginFlowsJob, &BaseJob::finished, this, [this] { + if (d->loginFlowsJob->status().good()) + d->loginFlows = d->loginFlowsJob->flows(); else d->loginFlows.clear(); emit loginFlowsChanged(); -- cgit v1.2.3 From cf4d3d7ed82e62b57a11fbc7f491535a761dc75c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 27 May 2020 19:03:52 +0200 Subject: Move around and format code No functional changes here. --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 9f4f7082..efc7163e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -20,7 +20,7 @@ #include "connectiondata.h" #ifdef Quotient_E2EE_ENABLED -#include "encryptionmanager.h" +# include "encryptionmanager.h" #endif // Quotient_E2EE_ENABLED #include "room.h" #include "settings.h" @@ -45,7 +45,7 @@ #include "jobs/syncjob.h" #ifdef Quotient_E2EE_ENABLED -#include "account.h" // QtOlm +# include "account.h" // QtOlm #endif // Quotient_E2EE_ENABLED #include -- cgit v1.2.3 From 79ede9bcb6d4fc5e269f506b2a10bfe4c302040a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 5 Jun 2020 07:40:01 +0200 Subject: Connection::run(): Q_INVOKABLE and chaining --- lib/connection.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index efc7163e..f497601e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1622,11 +1622,14 @@ void Connection::setLazyLoading(bool newValue) } } -void Connection::run(BaseJob* job, RunningPolicy runningPolicy) +BaseJob* Connection::run(BaseJob* job, RunningPolicy runningPolicy) { - job->setParent(this); // Protects from #397, #398 + // Reparent to protect from #397, #398 and to prevent BaseJob* from being + // garbage-collected if made by or returned to QML/JavaScript. + job->setParent(this); connect(job, &BaseJob::failure, this, &Connection::requestFailed); job->initiate(d->data.get(), runningPolicy & BackgroundRequest); + return job; } void Connection::getTurnServers() -- cgit v1.2.3 From db8b9569f83ba3643c9005359ba556f2c3e357f4 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 5 Jun 2020 07:42:33 +0200 Subject: Use CBOR for binary JSON caching on Qt 5.15+ Qt 5.15 deprecates binary JSON format in favour of CBOR now used as a backend for its JSON classes. --- lib/connection.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index f497601e..26de0c5f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -48,6 +48,10 @@ # include "account.h" // QtOlm #endif // Quotient_E2EE_ENABLED +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) +# include +#endif + #include #include #include @@ -1492,9 +1496,16 @@ void Connection::saveRoomState(Room* r) const QFile outRoomFile { stateCacheDir().filePath( SyncData::fileNameForRoom(r->id())) }; if (outRoomFile.open(QFile::WriteOnly)) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + const auto data = + d->cacheToBinary + ? QCborValue::fromJsonValue(r->toJson()).toCbor() + : QJsonDocument(r->toJson()).toJson(QJsonDocument::Compact); +#else QJsonDocument json { r->toJson() }; - auto data = d->cacheToBinary ? json.toBinaryData() - : json.toJson(QJsonDocument::Compact); + const auto data = d->cacheToBinary ? json.toBinaryData() + : json.toJson(QJsonDocument::Compact); +#endif outRoomFile.write(data.data(), data.size()); qCDebug(MAIN) << "Room state cache saved to" << outRoomFile.fileName(); } else { @@ -1558,9 +1569,15 @@ void Connection::saveState() const { QStringLiteral("events"), accountDataEvents } }); } +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + const auto data = + d->cacheToBinary ? QCborValue::fromJsonValue(rootObj).toCbor() + : QJsonDocument(rootObj).toJson(QJsonDocument::Compact); +#else QJsonDocument json { rootObj }; - auto data = d->cacheToBinary ? json.toBinaryData() - : json.toJson(QJsonDocument::Compact); + const auto data = d->cacheToBinary ? json.toBinaryData() + : json.toJson(QJsonDocument::Compact); +#endif qCDebug(PROFILER) << "Cache for" << userId() << "generated in" << et; outFile.write(data.data(), data.size()); -- cgit v1.2.3 From 0898550adcc5e6fe2648fcd4e181ecab9b5befea Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 6 Jun 2020 20:49:17 +0200 Subject: Small updates to match the new generated definitions --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 26de0c5f..50e31d52 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -480,8 +480,8 @@ void Connection::sync(int timeout) d->syncTimeout = timeout; Filter filter; - filter.room.edit().timeline.edit().limit.emplace(100); - filter.room.edit().state.edit().lazyLoadMembers.emplace(d->lazyLoading); + filter.room.timeline.limit.emplace(100); + filter.room.state.lazyLoadMembers.emplace(d->lazyLoading); auto job = d->syncJob = callApi(BackgroundRequest, d->data->lastEvent(), filter, timeout); -- cgit v1.2.3 From b1071cf34b86685c3cdb5004d6112881966a7ce6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 12 Jun 2020 20:29:46 +0200 Subject: Connection::syncLoop: give a pause between syncs As it's observed now, Synapse responds almost immediately on /sync requests - even if there are no events to return. This downgrades long-polling to simply polling, and since clients don't expect it, polling loops become pretty violent. To alleviate that somehow, syncLoop now accepts the second parameter, msecBetween (500 msecs by default), to configure waiting between the previous sync response and the next sync request. This is only for syncLoop(); Connection::sync() fires instantly, as before. --- lib/connection.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 50e31d52..d53c308f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -506,21 +507,23 @@ void Connection::sync(int timeout) }); } -void Connection::syncLoop(int timeout) +void Connection::syncLoop(int timeout, int msecBetween) { if (d->syncLoopConnection && d->syncTimeout == timeout) { qCInfo(MAIN) << "Attempt to run sync loop but there's one already " "running; nothing will be done"; return; } - std::swap(d->syncTimeout, timeout); + std::swap(d->syncTimeout, timeout); // swap() is for the nice log below if (d->syncLoopConnection) { qCInfo(MAIN) << "Timeout for next syncs changed from" << timeout << "to" << d->syncTimeout; } else { - d->syncLoopConnection = connect(this, &Connection::syncDone, - this, &Connection::syncLoopIteration, - Qt::QueuedConnection); + d->syncLoopConnection = + connect(this, &Connection::syncDone, this, [this, msecBetween] { + QTimer::singleShot(msecBetween, this, + &Connection::syncLoopIteration); + }); syncLoopIteration(); // initial sync to start the loop } } -- cgit v1.2.3 From f85badbd7bae173871b7681322745c73b23aa512 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 12 Jun 2020 20:37:33 +0200 Subject: The previous commit is incomplete, this completes it --- lib/connection.cpp | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d53c308f..0fb301d1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -125,6 +125,7 @@ public: QPointer loginFlowsJob = nullptr; SyncJob* syncJob = nullptr; + QPointer logoutJob = nullptr; bool cacheState = true; bool cacheToBinary = @@ -451,24 +452,30 @@ void Connection::checkAndConnect(const QString& userId, void Connection::logout() { - // If there's an ongoing sync job, stop it but don't break the sync loop yet - const auto syncWasRunning = bool(d->syncJob); - if (syncWasRunning) + // If there's an ongoing sync job, stop it (this also suspends sync loop) + const auto wasSyncing = bool(d->syncJob); + if (wasSyncing) { d->syncJob->abandon(); d->syncJob = nullptr; } - const auto* job = callApi(); - connect(job, &LogoutJob::finished, this, [this, job, syncWasRunning] { - if (job->status().good() || job->error() == BaseJob::Unauthorised - || job->error() == BaseJob::ContentAccessError) { + + d->logoutJob = callApi(); + emit stateChanged(); // isLoggedIn() == false from now + + connect(d->logoutJob, &LogoutJob::finished, this, [this, wasSyncing] { + if (d->logoutJob->status().good() + || d->logoutJob->error() == BaseJob::Unauthorised + || d->logoutJob->error() == BaseJob::ContentAccessError) { if (d->syncLoopConnection) disconnect(d->syncLoopConnection); d->data->setToken({}); - emit stateChanged(); emit loggedOut(); - } else if (syncWasRunning) - syncLoopIteration(); // Resume sync loop (or a single sync) + } else { // logout() somehow didn't proceed - restore the session state + emit stateChanged(); + if (wasSyncing) + syncLoopIteration(); // Resume sync loop (or a single sync) + } }); } @@ -478,6 +485,10 @@ void Connection::sync(int timeout) qCInfo(MAIN) << d->syncJob << "is already running"; return; } + if (!isLoggedIn()) { + qCWarning(MAIN) << "Not logged in, not going to sync"; + return; + } d->syncTimeout = timeout; Filter filter; @@ -528,7 +539,13 @@ void Connection::syncLoop(int timeout, int msecBetween) } } -void Connection::syncLoopIteration() { sync(d->syncTimeout); } +void Connection::syncLoopIteration() +{ + if (isLoggedIn()) + sync(d->syncTimeout); + else + qCInfo(MAIN) << "Logged out, sync loop will stop now"; +} QJsonObject toJson(const DirectChatsMap& directChats) { @@ -1155,7 +1172,14 @@ QString Connection::userId() const { return d->data->userId(); } QString Connection::deviceId() const { return d->data->deviceId(); } -QByteArray Connection::accessToken() const { return d->data->accessToken(); } +QByteArray Connection::accessToken() const +{ + // The logout job needs access token to do its job; so the token is + // kept inside d->data but no more exposed to the outside world. + return isJobRunning(d->logoutJob) ? QByteArray() : d->data->accessToken(); +} + +bool Connection::isLoggedIn() const { return !accessToken().isEmpty(); } #ifdef Quotient_E2EE_ENABLED QtOlm::Account* Connection::olmAccount() const -- cgit v1.2.3 From f6ad01c959e19362a0b15779b816432595153d3b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 19 Jun 2020 15:59:50 +0200 Subject: Revert adding a pause between syncs, use sane timeout defaults This reverts commit b1071cf34b86685c3cdb5004d6112881966a7ce6. Passing -1 to sync() and, respectively, to SyncJob does not add any timeout; however, careful reading of the spec reveals that the default value for the timeout (0) means to return as soon as possible, not as late as possible. As a consequence, syncLoop() without parameters initiates a sync polling frenzy, with the client sending a new request as soon as the previous returns, while the server returns the request as soon as it practically can, not as soon as another event for the client comes around. To fix this, the default value for syncLoop() is changed to 30 seconds. The recently added msecBetween parameter is abolished; we really don't want to steer people to classic polling from long polling. --- lib/connection.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 0fb301d1..b702c1f9 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -57,7 +57,6 @@ #include #include #include -#include #include #include #include @@ -518,23 +517,21 @@ void Connection::sync(int timeout) }); } -void Connection::syncLoop(int timeout, int msecBetween) +void Connection::syncLoop(int timeout) { if (d->syncLoopConnection && d->syncTimeout == timeout) { qCInfo(MAIN) << "Attempt to run sync loop but there's one already " "running; nothing will be done"; return; } - std::swap(d->syncTimeout, timeout); // swap() is for the nice log below + std::swap(d->syncTimeout, timeout); if (d->syncLoopConnection) { qCInfo(MAIN) << "Timeout for next syncs changed from" << timeout << "to" << d->syncTimeout; } else { - d->syncLoopConnection = - connect(this, &Connection::syncDone, this, [this, msecBetween] { - QTimer::singleShot(msecBetween, this, - &Connection::syncLoopIteration); - }); + d->syncLoopConnection = connect(this, &Connection::syncDone, + this, &Connection::syncLoopIteration, + Qt::QueuedConnection); syncLoopIteration(); // initial sync to start the loop } } -- cgit v1.2.3 From 9f9577ccdebad84faf96766f8e5b07e2f2b605c5 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 21 Jul 2020 18:24:45 +0200 Subject: Connection: tolerate an empty set of room versions Closes #314. --- lib/connection.cpp | 72 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 34 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b702c1f9..46494a56 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -357,23 +357,24 @@ void Connection::assumeIdentity(const QString& userId, void Connection::reloadCapabilities() { d->capabilitiesJob = callApi(BackgroundRequest); - connect(d->capabilitiesJob, &BaseJob::finished, this, [this] { - if (d->capabilitiesJob->error() == BaseJob::Success) - d->capabilities = d->capabilitiesJob->capabilities(); - else if (d->capabilitiesJob->error() == BaseJob::IncorrectRequestError) - qCDebug(MAIN) << "Server doesn't support /capabilities"; - - if (!d->capabilities.roomVersions) { - qCWarning(MAIN) << "Pinning supported room version to 1"; - d->capabilities.roomVersions = { "1", { { "1", "stable" } } }; - } else { + connect(d->capabilitiesJob, &BaseJob::success, this, [this] { + d->capabilities = d->capabilitiesJob->capabilities(); + + if (d->capabilities.roomVersions) { qCDebug(MAIN) << "Room versions:" << defaultRoomVersion() << "is default, full list:" << availableRoomVersions(); - } - Q_ASSERT(d->capabilities.roomVersions.has_value()); - emit capabilitiesLoaded(); - for (auto* r : qAsConst(d->roomMap)) - r->checkVersion(); + emit capabilitiesLoaded(); + for (auto* r: std::as_const(d->roomMap)) + r->checkVersion(); + } else + qCWarning(MAIN) + << "The server returned an empty set of supported versions;" + " disabling version upgrade recommendations to reduce noise"; + }); + connect(d->capabilitiesJob, &BaseJob::failure, this, [this] { + if (d->capabilitiesJob->error() == BaseJob::IncorrectRequestError) + qCDebug(MAIN) << "Server doesn't support /capabilities;" + " version upgrade recommendations won't be issued"; }); } @@ -1685,18 +1686,20 @@ const QString Connection::SupportedRoomVersion::StableTag = QString Connection::defaultRoomVersion() const { - Q_ASSERT(d->capabilities.roomVersions.has_value()); - return d->capabilities.roomVersions->defaultVersion; + return d->capabilities.roomVersions + ? d->capabilities.roomVersions->defaultVersion + : QString(); } QStringList Connection::stableRoomVersions() const { - Q_ASSERT(d->capabilities.roomVersions.has_value()); QStringList l; - const auto& allVersions = d->capabilities.roomVersions->available; - for (auto it = allVersions.begin(); it != allVersions.end(); ++it) - if (it.value() == SupportedRoomVersion::StableTag) - l.push_back(it.key()); + if (d->capabilities.roomVersions) { + const auto& allVersions = d->capabilities.roomVersions->available; + for (auto it = allVersions.begin(); it != allVersions.end(); ++it) + if (it.value() == SupportedRoomVersion::StableTag) + l.push_back(it.key()); + } return l; } @@ -1711,18 +1714,19 @@ inline bool roomVersionLess(const Connection::SupportedRoomVersion& v1, QVector Connection::availableRoomVersions() const { - Q_ASSERT(d->capabilities.roomVersions.has_value()); QVector result; - result.reserve(d->capabilities.roomVersions->available.size()); - for (auto it = d->capabilities.roomVersions->available.begin(); - it != d->capabilities.roomVersions->available.end(); ++it) - result.push_back({ it.key(), it.value() }); - // Put stable versions over unstable; within each group, - // sort numeric versions as numbers, the rest as strings. - const auto mid = std::partition(result.begin(), result.end(), - std::mem_fn(&SupportedRoomVersion::isStable)); - std::sort(result.begin(), mid, roomVersionLess); - std::sort(mid, result.end(), roomVersionLess); - + if (d->capabilities.roomVersions) { + const auto& allVersions = d->capabilities.roomVersions->available; + result.reserve(allVersions.size()); + for (auto it = allVersions.begin(); it != allVersions.end(); ++it) + result.push_back({ it.key(), it.value() }); + // Put stable versions over unstable; within each group, + // sort numeric versions as numbers, the rest as strings. + const auto mid = + std::partition(result.begin(), result.end(), + std::mem_fn(&SupportedRoomVersion::isStable)); + std::sort(result.begin(), mid, roomVersionLess); + std::sort(mid, result.end(), roomVersionLess); + } return result; } -- cgit v1.2.3 From 68a4875c047611e7f58ed001dc9ec4231a11728a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 4 Aug 2020 13:30:21 +0200 Subject: Connection: stop the sync loop on SyncJob::failure --- lib/connection.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 46494a56..4530d95a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -508,7 +508,9 @@ void Connection::sync(int timeout) retriesTaken, nextInMilliseconds); }); connect(job, &SyncJob::failure, this, [this, job] { - d->syncJob = nullptr; + // SyncJob persists with retries on transient errors; if it fails, + // there's likely something serious enough to stop the loop. + stopSync(); if (job->error() == BaseJob::Unauthorised) { qCWarning(SYNCJOB) << "Sync job failed with Unauthorised - login expired?"; @@ -742,7 +744,8 @@ void Connection::stopSync() disconnect(d->syncLoopConnection); if (d->syncJob) // If there's an ongoing sync job, stop it too { - d->syncJob->abandon(); + if (d->syncJob->status().code == BaseJob::Pending) + d->syncJob->abandon(); d->syncJob = nullptr; } } -- cgit v1.2.3 From 13153e65276ae35cbfd02fdbb120c44f4051a2aa Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 4 Aug 2020 17:20:32 +0200 Subject: Connection: self-delete after emitting loggedOut() The Connection object has quite few uses after logging out - neither rooms nor users under it no more represent actual situation, and the object cannot be cleanly reused for a new login (also, the use case for that is pretty dubious). This doesn't cover the case when the session has been forcibly logged-out by the server (causing loginError() to be emitted) - in that case re-authentication is an expected flow. --- lib/connection.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4530d95a..97805fe7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -471,6 +471,7 @@ void Connection::logout() disconnect(d->syncLoopConnection); d->data->setToken({}); emit loggedOut(); + deleteLater(); } else { // logout() somehow didn't proceed - restore the session state emit stateChanged(); if (wasSyncing) -- cgit v1.2.3 From 5eff546aea4f15f7c60af40b452661d058bcabf7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 4 Sep 2020 22:46:22 +0200 Subject: Fixes of clazy warnings --- lib/connection.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 97805fe7..d3324d47 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -931,7 +931,7 @@ void Connection::doInDirectChat(User* u, // There can be more than one DC; find the first valid (existing and // not left), and delete inexistent (forgotten?) ones along the way. DirectChatsMap removals; - for (auto it = d->directChats.find(u); + for (auto it = std::as_const(d->directChats).find(u); it != d->directChats.end() && it.key() == u; ++it) { const auto& roomId = *it; if (auto r = room(roomId, JoinState::Join)) { @@ -1232,7 +1232,7 @@ int Connection::roomsCount(JoinStates joinStates) const { // Using int to maintain compatibility with QML // (consider also that QHash<>::size() returns int anyway). - return int(std::count_if(d->roomMap.begin(), d->roomMap.end(), + return int(std::count_if(d->roomMap.cbegin(), d->roomMap.cend(), [joinStates](Room* r) { return joinStates.testFlag(r->joinState()); })); @@ -1297,7 +1297,8 @@ QStringList Connection::tagNames() const QVector Connection::roomsWithTag(const QString& tagName) const { QVector rooms; - std::copy_if(d->roomMap.begin(), d->roomMap.end(), std::back_inserter(rooms), + std::copy_if(d->roomMap.cbegin(), d->roomMap.cend(), + std::back_inserter(rooms), [&tagName](Room* r) { return r->tags().contains(tagName); }); return rooms; } -- cgit v1.2.3 From e00b3674f9fd8b606ea4d293dfb078ed4a621e12 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 11 Sep 2020 06:50:45 +0200 Subject: More stringent serverpart checks in user ids May lead to new crashes due to nullptr returned from Connection::user() on more utterly invalid content from the wire that the library still doesn't properly invalidate. This has long been quite a good case for exceptions, or another error-handling framework: Connection::user() can return nullptr either when out of memory or when the id is invalid or empty, and other places are likely to treat invalid ids in different ways but probably just hope that memory exhaustion "never happens", or try to handle it in a quite different way than an empty or invalid id. Something to think of in 0.7. (cherry picked from commit 3c85f049389dec3b0ee6406f0be2cfaf0089f1fe) --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d3324d47..0f53643b 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1151,7 +1151,7 @@ User* Connection::user(const QString& uId) { if (uId.isEmpty()) return nullptr; - if (!uId.startsWith('@') || !uId.contains(':')) { + if (!uId.startsWith('@') || serverPart(uId).isEmpty()) { qCCritical(MAIN) << "Malformed userId:" << uId; return nullptr; } -- cgit v1.2.3 From 4cc8838c3bbe712493a4f6ddbecd0f7093e907bb Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 12 Nov 2020 18:51:35 +0100 Subject: More JSON key constants --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 0f53643b..e84b8080 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -202,7 +202,7 @@ public: auto&& decryptedEvent = fromJson(QJsonDocument::fromJson(decrypted.toUtf8())); - if (auto sender = decryptedEvent->fullJson()["sender"_ls].toString(); + if (auto sender = decryptedEvent->fullJson()[SenderKeyL].toString(); sender != encryptedEvent.senderId()) { qCWarning(E2EE) << "Found user" << sender << "instead of sender" << encryptedEvent.senderId() -- cgit v1.2.3 From 3ef036cd5936df4c0324ef54aa2bc11d745bd4c7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 23 Dec 2020 17:18:12 +0100 Subject: Connection::resolveServer(): fix error handling Part of the fix for #421. (cherry picked from commit 104356d945671762af23e346f7898a3208770d97) --- lib/connection.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index e84b8080..386c6564 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -270,8 +270,8 @@ void Connection::resolveServer(const QString& mxid) d->data->setBaseUrl(maybeBaseUrl); // Just enough to check .well-known file d->resolverJob = callApi(); connect(d->resolverJob, &BaseJob::finished, this, [this, maybeBaseUrl] { - if (d->resolverJob->status() != BaseJob::NotFoundError) { - if (d->resolverJob->status() != BaseJob::Success) { + if (d->resolverJob->error() != BaseJob::NotFoundError) { + if (!d->resolverJob->status().good()) { qCWarning(MAIN) << "Fetching .well-known file failed, FAIL_PROMPT"; emit resolveError(tr("Failed resolving the homeserver")); @@ -301,8 +301,7 @@ void Connection::resolveServer(const QString& mxid) &Connection::resolved); connect(d->loginFlowsJob, &BaseJob::failure, this, [this] { qCWarning(MAIN) << "Homeserver base URL sanity check failed"; - emit resolveError( - tr("The homeserver base URL doesn't seem to work")); + emit resolveError(tr("The homeserver doesn't seem to be working")); }); }); } -- cgit v1.2.3 From e617f0151df9a5edbefeb2c36d306a2989a278af Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 24 Dec 2020 18:20:04 +0100 Subject: Fix clang-tidy/clazy warnings (cherry picked from commit 0a2acd750a4155969092be674ed3dd9a71b2354f) --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 386c6564..b76ca691 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -930,8 +930,8 @@ void Connection::doInDirectChat(User* u, // There can be more than one DC; find the first valid (existing and // not left), and delete inexistent (forgotten?) ones along the way. DirectChatsMap removals; - for (auto it = std::as_const(d->directChats).find(u); - it != d->directChats.end() && it.key() == u; ++it) { + for (auto it = d->directChats.constFind(u); + it != d->directChats.cend() && it.key() == u; ++it) { const auto& roomId = *it; if (auto r = room(roomId, JoinState::Join)) { Q_ASSERT(r->id() == roomId); -- cgit v1.2.3 From cd9c9296bb1ac7af7ebbbf66931e731dbf581bc8 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sat, 26 Dec 2020 14:54:31 +0100 Subject: Port existing copyright statement to reuse using licensedigger --- lib/connection.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b76ca691..8f95f3a6 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2015 Felix Rohrbach + * SPDX-FileCopyrightText: 2015 Felix Rohrbach * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "connection.h" -- cgit v1.2.3 From 7c29f33121f58a52f43fa83183eaca47fa374980 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 27 Dec 2020 18:33:51 +0100 Subject: More comments/documentation Notably, recommend using loginFlowsChanged() rather than homeserverChanged() to detect when a Connection object is ready for a login sequence. Related: #427. --- lib/connection.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b76ca691..694e4f16 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -345,11 +345,10 @@ void Connection::loginWithToken(const QByteArray& loginToken, loginToken, deviceId, initialDeviceName); } -void Connection::assumeIdentity(const QString& userId, - const QString& accessToken, +void Connection::assumeIdentity(const QString& mxId, const QString& accessToken, const QString& deviceId) { - checkAndConnect(userId, + checkAndConnect(mxId, [=] { d->assumeIdentity(userId, accessToken, deviceId); }); } -- cgit v1.2.3 From 6c9ff40dbd91cc4966f0ecf9ed817efc2495a2fb Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 27 Dec 2020 21:13:13 +0100 Subject: Connection: refactor the resolve/login code 1. resolveServer() now emits homeserverChanged() even when there's no .well-known file found. 2. checkAndConnect() entirely removed from the header file. 3. Sunny-day scenario for assumeIdentity() is now asynchronous, triggering a call to /whoami to double-check the user. 4. LoginFlow aliases is moved out from LoginFlows to Quotient namespace. --- lib/connection.cpp | 109 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 30 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 694e4f16..f59d2962 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -37,6 +37,7 @@ #include "csapi/versions.h" #include "csapi/voip.h" #include "csapi/wellknown.h" +#include "csapi/whoami.h" #include "events/directchatevent.h" #include "events/eventloader.h" @@ -133,10 +134,27 @@ public: != "json"; bool lazyLoading = false; + /** \brief Check the homeserver and resolve it if needed, before connecting + * + * A single entry for functions that need to check whether the homeserver + * is valid before running. May execute connectFn either synchronously + * or asynchronously. In case of errors, emits resolveError() if + * the homeserver URL is not valid and cannot be resolved from userId, or + * the homeserver doesn't support the requested login flow. + * + * \param userId fully-qualified MXID to resolve HS from + * \param connectFn a function to execute once the HS URL is good + * \param flow optionally, a login flow that should be supported for + * connectFn to work; `none`, if there's no login flow + * requirements + * \sa resolveServer, resolveError + */ + void checkAndConnect(const QString &userId, + const std::function &connectFn, + const std::optional &flow = none); template void loginToServer(LoginArgTs&&... loginArgs); - void assumeIdentity(const QString& userId, const QString& accessToken, - const QString& deviceId); + void completeSetup(const QString &mxId); void removeRoom(const QString& roomId); void consumeRoomData(SyncDataList&& roomDataList, bool fromCache); @@ -264,12 +282,15 @@ void Connection::resolveServer(const QString& mxid) return; } - auto domain = maybeBaseUrl.host(); - qCDebug(MAIN) << "Finding the server" << domain; + qCDebug(MAIN) << "Finding the server" << maybeBaseUrl.host(); - d->data->setBaseUrl(maybeBaseUrl); // Just enough to check .well-known file + const auto& oldBaseUrl = d->data->baseUrl(); + d->data->setBaseUrl(maybeBaseUrl); // Temporarily set it for this one call d->resolverJob = callApi(); - connect(d->resolverJob, &BaseJob::finished, this, [this, maybeBaseUrl] { + connect(d->resolverJob, &BaseJob::finished, this, [this, maybeBaseUrl, oldBaseUrl] { + // Revert baseUrl so that setHomeserver() below triggers signals + // in case the base URL actually changed + d->data->setBaseUrl(oldBaseUrl); if (d->resolverJob->error() != BaseJob::NotFoundError) { if (!d->resolverJob->status().good()) { qCWarning(MAIN) @@ -297,6 +318,7 @@ void Connection::resolveServer(const QString& mxid) << "for base URL"; setHomeserver(maybeBaseUrl); } + Q_ASSERT(d->loginFlowsJob != nullptr); connect(d->loginFlowsJob, &BaseJob::success, this, &Connection::resolved); connect(d->loginFlowsJob, &BaseJob::failure, this, [this] { @@ -324,10 +346,10 @@ void Connection::loginWithPassword(const QString& userId, const QString& initialDeviceName, const QString& deviceId) { - checkAndConnect(userId, [=] { + d->checkAndConnect(userId, [=] { d->loginToServer(LoginFlows::Password.type, makeUserIdentifier(userId), password, /*token*/ "", deviceId, initialDeviceName); - }); + }, LoginFlows::Password); } SsoSession* Connection::prepareForSso(const QString& initialDeviceName, @@ -340,6 +362,7 @@ void Connection::loginWithToken(const QByteArray& loginToken, const QString& initialDeviceName, const QString& deviceId) { + Q_ASSERT(d->data->baseUrl().isValid() && d->loginFlows.contains(LoginFlows::Token)); d->loginToServer(LoginFlows::Token.type, none /*user is encoded in loginToken*/, "" /*password*/, loginToken, deviceId, initialDeviceName); @@ -348,8 +371,21 @@ void Connection::loginWithToken(const QByteArray& loginToken, void Connection::assumeIdentity(const QString& mxId, const QString& accessToken, const QString& deviceId) { - checkAndConnect(mxId, - [=] { d->assumeIdentity(userId, accessToken, deviceId); }); + d->checkAndConnect(mxId, [this, mxId, accessToken, deviceId] { + d->data->setToken(accessToken.toLatin1()); + d->data->setDeviceId(deviceId); // Can't we deduce this from access_token? + auto* job = callApi(); + connect(job, &BaseJob::success, this, [this, job, mxId] { + if (mxId != job->userId()) + qCWarning(MAIN).nospace() + << "The access_token owner (" << job->userId() + << ") is different from passed MXID (" << mxId << ")!"; + d->completeSetup(job->userId()); + }); + connect(job, &BaseJob::failure, this, [this, job] { + emit loginError(job->errorString(), job->rawDataSample()); + }); + }); } void Connection::reloadCapabilities() @@ -389,8 +425,9 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) auto loginJob = q->callApi(std::forward(loginArgs)...); connect(loginJob, &BaseJob::success, q, [this, loginJob] { - assumeIdentity(loginJob->userId(), loginJob->accessToken(), - loginJob->deviceId()); + data->setToken(loginJob->accessToken().toLatin1()); + data->setDeviceId(loginJob->deviceId()); + completeSetup(loginJob->userId()); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED @@ -403,17 +440,14 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) }); } -void Connection::Private::assumeIdentity(const QString& userId, - const QString& accessToken, - const QString& deviceId) +void Connection::Private::completeSetup(const QString& mxId) { - data->setUserId(userId); + data->setUserId(mxId); q->user(); // Creates a User object for the local user - data->setToken(accessToken.toLatin1()); - data->setDeviceId(deviceId); - q->setObjectName(userId % '/' % deviceId); + q->setObjectName(data->userId() % '/' % data->deviceId()); qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() - << "by user" << userId << "from device" << deviceId; + << "by user" << data->userId() + << "from device" << data->deviceId(); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED @@ -430,22 +464,37 @@ void Connection::Private::assumeIdentity(const QString& userId, q->reloadCapabilities(); } -void Connection::checkAndConnect(const QString& userId, - std::function connectFn) +void Connection::Private::checkAndConnect(const QString& userId, + const std::function& connectFn, + const std::optional& flow) { - if (d->data->baseUrl().isValid()) { + if (data->baseUrl().isValid() && (!flow || loginFlows.contains(*flow))) { connectFn(); return; } - // Not good to go, try to fix the homeserver URL. + // Not good to go, try to ascertain the homeserver URL and flows if (userId.startsWith('@') && userId.indexOf(':') != -1) { - connectSingleShot(this, &Connection::homeserverChanged, this, connectFn); - // NB: doResolveServer can emit resolveError, so this is a part of - // checkAndConnect function contract. - resolveServer(userId); + q->resolveServer(userId); + if (flow) + connectSingleShot(q, &Connection::loginFlowsChanged, q, + [this, flow, connectFn] { + if (loginFlows.contains(*flow)) + connectFn(); + else + emit q->loginError( + tr("The homeserver at %1 does not support" + " the login flow '%2'") + .arg(data->baseUrl().toDisplayString()), + flow->type); + }); + else + connectSingleShot(q, &Connection::homeserverChanged, q, connectFn); } else - emit resolveError(tr("%1 is an invalid homeserver URL") - .arg(d->data->baseUrl().toString())); + emit q->resolveError(tr("Please provide the fully-qualified user id" + " (such as @user:example.org) so that the" + " homeserver could be resolved; the current" + " homeserver URL(%1) is not good") + .arg(data->baseUrl().toDisplayString())); } void Connection::logout() -- cgit v1.2.3 From 0f974c0f96f29035ee766e8913504fed4a4903a5 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 28 Dec 2020 19:18:34 +0100 Subject: Connection: fix FTBFS with Quotient_E2EE_ENABLED --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index f59d2962..42b17570 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -451,7 +451,7 @@ void Connection::Private::completeSetup(const QString& mxId) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED - AccountSettings accountSettings(userId); + AccountSettings accountSettings(data->userId()); encryptionManager.reset( new EncryptionManager(accountSettings.encryptionAccountPickle())); if (accountSettings.encryptionAccountPickle().isEmpty()) { -- cgit v1.2.3 From 8fc5de0529458851a4cd5c042b2b2f2543068c22 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 7 Jan 2021 20:16:59 +0100 Subject: Prefer connecting to BaseJob::result(), not finished() ...because finished() includes abandoning and should only be relevant when lifecycle issues are involved. (cherry picked from commit 90d41b697af39253483d9bcca4e57b11d2197112) --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 42b17570..9afdfe7e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1555,7 +1555,7 @@ void Connection::setHomeserver(const QUrl& url) // Whenever a homeserver is updated, retrieve available login flows from it d->loginFlowsJob = callApi(BackgroundRequest); - connect(d->loginFlowsJob, &BaseJob::finished, this, [this] { + connect(d->loginFlowsJob, &BaseJob::result, this, [this] { if (d->loginFlowsJob->status().good()) d->loginFlows = d->loginFlowsJob->flows(); else -- cgit v1.2.3 From 6af9ae29cb3c29e8e196d303409da369d23c3450 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 7 Jan 2021 21:22:55 +0100 Subject: Connection::resolveServer: abandon is not a failure So just reset the base URL and return, with no error signals. (cherry picked from commit be00308ad67286b45912202750fe49fb87f16e4a) --- lib/connection.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 9afdfe7e..b8b131bf 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -287,10 +287,14 @@ void Connection::resolveServer(const QString& mxid) const auto& oldBaseUrl = d->data->baseUrl(); d->data->setBaseUrl(maybeBaseUrl); // Temporarily set it for this one call d->resolverJob = callApi(); + // Connect to finished() to make sure baseUrl is restored in any case connect(d->resolverJob, &BaseJob::finished, this, [this, maybeBaseUrl, oldBaseUrl] { // Revert baseUrl so that setHomeserver() below triggers signals // in case the base URL actually changed d->data->setBaseUrl(oldBaseUrl); + if (d->resolverJob->error() == BaseJob::Abandoned) + return; + if (d->resolverJob->error() != BaseJob::NotFoundError) { if (!d->resolverJob->status().good()) { qCWarning(MAIN) @@ -318,7 +322,7 @@ void Connection::resolveServer(const QString& mxid) << "for base URL"; setHomeserver(maybeBaseUrl); } - Q_ASSERT(d->loginFlowsJob != nullptr); + Q_ASSERT(d->loginFlowsJob != nullptr); // Ensured by setHomeserver() connect(d->loginFlowsJob, &BaseJob::success, this, &Connection::resolved); connect(d->loginFlowsJob, &BaseJob::failure, this, [this] { -- cgit v1.2.3 From 78a3137920d9680072dc3796dd90f849e8467fd4 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 7 Jan 2021 21:32:07 +0100 Subject: isJobRunning() -> isJobPending() To be very clear what this function checks. See also #437. --- lib/connection.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b8b131bf..b8294393 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -271,7 +271,7 @@ Connection::~Connection() void Connection::resolveServer(const QString& mxid) { - if (isJobRunning(d->resolverJob)) + if (isJobPending(d->resolverJob)) d->resolverJob->abandon(); auto maybeBaseUrl = QUrl::fromUserInput(serverPart(mxid)); @@ -1229,7 +1229,7 @@ QByteArray Connection::accessToken() const { // The logout job needs access token to do its job; so the token is // kept inside d->data but no more exposed to the outside world. - return isJobRunning(d->logoutJob) ? QByteArray() : d->data->accessToken(); + return isJobPending(d->logoutJob) ? QByteArray() : d->data->accessToken(); } bool Connection::isLoggedIn() const { return !accessToken().isEmpty(); } @@ -1544,10 +1544,10 @@ QByteArray Connection::generateTxnId() const void Connection::setHomeserver(const QUrl& url) { - if (isJobRunning(d->resolverJob)) + if (isJobPending(d->resolverJob)) d->resolverJob->abandon(); d->resolverJob = nullptr; - if (isJobRunning(d->loginFlowsJob)) + if (isJobPending(d->loginFlowsJob)) d->loginFlowsJob->abandon(); d->loginFlowsJob = nullptr; d->loginFlows.clear(); -- cgit v1.2.3 From 6101971af86fdecd084759aa039b9d20a9d662a7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 7 Jan 2021 21:39:28 +0100 Subject: Connection: don't explicitly reset QPointers See #437 for the discussion. --- lib/connection.cpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b8294393..fce135ed 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1546,10 +1546,8 @@ void Connection::setHomeserver(const QUrl& url) { if (isJobPending(d->resolverJob)) d->resolverJob->abandon(); - d->resolverJob = nullptr; if (isJobPending(d->loginFlowsJob)) d->loginFlowsJob->abandon(); - d->loginFlowsJob = nullptr; d->loginFlows.clear(); if (homeserver() != url) { -- cgit v1.2.3 From 0a775d9b3209be15dea8b8915fc0a1c8e0046ba6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 16 Jan 2021 18:19:45 +0100 Subject: Updated copyright statements upon Git audit After going through all the files and the history of commits on them it was clear that some copyright statements are obsolete (the code has been overwritten since) and some are missing. This commit tries best to remedy that, along with adding SPDX tags where they were still not used. Also, a minimal SPDX convention is documented for further contributions. Closes #426. --- lib/connection.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 015e73c9..d773f0d8 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1,8 +1,8 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-FileCopyrightText: 2017 Roman Plášil +// SPDX-FileCopyrightText: 2019 Ville Ranki +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// SPDX-License-Identifier: LGPL-2.1-or-later #include "connection.h" -- cgit v1.2.3 From f5862e8b6fccb85f7080ccddcdea436540588b36 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 26 Feb 2021 15:00:55 +0100 Subject: Add public method to determine if we can change the user password --- lib/connection.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d773f0d8..91de2e4e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1745,6 +1745,15 @@ QStringList Connection::stableRoomVersions() const return l; } +bool Connection::canChangePassword() const +{ + if (!d->capabilities.changePassword) { + // assume we can + return true; + } + return d->capabilities.changePassword->enabled; +} + inline bool roomVersionLess(const Connection::SupportedRoomVersion& v1, const Connection::SupportedRoomVersion& v2) { -- cgit v1.2.3 From 1335534aa71519ce55bee226ac67c1b0188f4b4b Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 26 Feb 2021 18:04:14 +0100 Subject: Apply suggestion Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 91de2e4e..579c7920 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1747,11 +1747,10 @@ QStringList Connection::stableRoomVersions() const bool Connection::canChangePassword() const { - if (!d->capabilities.changePassword) { - // assume we can - return true; - } - return d->capabilities.changePassword->enabled; + // By default assume we can + return d->capabilities.changePassword + ? d->capabilities.changePassword->enabled + : true; } inline bool roomVersionLess(const Connection::SupportedRoomVersion& v1, -- cgit v1.2.3 From 0c8a819f8a3c31360e3f6044374aa219e93ec21e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 8 May 2021 10:07:54 +0200 Subject: Fix joinedRoom signal not being emitted in some cases An alternative implementation of #463 (and thanks to Carl for spotting the original problem). --- lib/connection.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 579c7920..a384783c 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -807,7 +807,7 @@ JoinRoomJob* Connection::joinRoom(const QString& roomAlias, // that may add their own slots to finished(). connect(job, &BaseJob::finished, this, [this, job] { if (job->status().good()) - provideRoom(job->roomId()); + provideRoom(job->roomId(), JoinState::Join); }); return job; } @@ -1464,11 +1464,13 @@ Room* Connection::provideRoom(const QString& id, Omittable joinState) room = d->roomMap.value({ id, true }, nullptr); if (room) return room; - // No Invite either, setup a new room object below + // No Invite either, setup a new room object in Join state + joinState = JoinState::Join; } if (!room) { - room = roomFactory()(this, id, joinState.value_or(JoinState::Join)); + Q_ASSERT(joinState.has_value()); + room = roomFactory()(this, id, *joinState); if (!room) { qCCritical(MAIN) << "Failed to create a room" << id; return nullptr; -- cgit v1.2.3 From 0571ba1fb1948a6cc050230a85201291ababbf04 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 7 Jun 2021 07:55:23 +0200 Subject: Connection::joinRoom() shouldn't enforce room state This is an adjustment to the earlier fix of #471: if a join is immediately followed by a leave (e.g. from another client/bot - you can't do it programmatically from libQuotient) the sync may bring the room already in the Leave state; therefore `joinRoom` should not impose the state but rather ask `provideRoom` to create a `Join` room - just as it's designed when passed an empty `joinState`. --- lib/connection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index a384783c..55067bb7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -800,14 +800,14 @@ PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) JoinRoomJob* Connection::joinRoom(const QString& roomAlias, const QStringList& serverNames) { - auto job = callApi(roomAlias, serverNames); - // Upon completion, ensure a room object in Join state is created - // (or it might already be there due to a sync completing earlier). - // finished() is used here instead of success() to overtake clients - // that may add their own slots to finished(). + auto* const job = callApi(roomAlias, serverNames); + // Upon completion, ensure a room object is created in case it hasn't come + // with a sync yet. If the room object is not there, provideRoom() will + // create it in Join state. finished() is used here instead of success() + // to overtake clients that may add their own slots to finished(). connect(job, &BaseJob::finished, this, [this, job] { if (job->status().good()) - provideRoom(job->roomId(), JoinState::Join); + provideRoom(job->roomId()); }); return job; } -- cgit v1.2.3 From c18a591b484db451eb084ec4f1f17057813800df Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 12 Jun 2021 20:51:17 +0200 Subject: Adjust to new moc/QMetaType requirements See https://www.qt.io/blog/whats-new-in-qmetatype-qvariant#qmetatype-knows-your-properties-and-methods-types --- lib/connection.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 55067bb7..b3006084 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -14,6 +14,9 @@ #include "settings.h" #include "user.h" +// NB: since Qt 6, moc_connection.cpp needs Room and User fully defined +#include "moc_connection.cpp" + #include "csapi/account-data.h" #include "csapi/capabilities.h" #include "csapi/joining.h" -- cgit v1.2.3 From ff171e91877048f132955abaa617a26c63632bdf Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 12 Jun 2021 20:56:41 +0200 Subject: connection.cpp: erase_if -> remove_if erase_if is now also provided by Qt; doing pretty much the same thing, the Qt implementation only returns the number of removed entries instead of returning a collection of them, however. Worth admitting at this point that the function in connection.cpp has never had the semantics of STL's erase_if() and doesn't quite have the semantics of remove_if() either; but at least it's closer to remove_if(). --- lib/connection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b3006084..e076957a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -58,7 +58,7 @@ using namespace Quotient; // This is very much Qt-specific; STL iterators don't have key() and value() template -HashT erase_if(HashT& hashMap, Pred pred) +HashT remove_if(HashT& hashMap, Pred pred) { HashT removals; for (auto it = hashMap.begin(); it != hashMap.end();) { @@ -668,16 +668,16 @@ void Connection::Private::consumeAccountData(Events&& accountDataEvents) // https://github.com/quotient-im/libQuotient/wiki/Handling-direct-chat-events const auto& usersToDCs = dce.usersToDirectChats(); DirectChatsMap remoteRemovals = - erase_if(directChats, [&usersToDCs, this](auto it) { + remove_if(directChats, [&usersToDCs, this](auto it) { return !( usersToDCs.contains(it.key()->id(), it.value()) || dcLocalAdditions.contains(it.key(), it.value())); }); - erase_if(directChatUsers, [&remoteRemovals](auto it) { + remove_if(directChatUsers, [&remoteRemovals](auto it) { return remoteRemovals.contains(it.value(), it.key()); }); // Remove from dcLocalRemovals what the server already has. - erase_if(dcLocalRemovals, [&remoteRemovals](auto it) { + remove_if(dcLocalRemovals, [&remoteRemovals](auto it) { return remoteRemovals.contains(it.key(), it.value()); }); if (MAIN().isDebugEnabled()) @@ -705,7 +705,7 @@ void Connection::Private::consumeAccountData(Events&& accountDataEvents) << "Couldn't get a user object for" << it.key(); } // Remove from dcLocalAdditions what the server already has. - erase_if(dcLocalAdditions, [&remoteAdditions](auto it) { + remove_if(dcLocalAdditions, [&remoteAdditions](auto it) { return remoteAdditions.contains(it.key(), it.value()); }); if (!remoteAdditions.isEmpty() || !remoteRemovals.isEmpty()) @@ -1388,7 +1388,7 @@ void Connection::removeFromDirectChats(const QString& roomId, User* user) removals.insert(user, roomId); d->dcLocalRemovals.insert(user, roomId); } else { - removals = erase_if(d->directChats, + removals = remove_if(d->directChats, [&roomId](auto it) { return it.value() == roomId; }); d->directChatUsers.remove(roomId); d->dcLocalRemovals += removals; -- cgit v1.2.3 From c05b5c2b79f9ab301fee587ee781b9c8e18b8a2f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 16 Jul 2021 20:03:06 +0200 Subject: MembershipType -> Membership, also used for JoinState Instead of being defined independently, JoinState now uses values from the Membership enumeration (former MemberEventContent::MembershipType) that was moved to quotient_common.h for that purpose. Both enumerations gained a Q_FLAG_NS decoration and operator<< overrides that strip "Quotient::" prefix when dumping member/join state values to the log - obviating toCString(JoinState) along the way. Quotient::MembershipType alias is deprecated from now. --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index e076957a..7dd04aaa 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -640,7 +640,7 @@ void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, } qWarning(MAIN) << "Room" << roomData.roomId << "has just been forgotten but /sync returned it in" - << toCString(roomData.joinState) + << roomData.joinState << "state - suspiciously fast turnaround"; } if (auto* r = q->provideRoom(roomData.roomId, roomData.joinState)) { @@ -1356,7 +1356,7 @@ void Connection::Private::removeRoom(const QString& roomId) for (auto f : { false, true }) if (auto r = roomMap.take({ roomId, f })) { qCDebug(MAIN) << "Room" << r->objectName() << "in state" - << toCString(r->joinState()) << "will be deleted"; + << r->joinState() << "will be deleted"; emit r->beforeDestruction(r); r->deleteLater(); } -- cgit v1.2.3 From c26015503aa0fbca37abdfc4870ac94bb7befeee Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 20:19:15 +0200 Subject: Drop other stuff deprecated pre- or early 0.6 BaseJob: StatusCode::JsonParseError Connection: resolved() and reconnected() signals; roomMap(); postReceipt() User: bridged() and rawName() ConnectionData: setHost() and setPort() StateEventBase: prev_content() --- lib/connection.cpp | 21 --------------------- 1 file changed, 21 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 7dd04aaa..222c3b71 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -314,8 +314,6 @@ void Connection::resolveServer(const QString& mxid) setHomeserver(maybeBaseUrl); } Q_ASSERT(d->loginFlowsJob != nullptr); // Ensured by setHomeserver() - connect(d->loginFlowsJob, &BaseJob::success, this, - &Connection::resolved); connect(d->loginFlowsJob, &BaseJob::failure, this, [this] { qCWarning(MAIN) << "Homeserver base URL sanity check failed"; emit resolveError(tr("The homeserver doesn't seem to be working")); @@ -795,11 +793,6 @@ void Connection::stopSync() QString Connection::nextBatchToken() const { return d->data->lastEvent(); } -PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) -{ - return callApi(room->id(), "m.read", event->id()); -} - JoinRoomJob* Connection::joinRoom(const QString& roomAlias, const QStringList& serverNames) { @@ -1239,20 +1232,6 @@ int Connection::millisToReconnect() const return d->syncJob ? d->syncJob->millisToRetry() : 0; } -QHash, Room*> Connection::roomMap() const -{ - // Copy-on-write-and-remove-elements is faster than copying elements one by - // one. - QHash, Room*> roomMap = d->roomMap; - for (auto it = roomMap.begin(); it != roomMap.end();) { - if (it.value()->joinState() == JoinState::Leave) - it = roomMap.erase(it); - else - ++it; - } - return roomMap; -} - QVector Connection::allRooms() const { QVector result; -- cgit v1.2.3 From 2bf18a64d236c2364e12d4c2f1a9464cc6a2ebf9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 10 Sep 2021 22:38:10 +0200 Subject: Move URL creation to Room/Connection; use query instead of fragment The query is easier to manipulate; and the original mxc URL is not used for the real network request anyway. --- lib/connection.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 222c3b71..51946b2f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -836,6 +836,15 @@ inline auto splitMediaId(const QString& mediaId) return idParts; } +QUrl Connection::makeMediaUrl(QUrl mxcUrl) const +{ + Q_ASSERT(mxcUrl.scheme() == "mxc"); + QUrlQuery q(mxcUrl.query()); + q.addQueryItem(QStringLiteral("user_id"), userId()); + mxcUrl.setQuery(q); + return mxcUrl; +} + MediaThumbnailJob* Connection::getThumbnail(const QString& mediaId, QSize requestedSize, RunningPolicy policy) -- cgit v1.2.3 From 06a4fbb5c0ad0fadba1e5924f73d067850a78312 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 12 Sep 2021 04:38:23 +0200 Subject: Connection: update AccountRegistry Clients don't need to do it themselves. --- lib/connection.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 51946b2f..4abf5097 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -13,6 +13,7 @@ #include "room.h" #include "settings.h" #include "user.h" +#include "accountregistry.h" // NB: since Qt 6, moc_connection.cpp needs Room and User fully defined #include "moc_connection.cpp" @@ -258,6 +259,7 @@ Connection::~Connection() { qCDebug(MAIN) << "deconstructing connection object for" << userId(); stopSync(); + AccountRegistry::instance().drop(this); } void Connection::resolveServer(const QString& mxid) @@ -441,6 +443,7 @@ void Connection::Private::completeSetup(const QString& mxId) qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << data->userId() << "from device" << data->deviceId(); + AccountRegistry::instance().add(q); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED -- cgit v1.2.3 From 67da887e864d292608e7132388f518596374af34 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 5 Oct 2021 03:43:22 +0200 Subject: BaseJob::StatusCode: officially deprecate most *Error enumerators --- lib/connection.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4abf5097..093362ab 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -288,7 +288,7 @@ void Connection::resolveServer(const QString& mxid) if (d->resolverJob->error() == BaseJob::Abandoned) return; - if (d->resolverJob->error() != BaseJob::NotFoundError) { + if (d->resolverJob->error() != BaseJob::NotFound) { if (!d->resolverJob->status().good()) { qCWarning(MAIN) << "Fetching .well-known file failed, FAIL_PROMPT"; @@ -401,7 +401,7 @@ void Connection::reloadCapabilities() " disabling version upgrade recommendations to reduce noise"; }); connect(d->capabilitiesJob, &BaseJob::failure, this, [this] { - if (d->capabilitiesJob->error() == BaseJob::IncorrectRequestError) + if (d->capabilitiesJob->error() == BaseJob::IncorrectRequest) qCDebug(MAIN) << "Server doesn't support /capabilities;" " version upgrade recommendations won't be issued"; }); @@ -1058,7 +1058,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) connect(leaveJob, &BaseJob::result, this, [this, leaveJob, forgetJob, room] { if (leaveJob->error() == BaseJob::Success - || leaveJob->error() == BaseJob::NotFoundError) { + || leaveJob->error() == BaseJob::NotFound) { run(forgetJob); // If the matching /sync response hasn't arrived yet, // mark the room for explicit deletion @@ -1077,7 +1077,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) connect(forgetJob, &BaseJob::result, this, [this, id, forgetJob] { // Leave room in case of success, or room not known by server if (forgetJob->error() == BaseJob::Success - || forgetJob->error() == BaseJob::NotFoundError) + || forgetJob->error() == BaseJob::NotFound) d->removeRoom(id); // Delete the room from roomMap else qCWarning(MAIN).nospace() << "Error forgetting room " << id << ": " -- cgit v1.2.3 From 107471447a62663eaf97b4b982d8c3f3e1b3364e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 5 Oct 2021 03:44:43 +0200 Subject: Connection: fix C++20 warnings --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 093362ab..1fe0d2d0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -341,7 +341,7 @@ void Connection::loginWithPassword(const QString& userId, const QString& initialDeviceName, const QString& deviceId) { - d->checkAndConnect(userId, [=] { + d->checkAndConnect(userId, [=,this] { d->loginToServer(LoginFlows::Password.type, makeUserIdentifier(userId), password, /*token*/ "", deviceId, initialDeviceName); }, LoginFlows::Password); @@ -1716,7 +1716,7 @@ void Connection::getTurnServers() { auto job = callApi(); connect(job, &GetTurnServerJob::success, this, - [=] { emit turnServersChanged(job->data()); }); + [this,job] { emit turnServersChanged(job->data()); }); } const QString Connection::SupportedRoomVersion::StableTag = -- cgit v1.2.3 From 0aa70ed9b3cff3c9e0dbbea9beb419d840e42341 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 13 Oct 2021 19:57:01 +0200 Subject: Connection::resolveServer(): don't connect to loginFlowsJob Checking whether any login flows are available is a good enough measure of the homeserver actual workability. Closes #515. --- lib/connection.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1fe0d2d0..2ad10694 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -316,10 +316,6 @@ void Connection::resolveServer(const QString& mxid) setHomeserver(maybeBaseUrl); } Q_ASSERT(d->loginFlowsJob != nullptr); // Ensured by setHomeserver() - connect(d->loginFlowsJob, &BaseJob::failure, this, [this] { - qCWarning(MAIN) << "Homeserver base URL sanity check failed"; - emit resolveError(tr("The homeserver doesn't seem to be working")); - }); }); } @@ -478,10 +474,11 @@ void Connection::Private::checkAndConnect(const QString& userId, connectFn(); else emit q->loginError( + tr("Unsupported login flow"), tr("The homeserver at %1 does not support" " the login flow '%2'") - .arg(data->baseUrl().toDisplayString()), - flow->type); + .arg(data->baseUrl().toDisplayString(), + flow->type)); }); else connectSingleShot(q, &Connection::homeserverChanged, q, connectFn); -- cgit v1.2.3 From 061de37889b0fa4bf8baae1f11693950297418c5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 8 Nov 2021 14:46:56 +0100 Subject: Q_DISABLE_MOVE/COPY_MOVE; QT_IGNORE_DEPRECATIONS DISABLE_MOVE is no more; instead, the library provides Q_DISABLE_MOVE (and also Q_DISABLE_COPY_MOVE while at it) for Qt pre-5.13 that don't have it yet. Same for QT_IGNORE_DEPRECATIONS - it only arrived in 5.15 but all the building pieces existed prior so libQuotient has it regardless of the Qt version used for building. --- lib/connection.cpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 2ad10694..75966731 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -77,8 +77,6 @@ public: explicit Private(std::unique_ptr&& connection) : data(move(connection)) {} - Q_DISABLE_COPY(Private) - DISABLE_MOVE(Private) Connection* q = nullptr; std::unique_ptr data; -- cgit v1.2.3 From d6cf6b32cdd2843c40fc696accd8a6456f1ea15c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 19 Nov 2021 12:45:50 +0100 Subject: Make enum values logging more terse() By default enum class values are logged along with the qualifier; this may or may not be desirable in a given setting. For JoinState(s) and Membership(Mask) operator<< was overloaded to implicitly suppress qualification; however, this is both overly sweeping and uses Qt's internal API for the backend. Instead, a new QDebug manipulator, terse(), is introduced, that does the same as those operator<< overloads but on a per-invocation basis. This makes it slightly more verbose to log enums but makes the QDebug reconfiguration explicit and doesn't require to produce new overloads every time a new enum ends up in logs. And it's built entirely on the published Qt API, reusing the QDebugManip framework that Quotient already has. Also: operator<<(QDebug, QDebugManip) has been moved out of the namespace to fix lookup issues when there's no prior `using namespace Quotient`. --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 75966731..e65fdac4 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -636,7 +636,7 @@ void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, } qWarning(MAIN) << "Room" << roomData.roomId << "has just been forgotten but /sync returned it in" - << roomData.joinState + << terse << roomData.joinState << "state - suspiciously fast turnaround"; } if (auto* r = q->provideRoom(roomData.roomId, roomData.joinState)) { @@ -1341,7 +1341,7 @@ void Connection::Private::removeRoom(const QString& roomId) { for (auto f : { false, true }) if (auto r = roomMap.take({ roomId, f })) { - qCDebug(MAIN) << "Room" << r->objectName() << "in state" + qCDebug(MAIN) << "Room" << r->objectName() << "in state" << terse << r->joinState() << "will be deleted"; emit r->beforeDestruction(r); r->deleteLater(); -- cgit v1.2.3 From d72f220e3e3a3b243fdafd93d1405f8207dc516a Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Thu, 28 Jan 2021 23:51:56 +0300 Subject: E2EE: initial port to internal olm wrapper Remove qtolm git module. Update CMakeLists.txt. Rename olm to crypto subdir to prevent disambiguation. Rename internal files accordingly. Comment out not ported E2EE API usage. --- lib/connection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index e65fdac4..f96eeb71 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -38,7 +38,7 @@ #include "jobs/syncjob.h" #ifdef Quotient_E2EE_ENABLED -# include "account.h" // QtOlm +# include "crypto/qolmaccount.h" #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -191,7 +191,7 @@ public: return {}; const auto identityKey = - encryptionManager->account()->curve25519IdentityKey(); + encryptionManager->account()->identityKeys().curve25519; const auto personalCipherObject = encryptedEvent.ciphertext(identityKey); if (personalCipherObject.isEmpty()) { @@ -203,7 +203,7 @@ public: if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" << encryptedEvent.senderKey() - << encryptionManager->account()->oneTimeKeys(); + << encryptionManager->account()->oneTimeKeys().keys; return {}; } @@ -232,10 +232,10 @@ public: .value(Ed25519Key).toString(); if (ourKey != QString::fromUtf8( - encryptionManager->account()->ed25519IdentityKey())) { + encryptionManager->account()->identityKeys().ed25519)) { qCDebug(E2EE) << "Found key" << ourKey << "instead of ours own ed25519 key" - << encryptionManager->account()->ed25519IdentityKey() + << encryptionManager->account()->identityKeys().ed25519 << "in Olm plaintext"; return {}; } @@ -1226,7 +1226,7 @@ QByteArray Connection::accessToken() const bool Connection::isLoggedIn() const { return !accessToken().isEmpty(); } #ifdef Quotient_E2EE_ENABLED -QtOlm::Account* Connection::olmAccount() const +QOlmAccount *Connection::olmAccount() const { return d->encryptionManager->account(); } -- cgit v1.2.3 From 10b89faeea9e385ea901d45418491cd91dff99b9 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 29 Jan 2021 20:23:42 +0100 Subject: More tests --- lib/connection.cpp | 51 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 18 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index f96eeb71..704bc1b4 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -108,7 +108,8 @@ public: QVector loginFlows; #ifdef Quotient_E2EE_ENABLED - QScopedPointer encryptionManager; + std::unique_ptr olmAccount; + //QScopedPointer encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -183,6 +184,9 @@ public: EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { + qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; + return {}; + /* #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return {}; @@ -242,6 +246,7 @@ public: return std::move(decryptedEvent); #endif // Quotient_E2EE_ENABLED +*/ } }; @@ -420,8 +425,8 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED - encryptionManager->uploadIdentityKeys(q); - encryptionManager->uploadOneTimeKeys(q); + //encryptionManager->uploadIdentityKeys(q); + //encryptionManager->uploadOneTimeKeys(q); #endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { @@ -442,11 +447,19 @@ void Connection::Private::completeSetup(const QString& mxId) qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED AccountSettings accountSettings(data->userId()); - encryptionManager.reset( - new EncryptionManager(accountSettings.encryptionAccountPickle())); + + // init olmAccount + olmAccount = std::make_unique(data->userId(), data->deviceId()); + if (accountSettings.encryptionAccountPickle().isEmpty()) { - accountSettings.setEncryptionAccountPickle( - encryptionManager->olmAccountPickle()); + // create new account and save unpickle data + olmAccount->createNewAccount(); + accountSettings.setEncryptionAccountPickle(std::get(olmAccount->pickle(Unencrypted{}))); + // TODO handle pickle errors + } else { + // account already existing + auto pickle = accountSettings.encryptionAccountPickle(); + olmAccount->unpickle(pickle, Unencrypted{}); } #endif // Quotient_E2EE_ENABLED emit q->stateChanged(); @@ -608,16 +621,16 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumeToDeviceEvents(data.takeToDeviceEvents()); #ifdef Quotient_E2EE_ENABLED // handling device_one_time_keys_count - if (!d->encryptionManager) - { - qCDebug(E2EE) << "Encryption manager is not there yet, updating " - "one-time key counts will be skipped"; - return; - } - if (const auto deviceOneTimeKeysCount = data.deviceOneTimeKeysCount(); - !deviceOneTimeKeysCount.isEmpty()) - d->encryptionManager->updateOneTimeKeyCounts(this, - deviceOneTimeKeysCount); + //if (!d->encryptionManager) + //{ + // qCDebug(E2EE) << "Encryption manager is not there yet, updating " + // "one-time key counts will be skipped"; + // return; + //} + //if (const auto deviceOneTimeKeysCount = data.deviceOneTimeKeysCount(); + // !deviceOneTimeKeysCount.isEmpty()) + // d->encryptionManager->updateOneTimeKeyCounts(this, + // deviceOneTimeKeysCount); #endif // Quotient_E2EE_ENABLED } @@ -745,6 +758,7 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { +/* #ifdef Quotient_E2EE_ENABLED // handling m.room_key to-device encrypted event visitEach(toDeviceEvents, [this](const EncryptedEvent& ee) { @@ -775,6 +789,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) }); }); #endif +*/ } void Connection::stopSync() @@ -1228,7 +1243,7 @@ bool Connection::isLoggedIn() const { return !accessToken().isEmpty(); } #ifdef Quotient_E2EE_ENABLED QOlmAccount *Connection::olmAccount() const { - return d->encryptionManager->account(); + return d->olmAccount.get(); //d->encryptionManager->account(); } #endif // Quotient_E2EE_ENABLED -- cgit v1.2.3 From c2836e007e2d46c0c20270b99ede5b78d2c7170b Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 22 Feb 2021 13:51:51 +0100 Subject: ssl --- lib/connection.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 704bc1b4..62427ae1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -572,6 +572,11 @@ void Connection::sync(int timeout) }); } +void Connection::ignoreSslErrors(bool ignore) +{ + connectionData()->ignoreSslErrors(ignore); +} + void Connection::syncLoop(int timeout) { if (d->syncLoopConnection && d->syncTimeout == timeout) { -- cgit v1.2.3 From 0a75a095665101d4ffcbec10b43633eee5a0d6d3 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 6 May 2021 01:08:53 +0200 Subject: Fix everything --- lib/connection.cpp | 5 ----- 1 file changed, 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 62427ae1..704bc1b4 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -572,11 +572,6 @@ void Connection::sync(int timeout) }); } -void Connection::ignoreSslErrors(bool ignore) -{ - connectionData()->ignoreSslErrors(ignore); -} - void Connection::syncLoop(int timeout) { if (d->syncLoopConnection && d->syncTimeout == timeout) { -- cgit v1.2.3 From 65877dc9fb6e024d456343d42ef55e0c5c8b67b3 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 24 May 2021 16:52:45 +0200 Subject: Upload one-time keys when their count is low --- lib/connection.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 704bc1b4..9883b8f3 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -620,17 +620,15 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumePresenceData(data.takePresenceData()); d->consumeToDeviceEvents(data.takeToDeviceEvents()); #ifdef Quotient_E2EE_ENABLED - // handling device_one_time_keys_count - //if (!d->encryptionManager) - //{ - // qCDebug(E2EE) << "Encryption manager is not there yet, updating " - // "one-time key counts will be skipped"; - // return; - //} - //if (const auto deviceOneTimeKeysCount = data.deviceOneTimeKeysCount(); - // !deviceOneTimeKeysCount.isEmpty()) - // d->encryptionManager->updateOneTimeKeyCounts(this, - // deviceOneTimeKeysCount); + if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys()) { + d->olmAccount->generateOneTimeKeys(d->olmAccount->maxNumberOfOneTimeKeys() - data.deviceOneTimeKeysCount()["signed_curve25519"]); + auto keys = d->olmAccount->oneTimeKeys(); + auto job = d->olmAccount->createUploadKeyRequest(keys); + run(job, ForegroundRequest); + connect(job, &BaseJob::success, this, [=](){ + d->olmAccount->markKeysAsPublished(); + }); + } #endif // Quotient_E2EE_ENABLED } -- cgit v1.2.3 From 1186c9fc980f9659191df4b3f5b540befe946dfa Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 24 May 2021 18:01:27 +0200 Subject: Make sure that only one upload is running --- lib/connection.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 9883b8f3..b91a1a90 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -110,6 +110,7 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; //QScopedPointer encryptionManager; + bool isUploadingKeys = false; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -620,7 +621,8 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumePresenceData(data.takePresenceData()); d->consumeToDeviceEvents(data.takeToDeviceEvents()); #ifdef Quotient_E2EE_ENABLED - if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys()) { + if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() && !d->isUploadingKeys) { + d->isUploadingKeys = true; d->olmAccount->generateOneTimeKeys(d->olmAccount->maxNumberOfOneTimeKeys() - data.deviceOneTimeKeysCount()["signed_curve25519"]); auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); @@ -628,6 +630,9 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) connect(job, &BaseJob::success, this, [=](){ d->olmAccount->markKeysAsPublished(); }); + connect(job, &BaseJob::result, this, [=](){ + d->isUploadingKeys = false; + }); } #endif // Quotient_E2EE_ENABLED } -- cgit v1.2.3 From 211d0c1b96c13f949f50799f5a4412ae31586546 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 24 May 2021 00:40:30 +0200 Subject: Uncomment some stuff --- lib/connection.cpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b91a1a90..0c0bada6 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -109,8 +109,8 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; - //QScopedPointer encryptionManager; bool isUploadingKeys = false; + QScopedPointer encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -187,7 +187,6 @@ public: { qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return {}; - /* #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return {}; @@ -247,7 +246,6 @@ public: return std::move(decryptedEvent); #endif // Quotient_E2EE_ENABLED -*/ } }; @@ -761,7 +759,6 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { -/* #ifdef Quotient_E2EE_ENABLED // handling m.room_key to-device encrypted event visitEach(toDeviceEvents, [this](const EncryptedEvent& ee) { @@ -771,20 +768,15 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) return; } - // TODO: full maintaining of the device keys - // with device_lists sync extention and /keys/query - qCDebug(E2EE) << "Getting device keys for the m.room_key sender:" - << ee.senderId(); - // encryptionManager->updateDeviceKeys(); - visit(*sessionDecryptMessage(ee), [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { - if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) + if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); - else + } else { qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); + } }, [](const Event& evt) { qCDebug(E2EE) << "Skipping encrypted to_device event, type" @@ -792,7 +784,6 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) }); }); #endif -*/ } void Connection::stopSync() -- cgit v1.2.3 From 1d851e7b2e5e0c937413b8fd4bcdb35c8492430b Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 24 May 2021 01:51:15 +0200 Subject: Upload device keys when creating a new olm account --- lib/connection.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 0c0bada6..1485a347 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -455,6 +455,10 @@ void Connection::Private::completeSetup(const QString& mxId) olmAccount->createNewAccount(); accountSettings.setEncryptionAccountPickle(std::get(olmAccount->pickle(Unencrypted{}))); // TODO handle pickle errors + auto job = q->callApi(olmAccount->deviceKeys()); + connect(job, &BaseJob::failure, q, [=]{ + qCWarning(E2EE) << "Failed to upload device keys:" << job->errorString(); + }); } else { // account already existing auto pickle = accountSettings.encryptionAccountPickle(); -- cgit v1.2.3 From 00b6103ceeed63238cb8c691163ed8489dd72ddb Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 15 May 2021 22:44:42 +0200 Subject: Start implementing device key tracking --- lib/connection.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1485a347..66590bd8 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -102,6 +102,13 @@ public: QMetaObject::Connection syncLoopConnection {}; int syncTimeout = -1; +#ifdef Quotient_E2EE_ENABLED + QSet trackedUsers; + QSet outdatedUsers; + QHash> deviceKeys; + QueryKeysJob *currentQueryKeysJob = nullptr; +#endif + GetCapabilitiesJob* capabilitiesJob = nullptr; GetCapabilitiesJob::Capabilities capabilities; @@ -153,6 +160,7 @@ public: void consumeAccountData(Events&& accountDataEvents); void consumePresenceData(Events&& presenceData); void consumeToDeviceEvents(Events&& toDeviceEvents); + void consumeDevicesList(DevicesList&& devicesList); template EventT* unpackAccountData() const @@ -247,6 +255,10 @@ public: return std::move(decryptedEvent); #endif // Quotient_E2EE_ENABLED } +#ifdef Quotient_E2EE_ENABLED + void loadOutdatedUserDevices(); + void createDevicesList(); +#endif }; Connection::Connection(const QUrl& server, QObject* parent) @@ -468,6 +480,11 @@ void Connection::Private::completeSetup(const QString& mxId) emit q->stateChanged(); emit q->connected(); q->reloadCapabilities(); +#ifdef Quotient_E2EE_ENABLED + connectSingleShot(q, &Connection::syncDone, q, [=](){ + createDevicesList(); + }); +#endif } void Connection::Private::checkAndConnect(const QString& userId, @@ -637,6 +654,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) }); } #endif // Quotient_E2EE_ENABLED + d->consumeDevicesList(data.takeDevicesList()); } void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, @@ -790,6 +808,21 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) #endif } +void Connection::Private::consumeDevicesList(DevicesList&& devicesList) +{ +#ifdef Quotient_E2EE_ENABLED + for(const auto &changed : devicesList.changed) { + outdatedUsers += changed; + } + for(const auto &left : devicesList.left) { + trackedUsers -= left; + outdatedUsers -= left; + deviceKeys.remove(left); + } + loadOutdatedUserDevices(); +#endif +} + void Connection::stopSync() { // If there's a sync loop, break it @@ -1784,3 +1817,43 @@ QVector Connection::availableRoomVersions() co } return result; } + +#ifdef Quotient_E2EE_ENABLED +void Connection::Private::createDevicesList() +{ + for(const auto &room : q->allRooms()) { + if(!room->usesEncryption()) { + continue; + } + for(const auto &user : room->users()) { + if(user->id() != q->userId()) { + trackedUsers += user->id(); + } + } + } + outdatedUsers += trackedUsers; + loadOutdatedUserDevices(); +} + +void Connection::Private::loadOutdatedUserDevices() +{ + QHash users; + for(const auto &user : outdatedUsers) { + users[user] += QStringList(); + } + if(currentQueryKeysJob) { + currentQueryKeysJob->abandon(); + currentQueryKeysJob = nullptr; + } + auto queryKeysJob = q->callApi(users); + currentQueryKeysJob = queryKeysJob; + connect(queryKeysJob, &BaseJob::success, q, [=](){ + const auto data = queryKeysJob->deviceKeys(); + for(const auto &[user, keys] : asKeyValueRange(data)) { + //TODO Check key signature + deviceKeys[user] = keys; + outdatedUsers -= user; + } + }); +} +#endif -- cgit v1.2.3 From 1f2ae094b29aa3fc237919659ced66c6d236d068 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 16 May 2021 19:58:00 +0200 Subject: Clear current query job when it finished --- lib/connection.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 66590bd8..41dd71f6 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1848,6 +1848,7 @@ void Connection::Private::loadOutdatedUserDevices() auto queryKeysJob = q->callApi(users); currentQueryKeysJob = queryKeysJob; connect(queryKeysJob, &BaseJob::success, q, [=](){ + currentQueryKeysJob = nullptr; const auto data = queryKeysJob->deviceKeys(); for(const auto &[user, keys] : asKeyValueRange(data)) { //TODO Check key signature -- cgit v1.2.3 From e0945db3c4c539040f07ff7683efa9dc4e6b9e6a Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 18 May 2021 21:44:09 +0200 Subject: Actually load devices from sync data and filter relevant users --- lib/connection.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 41dd71f6..8ceb2a44 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -812,14 +812,18 @@ void Connection::Private::consumeDevicesList(DevicesList&& devicesList) { #ifdef Quotient_E2EE_ENABLED for(const auto &changed : devicesList.changed) { - outdatedUsers += changed; + if(trackedUsers.contains(changed)) { + outdatedUsers += changed; + } } for(const auto &left : devicesList.left) { trackedUsers -= left; outdatedUsers -= left; deviceKeys.remove(left); } - loadOutdatedUserDevices(); + if(!outdatedUsers.isEmpty()) { + loadOutdatedUserDevices(); + } #endif } -- cgit v1.2.3 From c408b460bea2010c6745e03c549e136d6b1d9ec6 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 19 May 2021 00:35:53 +0200 Subject: Start tracking user's devices when a a room starts being encrypted --- lib/connection.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 8ceb2a44..2864f3b3 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1861,4 +1861,17 @@ void Connection::Private::loadOutdatedUserDevices() } }); } + +void Connection::newEncryptedRoom(Room *room) +{ + for(const auto &user : room->users()) { + if(!d->trackedUsers.contains(user->id())) { + d->trackedUsers += user->id(); + d->outdatedUsers += user->id(); + } + } + if(!d->outdatedUsers.isEmpty()) { + d->loadOutdatedUserDevices(); + } +} #endif -- cgit v1.2.3 From f451813f21a76e8c011bbd27f4ded1d31044a572 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 19 May 2021 22:25:50 +0200 Subject: Update tracked users list when new user joins encrypted room --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 2864f3b3..b87610b7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1862,7 +1862,7 @@ void Connection::Private::loadOutdatedUserDevices() }); } -void Connection::newEncryptedRoom(Room *room) +void Connection::encryptionUpdate(Room *room) { for(const auto &user : room->users()) { if(!d->trackedUsers.contains(user->id())) { -- cgit v1.2.3 From 6449f66152396ed539904b0e89d41601aeadf30d Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 19 May 2021 23:23:46 +0200 Subject: Verify deviceKeys signatures --- lib/connection.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b87610b7..06b9bcbc 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1855,8 +1855,22 @@ void Connection::Private::loadOutdatedUserDevices() currentQueryKeysJob = nullptr; const auto data = queryKeysJob->deviceKeys(); for(const auto &[user, keys] : asKeyValueRange(data)) { - //TODO Check key signature - deviceKeys[user] = keys; + deviceKeys[user].clear(); + for(const auto &device : keys) { + if(device.userId != user) { + qCWarning(E2EE) << "mxId mismatch during device key verification:" << device.userId << user; + continue; + } + if(!device.algorithms.contains("m.olm.v1.curve25519-aes-sha2") || !device.algorithms.contains("m.megolm.v1.aes-sha2")) { + qCWarning(E2EE) << "Unsupported encryption algorithms found" << device.algorithms; + continue; + } + if(verifyIdentitySignature(device, device.deviceId, device.userId)) { + qCWarning(E2EE) << "Failed to verify devicekeys signature. Skipping this device"; + continue; + } + deviceKeys[user][device.deviceId] = device; + } outdatedUsers -= user; } }); -- cgit v1.2.3 From 5d3fe54fdfae4b74272a80c4bbe8f5d8a3e4c5cb Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 20 May 2021 15:44:02 +0200 Subject: Fix signature verification logic --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 06b9bcbc..6facd316 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1865,7 +1865,7 @@ void Connection::Private::loadOutdatedUserDevices() qCWarning(E2EE) << "Unsupported encryption algorithms found" << device.algorithms; continue; } - if(verifyIdentitySignature(device, device.deviceId, device.userId)) { + if(!verifyIdentitySignature(device, device.deviceId, device.userId)) { qCWarning(E2EE) << "Failed to verify devicekeys signature. Skipping this device"; continue; } -- cgit v1.2.3 From 265f105d77bf91c127c363b0c880357f91df7db4 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 21 May 2021 00:29:08 +0200 Subject: Cache deviceslist across restarts --- lib/connection.cpp | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 6facd316..e7a26f4b 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -258,6 +258,8 @@ public: #ifdef Quotient_E2EE_ENABLED void loadOutdatedUserDevices(); void createDevicesList(); + void saveDevicesList(); + void loadDevicesList(); #endif }; @@ -482,7 +484,7 @@ void Connection::Private::completeSetup(const QString& mxId) q->reloadCapabilities(); #ifdef Quotient_E2EE_ENABLED connectSingleShot(q, &Connection::syncDone, q, [=](){ - createDevicesList(); + loadDevicesList(); }); #endif } @@ -1873,6 +1875,7 @@ void Connection::Private::loadOutdatedUserDevices() } outdatedUsers -= user; } + saveDevicesList(); }); } @@ -1888,4 +1891,98 @@ void Connection::encryptionUpdate(Room *room) d->loadOutdatedUserDevices(); } } + +void Connection::Private::saveDevicesList() +{ + if (!cacheState) + return; + + QElapsedTimer et; + et.start(); + + QFile outFile { q->stateCacheDir().filePath("deviceslist.json") }; + if (!outFile.open(QFile::WriteOnly)) { + qCWarning(MAIN) << "Error opening" << outFile.fileName() << ":" + << outFile.errorString(); + qCWarning(MAIN) << "Caching the rooms state disabled"; + cacheState = false; + return; + } + + QJsonObject rootObj { + { QStringLiteral("cache_version"), + QJsonObject { + { QStringLiteral("major"), SyncData::cacheVersion().first }, + { QStringLiteral("minor"), SyncData::cacheVersion().second } } } + }; + { + QJsonObject trackedUsersJson; + QJsonObject outdatedUsersJson; + for (const auto &user : trackedUsers) { + trackedUsersJson.insert(user, QJsonValue::Null); + } + for (const auto &user : outdatedUsers) { + outdatedUsersJson.insert(user, QJsonValue::Null); + } + rootObj.insert(QStringLiteral("tracked_users"), trackedUsersJson); + rootObj.insert(QStringLiteral("outdated_users"), outdatedUsersJson); + QJsonObject devicesList = toJson>>(deviceKeys); + rootObj.insert(QStringLiteral("devices_list"), devicesList); + rootObj.insert(QStringLiteral("sync_token"), q->nextBatchToken()); + } + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + const auto data = + cacheToBinary ? QCborValue::fromJsonValue(rootObj).toCbor() + : QJsonDocument(rootObj).toJson(QJsonDocument::Compact); +#else + QJsonDocument json { rootObj }; + const auto data = cacheToBinary ? json.toBinaryData() + : json.toJson(QJsonDocument::Compact); +#endif + qCDebug(PROFILER) << "DeviceList generated in" << et; + + outFile.write(data.data(), data.size()); + qCDebug(E2EE) << "DevicesList saved to" << outFile.fileName(); +} + +void Connection::Private::loadDevicesList() +{ + QFile file { q->stateCacheDir().filePath("deviceslist.json") }; + if(!file.exists() || !file.open(QIODevice::ReadOnly)) { + qCDebug(E2EE) << "No devicesList cache exists. Creating new"; + createDevicesList(); + return; + } + auto data = file.readAll(); + const auto json = data.startsWith('{') + ? QJsonDocument::fromJson(data).object() +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + : QCborValue::fromCbor(data).toJsonValue().toObject() +#else + : QJsonDocument::fromBinaryData(data).object() +#endif + ; + if (json.isEmpty()) { + qCWarning(MAIN) << "DevicesList cache is broken or empty, discarding"; + createDevicesList(); + return; + } + for(const auto &user : json["tracked_users"].toArray()) { + trackedUsers += user.toString(); + } + for(const auto &user : json["outdated_users"].toArray()) { + outdatedUsers += user.toString(); + } + + deviceKeys = fromJson>>(json["devices_list"].toObject()); + auto oldToken = json["sync_token"].toString(); + auto changesJob = q->callApi(oldToken, q->nextBatchToken()); + connect(changesJob, &BaseJob::success, q, [=](){ + for(const auto &user : changesJob->changed()) { + outdatedUsers += user; + } + loadOutdatedUserDevices(); + }); +} #endif -- cgit v1.2.3 From a2b65a3abb635a478555b61de33cb5257d8dd34e Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 8 Jun 2021 21:38:26 +0200 Subject: Query for keys less and actually load users from cache --- lib/connection.cpp | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index e7a26f4b..35b2f7fc 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -482,11 +482,6 @@ void Connection::Private::completeSetup(const QString& mxId) emit q->stateChanged(); emit q->connected(); q->reloadCapabilities(); -#ifdef Quotient_E2EE_ENABLED - connectSingleShot(q, &Connection::syncDone, q, [=](){ - loadDevicesList(); - }); -#endif } void Connection::Private::checkAndConnect(const QString& userId, @@ -636,11 +631,6 @@ QJsonObject toJson(const DirectChatsMap& directChats) void Connection::onSyncSuccess(SyncData&& data, bool fromCache) { - d->data->setLastEvent(data.nextBatch()); - d->consumeRoomData(data.takeRoomData(), fromCache); - d->consumeAccountData(data.takeAccountData()); - d->consumePresenceData(data.takePresenceData()); - d->consumeToDeviceEvents(data.takeToDeviceEvents()); #ifdef Quotient_E2EE_ENABLED if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() && !d->isUploadingKeys) { d->isUploadingKeys = true; @@ -655,8 +645,19 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->isUploadingKeys = false; }); } -#endif // Quotient_E2EE_ENABLED + static bool first = true; + if(first) { + d->loadDevicesList(); + first = false; + } + d->consumeDevicesList(data.takeDevicesList()); +#endif // Quotient_E2EE_ENABLED + d->data->setLastEvent(data.nextBatch()); + d->consumeRoomData(data.takeRoomData(), fromCache); + d->consumeAccountData(data.takeAccountData()); + d->consumePresenceData(data.takePresenceData()); + d->consumeToDeviceEvents(data.takeToDeviceEvents()); } void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, @@ -813,9 +814,11 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) void Connection::Private::consumeDevicesList(DevicesList&& devicesList) { #ifdef Quotient_E2EE_ENABLED + bool hasNewOutdatedUser = false; for(const auto &changed : devicesList.changed) { if(trackedUsers.contains(changed)) { outdatedUsers += changed; + hasNewOutdatedUser = true; } } for(const auto &left : devicesList.left) { @@ -823,7 +826,7 @@ void Connection::Private::consumeDevicesList(DevicesList&& devicesList) outdatedUsers -= left; deviceKeys.remove(left); } - if(!outdatedUsers.isEmpty()) { + if(hasNewOutdatedUser) { loadOutdatedUserDevices(); } #endif @@ -1881,13 +1884,15 @@ void Connection::Private::loadOutdatedUserDevices() void Connection::encryptionUpdate(Room *room) { + bool hasNewOutdatedUser = false; for(const auto &user : room->users()) { if(!d->trackedUsers.contains(user->id())) { d->trackedUsers += user->id(); d->outdatedUsers += user->id(); + hasNewOutdatedUser = true; } } - if(!d->outdatedUsers.isEmpty()) { + if(hasNewOutdatedUser) { d->loadOutdatedUserDevices(); } } @@ -1916,13 +1921,13 @@ void Connection::Private::saveDevicesList() { QStringLiteral("minor"), SyncData::cacheVersion().second } } } }; { - QJsonObject trackedUsersJson; - QJsonObject outdatedUsersJson; + QJsonArray trackedUsersJson; + QJsonArray outdatedUsersJson; for (const auto &user : trackedUsers) { - trackedUsersJson.insert(user, QJsonValue::Null); + trackedUsersJson += user; } for (const auto &user : outdatedUsers) { - outdatedUsersJson.insert(user, QJsonValue::Null); + outdatedUsersJson += user; } rootObj.insert(QStringLiteral("tracked_users"), trackedUsersJson); rootObj.insert(QStringLiteral("outdated_users"), outdatedUsersJson); @@ -1979,10 +1984,14 @@ void Connection::Private::loadDevicesList() auto oldToken = json["sync_token"].toString(); auto changesJob = q->callApi(oldToken, q->nextBatchToken()); connect(changesJob, &BaseJob::success, q, [=](){ + bool hasNewOutdatedUser = false; for(const auto &user : changesJob->changed()) { outdatedUsers += user; + hasNewOutdatedUser = true; + } + if(hasNewOutdatedUser) { + loadOutdatedUserDevices(); } - loadOutdatedUserDevices(); }); } #endif -- cgit v1.2.3 From d997bbf54e755c42b62fadca8ee63b27aa0e7480 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 9 Jun 2021 15:59:10 +0200 Subject: More fixes --- lib/connection.cpp | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 35b2f7fc..9ea09258 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -107,6 +107,7 @@ public: QSet outdatedUsers; QHash> deviceKeys; QueryKeysJob *currentQueryKeysJob = nullptr; + bool encryptionUpdateRequired = false; #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -257,7 +258,6 @@ public: } #ifdef Quotient_E2EE_ENABLED void loadOutdatedUserDevices(); - void createDevicesList(); void saveDevicesList(); void loadDevicesList(); #endif @@ -658,6 +658,10 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumeAccountData(data.takeAccountData()); d->consumePresenceData(data.takePresenceData()); d->consumeToDeviceEvents(data.takeToDeviceEvents()); + if(d->encryptionUpdateRequired) { + d->loadOutdatedUserDevices(); + d->encryptionUpdateRequired = false; + } } void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, @@ -1828,22 +1832,6 @@ QVector Connection::availableRoomVersions() co } #ifdef Quotient_E2EE_ENABLED -void Connection::Private::createDevicesList() -{ - for(const auto &room : q->allRooms()) { - if(!room->usesEncryption()) { - continue; - } - for(const auto &user : room->users()) { - if(user->id() != q->userId()) { - trackedUsers += user->id(); - } - } - } - outdatedUsers += trackedUsers; - loadOutdatedUserDevices(); -} - void Connection::Private::loadOutdatedUserDevices() { QHash users; @@ -1884,17 +1872,13 @@ void Connection::Private::loadOutdatedUserDevices() void Connection::encryptionUpdate(Room *room) { - bool hasNewOutdatedUser = false; for(const auto &user : room->users()) { if(!d->trackedUsers.contains(user->id())) { d->trackedUsers += user->id(); d->outdatedUsers += user->id(); - hasNewOutdatedUser = true; + d->encryptionUpdateRequired = true; } } - if(hasNewOutdatedUser) { - d->loadOutdatedUserDevices(); - } } void Connection::Private::saveDevicesList() @@ -1956,7 +1940,6 @@ void Connection::Private::loadDevicesList() QFile file { q->stateCacheDir().filePath("deviceslist.json") }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No devicesList cache exists. Creating new"; - createDevicesList(); return; } auto data = file.readAll(); @@ -1970,7 +1953,6 @@ void Connection::Private::loadDevicesList() ; if (json.isEmpty()) { qCWarning(MAIN) << "DevicesList cache is broken or empty, discarding"; - createDevicesList(); return; } for(const auto &user : json["tracked_users"].toArray()) { -- cgit v1.2.3 From 8b573fc9f9f4b65ace0fdc5b4598f1974e01f7e3 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 9 Jun 2021 16:05:16 +0200 Subject: Fix compilation without E2EE --- lib/connection.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 9ea09258..10256d9c 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -658,10 +658,12 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumeAccountData(data.takeAccountData()); d->consumePresenceData(data.takePresenceData()); d->consumeToDeviceEvents(data.takeToDeviceEvents()); +#ifdef Quotient_E2EE_ENABLED if(d->encryptionUpdateRequired) { d->loadOutdatedUserDevices(); d->encryptionUpdateRequired = false; } +#endif } void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, -- cgit v1.2.3 From 40d6616ef0c4a9be20d5fe5e50f4b9959d0ab3d1 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 11 Jun 2021 18:37:58 +0200 Subject: Cleanup and Refactor EncryptionManager --- lib/connection.cpp | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 10256d9c..2d040e8a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -118,7 +118,7 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; bool isUploadingKeys = false; - QScopedPointer encryptionManager; + EncryptionManager *encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -194,17 +194,14 @@ public: EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { - qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; - return {}; #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return {}; -#else // Quotient_E2EE_ENABLED +#else if (encryptedEvent.algorithm() != OlmV1Curve25519AesSha2AlgoKey) return {}; - const auto identityKey = - encryptionManager->account()->identityKeys().curve25519; + const auto identityKey = olmAccount->identityKeys().curve25519; const auto personalCipherObject = encryptedEvent.ciphertext(identityKey); if (personalCipherObject.isEmpty()) { @@ -212,11 +209,11 @@ public: return {}; } const auto decrypted = encryptionManager->sessionDecryptMessage( - personalCipherObject, encryptedEvent.senderKey().toLatin1()); + personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" << encryptedEvent.senderKey() - << encryptionManager->account()->oneTimeKeys().keys; + << olmAccount->oneTimeKeys().keys; return {}; } @@ -233,22 +230,18 @@ public: // TODO: keys to constants const auto decryptedEventObject = decryptedEvent->fullJson(); - const auto recipient = - decryptedEventObject.value("recipient"_ls).toString(); + const auto recipient = decryptedEventObject.value("recipient"_ls).toString(); if (recipient != data->userId()) { qCDebug(E2EE) << "Found user" << recipient << "instead of us" << data->userId() << "in Olm plaintext"; return {}; } - const auto ourKey = - decryptedEventObject.value("recipient_keys"_ls).toObject() - .value(Ed25519Key).toString(); - if (ourKey - != QString::fromUtf8( - encryptionManager->account()->identityKeys().ed25519)) { + const auto ourKey = decryptedEventObject.value("recipient_keys"_ls).toObject() + .value(Ed25519Key).toString(); + if (ourKey != QString::fromUtf8(olmAccount->identityKeys().ed25519)) { qCDebug(E2EE) << "Found key" << ourKey << "instead of ours own ed25519 key" - << encryptionManager->account()->identityKeys().ed25519 + << olmAccount->identityKeys().ed25519 << "in Olm plaintext"; return {}; } @@ -266,6 +259,7 @@ public: Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent), d(new Private(std::make_unique(server))) { + d->encryptionManager = new EncryptionManager(this); d->q = this; // All d initialization should occur before this line } @@ -791,21 +785,20 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED - // handling m.room_key to-device encrypted event - visitEach(toDeviceEvents, [this](const EncryptedEvent& ee) { - if (ee.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { - qCDebug(E2EE) << "Encrypted event" << ee.id() << "algorithm" - << ee.algorithm() << "is not supported"; + qWarning() << "Consuming to device events" << toDeviceEvents.size(); + if(toDeviceEvents.size() > 0) + visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { + if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { + qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); return; } - visit(*sessionDecryptMessage(ee), - [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { + visit(*sessionDecryptMessage(event), + [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); } else { - qCDebug(E2EE) - << "Encrypted event room id" << roomKeyEvent.roomId() + qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); } }, -- cgit v1.2.3 From 3e51359d1b7891b9a6e4611662c753653c3618bf Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 2 Jun 2021 19:39:34 +0200 Subject: Things --- lib/connection.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 2d040e8a..494db170 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -796,6 +796,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) visit(*sessionDecryptMessage(event), [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { + qWarning() << "IT'S A ROOMKEY EVENT, RUUUUUUUUUUUUUUUUUN"; detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); } else { qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() -- cgit v1.2.3 From 1ce9d98cc3957af5a81f6672f3fb86a4dd170ed9 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 24 May 2021 00:40:30 +0200 Subject: Uncomment some stuff --- lib/connection.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 494db170..d652c113 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -793,13 +793,13 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) return; } - visit(*sessionDecryptMessage(event), - [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { + visit(*sessionDecryptMessage(ee), + [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { - qWarning() << "IT'S A ROOMKEY EVENT, RUUUUUUUUUUUUUUUUUN"; detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); } else { - qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() + qCDebug(E2EE) + << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); } }, -- cgit v1.2.3 From 2c5c990f4248a3112d26c3a92e011655064f3fcf Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 24 May 2021 18:04:30 +0200 Subject: Update the OlmAccountPickle in the accountsettings when the olmaccount changes --- lib/connection.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d652c113..d7115885 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -456,7 +456,11 @@ void Connection::Private::completeSetup(const QString& mxId) AccountSettings accountSettings(data->userId()); // init olmAccount - olmAccount = std::make_unique(data->userId(), data->deviceId()); + olmAccount = std::make_unique(data->userId(), data->deviceId(), q); + connect(olmAccount.get(), &QOlmAccount::needsSave, q, [=](){ + auto pickle = olmAccount->pickle(Unencrypted{}); + AccountSettings(data->userId()).setEncryptionAccountPickle(std::get(pickle)); + }); if (accountSettings.encryptionAccountPickle().isEmpty()) { // create new account and save unpickle data @@ -1283,7 +1287,7 @@ bool Connection::isLoggedIn() const { return !accessToken().isEmpty(); } #ifdef Quotient_E2EE_ENABLED QOlmAccount *Connection::olmAccount() const { - return d->olmAccount.get(); //d->encryptionManager->account(); + return d->olmAccount.get(); } #endif // Quotient_E2EE_ENABLED -- cgit v1.2.3 From f89c05408bc9a4bf59366f92c50f055d527b9a28 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 12 Jun 2021 18:58:54 +0200 Subject: Remove unrelated changes --- lib/connection.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d7115885..ab5e030b 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -797,13 +797,12 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) return; } - visit(*sessionDecryptMessage(ee), - [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { + visit(*sessionDecryptMessage(event), + [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); } else { - qCDebug(E2EE) - << "Encrypted event room id" << roomKeyEvent.roomId() + qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); } }, -- cgit v1.2.3 From 46e7f0d69db376cf45b354fef69ecba1e4636805 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 12 Jun 2021 19:57:20 +0200 Subject: Emit needsSave when required --- lib/connection.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index ab5e030b..aaa17cdd 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -460,13 +460,12 @@ void Connection::Private::completeSetup(const QString& mxId) connect(olmAccount.get(), &QOlmAccount::needsSave, q, [=](){ auto pickle = olmAccount->pickle(Unencrypted{}); AccountSettings(data->userId()).setEncryptionAccountPickle(std::get(pickle)); + //TODO handle errors }); if (accountSettings.encryptionAccountPickle().isEmpty()) { // create new account and save unpickle data olmAccount->createNewAccount(); - accountSettings.setEncryptionAccountPickle(std::get(olmAccount->pickle(Unencrypted{}))); - // TODO handle pickle errors auto job = q->callApi(olmAccount->deviceKeys()); connect(job, &BaseJob::failure, q, [=]{ qCWarning(E2EE) << "Failed to upload device keys:" << job->errorString(); -- cgit v1.2.3 From a30d457161fcaadfe944e4411d4b0e487e856178 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 00:48:23 +0200 Subject: Fix build without E2EE --- lib/connection.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index aaa17cdd..98686ed0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -259,7 +259,9 @@ public: Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent), d(new Private(std::make_unique(server))) { +#ifdef Quotient_E2EE_ENABLED d->encryptionManager = new EncryptionManager(this); +#endif d->q = this; // All d initialization should occur before this line } -- cgit v1.2.3 From 703b3f89ef54d9d40c9117788d0920b6b745bd62 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 12 Jun 2021 23:04:17 +0200 Subject: Implement (meg)olm key caching, megolm decrypting, EncryptedEvent decryption, handling of encrypted redactions and replies --- lib/connection.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 98686ed0..4a220e0d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -464,6 +464,7 @@ void Connection::Private::completeSetup(const QString& mxId) AccountSettings(data->userId()).setEncryptionAccountPickle(std::get(pickle)); //TODO handle errors }); + encryptionManager = new EncryptionManager(q); if (accountSettings.encryptionAccountPickle().isEmpty()) { // create new account and save unpickle data @@ -1891,9 +1892,9 @@ void Connection::Private::saveDevicesList() QFile outFile { q->stateCacheDir().filePath("deviceslist.json") }; if (!outFile.open(QFile::WriteOnly)) { - qCWarning(MAIN) << "Error opening" << outFile.fileName() << ":" + qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); - qCWarning(MAIN) << "Caching the rooms state disabled"; + qCWarning(E2EE) << "Caching the rooms state disabled"; cacheState = false; return; } -- cgit v1.2.3 From 60bd11426e941f9d349962b8b2ea4bddd9488965 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 17 Aug 2021 18:10:58 +0200 Subject: Don't crash when ToDeviceEvent decryption fails --- lib/connection.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4a220e0d..60ffed09 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -798,8 +798,13 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); return; } + const auto decryptedEvent = sessionDecryptMessage(event); + if(!decryptedEvent) { + qCWarning(E2EE) << "Failed to decrypt event" << event.id(); + return; + } - visit(*sessionDecryptMessage(event), + visit(*decryptedEvent, [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); -- cgit v1.2.3 From 244938d2c99674ba09f3c1f92b2a4f8507ac5e58 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 17 Aug 2021 20:51:29 +0200 Subject: Various fixes --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 60ffed09..7a96bc50 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -634,7 +634,7 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) #ifdef Quotient_E2EE_ENABLED if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() && !d->isUploadingKeys) { d->isUploadingKeys = true; - d->olmAccount->generateOneTimeKeys(d->olmAccount->maxNumberOfOneTimeKeys() - data.deviceOneTimeKeysCount()["signed_curve25519"]); + d->olmAccount->generateOneTimeKeys(d->olmAccount->maxNumberOfOneTimeKeys() / 2 - data.deviceOneTimeKeysCount()["signed_curve25519"]); auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); run(job, ForegroundRequest); -- cgit v1.2.3 From 0583534d83f902235b46ef6761d6698ddb6e6aba Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 18 Aug 2021 02:00:15 +0200 Subject: Store pickling key in qtkeychain and pickle encrypted --- lib/connection.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 7a96bc50..77ab3b72 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -39,6 +39,7 @@ #ifdef Quotient_E2EE_ENABLED # include "crypto/qolmaccount.h" +# include "crypto/qolmutils.h" #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -55,6 +56,13 @@ #include #include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +# include +#else +# include +#endif + using namespace Quotient; // This is very much Qt-specific; STL iterators don't have key() and value() @@ -108,6 +116,7 @@ public: QHash> deviceKeys; QueryKeysJob *currentQueryKeysJob = nullptr; bool encryptionUpdateRequired = false; + PicklingMode picklingMode = Unencrypted {}; #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -457,10 +466,40 @@ void Connection::Private::completeSetup(const QString& mxId) #else // Quotient_E2EE_ENABLED AccountSettings accountSettings(data->userId()); + QKeychain::ReadPasswordJob job(qAppName()); + job.setAutoDelete(false); + job.setKey(accountSettings.userId() + QStringLiteral("-Pickle")); + QEventLoop loop; + QKeychain::ReadPasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); + job.start(); + loop.exec(); + + if (job.error() == QKeychain::Error::EntryNotFound) { + picklingMode = Encrypted { getRandom(128) }; + QKeychain::WritePasswordJob job(qAppName()); + job.setAutoDelete(false); + job.setKey(accountSettings.userId() + QStringLiteral("-Pickle")); + job.setBinaryData(std::get(picklingMode).key); + QEventLoop loop; + QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); + job.start(); + loop.exec(); + + if (job.error()) { + qCWarning(E2EE) << "Could not save pickling key to keychain: " << job.errorString(); + } + } else if(job.error() != QKeychain::Error::NoError) { + //TODO Error, do something + qCWarning(E2EE) << "Error loading pickling key from keychain:" << job.error(); + } else { + qCDebug(E2EE) << "Successfully loaded pickling key from keychain"; + picklingMode = Encrypted { job.binaryData() }; + } + // init olmAccount olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, [=](){ - auto pickle = olmAccount->pickle(Unencrypted{}); + auto pickle = olmAccount->pickle(picklingMode); AccountSettings(data->userId()).setEncryptionAccountPickle(std::get(pickle)); //TODO handle errors }); @@ -476,7 +515,7 @@ void Connection::Private::completeSetup(const QString& mxId) } else { // account already existing auto pickle = accountSettings.encryptionAccountPickle(); - olmAccount->unpickle(pickle, Unencrypted{}); + olmAccount->unpickle(pickle, picklingMode); } #endif // Quotient_E2EE_ENABLED emit q->stateChanged(); @@ -1982,4 +2021,9 @@ void Connection::Private::loadDevicesList() } }); } + +PicklingMode Connection::picklingMode() const +{ + return d->picklingMode; +} #endif -- cgit v1.2.3 From 77a13cfdace5cb27adb52b3a644a155aee522b12 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 28 Aug 2021 01:03:49 +0200 Subject: Implement download and decryption of encrypted files --- lib/connection.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 77ab3b72..4a1130ae 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1017,6 +1017,21 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, return job; } +#ifdef Quotient_E2EE_ENABLED +DownloadFileJob* Connection::downloadFile(const QUrl& url, + const QString& key, + const QString& iv, + const QString& sha256, + const QString& localFilename) +{ + auto mediaId = url.authority() + url.path(); + auto idParts = splitMediaId(mediaId); + auto* job = + callApi(idParts.front(), idParts.back(), key, iv, sha256, localFilename); + return job; +} +#endif + CreateRoomJob* Connection::createRoom(RoomVisibility visibility, const QString& alias, const QString& name, const QString& topic, -- cgit v1.2.3 From 8636c7028b45ee8de3125bcf4df40ad60ed949a0 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 31 Aug 2021 00:09:59 +0200 Subject: Add mxc protocol to the networkaccessmanager --- lib/connection.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4a1130ae..d8e98bb0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1019,15 +1019,13 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, #ifdef Quotient_E2EE_ENABLED DownloadFileJob* Connection::downloadFile(const QUrl& url, - const QString& key, - const QString& iv, - const QString& sha256, + const EncryptedFile file, const QString& localFilename) { auto mediaId = url.authority() + url.path(); auto idParts = splitMediaId(mediaId); auto* job = - callApi(idParts.front(), idParts.back(), key, iv, sha256, localFilename); + callApi(idParts.front(), idParts.back(), file, localFilename); return job; } #endif -- cgit v1.2.3 From 34db4fd1294e41765a5db58ee1a0c59712af62c6 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 15 Nov 2021 21:26:47 +0100 Subject: Various improvements and fixes --- lib/connection.cpp | 54 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 26 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d8e98bb0..f36166ff 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -830,33 +830,35 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED - qWarning() << "Consuming to device events" << toDeviceEvents.size(); - if(toDeviceEvents.size() > 0) - visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { - if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { - qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); - return; - } - const auto decryptedEvent = sessionDecryptMessage(event); - if(!decryptedEvent) { - qCWarning(E2EE) << "Failed to decrypt event" << event.id(); - return; - } + if(toDeviceEvents.size() > 0) { + qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; + visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { + if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { + qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); + return; + } + qWarning() << event.fullJson(); + const auto decryptedEvent = sessionDecryptMessage(event); + if(!decryptedEvent) { + qCWarning(E2EE) << "Failed to decrypt event" << event.id(); + return; + } - visit(*decryptedEvent, - [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { - if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { - detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); - } else { - qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() - << "is not found at the connection" << q->objectName(); - } - }, - [](const Event& evt) { - qCDebug(E2EE) << "Skipping encrypted to_device event, type" - << evt.matrixType(); - }); - }); + visit(*decryptedEvent, + [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { + if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { + detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); + } else { + qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() + << "is not found at the connection" << q->objectName(); + } + }, + [](const Event& evt) { + qCDebug(E2EE) << "Skipping encrypted to_device event, type" + << evt.matrixType(); + }); + }); + } #endif } -- cgit v1.2.3 From cab1c772abaf380f30a504231fc06b070feb09ec Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 15 Nov 2021 21:35:11 +0100 Subject: Save olm account on shutdown --- lib/connection.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index f36166ff..20b4a113 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -270,6 +270,9 @@ Connection::Connection(const QUrl& server, QObject* parent) { #ifdef Quotient_E2EE_ENABLED d->encryptionManager = new EncryptionManager(this); + connect(qApp, &QCoreApplication::aboutToQuit, this, [this](){ + saveOlmAccount(); + }); #endif d->q = this; // All d initialization should occur before this line } @@ -498,11 +501,8 @@ void Connection::Private::completeSetup(const QString& mxId) // init olmAccount olmAccount = std::make_unique(data->userId(), data->deviceId(), q); - connect(olmAccount.get(), &QOlmAccount::needsSave, q, [=](){ - auto pickle = olmAccount->pickle(picklingMode); - AccountSettings(data->userId()).setEncryptionAccountPickle(std::get(pickle)); - //TODO handle errors - }); + connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); + encryptionManager = new EncryptionManager(q); if (accountSettings.encryptionAccountPickle().isEmpty()) { @@ -2042,3 +2042,11 @@ PicklingMode Connection::picklingMode() const return d->picklingMode; } #endif + +void Connection::saveOlmAccount() +{ + qCDebug(E2EE) << "Saving olm account"; + auto pickle = d->olmAccount->pickle(d->picklingMode); + AccountSettings(d->data->userId()).setEncryptionAccountPickle(std::get(pickle)); + //TODO handle errors +} -- cgit v1.2.3 From 06facdb1179e2e6789d7263541294fb427f649e5 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 15 Nov 2021 21:57:59 +0100 Subject: Move non-cache data to a non-cache location --- lib/connection.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 20b4a113..cd4c9838 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1949,7 +1949,7 @@ void Connection::Private::saveDevicesList() QElapsedTimer et; et.start(); - QFile outFile { q->stateCacheDir().filePath("deviceslist.json") }; + QFile outFile { q->e2eeDataDir() + QStringLiteral("/deviceslist.json") }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); @@ -1997,7 +1997,7 @@ void Connection::Private::saveDevicesList() void Connection::Private::loadDevicesList() { - QFile file { q->stateCacheDir().filePath("deviceslist.json") }; + QFile file { q->e2eeDataDir() + QStringLiteral("/deviceslist.json") }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No devicesList cache exists. Creating new"; return; @@ -2050,3 +2050,15 @@ void Connection::saveOlmAccount() AccountSettings(d->data->userId()).setEncryptionAccountPickle(std::get(pickle)); //TODO handle errors } + +QString Connection::e2eeDataDir() const +{ + auto safeUserId = userId(); + safeUserId.replace(':', '_'); + const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) % '/' + % safeUserId % '/'; + QDir dir; + if (!dir.exists(path)) + dir.mkpath(path); + return path; +} -- cgit v1.2.3 From 15e75b20d5bb9339a8b769b717db00fb5c16b050 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 16 Nov 2021 23:23:26 +0100 Subject: Add function to decrypt notifications --- lib/connection.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index cd4c9838..6ed116c4 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -837,7 +837,6 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); return; } - qWarning() << event.fullJson(); const auto decryptedEvent = sessionDecryptMessage(event); if(!decryptedEvent) { qCWarning(E2EE) << "Failed to decrypt event" << event.id(); @@ -2062,3 +2061,14 @@ QString Connection::e2eeDataDir() const dir.mkpath(path); return path; } + +QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) +{ + auto room = provideRoom(notification["room_id"].toString()); + auto event = makeEvent(notification["event"].toObject()); + auto decrypted = room->decryptMessage(*event); + if(!decrypted) { + return QJsonObject(); + } + return decrypted->fullJson(); +} -- cgit v1.2.3 From 545852ca45fadb3ee43072763e81cbfba0366e25 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 19 Nov 2021 21:49:46 +0100 Subject: Fix compilation --- lib/connection.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 6ed116c4..95ed1eb6 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2045,9 +2045,11 @@ PicklingMode Connection::picklingMode() const void Connection::saveOlmAccount() { qCDebug(E2EE) << "Saving olm account"; +#ifdef Quotient_E2EE_ENABLED auto pickle = d->olmAccount->pickle(d->picklingMode); AccountSettings(d->data->userId()).setEncryptionAccountPickle(std::get(pickle)); //TODO handle errors +#endif } QString Connection::e2eeDataDir() const @@ -2062,6 +2064,7 @@ QString Connection::e2eeDataDir() const return path; } +#ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { auto room = provideRoom(notification["room_id"].toString()); @@ -2072,3 +2075,4 @@ QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) } return decrypted->fullJson(); } +#endif -- cgit v1.2.3 From e99802772ebab9802e2f35d83ce1de9f83691d90 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 27 Nov 2021 00:11:36 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 95ed1eb6..df9ff445 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -57,7 +57,7 @@ #include -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#if QT_VERSION_MAJOR >= 6 # include #else # include @@ -830,7 +830,7 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED - if(toDeviceEvents.size() > 0) { + if (!toDeviceEvents.empty()) { qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { @@ -1020,7 +1020,7 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, #ifdef Quotient_E2EE_ENABLED DownloadFileJob* Connection::downloadFile(const QUrl& url, - const EncryptedFile file, + const EncryptedFile& file, const QString& localFilename) { auto mediaId = url.authority() + url.path(); @@ -1996,7 +1996,7 @@ void Connection::Private::saveDevicesList() void Connection::Private::loadDevicesList() { - QFile file { q->e2eeDataDir() + QStringLiteral("/deviceslist.json") }; + QFile file { q->e2eeDataDir() % "/deviceslist.json" }; if(!file.exists() || !file.open(QIODevice::ReadOnly)) { qCDebug(E2EE) << "No devicesList cache exists. Creating new"; return; -- cgit v1.2.3 From dcc4556a761f96ae6c71115bf6297feca32581bf Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 27 Nov 2021 01:58:02 +0100 Subject: More improvements --- lib/connection.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index df9ff445..a7af1477 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -509,7 +509,7 @@ void Connection::Private::completeSetup(const QString& mxId) // create new account and save unpickle data olmAccount->createNewAccount(); auto job = q->callApi(olmAccount->deviceKeys()); - connect(job, &BaseJob::failure, q, [=]{ + connect(job, &BaseJob::failure, q, [job]{ qCWarning(E2EE) << "Failed to upload device keys:" << job->errorString(); }); } else { @@ -677,10 +677,10 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); run(job, ForegroundRequest); - connect(job, &BaseJob::success, this, [=](){ + connect(job, &BaseJob::success, this, [this](){ d->olmAccount->markKeysAsPublished(); }); - connect(job, &BaseJob::result, this, [=](){ + connect(job, &BaseJob::result, this, [this](){ d->isUploadingKeys = false; }); } @@ -1903,7 +1903,7 @@ void Connection::Private::loadOutdatedUserDevices() } auto queryKeysJob = q->callApi(users); currentQueryKeysJob = queryKeysJob; - connect(queryKeysJob, &BaseJob::success, q, [=](){ + connect(queryKeysJob, &BaseJob::success, q, [this, queryKeysJob](){ currentQueryKeysJob = nullptr; const auto data = queryKeysJob->deviceKeys(); for(const auto &[user, keys] : asKeyValueRange(data)) { @@ -2024,7 +2024,7 @@ void Connection::Private::loadDevicesList() deviceKeys = fromJson>>(json["devices_list"].toObject()); auto oldToken = json["sync_token"].toString(); auto changesJob = q->callApi(oldToken, q->nextBatchToken()); - connect(changesJob, &BaseJob::success, q, [=](){ + connect(changesJob, &BaseJob::success, q, [this, changesJob](){ bool hasNewOutdatedUser = false; for(const auto &user : changesJob->changed()) { outdatedUsers += user; -- cgit v1.2.3 From 9217026e46d7ac0d761cc5206d7ef00978558c47 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:58:38 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index a7af1477..ac428a62 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -56,7 +56,6 @@ #include #include - #if QT_VERSION_MAJOR >= 6 # include #else @@ -1948,7 +1947,7 @@ void Connection::Private::saveDevicesList() QElapsedTimer et; et.start(); - QFile outFile { q->e2eeDataDir() + QStringLiteral("/deviceslist.json") }; + QFile outFile { q->e2eeDataDir() % "/deviceslist.json" }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); -- cgit v1.2.3 From ae0ad49f36e8ba5983839581302ed16ddbd75d5f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Dec 2021 14:04:49 +0100 Subject: visit(Event, ...) -> switchOnType() It has not much to do with the Visitor design pattern; also, std::visit() has different conventions on the order of parameters. --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index e65fdac4..25219def 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -659,7 +659,7 @@ void Connection::Private::consumeAccountData(Events&& accountDataEvents) // After running this loop, the account data events not saved in // accountData (see the end of the loop body) are auto-cleaned away for (auto&& eventPtr: accountDataEvents) { - visit(*eventPtr, + switchOnType(*eventPtr, [this](const DirectChatEvent& dce) { // https://github.com/quotient-im/libQuotient/wiki/Handling-direct-chat-events const auto& usersToDCs = dce.usersToDirectChats(); @@ -760,7 +760,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) << ee.senderId(); // encryptionManager->updateDeviceKeys(); - visit(*sessionDecryptMessage(ee), + switchOnType(*sessionDecryptMessage(ee), [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); -- cgit v1.2.3 From 47bd4dfb2bc720d2b5919b93985f87d918af572a Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Dec 2021 00:25:05 +0100 Subject: Port E2EE to database instead of JSON files --- lib/connection.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index ac428a62..f344807e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -62,6 +62,8 @@ # include #endif +#include "database.h" + using namespace Quotient; // This is very much Qt-specific; STL iterators don't have key() and value() @@ -274,6 +276,7 @@ Connection::Connection(const QUrl& server, QObject* parent) }); #endif d->q = this; // All d initialization should occur before this line + Database::instance(); } Connection::Connection(QObject* parent) : Connection({}, parent) {} @@ -439,6 +442,7 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) auto loginJob = q->callApi(std::forward(loginArgs)...); connect(loginJob, &BaseJob::success, q, [this, loginJob] { + Database::instance().clear(loginJob->userId()); data->setToken(loginJob->accessToken().toLatin1()); data->setDeviceId(loginJob->deviceId()); completeSetup(loginJob->userId()); @@ -504,7 +508,7 @@ void Connection::Private::completeSetup(const QString& mxId) encryptionManager = new EncryptionManager(q); - if (accountSettings.encryptionAccountPickle().isEmpty()) { + if (Database::instance().accountPickle(data->userId()).isEmpty()) { // create new account and save unpickle data olmAccount->createNewAccount(); auto job = q->callApi(olmAccount->deviceKeys()); @@ -513,7 +517,7 @@ void Connection::Private::completeSetup(const QString& mxId) }); } else { // account already existing - auto pickle = accountSettings.encryptionAccountPickle(); + auto pickle = Database::instance().accountPickle(data->userId()); olmAccount->unpickle(pickle, picklingMode); } #endif // Quotient_E2EE_ENABLED @@ -1978,15 +1982,9 @@ void Connection::Private::saveDevicesList() rootObj.insert(QStringLiteral("sync_token"), q->nextBatchToken()); } -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - const auto data = - cacheToBinary ? QCborValue::fromJsonValue(rootObj).toCbor() - : QJsonDocument(rootObj).toJson(QJsonDocument::Compact); -#else + QJsonDocument json { rootObj }; - const auto data = cacheToBinary ? json.toBinaryData() - : json.toJson(QJsonDocument::Compact); -#endif + const auto data = json.toJson(); qCDebug(PROFILER) << "DeviceList generated in" << et; outFile.write(data.data(), data.size()); @@ -2043,11 +2041,10 @@ PicklingMode Connection::picklingMode() const void Connection::saveOlmAccount() { - qCDebug(E2EE) << "Saving olm account"; + qDebug() << "Saving olm account"; #ifdef Quotient_E2EE_ENABLED auto pickle = d->olmAccount->pickle(d->picklingMode); - AccountSettings(d->data->userId()).setEncryptionAccountPickle(std::get(pickle)); - //TODO handle errors + Database::instance().setAccountPickle(userId(), std::get(pickle)); #endif } -- cgit v1.2.3 From 2c6fa33ca52842e9dfba0dd3893a9d5526e10e60 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 7 Dec 2021 19:08:29 +0100 Subject: Rename "crypto" -> "e2ee" --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index f344807e..c7591e43 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -38,8 +38,8 @@ #include "jobs/syncjob.h" #ifdef Quotient_E2EE_ENABLED -# include "crypto/qolmaccount.h" -# include "crypto/qolmutils.h" +# include "e2ee/qolmaccount.h" +# include "e2ee/qolmutils.h" #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) -- cgit v1.2.3 From 1f6771c3b14453ae9b6651a9edb1f7778d3f71f3 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Thu, 9 Dec 2021 23:59:38 +0100 Subject: Update lib/connection.cpp Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index c7591e43..b7aaca86 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2041,7 +2041,7 @@ PicklingMode Connection::picklingMode() const void Connection::saveOlmAccount() { - qDebug() << "Saving olm account"; + qCDebug(E2EE) << "Saving olm account"; #ifdef Quotient_E2EE_ENABLED auto pickle = d->olmAccount->pickle(d->picklingMode); Database::instance().setAccountPickle(userId(), std::get(pickle)); -- cgit v1.2.3 From 58798ce15f0f235d64f9c34b3f8c013678ebf25f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 9 Dec 2021 23:26:24 +0100 Subject: Ifdef all the things --- lib/connection.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b7aaca86..433dd942 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -7,9 +7,6 @@ #include "connection.h" #include "connectiondata.h" -#ifdef Quotient_E2EE_ENABLED -# include "encryptionmanager.h" -#endif // Quotient_E2EE_ENABLED #include "room.h" #include "settings.h" #include "user.h" @@ -40,6 +37,8 @@ #ifdef Quotient_E2EE_ENABLED # include "e2ee/qolmaccount.h" # include "e2ee/qolmutils.h" +# include "encryptionmanager.h" +# include "database.h" #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -62,7 +61,6 @@ # include #endif -#include "database.h" using namespace Quotient; @@ -274,9 +272,9 @@ Connection::Connection(const QUrl& server, QObject* parent) connect(qApp, &QCoreApplication::aboutToQuit, this, [this](){ saveOlmAccount(); }); + Database::instance(); #endif d->q = this; // All d initialization should occur before this line - Database::instance(); } Connection::Connection(QObject* parent) : Connection({}, parent) {} @@ -442,15 +440,13 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) auto loginJob = q->callApi(std::forward(loginArgs)...); connect(loginJob, &BaseJob::success, q, [this, loginJob] { - Database::instance().clear(loginJob->userId()); data->setToken(loginJob->accessToken().toLatin1()); data->setDeviceId(loginJob->deviceId()); completeSetup(loginJob->userId()); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED - //encryptionManager->uploadIdentityKeys(q); - //encryptionManager->uploadOneTimeKeys(q); + Database::instance().clear(loginJob->userId()); #endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { -- cgit v1.2.3 From 7129118a7735a13af0db7d71efd60a330feac877 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 10 Dec 2021 00:12:39 +0100 Subject: ifdef more things --- lib/connection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 433dd942..d1a29a7d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -39,6 +39,12 @@ # include "e2ee/qolmutils.h" # include "encryptionmanager.h" # include "database.h" + +#if QT_VERSION_MAJOR >= 6 +# include +#else +# include +#endif #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -55,12 +61,6 @@ #include #include -#if QT_VERSION_MAJOR >= 6 -# include -#else -# include -#endif - using namespace Quotient; -- cgit v1.2.3 From b4cc38fc7c2c63d8122106a2451aec2c60176a4b Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 10 Dec 2021 16:10:10 +0100 Subject: Use individual databases for each connection --- lib/connection.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d1a29a7d..8b9f9688 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -116,6 +116,7 @@ public: QueryKeysJob *currentQueryKeysJob = nullptr; bool encryptionUpdateRequired = false; PicklingMode picklingMode = Unencrypted {}; + Database *database = nullptr; #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -268,11 +269,9 @@ Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent), d(new Private(std::make_unique(server))) { #ifdef Quotient_E2EE_ENABLED - d->encryptionManager = new EncryptionManager(this); connect(qApp, &QCoreApplication::aboutToQuit, this, [this](){ saveOlmAccount(); }); - Database::instance(); #endif d->q = this; // All d initialization should occur before this line } @@ -446,7 +445,8 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED - Database::instance().clear(loginJob->userId()); + database = new Database(loginJob->userId(), q); + database->clear(); #endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { @@ -502,9 +502,13 @@ void Connection::Private::completeSetup(const QString& mxId) olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); + if (!database) { + database = new Database(data->userId(), q); + } + encryptionManager = new EncryptionManager(q); - if (Database::instance().accountPickle(data->userId()).isEmpty()) { + if (database->accountPickle().isEmpty()) { // create new account and save unpickle data olmAccount->createNewAccount(); auto job = q->callApi(olmAccount->deviceKeys()); @@ -513,7 +517,7 @@ void Connection::Private::completeSetup(const QString& mxId) }); } else { // account already existing - auto pickle = Database::instance().accountPickle(data->userId()); + auto pickle = database->accountPickle(); olmAccount->unpickle(pickle, picklingMode); } #endif // Quotient_E2EE_ENABLED @@ -2040,7 +2044,7 @@ void Connection::saveOlmAccount() qCDebug(E2EE) << "Saving olm account"; #ifdef Quotient_E2EE_ENABLED auto pickle = d->olmAccount->pickle(d->picklingMode); - Database::instance().setAccountPickle(userId(), std::get(pickle)); + d->database->setAccountPickle(std::get(pickle)); #endif } @@ -2067,4 +2071,9 @@ QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) } return decrypted->fullJson(); } + +Database* Connection::database() +{ + return d->database; +} #endif -- cgit v1.2.3 From aeed7f3bada2cefcb52d4418b5ed76f19980d702 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 24 Dec 2021 14:29:07 +0100 Subject: Cache deviceslist to binary when possible --- lib/connection.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 8b9f9688..e28ffb22 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1982,9 +1982,16 @@ void Connection::Private::saveDevicesList() rootObj.insert(QStringLiteral("sync_token"), q->nextBatchToken()); } - - QJsonDocument json { rootObj }; - const auto data = json.toJson(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + const auto data = + cacheToBinary + ? QCborValue::fromJsonValue(rootObj).toCbor() + : QJsonDocument(rootObj).toJson(QJsonDocument::Compact); +#else + QJsonDocument json { rootObj }; + const auto data = cacheToBinary ? json.toBinaryData() + : json.toJson(QJsonDocument::Compact); +#endif qCDebug(PROFILER) << "DeviceList generated in" << et; outFile.write(data.data(), data.size()); -- cgit v1.2.3 From d56bb69f06c70bb7cf659d5fdde4e2306d7fb2f2 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 25 Dec 2021 18:19:46 +0100 Subject: =?UTF-8?q?Don't=20save=20olm=20account=20=C3=B3n=20shutdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It causes the program to crash for some reason --- lib/connection.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index e28ffb22..138f968f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -269,9 +269,7 @@ Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent), d(new Private(std::make_unique(server))) { #ifdef Quotient_E2EE_ENABLED - connect(qApp, &QCoreApplication::aboutToQuit, this, [this](){ - saveOlmAccount(); - }); + //connect(qApp, &QCoreApplication::aboutToQuit, this, &Connection::saveOlmAccount); #endif d->q = this; // All d initialization should occur before this line } -- cgit v1.2.3 From ff415143b0d953f04a207125e265111b925ce763 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 25 Dec 2021 21:51:17 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 138f968f..89b80909 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -112,7 +112,7 @@ public: #ifdef Quotient_E2EE_ENABLED QSet trackedUsers; QSet outdatedUsers; - QHash> deviceKeys; + QHash> deviceKeys; QueryKeysJob *currentQueryKeysJob = nullptr; bool encryptionUpdateRequired = false; PicklingMode picklingMode = Unencrypted {}; @@ -1975,7 +1975,7 @@ void Connection::Private::saveDevicesList() } rootObj.insert(QStringLiteral("tracked_users"), trackedUsersJson); rootObj.insert(QStringLiteral("outdated_users"), outdatedUsersJson); - QJsonObject devicesList = toJson>>(deviceKeys); + const auto devicesList = toJson(deviceKeys); rootObj.insert(QStringLiteral("devices_list"), devicesList); rootObj.insert(QStringLiteral("sync_token"), q->nextBatchToken()); } @@ -2023,7 +2023,7 @@ void Connection::Private::loadDevicesList() outdatedUsers += user.toString(); } - deviceKeys = fromJson>>(json["devices_list"].toObject()); + fromJson(json["devices_list"], deviceKeys); auto oldToken = json["sync_token"].toString(); auto changesJob = q->callApi(oldToken, q->nextBatchToken()); connect(changesJob, &BaseJob::success, q, [this, changesJob](){ -- cgit v1.2.3 From 58b2501aeecf2c169fd1f583dca292169568b5fa Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 26 Dec 2021 09:29:00 +0100 Subject: Connection: Simplify room/user factory code There's no need to return lambdas where pointers to specialised function templates would work just fine. --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 25219def..8d1c80f1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1513,8 +1513,8 @@ room_factory_t Connection::roomFactory() { return _roomFactory; } user_factory_t Connection::userFactory() { return _userFactory; } -room_factory_t Connection::_roomFactory = defaultRoomFactory<>(); -user_factory_t Connection::_userFactory = defaultUserFactory<>(); +room_factory_t Connection::_roomFactory = defaultRoomFactory<>; +user_factory_t Connection::_userFactory = defaultUserFactory<>; QByteArray Connection::generateTxnId() const { -- cgit v1.2.3 From 7d37d296f942ac993d041b4576ed52265170c4a8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 2 Jan 2022 06:03:26 +0100 Subject: Add ImplPtr and makeImpl The original (more complex and comprehensive) solution belongs to https://oliora.github.io/2015/12/29/pimpl-and-rule-of-zero.html - this commit only provides a small wrapper for non-copyable Private class implementations common throughout libQuotient. Unlike the original, default initialisation is made explicit - you have to pass ZeroImpl() instead (and I firmly believe it's a good thing: normally pointers to Private should not remain nullptr). The reason ZeroImpl<> is not a template variable is quite simple: unique_ptr is non-copyable and so cannot be initialised from; while a template function will initialise the value in-place thanks to copy elision. --- lib/connection.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 8d1c80f1..1915c2a9 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -246,7 +246,8 @@ public: }; Connection::Connection(const QUrl& server, QObject* parent) - : QObject(parent), d(new Private(std::make_unique(server))) + : QObject(parent) + , d(makeImpl(std::make_unique(server))) { d->q = this; // All d initialization should occur before this line } -- cgit v1.2.3 From aaa57e0e458435e23047fcb325872f0350171bad Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 14 Jan 2022 22:37:28 +0100 Subject: AccountRegistry: derive from QVector and clean up Notably, Quotient::AccountRegistry::instance() is now deprecated in favour of Quotient::Accounts inline variable. --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1915c2a9..67bc83f9 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -258,7 +258,7 @@ Connection::~Connection() { qCDebug(MAIN) << "deconstructing connection object for" << userId(); stopSync(); - AccountRegistry::instance().drop(this); + Accounts.drop(this); } void Connection::resolveServer(const QString& mxid) @@ -438,7 +438,7 @@ void Connection::Private::completeSetup(const QString& mxId) qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << data->userId() << "from device" << data->deviceId(); - AccountRegistry::instance().add(q); + Accounts.add(q); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED -- cgit v1.2.3 From 2fc62d38e8f748d1a78baca8b6f2df40e7cfa1a9 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 30 Jan 2022 21:20:00 +0100 Subject: Use room() instead of provideRoom() --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 89b80909..dbc6261d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2068,7 +2068,7 @@ QString Connection::e2eeDataDir() const #ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { - auto room = provideRoom(notification["room_id"].toString()); + auto room = this->room(notification["room_id"].toString()); auto event = makeEvent(notification["event"].toObject()); auto decrypted = room->decryptMessage(*event); if(!decrypted) { -- cgit v1.2.3 From 023ef3005d3fae80637c6ce140e84db26250d564 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 30 Jan 2022 23:13:01 +0100 Subject: Port devices list to database --- lib/connection.cpp | 156 ++++++++++++++++++++++------------------------------- 1 file changed, 63 insertions(+), 93 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index dbc6261d..54d79674 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -442,10 +442,9 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) completeSetup(loginJob->userId()); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; -#else // Quotient_E2EE_ENABLED +#endif // Quotient_E2EE_ENABLED database = new Database(loginJob->userId(), q); database->clear(); -#endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -1891,7 +1890,6 @@ QVector Connection::availableRoomVersions() co return result; } -#ifdef Quotient_E2EE_ENABLED void Connection::Private::loadOutdatedUserDevices() { QHash users; @@ -1930,112 +1928,84 @@ void Connection::Private::loadOutdatedUserDevices() }); } -void Connection::encryptionUpdate(Room *room) -{ - for(const auto &user : room->users()) { - if(!d->trackedUsers.contains(user->id())) { - d->trackedUsers += user->id(); - d->outdatedUsers += user->id(); - d->encryptionUpdateRequired = true; - } - } -} - void Connection::Private::saveDevicesList() { - if (!cacheState) - return; - - QElapsedTimer et; - et.start(); + q->database()->transaction(); + auto query = q->database()->prepareQuery(QStringLiteral("DELETE FROM tracked_users")); + q->database()->execute(query); + query.prepare(QStringLiteral("INSERT INTO tracked_users(matrixId) VALUES(:matrixId);")); + for (const auto& user : trackedUsers) { + query.bindValue(":matrixId", user); + q->database()->execute(query); + } - QFile outFile { q->e2eeDataDir() % "/deviceslist.json" }; - if (!outFile.open(QFile::WriteOnly)) { - qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" - << outFile.errorString(); - qCWarning(E2EE) << "Caching the rooms state disabled"; - cacheState = false; - return; + query.prepare(QStringLiteral("DELETE FROM outdated_users")); + q->database()->execute(query); + query.prepare(QStringLiteral("INSERT INTO outdated_users(matrixId) VALUES(:matrixId);")); + for (const auto& user : outdatedUsers) { + query.bindValue(":matrixId", user); + q->database()->execute(query); } - QJsonObject rootObj { - { QStringLiteral("cache_version"), - QJsonObject { - { QStringLiteral("major"), SyncData::cacheVersion().first }, - { QStringLiteral("minor"), SyncData::cacheVersion().second } } } - }; - { - QJsonArray trackedUsersJson; - QJsonArray outdatedUsersJson; - for (const auto &user : trackedUsers) { - trackedUsersJson += user; - } - for (const auto &user : outdatedUsers) { - outdatedUsersJson += user; + query.prepare(QStringLiteral("INSERT INTO tracked_devices(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey) VALUES(:matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey);")); + for (const auto& user : deviceKeys.keys()) { + for (const auto& device : deviceKeys[user]) { + auto keys = device.keys.keys(); + auto curveKeyId = keys[0].startsWith(QLatin1String("curve")) ? keys[0] : keys[1]; + auto edKeyId = keys[0].startsWith(QLatin1String("ed")) ? keys[0] : keys[1]; + + query.bindValue(":matrixId", user); + query.bindValue(":deviceId", device.deviceId); + query.bindValue(":curveKeyId", curveKeyId); + query.bindValue(":curveKey", device.keys[curveKeyId]); + query.bindValue(":edKeyId", edKeyId); + query.bindValue(":edKey", device.keys[edKeyId]); + + q->database()->execute(query); } - rootObj.insert(QStringLiteral("tracked_users"), trackedUsersJson); - rootObj.insert(QStringLiteral("outdated_users"), outdatedUsersJson); - const auto devicesList = toJson(deviceKeys); - rootObj.insert(QStringLiteral("devices_list"), devicesList); - rootObj.insert(QStringLiteral("sync_token"), q->nextBatchToken()); } - -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - const auto data = - cacheToBinary - ? QCborValue::fromJsonValue(rootObj).toCbor() - : QJsonDocument(rootObj).toJson(QJsonDocument::Compact); -#else - QJsonDocument json { rootObj }; - const auto data = cacheToBinary ? json.toBinaryData() - : json.toJson(QJsonDocument::Compact); -#endif - qCDebug(PROFILER) << "DeviceList generated in" << et; - - outFile.write(data.data(), data.size()); - qCDebug(E2EE) << "DevicesList saved to" << outFile.fileName(); + q->database()->commit(); } void Connection::Private::loadDevicesList() { - QFile file { q->e2eeDataDir() % "/deviceslist.json" }; - if(!file.exists() || !file.open(QIODevice::ReadOnly)) { - qCDebug(E2EE) << "No devicesList cache exists. Creating new"; - return; - } - auto data = file.readAll(); - const auto json = data.startsWith('{') - ? QJsonDocument::fromJson(data).object() -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - : QCborValue::fromCbor(data).toJsonValue().toObject() -#else - : QJsonDocument::fromBinaryData(data).object() -#endif - ; - if (json.isEmpty()) { - qCWarning(MAIN) << "DevicesList cache is broken or empty, discarding"; - return; + auto query = q->database()->prepareQuery(QStringLiteral("SELECT * FROM tracked_users;")); + q->database()->execute(query); + while(query.next()) { + trackedUsers += query.value(0).toString(); } - for(const auto &user : json["tracked_users"].toArray()) { - trackedUsers += user.toString(); + + query = q->database()->prepareQuery(QStringLiteral("SELECT * FROM outdated_users;")); + q->database()->execute(query); + while(query.next()) { + outdatedUsers += query.value(0).toString(); } - for(const auto &user : json["outdated_users"].toArray()) { - outdatedUsers += user.toString(); + + query = q->database()->prepareQuery(QStringLiteral("SELECT * FROM tracked_devices;")); + q->database()->execute(query); + while(query.next()) { + deviceKeys[query.value("matrixId").toString()][query.value("deviceId").toString()] = DeviceKeys { + query.value("matrixId").toString(), + query.value("deviceId").toString(), + { "m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}, + {{query.value("curveKeyId").toString(), query.value("curveKey").toString()}, + {query.value("edKeyId").toString(), query.value("edKey").toString()}}, + {} // Signatures are not saved/loaded as they are not needed after initial validation + }; } - fromJson(json["devices_list"], deviceKeys); - auto oldToken = json["sync_token"].toString(); - auto changesJob = q->callApi(oldToken, q->nextBatchToken()); - connect(changesJob, &BaseJob::success, q, [this, changesJob](){ - bool hasNewOutdatedUser = false; - for(const auto &user : changesJob->changed()) { - outdatedUsers += user; - hasNewOutdatedUser = true; - } - if(hasNewOutdatedUser) { - loadOutdatedUserDevices(); +} + +#ifdef Quotient_E2EE_ENABLED +void Connection::encryptionUpdate(Room *room) +{ + for(const auto &user : room->users()) { + if(!d->trackedUsers.contains(user->id())) { + d->trackedUsers += user->id(); + d->outdatedUsers += user->id(); + d->encryptionUpdateRequired = true; } - }); + } } PicklingMode Connection::picklingMode() const -- cgit v1.2.3 From ef64359505778913235666f0e759dd0758f7f4ac Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 6 Feb 2022 20:06:19 +0100 Subject: Only create one database --- lib/connection.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 54d79674..58e3a9f8 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -443,8 +443,6 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #endif // Quotient_E2EE_ENABLED - database = new Database(loginJob->userId(), q); - database->clear(); }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -499,9 +497,7 @@ void Connection::Private::completeSetup(const QString& mxId) olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); - if (!database) { - database = new Database(data->userId(), q); - } + database = new Database(data->userId(), q); encryptionManager = new EncryptionManager(q); -- cgit v1.2.3 From 7b5edb737522b03d4f697e0e09f1771ad5edef89 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 7 Feb 2022 21:48:07 +0100 Subject: Remove encryptionmanager and various fixes --- lib/connection.cpp | 108 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 90 insertions(+), 18 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 58e3a9f8..1a1b284d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -37,7 +37,6 @@ #ifdef Quotient_E2EE_ENABLED # include "e2ee/qolmaccount.h" # include "e2ee/qolmutils.h" -# include "encryptionmanager.h" # include "database.h" #if QT_VERSION_MAJOR >= 6 @@ -117,6 +116,10 @@ public: bool encryptionUpdateRequired = false; PicklingMode picklingMode = Unencrypted {}; Database *database = nullptr; + + // A map from SenderKey to vector of InboundSession + UnorderedMap> olmSessions; + #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -127,7 +130,6 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; bool isUploadingKeys = false; - EncryptionManager *encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -201,6 +203,85 @@ public: return q->stateCacheDir().filePath("state.json"); } +#ifdef Quotient_E2EE_ENABLED + void loadSessions() { + olmSessions = q->database()->loadOlmSessions(q->picklingMode()); + } + void saveSession(QOlmSessionPtr& session, const QString &senderKey) { + auto pickleResult = session->pickle(q->picklingMode()); + if (std::holds_alternative(pickleResult)) { + qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); + return; + } + q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult)); + } + QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) + { + Q_ASSERT(message.type() == QOlmMessage::PreKey); + for(auto& session : olmSessions[senderKey]) { + const auto matches = session->matchesInboundSessionFrom(senderKey, message); + if(std::holds_alternative(matches) && std::get(matches)) { + qCDebug(E2EE) << "Found inbound session"; + const auto result = session->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message"; + return {}; + } + } + } + qCDebug(E2EE) << "Creating new inbound session"; + auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); + if(std::holds_alternative(newSessionResult)) { + qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); + return {}; + } + auto newSession = std::move(std::get(newSessionResult)); + auto error = olmAccount->removeOneTimeKeys(newSession); + if (error) { + qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); + } + const auto result = newSession->decrypt(message); + saveSession(newSession, senderKey); + olmSessions[senderKey].push_back(std::move(newSession)); + if(std::holds_alternative(result)) { + return std::get(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; + return {}; + } + } + QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + { + Q_ASSERT(message.type() == QOlmMessage::General); + for(auto& session : olmSessions[senderKey]) { + const auto result = session->decrypt(message); + if(std::holds_alternative(result)) { + return std::get(result); + } + } + qCWarning(E2EE) << "Failed to decrypt message"; + return {}; + } + + QString sessionDecryptMessage( + const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) + { + QString decrypted; + int type = personalCipherObject.value(TypeKeyL).toInt(-1); + QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); + if (type == 0) { + QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); + decrypted = sessionDecryptPrekey(preKeyMessage, senderKey, account); + } else if (type == 1) { + QOlmMessage message(body, QOlmMessage::General); + decrypted = sessionDecryptGeneral(message, senderKey); + } + return decrypted; + } +#endif + EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { #ifndef Quotient_E2EE_ENABLED @@ -217,7 +298,7 @@ public: qCDebug(E2EE) << "Encrypted event is not for the current device"; return {}; } - const auto decrypted = encryptionManager->sessionDecryptMessage( + const auto decrypted = sessionDecryptMessage( personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" @@ -443,6 +524,7 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #endif // Quotient_E2EE_ENABLED + database->clear(); }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -493,13 +575,15 @@ void Connection::Private::completeSetup(const QString& mxId) picklingMode = Encrypted { job.binaryData() }; } + database = new Database(data->userId(), q); + // init olmAccount olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); - database = new Database(data->userId(), q); - - encryptionManager = new EncryptionManager(q); +#ifdef Quotient_E2EE_ENABLED + loadSessions(); +#endif if (database->accountPickle().isEmpty()) { // create new account and save unpickle data @@ -2019,18 +2103,6 @@ void Connection::saveOlmAccount() #endif } -QString Connection::e2eeDataDir() const -{ - auto safeUserId = userId(); - safeUserId.replace(':', '_'); - const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) % '/' - % safeUserId % '/'; - QDir dir; - if (!dir.exists(path)) - dir.mkpath(path); - return path; -} - #ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { -- cgit v1.2.3 From 6415d6fb194799870eb89cbaff4ba07939aa6ccb Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 9 Feb 2022 22:25:24 +0100 Subject: Fix compilation without E2EE --- lib/connection.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1a1b284d..04ce1dc2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -523,8 +523,9 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) completeSetup(loginJob->userId()); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; -#endif // Quotient_E2EE_ENABLED +#else // Quotient_E2EE_ENABLED database->clear(); +#endif // Quotient_E2EE_ENABLED }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -1970,6 +1971,7 @@ QVector Connection::availableRoomVersions() co return result; } +#ifdef Quotient_E2EE_ENABLED void Connection::Private::loadOutdatedUserDevices() { QHash users; @@ -2076,7 +2078,6 @@ void Connection::Private::loadDevicesList() } -#ifdef Quotient_E2EE_ENABLED void Connection::encryptionUpdate(Room *room) { for(const auto &user : room->users()) { -- cgit v1.2.3 From 148bb1256fb15c73c605c5301da2b9602f859660 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 11 Feb 2022 23:07:35 +0100 Subject: Implement more suggestions --- lib/connection.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 04ce1dc2..cc5d8739 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -38,6 +38,7 @@ # include "e2ee/qolmaccount.h" # include "e2ee/qolmutils.h" # include "database.h" +# include "e2ee/qolminboundsession.h" #if QT_VERSION_MAJOR >= 6 # include @@ -2120,4 +2121,14 @@ Database* Connection::database() { return d->database; } + +UnorderedMap, QOlmInboundGroupSessionPtr> Connection::loadRoomMegolmSessions(Room* room) +{ + return database()->loadMegolmSessions(room->id(), picklingMode()); +} + +void Connection::saveMegolmSession(Room* room, const QString& senderKey, QOlmInboundGroupSession* session) +{ + database()->saveMegolmSession(room->id(), senderKey, session->sessionId(), session->pickle(picklingMode())); +} #endif -- cgit v1.2.3 From b0aef4af9cbf00755c7b70c71d77f0bf7ce0d200 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 12 Feb 2022 12:12:24 +0100 Subject: Replace QPair with std::pair --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index cc5d8739..a8de4030 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2122,7 +2122,7 @@ Database* Connection::database() return d->database; } -UnorderedMap, QOlmInboundGroupSessionPtr> Connection::loadRoomMegolmSessions(Room* room) +UnorderedMap, QOlmInboundGroupSessionPtr> Connection::loadRoomMegolmSessions(Room* room) { return database()->loadMegolmSessions(room->id(), picklingMode()); } -- cgit v1.2.3 From 53dfa70601b2d27a6be12d52e86af123d0b26b79 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 15 Feb 2022 20:51:32 +0100 Subject: Cleanup A note on switching to QLatin1String for JSON key constants - this is more concise and barely affects (if at all) runtime performance (padding each QChar with zeros is trivial for assignment; and comparison can be done directly with the same performance as for two QStrings). --- lib/connection.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 14188ace..3b8da6d1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -754,18 +754,20 @@ QJsonObject toJson(const DirectChatsMap& directChats) void Connection::onSyncSuccess(SyncData&& data, bool fromCache) { #ifdef Quotient_E2EE_ENABLED - if(data.deviceOneTimeKeysCount()["signed_curve25519"] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() && !d->isUploadingKeys) { + const auto oneTimeKeyCount = + static_cast(data.deviceOneTimeKeysCount()[SignedCurve25519Key]); + if (oneTimeKeyCount < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() + && !d->isUploadingKeys) { d->isUploadingKeys = true; - d->olmAccount->generateOneTimeKeys(d->olmAccount->maxNumberOfOneTimeKeys() / 2 - data.deviceOneTimeKeysCount()["signed_curve25519"]); + d->olmAccount->generateOneTimeKeys( + d->olmAccount->maxNumberOfOneTimeKeys() / 2 - oneTimeKeyCount); auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); run(job, ForegroundRequest); - connect(job, &BaseJob::success, this, [this](){ - d->olmAccount->markKeysAsPublished(); - }); - connect(job, &BaseJob::result, this, [this](){ - d->isUploadingKeys = false; - }); + connect(job, &BaseJob::success, this, + [this] { d->olmAccount->markKeysAsPublished(); }); + connect(job, &BaseJob::result, this, + [this] { d->isUploadingKeys = false; }); } static bool first = true; if(first) { @@ -1993,7 +1995,9 @@ void Connection::Private::loadOutdatedUserDevices() deviceKeys[user].clear(); for(const auto &device : keys) { if(device.userId != user) { - qCWarning(E2EE) << "mxId mismatch during device key verification:" << device.userId << user; + qCWarning(E2EE) + << "mxId mismatch during device key verification:" + << device.userId << user; continue; } if(!device.algorithms.contains("m.olm.v1.curve25519-aes-sha2") || !device.algorithms.contains("m.megolm.v1.aes-sha2")) { -- cgit v1.2.3 From 0f6506c022ff1ccaa648ff50b81ae29f5a6f2176 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 14 Feb 2022 08:55:50 +0100 Subject: Connection: guard device loading per-object Using a static variable is incorrect as it doesn't load the device list for any subsequent created Connection object. --- lib/connection.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 3b8da6d1..0562d416 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -131,6 +131,7 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr olmAccount; bool isUploadingKeys = false; + bool firstSync = true; #endif // Quotient_E2EE_ENABLED QPointer resolverJob = nullptr; @@ -769,10 +770,9 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) connect(job, &BaseJob::result, this, [this] { d->isUploadingKeys = false; }); } - static bool first = true; - if(first) { + if(d->firstSync) { d->loadDevicesList(); - first = false; + d->firstSync = false; } d->consumeDevicesList(data.takeDevicesList()); -- cgit v1.2.3 From 0a43c023b94e12b3130572f2dd0d6ac8bb4ed110 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 14 Feb 2022 15:25:24 +0100 Subject: isSupportedAlgorithm() That's a better primitive than just exposing SupportedAlgorithms list. --- lib/connection.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 0562d416..87fc8e2c 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2000,8 +2000,11 @@ void Connection::Private::loadOutdatedUserDevices() << device.userId << user; continue; } - if(!device.algorithms.contains("m.olm.v1.curve25519-aes-sha2") || !device.algorithms.contains("m.megolm.v1.aes-sha2")) { - qCWarning(E2EE) << "Unsupported encryption algorithms found" << device.algorithms; + if (!std::all_of(device.algorithms.cbegin(), + device.algorithms.cend(), + isSupportedAlgorithm)) { + qCWarning(E2EE) << "Unsupported encryption algorithms found" + << device.algorithms; continue; } if(!verifyIdentitySignature(device, device.deviceId, device.userId)) { -- cgit v1.2.3 From b5e1fc7d8fcf9336db0dfb351403aa06dcb226a0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 16 Feb 2022 08:40:56 +0100 Subject: More cleanup, especially in EncryptedFile For EncryptedFile: - JSON converter bodies moved away to .cpp; - instead of C-style casts, reinterpret_cast is used to convert from (const) char* to (const) unsigned char*; - the size for the target plain text takes into account the case where the cipher block size can be larger than 1 (after reading https://www.openssl.org/docs/man1.1.1/man3/EVP_DecryptUpdate.html). - file decryption is wrapped in #ifdef Quotient_E2EE_ENABLED, to avoid OpenSSL linking errors when compiling without E2EE. --- lib/connection.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 87fc8e2c..1ef2495d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2007,8 +2007,10 @@ void Connection::Private::loadOutdatedUserDevices() << device.algorithms; continue; } - if(!verifyIdentitySignature(device, device.deviceId, device.userId)) { - qCWarning(E2EE) << "Failed to verify devicekeys signature. Skipping this device"; + if (!verifyIdentitySignature(device, device.deviceId, + device.userId)) { + qCWarning(E2EE) << "Failed to verify devicekeys signature. " + "Skipping this device"; continue; } deviceKeys[user][device.deviceId] = device; @@ -2022,9 +2024,11 @@ void Connection::Private::loadOutdatedUserDevices() void Connection::Private::saveDevicesList() { q->database()->transaction(); - auto query = q->database()->prepareQuery(QStringLiteral("DELETE FROM tracked_users")); + auto query = q->database()->prepareQuery( + QStringLiteral("DELETE FROM tracked_users")); q->database()->execute(query); - query.prepare(QStringLiteral("INSERT INTO tracked_users(matrixId) VALUES(:matrixId);")); + query.prepare(QStringLiteral( + "INSERT INTO tracked_users(matrixId) VALUES(:matrixId);")); for (const auto& user : trackedUsers) { query.bindValue(":matrixId", user); q->database()->execute(query); @@ -2032,13 +2036,18 @@ void Connection::Private::saveDevicesList() query.prepare(QStringLiteral("DELETE FROM outdated_users")); q->database()->execute(query); - query.prepare(QStringLiteral("INSERT INTO outdated_users(matrixId) VALUES(:matrixId);")); + query.prepare(QStringLiteral( + "INSERT INTO outdated_users(matrixId) VALUES(:matrixId);")); for (const auto& user : outdatedUsers) { query.bindValue(":matrixId", user); q->database()->execute(query); } - query.prepare(QStringLiteral("INSERT INTO tracked_devices(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey) VALUES(:matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey);")); + query.prepare(QStringLiteral( + "INSERT INTO tracked_devices" + "(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey) " + "VALUES(:matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey);" + )); for (const auto& user : deviceKeys.keys()) { for (const auto& device : deviceKeys[user]) { auto keys = device.keys.keys(); -- cgit v1.2.3 From cfa64f86da6fcfe04947a634a208705543824810 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 24 Feb 2022 17:21:46 +0100 Subject: Fix all tests --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1ef2495d..0ef27486 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -579,7 +579,7 @@ void Connection::Private::completeSetup(const QString& mxId) picklingMode = Encrypted { job.binaryData() }; } - database = new Database(data->userId(), q); + database = new Database(data->userId(), data->deviceId(), q); // init olmAccount olmAccount = std::make_unique(data->userId(), data->deviceId(), q); -- cgit v1.2.3 From 9815e9a0a27f0c4a493ad96e9d865ee489ca9404 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 25 Feb 2022 15:35:07 +0100 Subject: Save key counts to state Otherwise new one time keys will be uploaded on every start --- lib/connection.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 0ef27486..4c614176 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -117,6 +117,7 @@ public: bool encryptionUpdateRequired = false; PicklingMode picklingMode = Unencrypted {}; Database *database = nullptr; + QHash oneTimeKeysCount; // A map from SenderKey to vector of InboundSession UnorderedMap> olmSessions; @@ -755,13 +756,12 @@ QJsonObject toJson(const DirectChatsMap& directChats) void Connection::onSyncSuccess(SyncData&& data, bool fromCache) { #ifdef Quotient_E2EE_ENABLED - const auto oneTimeKeyCount = - static_cast(data.deviceOneTimeKeysCount()[SignedCurve25519Key]); - if (oneTimeKeyCount < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() + d->oneTimeKeysCount = data.deviceOneTimeKeysCount(); + if (d->oneTimeKeysCount[SignedCurve25519Key] < 0.4 * d->olmAccount->maxNumberOfOneTimeKeys() && !d->isUploadingKeys) { d->isUploadingKeys = true; d->olmAccount->generateOneTimeKeys( - d->olmAccount->maxNumberOfOneTimeKeys() / 2 - oneTimeKeyCount); + d->olmAccount->maxNumberOfOneTimeKeys() / 2 - d->oneTimeKeysCount[SignedCurve25519Key]); auto keys = d->olmAccount->oneTimeKeys(); auto job = d->olmAccount->createUploadKeyRequest(keys); run(job, ForegroundRequest); @@ -1829,6 +1829,10 @@ void Connection::saveState() const QJsonObject { { QStringLiteral("events"), accountDataEvents } }); } + { + QJsonObject keysJson = toJson(d->oneTimeKeysCount); + rootObj.insert(QStringLiteral("device_one_time_keys_count"), keysJson); + } #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) const auto data = -- cgit v1.2.3 From b0e1455989405ef46eb6d9ed2cd559a1164d04f4 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 25 Feb 2022 16:03:59 +0100 Subject: Ifdef --- lib/connection.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4c614176..66df1e43 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1829,10 +1829,12 @@ void Connection::saveState() const QJsonObject { { QStringLiteral("events"), accountDataEvents } }); } +#ifdef Quotient_E2EE_ENABLED { QJsonObject keysJson = toJson(d->oneTimeKeysCount); rootObj.insert(QStringLiteral("device_one_time_keys_count"), keysJson); } +#endif #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) const auto data = -- cgit v1.2.3 From 458a0472bd0c6ea0a859c5e55ba5dd3d92ecec99 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 26 Feb 2022 18:55:11 +0100 Subject: Add convenience functions for querying user devices and keys from cache --- lib/connection.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 66df1e43..ec1c13f5 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2154,4 +2154,20 @@ void Connection::saveMegolmSession(Room* room, const QString& senderKey, QOlmInb { database()->saveMegolmSession(room->id(), senderKey, session->sessionId(), session->pickle(picklingMode())); } + +QStringList Connection::devicesForUser(User* user) const +{ + return d->deviceKeys[user->id()].keys(); +} + +QString Connection::curveKeyForUserDevice(const QString& user, const QString& device) const +{ + return d->deviceKeys[user][device].keys[QStringLiteral("curve25519:") + device]; +} + +QString Connection::edKeyForUserDevice(const QString& user, const QString& device) const +{ + return d->deviceKeys[user][device].keys[QStringLiteral("ed25519:") + device]; +} + #endif -- cgit v1.2.3 From ccff9edb1c7102cfcc846561a3bdc41fc1496df9 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 26 Feb 2022 18:48:53 +0100 Subject: Handle to-device messages before handling roomdata Probably improves the performance slightly If we handle to room data first, if a message arrives at the same time as the to-device message containing the key and we handle the message first, it will not be decryptable and stored as undecrypted. Then, when the key is handled, the cache of undecrypted messages is searched, the message decrypted and replaced. When handling the key first, the message can be decryped instantly. --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 66df1e43..7099c59c 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -777,11 +777,11 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->consumeDevicesList(data.takeDevicesList()); #endif // Quotient_E2EE_ENABLED + d->consumeToDeviceEvents(data.takeToDeviceEvents()); d->data->setLastEvent(data.nextBatch()); d->consumeRoomData(data.takeRoomData(), fromCache); d->consumeAccountData(data.takeAccountData()); d->consumePresenceData(data.takePresenceData()); - d->consumeToDeviceEvents(data.takeToDeviceEvents()); #ifdef Quotient_E2EE_ENABLED if(d->encryptionUpdateRequired) { d->loadOutdatedUserDevices(); -- cgit v1.2.3 From 29510632780f695eefc9d5107e223aa02caac2c6 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 26 Feb 2022 19:40:13 +0100 Subject: Use QOlmMessage::Type in more places Make sure that the enum values correspond to the values used in the spec and use them instead of magic constants --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 66df1e43..b06c35c2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -274,10 +274,10 @@ public: QString decrypted; int type = personalCipherObject.value(TypeKeyL).toInt(-1); QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); - if (type == 0) { + if (type == QOlmMessage::PreKey) { QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); decrypted = sessionDecryptPrekey(preKeyMessage, senderKey, account); - } else if (type == 1) { + } else if (type == QOlmMessage::General) { QOlmMessage message(body, QOlmMessage::General); decrypted = sessionDecryptGeneral(message, senderKey); } -- cgit v1.2.3 From f159d5ec0caf75468c802ee997630af8f7fda02d Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 27 Feb 2022 14:11:28 +0100 Subject: Apply suggestions --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index ec1c13f5..24951b42 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2162,12 +2162,12 @@ QStringList Connection::devicesForUser(User* user) const QString Connection::curveKeyForUserDevice(const QString& user, const QString& device) const { - return d->deviceKeys[user][device].keys[QStringLiteral("curve25519:") + device]; + return d->deviceKeys[user][device].keys["curve25519:" % device]; } QString Connection::edKeyForUserDevice(const QString& user, const QString& device) const { - return d->deviceKeys[user][device].keys[QStringLiteral("ed25519:") + device]; + return d->deviceKeys[user][device].keys["ed25519:" % device]; } #endif -- cgit v1.2.3 From fb9d7dc22c74022b914b1964965ed6b8b850d831 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 7 Mar 2022 19:56:10 +0100 Subject: Store the device's ed25519 in the database --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4abb77a5..11c81edf 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2150,9 +2150,9 @@ UnorderedMap, QOlmInboundGroupSessionPtr> Connection return database()->loadMegolmSessions(room->id(), picklingMode()); } -void Connection::saveMegolmSession(Room* room, const QString& senderKey, QOlmInboundGroupSession* session) +void Connection::saveMegolmSession(Room* room, const QString& senderKey, QOlmInboundGroupSession* session, const QString& ed25519Key) { - database()->saveMegolmSession(room->id(), senderKey, session->sessionId(), session->pickle(picklingMode())); + database()->saveMegolmSession(room->id(), senderKey, session->sessionId(), ed25519Key, session->pickle(picklingMode())); } QStringList Connection::devicesForUser(User* user) const -- cgit v1.2.3 From cc7056851f92ba5b6224b5b82413ec55fd6aaa7f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 7 Mar 2022 20:20:10 +0100 Subject: Guard against device reuse attacks --- lib/connection.cpp | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4abb77a5..ae8532c3 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1998,6 +1998,7 @@ void Connection::Private::loadOutdatedUserDevices() currentQueryKeysJob = nullptr; const auto data = queryKeysJob->deviceKeys(); for(const auto &[user, keys] : asKeyValueRange(data)) { + QHash oldDevices = deviceKeys[user]; deviceKeys[user].clear(); for(const auto &device : keys) { if(device.userId != user) { @@ -2019,6 +2020,12 @@ void Connection::Private::loadOutdatedUserDevices() "Skipping this device"; continue; } + if (oldDevices.contains(device.deviceId)) { + if (oldDevices[device.deviceId].keys["ed25519:" % device.deviceId] != device.keys["ed25519:" % device.deviceId]) { + qCDebug(E2EE) << "Device reuse detected. Skipping this device"; + continue; + } + } deviceKeys[user][device.deviceId] = device; } outdatedUsers -= user; -- cgit v1.2.3 From 01f7641292eb5dff236f434d7140259c429521ca Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 8 Mar 2022 22:35:56 +0100 Subject: Store time of last decrypted message for each olm session Is required to correctly choose a session to use for sending messages --- lib/connection.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 11c81edf..506a2bc0 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -216,7 +216,7 @@ public: qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); return; } - q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult)); + q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult), QDateTime::currentDateTime()); } QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) { @@ -227,6 +227,7 @@ public: qCDebug(E2EE) << "Found inbound session"; const auto result = session->decrypt(message); if(std::holds_alternative(result)) { + q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); return std::get(result); } else { qCDebug(E2EE) << "Failed to decrypt prekey message"; @@ -261,6 +262,7 @@ public: for(auto& session : olmSessions[senderKey]) { const auto result = session->decrypt(message); if(std::holds_alternative(result)) { + q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); return std::get(result); } } -- cgit v1.2.3 From b8c22ff0663350a019ae918771fac4b6447d2b09 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 9 Mar 2022 20:23:23 +0100 Subject: Check for empty ed25519 key. --- lib/connection.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 506a2bc0..cb04ffe8 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -322,6 +322,11 @@ public: << "in Olm plaintext"; return {}; } + //TODO make this do the check mentioned in the E2EE Implementation guide instead + if (decryptedEvent->fullJson()["keys"]["ed25519"].toString().isEmpty()) { + qCDebug(E2EE) << "Event does not contain an ed25519 key"; + return {}; + } // TODO: keys to constants const auto decryptedEventObject = decryptedEvent->fullJson(); -- cgit v1.2.3 From 658e6db49364862be1ab8e264b03dc04a7bed721 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 2 Mar 2022 00:54:49 +0100 Subject: Implement sending encrypted messages --- lib/connection.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 45888bcb..88c61530 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1306,7 +1306,7 @@ Connection::sendToDevices(const QString& eventType, [&jsonUser](const auto& deviceToEvents) { jsonUser.insert( deviceToEvents.first, - deviceToEvents.second.contentJson()); + deviceToEvents.second->contentJson()); }); }); return callApi(BackgroundRequest, eventType, @@ -2184,4 +2184,32 @@ QString Connection::edKeyForUserDevice(const QString& user, const QString& devic return d->deviceKeys[user][device].keys["ed25519:" % device]; } +bool Connection::hasOlmSession(User* user, const QString& deviceId) const +{ + const auto& curveKey = curveKeyForUserDevice(user->id(), deviceId); + return d->olmSessions.contains(curveKey) && d->olmSessions[curveKey].size() > 0; +} + +QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) +{ + //TODO be smarter about choosing a session; see e2ee impl guide + //TODO create session? + const auto& curveKey = curveKeyForUserDevice(user->id(), device); + QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); + auto result = d->olmSessions[curveKey][0]->encrypt(message); + return qMakePair(type, result.toCiphertext()); +} + +//TODO be more consistent with curveKey and identityKey +void Connection::createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey) +{ + auto session = QOlmSession::createOutboundSession(olmAccount(), theirIdentityKey, theirOneTimeKey); + if (std::holds_alternative(session)) { + //TODO something + qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << std::get(session); + } + d->saveSession(std::get>(session), theirIdentityKey); + d->olmSessions[theirIdentityKey].push_back(std::move(std::get>(session))); +} + #endif -- cgit v1.2.3 From 30004d4e0d6aca06491777f77ed966a451eff50f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 2 Mar 2022 23:56:52 +0100 Subject: Save and load outgoing megolm session --- lib/connection.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 88c61530..d01b8ac4 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2193,7 +2193,6 @@ bool Connection::hasOlmSession(User* user, const QString& deviceId) const QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) { //TODO be smarter about choosing a session; see e2ee impl guide - //TODO create session? const auto& curveKey = curveKeyForUserDevice(user->id(), device); QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); @@ -2212,4 +2211,14 @@ void Connection::createOlmSession(const QString& theirIdentityKey, const QString d->olmSessions[theirIdentityKey].push_back(std::move(std::get>(session))); } +QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room) +{ + return d->database->loadCurrentOutboundMegolmSession(room->id(), d->picklingMode); +} + +void Connection::saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data) +{ + d->database->saveCurrentOutboundMegolmSession(room->id(), d->picklingMode, data); +} + #endif -- cgit v1.2.3 From 0ef080bdfa3a8a64d1faadf4a11a8b9dbb5bc055 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 6 Mar 2022 22:54:01 +0100 Subject: Keep log of where we send keys and send keys to new devices and users --- lib/connection.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d01b8ac4..691f4ab3 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2193,6 +2193,7 @@ bool Connection::hasOlmSession(User* user, const QString& deviceId) const QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) { //TODO be smarter about choosing a session; see e2ee impl guide + //TODO do we need to save the olm session after sending a message? const auto& curveKey = curveKeyForUserDevice(user->id(), device); QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); -- cgit v1.2.3 From 71eed90fdea8689d237da8de1bf385202b85cffd Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 10 Mar 2022 21:19:49 +0100 Subject: More work; Update olm pickle & timestamps in database; Remove TODOs --- lib/connection.cpp | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 691f4ab3..0ef002ca 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -33,6 +33,7 @@ #include "jobs/downloadfilejob.h" #include "jobs/mediathumbnailjob.h" #include "jobs/syncjob.h" +#include #ifdef Quotient_E2EE_ENABLED # include "e2ee/qolmaccount.h" @@ -221,13 +222,23 @@ public: QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) { Q_ASSERT(message.type() == QOlmMessage::PreKey); - for(auto& session : olmSessions[senderKey]) { + for (size_t i = 0; i < olmSessions[senderKey].size(); i++) { + auto& session = olmSessions[senderKey][i]; const auto matches = session->matchesInboundSessionFrom(senderKey, message); if(std::holds_alternative(matches) && std::get(matches)) { qCDebug(E2EE) << "Found inbound session"; const auto result = session->decrypt(message); if(std::holds_alternative(result)) { q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); + auto pickle = session->pickle(q->picklingMode()); + if (std::holds_alternative(pickle)) { + q->database()->updateOlmSession(senderKey, session->sessionId(), std::get(pickle)); + } else { + qCWarning(E2EE) << "Failed to pickle olm session."; + } + auto s = std::move(session); + olmSessions[senderKey].erase(olmSessions[senderKey].begin() + i); + olmSessions[senderKey].insert(olmSessions[senderKey].begin(), std::move(s)); return std::get(result); } else { qCDebug(E2EE) << "Failed to decrypt prekey message"; @@ -248,7 +259,7 @@ public: } const auto result = newSession->decrypt(message); saveSession(newSession, senderKey); - olmSessions[senderKey].push_back(std::move(newSession)); + olmSessions[senderKey].insert(olmSessions[senderKey].begin(), std::move(newSession)); if(std::holds_alternative(result)) { return std::get(result); } else { @@ -259,10 +270,20 @@ public: QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) { Q_ASSERT(message.type() == QOlmMessage::General); - for(auto& session : olmSessions[senderKey]) { + for (size_t i = 0; i < olmSessions[senderKey].size(); i++) { + auto& session = olmSessions[senderKey][i]; const auto result = session->decrypt(message); if(std::holds_alternative(result)) { q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); + auto pickle = session->pickle(q->picklingMode()); + if (std::holds_alternative(pickle)) { + q->database()->updateOlmSession(senderKey, session->sessionId(), std::get(pickle)); + } else { + qCWarning(E2EE) << "Failed to pickle olm session."; + } + auto s = std::move(session); + olmSessions[senderKey].erase(olmSessions[senderKey].begin() + i); + olmSessions[senderKey].insert(olmSessions[senderKey].begin(), std::move(s)); return std::get(result); } } @@ -2192,21 +2213,24 @@ bool Connection::hasOlmSession(User* user, const QString& deviceId) const QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) { - //TODO be smarter about choosing a session; see e2ee impl guide - //TODO do we need to save the olm session after sending a message? const auto& curveKey = curveKeyForUserDevice(user->id(), device); QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); + auto pickle = d->olmSessions[curveKey][0]->pickle(picklingMode()); + if (std::holds_alternative(pickle)) { + database()->updateOlmSession(curveKey, d->olmSessions[curveKey][0]->sessionId(), std::get(pickle)); + } else { + qCWarning(E2EE) << "Failed to pickle olm session."; + } return qMakePair(type, result.toCiphertext()); } -//TODO be more consistent with curveKey and identityKey void Connection::createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey) { auto session = QOlmSession::createOutboundSession(olmAccount(), theirIdentityKey, theirOneTimeKey); if (std::holds_alternative(session)) { - //TODO something qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << std::get(session); + return; } d->saveSession(std::get>(session), theirIdentityKey); d->olmSessions[theirIdentityKey].push_back(std::move(std::get>(session))); -- cgit v1.2.3 From 42abb01516ee4d3d0fe11ffddd47c7e76d786385 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 4 Apr 2022 17:28:52 +0200 Subject: Check edKey when receiving an olm message --- lib/connection.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 45888bcb..1250eddf 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -322,9 +322,17 @@ public: << "in Olm plaintext"; return {}; } - //TODO make this do the check mentioned in the E2EE Implementation guide instead - if (decryptedEvent->fullJson()["keys"]["ed25519"].toString().isEmpty()) { - qCDebug(E2EE) << "Event does not contain an ed25519 key"; + + auto query = database->prepareQuery(QStringLiteral("SELECT edKey FROM tracked_devices WHERE curveKey=:curveKey;")); + query.bindValue(":curveKey", encryptedEvent.contentJson()["sender_key"].toString()); + database->execute(query); + if (!query.next()) { + qCWarning(E2EE) << "Received olm message from unknown device" << encryptedEvent.contentJson()["sender_key"].toString(); + return {}; + } + auto edKey = decryptedEvent->fullJson()["keys"]["ed25519"].toString(); + if (edKey.isEmpty() || query.value(QStringLiteral("edKey")).toString() != edKey) { + qCDebug(E2EE) << "Received olm message with invalid ed key"; return {}; } -- cgit v1.2.3 From 5c93193508a49b79a92fd0d80cf4db14f1d0762e Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 8 Apr 2022 23:23:07 +0200 Subject: Make sure devices are known before decrypting olm messages --- lib/connection.cpp | 68 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 17 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1250eddf..d7460f08 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -118,6 +118,8 @@ public: PicklingMode picklingMode = Unencrypted {}; Database *database = nullptr; QHash oneTimeKeysCount; + std::vector> pendingEncryptedEvents; + void handleEncryptedToDeviceEvent(const EncryptedEvent& event); // A map from SenderKey to vector of InboundSession UnorderedMap> olmSessions; @@ -937,30 +939,44 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); return; } - const auto decryptedEvent = sessionDecryptMessage(event); - if(!decryptedEvent) { - qCWarning(E2EE) << "Failed to decrypt event" << event.id(); + if (q->isKnownCurveKey(event.senderId(), event.senderKey())) { + handleEncryptedToDeviceEvent(event); return; } - - switchOnType(*decryptedEvent, - [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { - if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { - detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); - } else { - qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() - << "is not found at the connection" << q->objectName(); - } - }, - [](const Event& evt) { - qCDebug(E2EE) << "Skipping encrypted to_device event, type" - << evt.matrixType(); - }); + trackedUsers += event.senderId(); + outdatedUsers += event.senderId(); + encryptionUpdateRequired = true; + pendingEncryptedEvents.push_back(std::make_unique(event.fullJson())); + }, [](const Event& e){ + // Unhandled }); } #endif } +void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& event) +{ + const auto decryptedEvent = sessionDecryptMessage(event); + if(!decryptedEvent) { + qCWarning(E2EE) << "Failed to decrypt event" << event.id(); + return; + } + + switchOnType(*decryptedEvent, + [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { + if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { + detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); + } else { + qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() + << "is not found at the connection" << q->objectName(); + } + }, + [](const Event& evt) { + qCDebug(E2EE) << "Skipping encrypted to_device event, type" + << evt.matrixType(); + }); +} + void Connection::Private::consumeDevicesList(DevicesList&& devicesList) { #ifdef Quotient_E2EE_ENABLED @@ -2046,6 +2062,15 @@ void Connection::Private::loadOutdatedUserDevices() outdatedUsers -= user; } saveDevicesList(); + + for(size_t i = 0; i < pendingEncryptedEvents.size();) { + if (q->isKnownCurveKey(pendingEncryptedEvents[i]->fullJson()[SenderKeyL].toString(), pendingEncryptedEvents[i]->contentJson()["sender_key"].toString())) { + handleEncryptedToDeviceEvent(*(pendingEncryptedEvents[i].get())); + pendingEncryptedEvents.erase(pendingEncryptedEvents.begin() + i); + } else { + i++; + } + } }); } @@ -2192,4 +2217,13 @@ QString Connection::edKeyForUserDevice(const QString& user, const QString& devic return d->deviceKeys[user][device].keys["ed25519:" % device]; } +bool Connection::isKnownCurveKey(const QString& user, const QString& curveKey) +{ + auto query = database()->prepareQuery(QStringLiteral("SELECT * FROM tracked_devices WHERE matrixId=:matrixId AND curveKey=:curveKey")); + query.bindValue(":matrixId", user); + query.bindValue(":curveKey", curveKey); + database()->execute(query); + return query.next(); +} + #endif -- cgit v1.2.3 From 2af8d83526ed7a24c18b185e1d64d97632e10f1e Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 9 Apr 2022 02:04:39 +0200 Subject: Prepare for MSC 3700 --- lib/connection.cpp | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d7460f08..0bdc0489 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -220,7 +220,7 @@ public: } q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult), QDateTime::currentDateTime()); } - QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) + std::pair sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) { Q_ASSERT(message.type() == QOlmMessage::PreKey); for(auto& session : olmSessions[senderKey]) { @@ -230,7 +230,7 @@ public: const auto result = session->decrypt(message); if(std::holds_alternative(result)) { q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return std::get(result); + return { std::get(result), session->sessionId() }; } else { qCDebug(E2EE) << "Failed to decrypt prekey message"; return {}; @@ -249,47 +249,53 @@ public: qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); } const auto result = newSession->decrypt(message); + QString sessionId = newSession->sessionId(); saveSession(newSession, senderKey); olmSessions[senderKey].push_back(std::move(newSession)); if(std::holds_alternative(result)) { - return std::get(result); + return { std::get(result), sessionId }; } else { qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; return {}; } } - QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + std::pair sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) { Q_ASSERT(message.type() == QOlmMessage::General); for(auto& session : olmSessions[senderKey]) { const auto result = session->decrypt(message); if(std::holds_alternative(result)) { q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return std::get(result); + return { std::get(result), session->sessionId() }; } } qCWarning(E2EE) << "Failed to decrypt message"; return {}; } - QString sessionDecryptMessage( + std::pair sessionDecryptMessage( const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) { QString decrypted; + QString olmSessionId; int type = personalCipherObject.value(TypeKeyL).toInt(-1); QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); if (type == QOlmMessage::PreKey) { QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); - decrypted = sessionDecryptPrekey(preKeyMessage, senderKey, account); + auto result = sessionDecryptPrekey(preKeyMessage, senderKey, account); + decrypted = result.first; + olmSessionId = result.second; } else if (type == QOlmMessage::General) { QOlmMessage message(body, QOlmMessage::General); - decrypted = sessionDecryptGeneral(message, senderKey); + auto result = sessionDecryptGeneral(message, senderKey); + decrypted = result.first; + olmSessionId = result.second; } - return decrypted; + return { decrypted, olmSessionId }; } #endif - EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) + std::pair sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; @@ -305,7 +311,7 @@ public: qCDebug(E2EE) << "Encrypted event is not for the current device"; return {}; } - const auto decrypted = sessionDecryptMessage( + const auto [decrypted, olmSessionId] = sessionDecryptMessage( personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" @@ -356,7 +362,7 @@ public: return {}; } - return std::move(decryptedEvent); + return { std::move(decryptedEvent), olmSessionId }; #endif // Quotient_E2EE_ENABLED } #ifdef Quotient_E2EE_ENABLED @@ -956,16 +962,16 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& event) { - const auto decryptedEvent = sessionDecryptMessage(event); + const auto [decryptedEvent, olmSessionId] = sessionDecryptMessage(event); if(!decryptedEvent) { qCWarning(E2EE) << "Failed to decrypt event" << event.id(); return; } switchOnType(*decryptedEvent, - [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { + [this, senderKey = event.senderKey(), &event, olmSessionId](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { - detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); + detectedRoom->handleRoomKeyEvent(roomKeyEvent, event.senderId(), olmSessionId); } else { qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); @@ -2192,14 +2198,14 @@ Database* Connection::database() return d->database; } -UnorderedMap, QOlmInboundGroupSessionPtr> Connection::loadRoomMegolmSessions(Room* room) +UnorderedMap Connection::loadRoomMegolmSessions(Room* room) { return database()->loadMegolmSessions(room->id(), picklingMode()); } -void Connection::saveMegolmSession(Room* room, const QString& senderKey, QOlmInboundGroupSession* session, const QString& ed25519Key) +void Connection::saveMegolmSession(Room* room, QOlmInboundGroupSession* session) { - database()->saveMegolmSession(room->id(), senderKey, session->sessionId(), ed25519Key, session->pickle(picklingMode())); + database()->saveMegolmSession(room->id(), session->sessionId(), session->pickle(picklingMode()), session->senderId(), session->olmSessionId()); } QStringList Connection::devicesForUser(User* user) const -- cgit v1.2.3 From ad50a5167b98579e8e4960352653dca995490368 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 16 Apr 2022 20:47:53 +0200 Subject: Try fixing lgtm.com --- lib/connection.cpp | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 0bdc0489..4ca82888 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -962,25 +962,28 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& event) { - const auto [decryptedEvent, olmSessionId] = sessionDecryptMessage(event); - if(!decryptedEvent) { - qCWarning(E2EE) << "Failed to decrypt event" << event.id(); - return; - } + const auto [decryptedEvent, id] = sessionDecryptMessage(event); + if(!decryptedEvent) { + qCWarning(E2EE) << "Failed to decrypt event" << event.id(); + return; + } - switchOnType(*decryptedEvent, - [this, senderKey = event.senderKey(), &event, olmSessionId](const RoomKeyEvent& roomKeyEvent) { - if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { - detectedRoom->handleRoomKeyEvent(roomKeyEvent, event.senderId(), olmSessionId); - } else { - qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() - << "is not found at the connection" << q->objectName(); - } - }, - [](const Event& evt) { - qCDebug(E2EE) << "Skipping encrypted to_device event, type" - << evt.matrixType(); - }); + // Yes, this is weird, but lgtm.com doesn't like it otherwise + const auto olmSessionId = id; + + switchOnType(*decryptedEvent, + [this, senderKey = event.senderKey(), &event, olmSessionId](const RoomKeyEvent& roomKeyEvent) { + if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { + detectedRoom->handleRoomKeyEvent(roomKeyEvent, event.senderId(), olmSessionId); + } else { + qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() + << "is not found at the connection" << q->objectName(); + } + }, + [](const Event& evt) { + qCDebug(E2EE) << "Skipping encrypted to_device event, type" + << evt.matrixType(); + }); } void Connection::Private::consumeDevicesList(DevicesList&& devicesList) -- cgit v1.2.3 From 06b7093aaa0b5cce58c1191c7e0bf9bdfe135005 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 16 Apr 2022 20:53:58 +0200 Subject: Only build function when E2EE is enabled --- lib/connection.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4ca82888..1a5e3c6e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -960,6 +960,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) #endif } +#ifdef Quotient_E2EE_ENABLED void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& event) { const auto [decryptedEvent, id] = sessionDecryptMessage(event); @@ -985,6 +986,7 @@ void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& eve << evt.matrixType(); }); } +#endif void Connection::Private::consumeDevicesList(DevicesList&& devicesList) { -- cgit v1.2.3 From c0c4cd014718fdb54ee691ccbdab46981e15d25f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 16 Apr 2022 22:09:12 +0200 Subject: Use more idiomatic C++ --- lib/connection.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1a5e3c6e..d99ab64d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -963,17 +963,14 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) #ifdef Quotient_E2EE_ENABLED void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& event) { - const auto [decryptedEvent, id] = sessionDecryptMessage(event); + const auto [decryptedEvent, olmSessionId] = sessionDecryptMessage(event); if(!decryptedEvent) { qCWarning(E2EE) << "Failed to decrypt event" << event.id(); return; } - // Yes, this is weird, but lgtm.com doesn't like it otherwise - const auto olmSessionId = id; - switchOnType(*decryptedEvent, - [this, senderKey = event.senderKey(), &event, olmSessionId](const RoomKeyEvent& roomKeyEvent) { + [this, senderKey = event.senderKey(), &event, olmSessionId = olmSessionId](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, event.senderId(), olmSessionId); } else { -- cgit v1.2.3 From 3a81aea545ec014d597f3756c362913aad4421df Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 16 Apr 2022 23:32:59 +0200 Subject: Fixes --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 28377dd9..42a5f5fc 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -241,7 +241,7 @@ public: auto s = std::move(session); olmSessions[senderKey].erase(olmSessions[senderKey].begin() + i); olmSessions[senderKey].insert(olmSessions[senderKey].begin(), std::move(s)); - return { std::get(result), session->sessionId() }; + return { std::get(result), olmSessions[senderKey][0]->sessionId() }; } else { qCDebug(E2EE) << "Failed to decrypt prekey message"; return {}; @@ -287,7 +287,7 @@ public: auto s = std::move(session); olmSessions[senderKey].erase(olmSessions[senderKey].begin() + i); olmSessions[senderKey].insert(olmSessions[senderKey].begin(), std::move(s)); - return { std::get(result), session->sessionId() }; + return { std::get(result), olmSessions[senderKey][0]->sessionId() }; } } qCWarning(E2EE) << "Failed to decrypt message"; -- cgit v1.2.3 From 534b6f3fb3a4bd14d78942cab7eb430dd98e471c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 23 Apr 2022 19:46:58 +0200 Subject: SLICE() Add a macro to make slicing clear in the code and quiet for static analysis. --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d99ab64d..a969b3b9 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2065,7 +2065,7 @@ void Connection::Private::loadOutdatedUserDevices() continue; } } - deviceKeys[user][device.deviceId] = device; + deviceKeys[user][device.deviceId] = SLICE(device, DeviceKeys); } outdatedUsers -= user; } -- cgit v1.2.3 From 16d4f4e48304543a0ab59b235edba07f5f2c2204 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 18 Apr 2022 20:07:12 +0200 Subject: Implement key verification --- lib/connection.cpp | 104 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 7 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 42a5f5fc..68aed4e4 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -40,6 +40,8 @@ # include "e2ee/qolmutils.h" # include "database.h" # include "e2ee/qolminboundsession.h" +# include "events/keyverificationevent.h" +# include "keyverificationsession.h" #if QT_VERSION_MAJOR >= 6 # include @@ -974,8 +976,23 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) outdatedUsers += event.senderId(); encryptionUpdateRequired = true; pendingEncryptedEvents.push_back(std::make_unique(event.fullJson())); - }, [](const Event& e){ - // Unhandled + }, [this](const KeyVerificationRequestEvent& event) { + auto session = new KeyVerificationSession(q->userId(), event, q, false, q); + emit q->newKeyVerificationSession(session); + }, [this](const KeyVerificationReadyEvent& event) { + emit q->incomingKeyVerificationReady(event); + }, [this](const KeyVerificationStartEvent& event) { + emit q->incomingKeyVerificationStart(event); + }, [this](const KeyVerificationAcceptEvent& event) { + emit q->incomingKeyVerificationAccept(event); + }, [this](const KeyVerificationKeyEvent& event) { + emit q->incomingKeyVerificationKey(event); + }, [this](const KeyVerificationMacEvent& event) { + emit q->incomingKeyVerificationMac(event); + }, [this](const KeyVerificationDoneEvent& event) { + emit q->incomingKeyVerificationDone(event); + }, [this](const KeyVerificationCancelEvent& event) { + emit q->incomingKeyVerificationCancel(event); }); } #endif @@ -998,9 +1015,25 @@ void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& eve qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); } - }, - [](const Event& evt) { - qCDebug(E2EE) << "Skipping encrypted to_device event, type" + }, [this](const KeyVerificationRequestEvent& event) { + auto session = new KeyVerificationSession(q->userId(), event, q, true, q); + emit q->newKeyVerificationSession(session); + }, [this](const KeyVerificationReadyEvent& event) { + emit q->incomingKeyVerificationReady(event); + }, [this](const KeyVerificationStartEvent& event) { + emit q->incomingKeyVerificationStart(event); + }, [this](const KeyVerificationAcceptEvent& event) { + emit q->incomingKeyVerificationAccept(event); + }, [this](const KeyVerificationKeyEvent& event) { + emit q->incomingKeyVerificationKey(event); + }, [this](const KeyVerificationMacEvent& event) { + emit q->incomingKeyVerificationMac(event); + }, [this](const KeyVerificationDoneEvent& event) { + emit q->incomingKeyVerificationDone(event); + }, [this](const KeyVerificationCancelEvent& event) { + emit q->incomingKeyVerificationCancel(event); + }, [](const Event& evt) { + qCWarning(E2EE) << "Skipping encrypted to_device event, type" << evt.matrixType(); }); } @@ -2127,8 +2160,8 @@ void Connection::Private::saveDevicesList() query.prepare(QStringLiteral( "INSERT INTO tracked_devices" - "(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey) " - "VALUES(:matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey);" + "(matrixId, deviceId, curveKeyId, curveKey, edKeyId, edKey, verified) " + "SELECT :matrixId, :deviceId, :curveKeyId, :curveKey, :edKeyId, :edKey, :verified WHERE NOT EXISTS(SELECT 1 FROM tracked_devices WHERE matrixId=:matrixId AND deviceId=:deviceId);" )); for (const auto& user : deviceKeys.keys()) { for (const auto& device : deviceKeys[user]) { @@ -2142,6 +2175,8 @@ void Connection::Private::saveDevicesList() query.bindValue(":curveKey", device.keys[curveKeyId]); query.bindValue(":edKeyId", edKeyId); query.bindValue(":edKey", device.keys[edKeyId]); + // If the device gets saved here, it can't be verified + query.bindValue(":verified", false); q->database()->execute(query); } @@ -2297,3 +2332,58 @@ bool Connection::isKnownCurveKey(const QString& user, const QString& curveKey) } #endif + +void Connection::startKeyVerificationSession(const QString& deviceId) +{ + auto session = new KeyVerificationSession(userId(), deviceId, this, this); + Q_EMIT newKeyVerificationSession(session); +} + +void Connection::sendToDevice(const QString& userId, const QString& deviceId, event_ptr_tt event, bool encrypted) +{ + + UsersToDevicesToEvents payload; + if (encrypted) { + QJsonObject payloadJson = event->fullJson(); + payloadJson["recipient"] = userId; + payloadJson["sender"] = user()->id(); + QJsonObject recipientObject; + recipientObject["ed25519"] = edKeyForUserDevice(userId, deviceId); + payloadJson["recipient_keys"] = recipientObject; + QJsonObject senderObject; + senderObject["ed25519"] = QString(olmAccount()->identityKeys().ed25519); + payloadJson["keys"] = senderObject; + + const auto& u = user(userId); + auto cipherText = olmEncryptMessage(u, deviceId, QJsonDocument(payloadJson).toJson(QJsonDocument::Compact)); + QJsonObject encryptedJson; + encryptedJson[curveKeyForUserDevice(userId, deviceId)] = QJsonObject{{"type", cipherText.first}, {"body", QString(cipherText.second)}, {"sender", this->userId()}}; + auto encryptedEvent = makeEvent(encryptedJson, olmAccount()->identityKeys().curve25519); + payload[userId][deviceId] = std::move(encryptedEvent); + } else { + payload[userId][deviceId] = std::move(event); + } + sendToDevices(payload[userId][deviceId]->matrixType(), payload); +} + +bool Connection::isVerifiedSession(const QString& megolmSessionId) +{ + auto query = database()->prepareQuery("SELECT olmSessionId FROM inbound_megolm_sessions WHERE sessionId=:sessionId;"_ls); + query.bindValue(":sessionId", megolmSessionId); + database()->execute(query); + if (!query.next()) { + return false; + } + auto olmSessionId = query.value("olmSessionId").toString(); + query.prepare("SELECT senderKey FROM olm_sessions WHERE sessionId=:sessionId;"_ls); + query.bindValue(":sessionId", olmSessionId); + database()->execute(query); + if (!query.next()) { + return false; + } + auto curveKey = query.value("senderKey"_ls).toString(); + query.prepare("SELECT verified FROM tracked_devices WHERE curveKey=:curveKey;"_ls); + query.bindValue(":curveKey", curveKey); + database()->execute(query); + return query.next() && query.value("verified").toBool(); +} -- cgit v1.2.3 From 843f7a0ac2619a2f2863d457cdeaa03707255793 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 4 May 2022 21:12:29 +0200 Subject: basic*EventJson() -> *Event::basicJson() This makes it easier and more intuitive to build a minimal JSON payload for a given event type. A common basicJson() call point is also convenient in template contexts (see next commits). --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index a969b3b9..1ea394a1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1858,11 +1858,11 @@ void Connection::saveState() const } { QJsonArray accountDataEvents { - basicEventJson(QStringLiteral("m.direct"), toJson(d->directChats)) + Event::basicJson(QStringLiteral("m.direct"), toJson(d->directChats)) }; for (const auto& e : d->accountData) accountDataEvents.append( - basicEventJson(e.first, e.second->contentJson())); + Event::basicJson(e.first, e.second->contentJson())); rootObj.insert(QStringLiteral("account_data"), QJsonObject { -- cgit v1.2.3 From 98fdf62391fdc5135d8324476903a4c43345e732 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 6 May 2022 22:39:34 +0200 Subject: Fix race condition in consumeRoomData() QCoreApplication::processEvents() is well-known to be a _wrong_ solution to the unresponsive UI problem; despite that, connection.cpp has long had that call to let UI update itself while processing bulky room updates (mainly from the initial sync). This commit finally fixes this, after an (admittedly rare) race condition has been hit, as follows: 0. Pre-requisite: quotest runs all the tests and is about to leave the room; there's an ongoing sync request. 1. Quotest calls /leave 2. Sync returns, with the batch of _several_ rooms (that's important) 3. The above code handles the first room in the batch 4. processEvents() is called, just in time for the /leave response. 5. The /leave response handler in quotest ends up calling Connection::logout() (processEvents() still hasn't returned). 6. Connection::logout() calls abandon() on the ongoing SyncJob, pulling the rug from under onSyncSuccess()/consumeRoomData(). 7. processEvents() returns and the above code proceeds to the next room - only to find that the roomDataList (that is a ref to a structure owned by SyncJob), is now pointing to garbage. Morals of the story: 1. processEvents() effectively makes code multi-threaded: one flow is suspended and another one may run _on the same data_. After the first flow is resumed, it cannot make any assumptions regarding which data the second flow touched and/or changed. 2. The library had quite a few cases of using &&-refs, avoiding even move operations but also leaving ownership of the data with the original producer (SyncJob). If the lifetime of that producer ends too soon, those refs become dangling. The fix makes two important things, respectively: 2. Ownership of room data is now transfered to the processing side, the moment it is scheduled (see below), in the form of moving into a lambda capture. 1. Instead of processEvents(), processing of room data is scheduled via QMetaObject::invokeMethod(), uncoupling the moment when the data was received in SyncJob from the moment they are processed in Room::updateData() (and all the numerous signal-slots it calls). Also: Room::baseStateLoaded now causes Connection::loadedRoomState, not the other way round - this is more natural and doesn't need Connection to keep firstTimeRooms map around. --- lib/connection.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1ea394a1..4418958e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -96,7 +96,6 @@ public: /// as of the last sync QHash roomAliasMap; QVector roomIdsToForget; - QVector firstTimeRooms; QVector pendingStateRoomIds; QMap userMap; DirectChatsMap directChats; @@ -833,16 +832,14 @@ void Connection::Private::consumeRoomData(SyncDataList&& roomDataList, } if (auto* r = q->provideRoom(roomData.roomId, roomData.joinState)) { pendingStateRoomIds.removeOne(roomData.roomId); - r->updateData(std::move(roomData), fromCache); - if (firstTimeRooms.removeOne(r)) { - emit q->loadedRoomState(r); - if (capabilities.roomVersions) - r->checkVersion(); - // Otherwise, the version will be checked in reloadCapabilities() - } + // Update rooms one by one, giving time to update the UI. + QMetaObject::invokeMethod( + r, + [r, rd = std::move(roomData), fromCache] () mutable { + r->updateData(std::move(rd), fromCache); + }, + Qt::QueuedConnection); } - // Let UI update itself after updating each room - QCoreApplication::processEvents(); } } @@ -1707,9 +1704,14 @@ Room* Connection::provideRoom(const QString& id, Omittable joinState) return nullptr; } d->roomMap.insert(roomKey, room); - d->firstTimeRooms.push_back(room); connect(room, &Room::beforeDestruction, this, &Connection::aboutToDeleteRoom); + connect(room, &Room::baseStateLoaded, this, [this, room] { + emit loadedRoomState(room); + if (d->capabilities.roomVersions) + room->checkVersion(); + // Otherwise, the version will be checked in reloadCapabilities() + }); emit newRoom(room); } if (!joinState) -- cgit v1.2.3 From 572b727b22d66a79431326c924236ef431fd972b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 14 May 2022 11:20:43 +0200 Subject: Cleanup across the board Mainly driven by clang-tidy and SonarCloud warnings (sadly, SonarCloud doesn't store historical reports so no link can be provided here). --- lib/connection.cpp | 72 ++++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 37 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 4418958e..bd4d9251 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -219,7 +219,9 @@ public: } q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult), QDateTime::currentDateTime()); } - std::pair sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr& olmAccount) + + std::pair sessionDecryptPrekey(const QOlmMessage& message, + const QString& senderKey) { Q_ASSERT(message.type() == QOlmMessage::PreKey); for(auto& session : olmSessions[senderKey]) { @@ -273,24 +275,19 @@ public: } std::pair sessionDecryptMessage( - const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr& account) + const QJsonObject& personalCipherObject, const QByteArray& senderKey) { - QString decrypted; - QString olmSessionId; - int type = personalCipherObject.value(TypeKeyL).toInt(-1); - QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); - if (type == QOlmMessage::PreKey) { - QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); - auto result = sessionDecryptPrekey(preKeyMessage, senderKey, account); - decrypted = result.first; - olmSessionId = result.second; - } else if (type == QOlmMessage::General) { - QOlmMessage message(body, QOlmMessage::General); - auto result = sessionDecryptGeneral(message, senderKey); - decrypted = result.first; - olmSessionId = result.second; - } - return { decrypted, olmSessionId }; + QOlmMessage message { + personalCipherObject.value(BodyKeyL).toString().toLatin1(), + static_cast( + personalCipherObject.value(TypeKeyL).toInt(-1)) + }; + if (message.type() == QOlmMessage::PreKey) + return sessionDecryptPrekey(message, senderKey); + if (message.type() == QOlmMessage::General) + return sessionDecryptGeneral(message, senderKey); + qCWarning(E2EE) << "Olm message has incorrect type" << message.type(); + return {}; } #endif @@ -310,8 +307,9 @@ public: qCDebug(E2EE) << "Encrypted event is not for the current device"; return {}; } - const auto [decrypted, olmSessionId] = sessionDecryptMessage( - personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); + const auto [decrypted, olmSessionId] = + sessionDecryptMessage(personalCipherObject, + encryptedEvent.senderKey().toLatin1()); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" << encryptedEvent.senderKey() @@ -950,8 +948,6 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) outdatedUsers += event.senderId(); encryptionUpdateRequired = true; pendingEncryptedEvents.push_back(std::make_unique(event.fullJson())); - }, [](const Event& e){ - // Unhandled }); } #endif @@ -967,7 +963,7 @@ void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& eve } switchOnType(*decryptedEvent, - [this, senderKey = event.senderKey(), &event, olmSessionId = olmSessionId](const RoomKeyEvent& roomKeyEvent) { + [this, &event, olmSessionId = olmSessionId](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, event.senderId(), olmSessionId); } else { @@ -2074,12 +2070,13 @@ void Connection::Private::loadOutdatedUserDevices() saveDevicesList(); for(size_t i = 0; i < pendingEncryptedEvents.size();) { - if (q->isKnownCurveKey(pendingEncryptedEvents[i]->fullJson()[SenderKeyL].toString(), pendingEncryptedEvents[i]->contentJson()["sender_key"].toString())) { - handleEncryptedToDeviceEvent(*(pendingEncryptedEvents[i].get())); + if (q->isKnownCurveKey( + pendingEncryptedEvents[i]->fullJson()[SenderKeyL].toString(), + pendingEncryptedEvents[i]->contentPart("sender_key"_ls))) { + handleEncryptedToDeviceEvent(*pendingEncryptedEvents[i]); pendingEncryptedEvents.erase(pendingEncryptedEvents.begin() + i); - } else { - i++; - } + } else + ++i; } }); } @@ -2188,13 +2185,10 @@ void Connection::saveOlmAccount() #ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { - auto room = this->room(notification["room_id"].toString()); + auto r = room(notification["room_id"].toString()); auto event = makeEvent(notification["event"].toObject()); - auto decrypted = room->decryptMessage(*event); - if(!decrypted) { - return QJsonObject(); - } - return decrypted->fullJson(); + const auto decrypted = r->decryptMessage(*event); + return decrypted ? decrypted->fullJson() : QJsonObject(); } Database* Connection::database() @@ -2202,14 +2196,18 @@ Database* Connection::database() return d->database; } -UnorderedMap Connection::loadRoomMegolmSessions(Room* room) +UnorderedMap +Connection::loadRoomMegolmSessions(const Room* room) { return database()->loadMegolmSessions(room->id(), picklingMode()); } -void Connection::saveMegolmSession(Room* room, QOlmInboundGroupSession* session) +void Connection::saveMegolmSession(const Room* room, + const QOlmInboundGroupSession& session) { - database()->saveMegolmSession(room->id(), session->sessionId(), session->pickle(picklingMode()), session->senderId(), session->olmSessionId()); + database()->saveMegolmSession(room->id(), session.sessionId(), + session.pickle(picklingMode()), + session.senderId(), session.olmSessionId()); } QStringList Connection::devicesForUser(User* user) const -- cgit v1.2.3 From 3fcc0c5acb160364e819688cc67a8aaf814fafef Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 14 May 2022 20:51:05 +0200 Subject: Optimise #includes for QOlm* classes --- lib/connection.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index bd4d9251..511b64e2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -35,16 +35,17 @@ #include "jobs/syncjob.h" #ifdef Quotient_E2EE_ENABLED -# include "e2ee/qolmaccount.h" -# include "e2ee/qolmutils.h" # include "database.h" +# include "e2ee/qolmaccount.h" # include "e2ee/qolminboundsession.h" +# include "e2ee/qolmsession.h" +# include "e2ee/qolmutils.h" -#if QT_VERSION_MAJOR >= 6 -# include -#else -# include -#endif +# if QT_VERSION_MAJOR >= 6 +# include +# else +# include +# endif #endif // Quotient_E2EE_ENABLED #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) -- cgit v1.2.3 From a3486fd0e9786c47564ce8173a2e4039135b10f9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 15 May 2022 22:08:09 +0200 Subject: Simplify QOlmSession::matchesInboundSession*() There's no particular use in letting `QOlmError` out, only to confirm that, well, `QOlmError` is just another form of no-match. --- lib/connection.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 511b64e2..955b5b1a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -226,8 +226,7 @@ public: { Q_ASSERT(message.type() == QOlmMessage::PreKey); for(auto& session : olmSessions[senderKey]) { - const auto matches = session->matchesInboundSessionFrom(senderKey, message); - if(std::holds_alternative(matches) && std::get(matches)) { + if (session->matchesInboundSessionFrom(senderKey, message)) { qCDebug(E2EE) << "Found inbound session"; const auto result = session->decrypt(message); if(std::holds_alternative(result)) { -- cgit v1.2.3 From 79b3dba1ed4b6870c4e989ada88e33b1ce0ddc21 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 16 May 2022 10:41:54 +0200 Subject: QOlmExpected and associated refactoring As mentioned in the commit introducing `Expected`, `QOlmExpected` is simply an alias for `Expected`. This simplifies quite a few function signatures in `QOlm*` classes and collapses unwieldy `std::holds_alternative<>`/`std::get<>` constructs into a neat contextual bool cast and an invocation of `operator*` or `value()`/`error()` accessors that don't need to specify the type. While refactoring the code, I found a couple of cases of mismatching `uint32_t` and `qint32_t` in return values; a couple of cases where `decrypt()` returns `QString` which is in fact `QByteArray` (e.g., in `QOlmSession::decrypt()`); there's a repetitive algorithm in `Connection::Private::sessionDecryptPrekey()` and `sessionDecryptGeneral()` --- lib/connection.cpp | 136 +++++++++++++++++++++++++++-------------------------- 1 file changed, 70 insertions(+), 66 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 955b5b1a..2528b70b 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -212,82 +212,83 @@ public: void loadSessions() { olmSessions = q->database()->loadOlmSessions(q->picklingMode()); } - void saveSession(QOlmSessionPtr& session, const QString &senderKey) { - auto pickleResult = session->pickle(q->picklingMode()); - if (std::holds_alternative(pickleResult)) { - qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); - return; - } - q->database()->saveOlmSession(senderKey, session->sessionId(), std::get(pickleResult), QDateTime::currentDateTime()); - } - - std::pair sessionDecryptPrekey(const QOlmMessage& message, - const QString& senderKey) + void saveSession(QOlmSession& session, const QString& senderKey) { - Q_ASSERT(message.type() == QOlmMessage::PreKey); - for(auto& session : olmSessions[senderKey]) { - if (session->matchesInboundSessionFrom(senderKey, message)) { - qCDebug(E2EE) << "Found inbound session"; - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return { std::get(result), session->sessionId() }; - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message"; - return {}; - } - } - } - qCDebug(E2EE) << "Creating new inbound session"; - auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); - if(std::holds_alternative(newSessionResult)) { - qCWarning(E2EE) << "Failed to create inbound session for" << senderKey << std::get(newSessionResult); - return {}; - } - auto newSession = std::move(std::get(newSessionResult)); - auto error = olmAccount->removeOneTimeKeys(newSession); - if (error) { - qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); - } - const auto result = newSession->decrypt(message); - QString sessionId = newSession->sessionId(); - saveSession(newSession, senderKey); - olmSessions[senderKey].push_back(std::move(newSession)); - if(std::holds_alternative(result)) { - return { std::get(result), sessionId }; - } else { - qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; - return {}; - } + if (auto pickleResult = session.pickle(q->picklingMode())) + q->database()->saveOlmSession(senderKey, session.sessionId(), + *pickleResult, + QDateTime::currentDateTime()); + else + qCWarning(E2EE) << "Failed to pickle olm session. Error" + << pickleResult.error(); } - std::pair sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + + template + std::pair doDecryptMessage(const QOlmSession& session, + const QOlmMessage& message, + FnT&& andThen) const { - Q_ASSERT(message.type() == QOlmMessage::General); - for(auto& session : olmSessions[senderKey]) { - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - q->database()->setOlmSessionLastReceived(QString(session->sessionId()), QDateTime::currentDateTime()); - return { std::get(result), session->sessionId() }; - } + const auto expectedMessage = session.decrypt(message); + if (expectedMessage) { + const auto result = + std::make_pair(*expectedMessage, session.sessionId()); + andThen(); + return result; } - qCWarning(E2EE) << "Failed to decrypt message"; + const auto errorLine = message.type() == QOlmMessage::PreKey + ? "Failed to decrypt prekey message:" + : "Failed to decrypt message:"; + qCDebug(E2EE) << errorLine << expectedMessage.error(); return {}; } std::pair sessionDecryptMessage( const QJsonObject& personalCipherObject, const QByteArray& senderKey) { + const auto msgType = static_cast( + personalCipherObject.value(TypeKeyL).toInt(-1)); + if (msgType != QOlmMessage::General && msgType != QOlmMessage::PreKey) { + qCWarning(E2EE) << "Olm message has incorrect type" << msgType; + return {}; + } QOlmMessage message { - personalCipherObject.value(BodyKeyL).toString().toLatin1(), - static_cast( - personalCipherObject.value(TypeKeyL).toInt(-1)) + personalCipherObject.value(BodyKeyL).toString().toLatin1(), msgType }; - if (message.type() == QOlmMessage::PreKey) - return sessionDecryptPrekey(message, senderKey); - if (message.type() == QOlmMessage::General) - return sessionDecryptGeneral(message, senderKey); - qCWarning(E2EE) << "Olm message has incorrect type" << message.type(); - return {}; + for (const auto& session : olmSessions[senderKey]) + if (msgType == QOlmMessage::General + || session->matchesInboundSessionFrom(senderKey, message)) { + return doDecryptMessage(*session, message, [this, &session] { + q->database()->setOlmSessionLastReceived( + session->sessionId(), QDateTime::currentDateTime()); + }); + } + + if (msgType == QOlmMessage::General) { + qCWarning(E2EE) << "Failed to decrypt message"; + return {}; + } + + qCDebug(E2EE) << "Creating new inbound session"; // Pre-key messages only + auto newSessionResult = + olmAccount->createInboundSessionFrom(senderKey, message); + if (!newSessionResult) { + qCWarning(E2EE) + << "Failed to create inbound session for" << senderKey + << "with error" << newSessionResult.error(); + return {}; + } + auto newSession = std::move(*newSessionResult); + auto error = olmAccount->removeOneTimeKeys(*newSession); + if (error) { + qWarning(E2EE) << "Failed to remove one time key for session" + << newSession->sessionId(); + // Keep going though + } + return doDecryptMessage( + *newSession, message, [this, &senderKey, &newSession] { + saveSession(*newSession, senderKey); + olmSessions[senderKey].push_back(std::move(newSession)); + }); } #endif @@ -2177,8 +2178,11 @@ void Connection::saveOlmAccount() { qCDebug(E2EE) << "Saving olm account"; #ifdef Quotient_E2EE_ENABLED - auto pickle = d->olmAccount->pickle(d->picklingMode); - d->database->setAccountPickle(std::get(pickle)); + if (const auto expectedPickle = d->olmAccount->pickle(d->picklingMode)) + d->database->setAccountPickle(*expectedPickle); + else + qCWarning(E2EE) << "Couldn't save Olm account pickle:" + << expectedPickle.error(); #endif } -- cgit v1.2.3 From 20bd96ac67c06617e619460c4cd07b5e15cc74d7 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 2 Mar 2022 00:54:49 +0100 Subject: Implement sending encrypted messages --- lib/connection.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 2528b70b..a66a4168 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1332,7 +1332,7 @@ Connection::sendToDevices(const QString& eventType, [&jsonUser](const auto& deviceToEvents) { jsonUser.insert( deviceToEvents.first, - deviceToEvents.second.contentJson()); + deviceToEvents.second->contentJson()); }); }); return callApi(BackgroundRequest, eventType, @@ -2238,4 +2238,32 @@ bool Connection::isKnownCurveKey(const QString& user, const QString& curveKey) return query.next(); } +bool Connection::hasOlmSession(User* user, const QString& deviceId) const +{ + const auto& curveKey = curveKeyForUserDevice(user->id(), deviceId); + return d->olmSessions.contains(curveKey) && d->olmSessions[curveKey].size() > 0; +} + +QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) +{ + //TODO be smarter about choosing a session; see e2ee impl guide + //TODO create session? + const auto& curveKey = curveKeyForUserDevice(user->id(), device); + QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); + auto result = d->olmSessions[curveKey][0]->encrypt(message); + return qMakePair(type, result.toCiphertext()); +} + +//TODO be more consistent with curveKey and identityKey +void Connection::createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey) +{ + auto session = QOlmSession::createOutboundSession(olmAccount(), theirIdentityKey, theirOneTimeKey); + if (std::holds_alternative(session)) { + //TODO something + qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << std::get(session); + } + d->saveSession(std::get>(session), theirIdentityKey); + d->olmSessions[theirIdentityKey].push_back(std::move(std::get>(session))); +} + #endif -- cgit v1.2.3 From 3eb7ad8b0a1ac0f6f9cda679108937a01268f184 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 16 May 2022 20:46:34 +0200 Subject: Save and load outgoing megolm session --- lib/connection.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index a66a4168..b11ec731 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2247,7 +2247,6 @@ bool Connection::hasOlmSession(User* user, const QString& deviceId) const QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) { //TODO be smarter about choosing a session; see e2ee impl guide - //TODO create session? const auto& curveKey = curveKeyForUserDevice(user->id(), device); QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); @@ -2266,4 +2265,14 @@ void Connection::createOlmSession(const QString& theirIdentityKey, const QString d->olmSessions[theirIdentityKey].push_back(std::move(std::get>(session))); } +QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room) +{ + return d->database->loadCurrentOutboundMegolmSession(room->id(), d->picklingMode); +} + +void Connection::saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data) +{ + d->database->saveCurrentOutboundMegolmSession(room->id(), d->picklingMode, data); +} + #endif -- cgit v1.2.3 From 6f5ac9b7315d75692960e5eac7b1eb6867c0d203 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 6 Mar 2022 22:54:01 +0100 Subject: Keep log of where we send keys and send keys to new devices and users --- lib/connection.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b11ec731..2a1b39f9 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2247,6 +2247,7 @@ bool Connection::hasOlmSession(User* user, const QString& deviceId) const QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) { //TODO be smarter about choosing a session; see e2ee impl guide + //TODO do we need to save the olm session after sending a message? const auto& curveKey = curveKeyForUserDevice(user->id(), device); QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); -- cgit v1.2.3 From e437c29654e8f988ad694083401bbef23fbbcb18 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 16 May 2022 20:51:41 +0200 Subject: More work; Update olm pickle & timestamps in database; Remove TODOs --- lib/connection.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 2a1b39f9..82046d53 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -33,6 +33,7 @@ #include "jobs/downloadfilejob.h" #include "jobs/mediathumbnailjob.h" #include "jobs/syncjob.h" +#include #ifdef Quotient_E2EE_ENABLED # include "database.h" @@ -2246,21 +2247,24 @@ bool Connection::hasOlmSession(User* user, const QString& deviceId) const QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) { - //TODO be smarter about choosing a session; see e2ee impl guide - //TODO do we need to save the olm session after sending a message? const auto& curveKey = curveKeyForUserDevice(user->id(), device); QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); + auto pickle = d->olmSessions[curveKey][0]->pickle(picklingMode()); + if (std::holds_alternative(pickle)) { + database()->updateOlmSession(curveKey, d->olmSessions[curveKey][0]->sessionId(), std::get(pickle)); + } else { + qCWarning(E2EE) << "Failed to pickle olm session."; + } return qMakePair(type, result.toCiphertext()); } -//TODO be more consistent with curveKey and identityKey void Connection::createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey) { auto session = QOlmSession::createOutboundSession(olmAccount(), theirIdentityKey, theirOneTimeKey); if (std::holds_alternative(session)) { - //TODO something qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << std::get(session); + return; } d->saveSession(std::get>(session), theirIdentityKey); d->olmSessions[theirIdentityKey].push_back(std::move(std::get>(session))); -- cgit v1.2.3 From 89d8f6c44f86a27df28b1d89a80564fb0d4d89fc Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 16 May 2022 21:26:14 +0200 Subject: Fix build failures --- lib/connection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 82046d53..a5615f64 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2251,8 +2251,8 @@ QPair Connection::olmEncryptMessage(User* user, c QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); auto pickle = d->olmSessions[curveKey][0]->pickle(picklingMode()); - if (std::holds_alternative(pickle)) { - database()->updateOlmSession(curveKey, d->olmSessions[curveKey][0]->sessionId(), std::get(pickle)); + if (pickle) { + database()->updateOlmSession(curveKey, d->olmSessions[curveKey][0]->sessionId(), *pickle); } else { qCWarning(E2EE) << "Failed to pickle olm session."; } @@ -2262,12 +2262,12 @@ QPair Connection::olmEncryptMessage(User* user, c void Connection::createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey) { auto session = QOlmSession::createOutboundSession(olmAccount(), theirIdentityKey, theirOneTimeKey); - if (std::holds_alternative(session)) { - qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << std::get(session); + if (!session) { + qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << session.error(); return; } - d->saveSession(std::get>(session), theirIdentityKey); - d->olmSessions[theirIdentityKey].push_back(std::move(std::get>(session))); + d->saveSession(**session, theirIdentityKey); + d->olmSessions[theirIdentityKey].push_back(std::move(*session)); } QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room) -- cgit v1.2.3 From c671867a0a3e2a6ad0e7ae6e93fa09467c06188f Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Wed, 18 May 2022 22:02:50 +0200 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index a5615f64..66e21a2a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -33,7 +33,6 @@ #include "jobs/downloadfilejob.h" #include "jobs/mediathumbnailjob.h" #include "jobs/syncjob.h" -#include #ifdef Quotient_E2EE_ENABLED # include "database.h" @@ -2242,7 +2241,7 @@ bool Connection::isKnownCurveKey(const QString& user, const QString& curveKey) bool Connection::hasOlmSession(User* user, const QString& deviceId) const { const auto& curveKey = curveKeyForUserDevice(user->id(), deviceId); - return d->olmSessions.contains(curveKey) && d->olmSessions[curveKey].size() > 0; + return d->olmSessions.contains(curveKey) && !d->olmSessions[curveKey].empty(); } QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) @@ -2254,9 +2253,9 @@ QPair Connection::olmEncryptMessage(User* user, c if (pickle) { database()->updateOlmSession(curveKey, d->olmSessions[curveKey][0]->sessionId(), *pickle); } else { - qCWarning(E2EE) << "Failed to pickle olm session."; + qCWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); } - return qMakePair(type, result.toCiphertext()); + return { type, result.toCiphertext() }; } void Connection::createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey) -- cgit v1.2.3 From b29eb3954b798ac9110906cd79c4f288deaa2596 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 18 May 2022 22:39:27 +0200 Subject: Make database independent of {Room, User, Connection} --- lib/connection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 66e21a2a..dba18cb1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2214,9 +2214,9 @@ void Connection::saveMegolmSession(const Room* room, session.senderId(), session.olmSessionId()); } -QStringList Connection::devicesForUser(User* user) const +QStringList Connection::devicesForUser(const QString& userId) const { - return d->deviceKeys[user->id()].keys(); + return d->deviceKeys[userId].keys(); } QString Connection::curveKeyForUserDevice(const QString& user, const QString& device) const @@ -2238,15 +2238,15 @@ bool Connection::isKnownCurveKey(const QString& user, const QString& curveKey) return query.next(); } -bool Connection::hasOlmSession(User* user, const QString& deviceId) const +bool Connection::hasOlmSession(const QString& user, const QString& deviceId) const { - const auto& curveKey = curveKeyForUserDevice(user->id(), deviceId); + const auto& curveKey = curveKeyForUserDevice(user, deviceId); return d->olmSessions.contains(curveKey) && !d->olmSessions[curveKey].empty(); } -QPair Connection::olmEncryptMessage(User* user, const QString& device, const QByteArray& message) +QPair Connection::olmEncryptMessage(const QString& user, const QString& device, const QByteArray& message) { - const auto& curveKey = curveKeyForUserDevice(user->id(), device); + const auto& curveKey = curveKeyForUserDevice(user, device); QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); auto result = d->olmSessions[curveKey][0]->encrypt(message); auto pickle = d->olmSessions[curveKey][0]->pickle(picklingMode()); -- cgit v1.2.3 From 946cd4cb73f95526aa09777afc2a493610d9696d Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 27 May 2022 21:03:31 +0200 Subject: Load and store accounts in the keychain --- lib/connection.cpp | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index dba18cb1..526feb26 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -41,12 +41,12 @@ # include "e2ee/qolmsession.h" # include "e2ee/qolmutils.h" -# if QT_VERSION_MAJOR >= 6 -# include -# else -# include -# endif #endif // Quotient_E2EE_ENABLED +#if QT_VERSION_MAJOR >= 6 +# include +#else +# include +#endif #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) # include @@ -368,6 +368,32 @@ public: void saveDevicesList(); void loadDevicesList(); #endif + + void saveAccessTokenToKeychain() + { + qCDebug(MAIN) << "Saving access token to keychain for" << q->userId(); + auto job = new QKeychain::WritePasswordJob(qAppName()); + job->setAutoDelete(false); + job->setKey(q->userId()); + job->setBinaryData(data->accessToken()); + job->start(); + //TODO error handling + } + + void removeAccessTokenFromKeychain() + { + qCDebug(MAIN) << "Removing access token from keychain for" << q->userId(); + auto job = new QKeychain::DeletePasswordJob(qAppName()); + job->setAutoDelete(true); + job->setKey(q->userId()); + job->start(); + + auto pickleJob = new QKeychain::DeletePasswordJob(qAppName()); + pickleJob->setAutoDelete(true); + pickleJob->setKey(q->userId() + "-Pickle"_ls); + pickleJob->start(); + //TODO error handling + } }; Connection::Connection(const QUrl& server, QObject* parent) @@ -546,6 +572,7 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) data->setToken(loginJob->accessToken().toLatin1()); data->setDeviceId(loginJob->deviceId()); completeSetup(loginJob->userId()); + saveAccessTokenToKeychain(); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED @@ -684,6 +711,8 @@ void Connection::logout() disconnect(d->syncLoopConnection); d->data->setToken({}); emit loggedOut(); + SettingsGroup("Accounts").remove(userId()); + d->removeAccessTokenFromKeychain(); deleteLater(); } else { // logout() somehow didn't proceed - restore the session state emit stateChanged(); -- cgit v1.2.3 From 0b5e72a2c6502f22a752b72b4df5fa25746fdd25 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 26 May 2022 08:51:22 +0200 Subject: Refactor EncryptedFile and EC::FileInfo::file Besides having a misleading name (and it goes back to the spec), EncryptedFile under `file` key preempts the `url` (or `thumbnail_url`) string value so only one of the two should exist. This is a case for using std::variant<> - despite its clumsy syntax, it can actually simplify and streamline code when all the necessary bits are in place (such as conversion to JSON and getting the common piece - the URL - out of it). This commit replaces `FileInfo::url` and `FileInfo::file` with a common field `source` of type `FileSourceInfo` that is an alias for a variant type covering both underlying types; and `url()` is reintroduced as a function instead, to allow simplified access to whichever URL is available inside the variant. Oh, and EncryptedFile is EncryptedFileMetadata now, to clarify that it does not represent the file payload itself but rather the data necessary to obtain that payload. --- lib/connection.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index dba18cb1..0994d85a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1137,15 +1137,14 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url, } #ifdef Quotient_E2EE_ENABLED -DownloadFileJob* Connection::downloadFile(const QUrl& url, - const EncryptedFile& file, - const QString& localFilename) +DownloadFileJob* Connection::downloadFile( + const QUrl& url, const EncryptedFileMetadata& fileMetadata, + const QString& localFilename) { auto mediaId = url.authority() + url.path(); auto idParts = splitMediaId(mediaId); - auto* job = - callApi(idParts.front(), idParts.back(), file, localFilename); - return job; + return callApi(idParts.front(), idParts.back(), + fileMetadata, localFilename); } #endif -- cgit v1.2.3 From 841846ea5efad80ce20e0d42b1885def224e58ad Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 26 May 2022 11:03:16 +0200 Subject: Cleanup and fix Sonar warnings --- lib/connection.cpp | 57 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 27 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 0994d85a..8fd2d6cf 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1322,18 +1322,11 @@ Connection::sendToDevices(const QString& eventType, { QHash> json; json.reserve(int(eventsMap.size())); - std::for_each(eventsMap.begin(), eventsMap.end(), - [&json](const auto& userTodevicesToEvents) { - auto& jsonUser = json[userTodevicesToEvents.first]; - const auto& devicesToEvents = userTodevicesToEvents.second; - std::for_each(devicesToEvents.begin(), - devicesToEvents.end(), - [&jsonUser](const auto& deviceToEvents) { - jsonUser.insert( - deviceToEvents.first, - deviceToEvents.second->contentJson()); - }); - }); + for (const auto& [userId, devicesToEvents] : eventsMap) { + auto& jsonUser = json[userId]; + for (const auto& [deviceId, event] : devicesToEvents) + jsonUser.insert(deviceId, event->contentJson()); + } return callApi(BackgroundRequest, eventType, generateTxnId(), json); } @@ -2218,20 +2211,23 @@ QStringList Connection::devicesForUser(const QString& userId) const return d->deviceKeys[userId].keys(); } -QString Connection::curveKeyForUserDevice(const QString& user, const QString& device) const +QString Connection::curveKeyForUserDevice(const QString& userId, + const QString& device) const { - return d->deviceKeys[user][device].keys["curve25519:" % device]; + return d->deviceKeys[userId][device].keys["curve25519:" % device]; } -QString Connection::edKeyForUserDevice(const QString& user, const QString& device) const +QString Connection::edKeyForUserDevice(const QString& userId, + const QString& device) const { - return d->deviceKeys[user][device].keys["ed25519:" % device]; + return d->deviceKeys[userId][device].keys["ed25519:" % device]; } -bool Connection::isKnownCurveKey(const QString& user, const QString& curveKey) +bool Connection::isKnownCurveKey(const QString& userId, + const QString& curveKey) const { auto query = database()->prepareQuery(QStringLiteral("SELECT * FROM tracked_devices WHERE matrixId=:matrixId AND curveKey=:curveKey")); - query.bindValue(":matrixId", user); + query.bindValue(":matrixId", userId); query.bindValue(":curveKey", curveKey); database()->execute(query); return query.next(); @@ -2243,25 +2239,32 @@ bool Connection::hasOlmSession(const QString& user, const QString& deviceId) con return d->olmSessions.contains(curveKey) && !d->olmSessions[curveKey].empty(); } -QPair Connection::olmEncryptMessage(const QString& user, const QString& device, const QByteArray& message) +std::pair Connection::olmEncryptMessage( + const QString& userId, const QString& device, const QByteArray& message) const { - const auto& curveKey = curveKeyForUserDevice(user, device); + const auto& curveKey = curveKeyForUserDevice(userId, device); QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); - auto result = d->olmSessions[curveKey][0]->encrypt(message); - auto pickle = d->olmSessions[curveKey][0]->pickle(picklingMode()); - if (pickle) { - database()->updateOlmSession(curveKey, d->olmSessions[curveKey][0]->sessionId(), *pickle); + const auto result = d->olmSessions[curveKey][0]->encrypt(message); + if (const auto pickle = + d->olmSessions[curveKey][0]->pickle(picklingMode())) { + database()->updateOlmSession(curveKey, + d->olmSessions[curveKey][0]->sessionId(), + *pickle); } else { qCWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); } return { type, result.toCiphertext() }; } -void Connection::createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey) +void Connection::createOlmSession(const QString& theirIdentityKey, + const QString& theirOneTimeKey) const { - auto session = QOlmSession::createOutboundSession(olmAccount(), theirIdentityKey, theirOneTimeKey); + auto session = QOlmSession::createOutboundSession(olmAccount(), + theirIdentityKey, + theirOneTimeKey); if (!session) { - qCWarning(E2EE) << "Failed to create olm session for " << theirIdentityKey << session.error(); + qCWarning(E2EE) << "Failed to create olm session for " + << theirIdentityKey << session.error(); return; } d->saveSession(**session, theirIdentityKey); -- cgit v1.2.3 From 64797165f04a16d290dd27c2f962060b40f85be3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 25 May 2022 22:48:53 +0200 Subject: Refactor creation of Megolm sessions in Room Notably, replace a multi-level hash map with QMultiHash and factor out Room::P::createOlmSession(). --- lib/connection.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 8fd2d6cf..1193eb75 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2187,7 +2187,7 @@ QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) return decrypted ? decrypted->fullJson() : QJsonObject(); } -Database* Connection::database() +Database* Connection::database() const { return d->database; } @@ -2271,14 +2271,18 @@ void Connection::createOlmSession(const QString& theirIdentityKey, d->olmSessions[theirIdentityKey].push_back(std::move(*session)); } -QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room) +QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession( + const QString& roomId) const { - return d->database->loadCurrentOutboundMegolmSession(room->id(), d->picklingMode); + return d->database->loadCurrentOutboundMegolmSession(roomId, + d->picklingMode); } -void Connection::saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data) +void Connection::saveCurrentOutboundMegolmSession( + const QString& roomId, const QOlmOutboundGroupSession& session) const { - d->database->saveCurrentOutboundMegolmSession(room->id(), d->picklingMode, data); + d->database->saveCurrentOutboundMegolmSession(roomId, d->picklingMode, + session); } #endif -- cgit v1.2.3 From 0f8335a32debc4c61d9fc9875c79c0ba6ba05357 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 27 May 2022 19:09:26 +0200 Subject: Move some Meg/Olm session logic from Room::Private to Connection::Private Functions (Room::Private::)createOlmSession, payloadForUserDevice and sendRoomKeyToDevices don't have a lot to do with the given Room object but deal with quite a few things stored in Connection. This commit moves them to Connection::Private, exposing sendSessionKeyToDevices (the new name for sendRoomKeyToDevices) in Connection so that Room could call it from Room::P::sendMegolmSession(). While moving these over, a few additional things were adjusted: - more functions marked as const - a few functions could be moved now from Connection to Connection::Private - false slots in Connection (such as picklingMode) are moved out of the slots block - keys.yml in Matrix CS API definitions has been adjusted to match the real structure of `/claim` response (see quotient-im/matrix-spec repo); csapi/keys.h has been regenerated accordingly. --- lib/connection.cpp | 227 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 181 insertions(+), 46 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 1193eb75..ab4a7dea 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -39,6 +39,7 @@ # include "e2ee/qolmaccount.h" # include "e2ee/qolminboundsession.h" # include "e2ee/qolmsession.h" +# include "e2ee/qolmutility.h" # include "e2ee/qolmutils.h" # if QT_VERSION_MAJOR >= 6 @@ -62,7 +63,6 @@ #include #include - using namespace Quotient; // This is very much Qt-specific; STL iterators don't have key() and value() @@ -210,11 +210,11 @@ public: #ifdef Quotient_E2EE_ENABLED void loadSessions() { - olmSessions = q->database()->loadOlmSessions(q->picklingMode()); + olmSessions = q->database()->loadOlmSessions(picklingMode); } - void saveSession(QOlmSession& session, const QString& senderKey) + void saveSession(const QOlmSession& session, const QString& senderKey) const { - if (auto pickleResult = session.pickle(q->picklingMode())) + if (auto pickleResult = session.pickle(picklingMode)) q->database()->saveOlmSession(senderKey, session.sessionId(), *pickleResult, QDateTime::currentDateTime()); @@ -364,9 +364,27 @@ public: #endif // Quotient_E2EE_ENABLED } #ifdef Quotient_E2EE_ENABLED + bool isKnownCurveKey(const QString& userId, const QString& curveKey) const; + void loadOutdatedUserDevices(); void saveDevicesList(); void loadDevicesList(); + + // This function assumes that an olm session with (user, device) exists + std::pair olmEncryptMessage( + const QString& userId, const QString& device, + const QByteArray& message) const; + bool createOlmSession(const QString& targetUserId, + const QString& targetDeviceId, + const QJsonObject& oneTimeKeyObject); + QString curveKeyForUserDevice(const QString& userId, + const QString& device) const; + QString edKeyForUserDevice(const QString& userId, + const QString& device) const; + std::unique_ptr makeEventForSessionKey( + const QString& roomId, const QString& targetUserId, + const QString& targetDeviceId, const QByteArray& sessionId, + const QByteArray& sessionKey) const; #endif }; @@ -935,20 +953,23 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED if (!toDeviceEvents.empty()) { - qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; + qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() + << "to-device events"; visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { - qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); + qCDebug(E2EE) << "Unsupported algorithm" << event.id() + << "for event" << event.algorithm(); return; } - if (q->isKnownCurveKey(event.senderId(), event.senderKey())) { + if (isKnownCurveKey(event.senderId(), event.senderKey())) { handleEncryptedToDeviceEvent(event); return; } trackedUsers += event.senderId(); outdatedUsers += event.senderId(); encryptionUpdateRequired = true; - pendingEncryptedEvents.push_back(std::make_unique(event.fullJson())); + pendingEncryptedEvents.push_back( + makeEvent(event.fullJson())); }); } #endif @@ -1316,9 +1337,8 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) return forgetJob; } -SendToDeviceJob* -Connection::sendToDevices(const QString& eventType, - const UsersToDevicesToEvents& eventsMap) +SendToDeviceJob* Connection::sendToDevices( + const QString& eventType, const UsersToDevicesToEvents& eventsMap) { QHash> json; json.reserve(int(eventsMap.size())); @@ -2063,7 +2083,7 @@ void Connection::Private::loadOutdatedUserDevices() saveDevicesList(); for(size_t i = 0; i < pendingEncryptedEvents.size();) { - if (q->isKnownCurveKey( + if (isKnownCurveKey( pendingEncryptedEvents[i]->fullJson()[SenderKeyL].toString(), pendingEncryptedEvents[i]->contentPart("sender_key"_ls))) { handleEncryptedToDeviceEvent(*pendingEncryptedEvents[i]); @@ -2193,13 +2213,13 @@ Database* Connection::database() const } UnorderedMap -Connection::loadRoomMegolmSessions(const Room* room) +Connection::loadRoomMegolmSessions(const Room* room) const { return database()->loadMegolmSessions(room->id(), picklingMode()); } void Connection::saveMegolmSession(const Room* room, - const QOlmInboundGroupSession& session) + const QOlmInboundGroupSession& session) const { database()->saveMegolmSession(room->id(), session.sessionId(), session.pickle(picklingMode()), @@ -2211,64 +2231,179 @@ QStringList Connection::devicesForUser(const QString& userId) const return d->deviceKeys[userId].keys(); } -QString Connection::curveKeyForUserDevice(const QString& userId, - const QString& device) const +QString Connection::Private::curveKeyForUserDevice(const QString& userId, + const QString& device) const { - return d->deviceKeys[userId][device].keys["curve25519:" % device]; + return deviceKeys[userId][device].keys["curve25519:" % device]; } -QString Connection::edKeyForUserDevice(const QString& userId, - const QString& device) const +QString Connection::Private::edKeyForUserDevice(const QString& userId, + const QString& device) const { - return d->deviceKeys[userId][device].keys["ed25519:" % device]; + return deviceKeys[userId][device].keys["ed25519:" % device]; } -bool Connection::isKnownCurveKey(const QString& userId, - const QString& curveKey) const +bool Connection::Private::isKnownCurveKey(const QString& userId, + const QString& curveKey) const { - auto query = database()->prepareQuery(QStringLiteral("SELECT * FROM tracked_devices WHERE matrixId=:matrixId AND curveKey=:curveKey")); + auto query = database->prepareQuery( + QStringLiteral("SELECT * FROM tracked_devices WHERE matrixId=:matrixId " + "AND curveKey=:curveKey")); query.bindValue(":matrixId", userId); query.bindValue(":curveKey", curveKey); - database()->execute(query); + database->execute(query); return query.next(); } -bool Connection::hasOlmSession(const QString& user, const QString& deviceId) const +bool Connection::hasOlmSession(const QString& user, + const QString& deviceId) const { - const auto& curveKey = curveKeyForUserDevice(user, deviceId); + const auto& curveKey = d->curveKeyForUserDevice(user, deviceId); return d->olmSessions.contains(curveKey) && !d->olmSessions[curveKey].empty(); } -std::pair Connection::olmEncryptMessage( - const QString& userId, const QString& device, const QByteArray& message) const +std::pair Connection::Private::olmEncryptMessage( + const QString& userId, const QString& device, + const QByteArray& message) const { const auto& curveKey = curveKeyForUserDevice(userId, device); - QOlmMessage::Type type = d->olmSessions[curveKey][0]->encryptMessageType(); - const auto result = d->olmSessions[curveKey][0]->encrypt(message); - if (const auto pickle = - d->olmSessions[curveKey][0]->pickle(picklingMode())) { - database()->updateOlmSession(curveKey, - d->olmSessions[curveKey][0]->sessionId(), - *pickle); + const auto& olmSession = olmSessions.at(curveKey).front(); + QOlmMessage::Type type = olmSession->encryptMessageType(); + const auto result = olmSession->encrypt(message); + if (const auto pickle = olmSession->pickle(picklingMode)) { + database->updateOlmSession(curveKey, olmSession->sessionId(), *pickle); } else { - qCWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); + qWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); } return { type, result.toCiphertext() }; } -void Connection::createOlmSession(const QString& theirIdentityKey, - const QString& theirOneTimeKey) const -{ - auto session = QOlmSession::createOutboundSession(olmAccount(), - theirIdentityKey, - theirOneTimeKey); +bool Connection::Private::createOlmSession(const QString& targetUserId, + const QString& targetDeviceId, + const QJsonObject& oneTimeKeyObject) +{ + static QOlmUtility verifier; + qDebug(E2EE) << "Creating a new session for" << targetUserId + << targetDeviceId; + if (oneTimeKeyObject.isEmpty()) { + qWarning(E2EE) << "No one time key for" << targetUserId + << targetDeviceId; + return false; + } + auto signedOneTimeKey = oneTimeKeyObject.constBegin()->toObject(); + // Verify contents of signedOneTimeKey - for that, drop `signatures` and + // `unsigned` and then verify the object against the respective signature + const auto signature = + signedOneTimeKey.take("signatures"_ls)[targetUserId]["ed25519:"_ls % targetDeviceId] + .toString() + .toLatin1(); + signedOneTimeKey.remove("unsigned"_ls); + if (!verifier.ed25519Verify( + edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), + QJsonDocument(signedOneTimeKey).toJson(QJsonDocument::Compact), + signature)) { + qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId + << targetDeviceId << ". Skipping this device."; + return false; + } + const auto recipientCurveKey = + curveKeyForUserDevice(targetUserId, targetDeviceId); + auto session = + QOlmSession::createOutboundSession(olmAccount.get(), recipientCurveKey, + signedOneTimeKey["key"].toString()); if (!session) { qCWarning(E2EE) << "Failed to create olm session for " - << theirIdentityKey << session.error(); + << recipientCurveKey << session.error(); + return false; + } + saveSession(**session, recipientCurveKey); + olmSessions[recipientCurveKey].push_back(std::move(*session)); + return true; +} + +std::unique_ptr Connection::Private::makeEventForSessionKey( + const QString& roomId, const QString& targetUserId, + const QString& targetDeviceId, const QByteArray& sessionId, + const QByteArray& sessionKey) const +{ + // Noisy but nice for debugging + // qDebug(E2EE) << "Creating the payload for" << data->userId() << device << + // sessionId << sessionKey.toHex(); + const auto event = makeEvent("m.megolm.v1.aes-sha2", roomId, + sessionId, sessionKey, + data->userId()); + auto payloadJson = event->fullJson(); + payloadJson.insert("recipient"_ls, targetUserId); + payloadJson.insert(SenderKeyL, data->userId()); + payloadJson.insert("recipient_keys"_ls, + QJsonObject { { Ed25519Key, + edKeyForUserDevice(targetUserId, + targetDeviceId) } }); + payloadJson.insert("keys"_ls, + QJsonObject { + { Ed25519Key, + QString(olmAccount->identityKeys().ed25519) } }); + payloadJson.insert("sender_device"_ls, data->deviceId()); + + const auto [type, cipherText] = olmEncryptMessage( + targetUserId, targetDeviceId, + QJsonDocument(payloadJson).toJson(QJsonDocument::Compact)); + QJsonObject encrypted { + { curveKeyForUserDevice(targetUserId, targetDeviceId), + QJsonObject { { "type"_ls, type }, + { "body"_ls, QString(cipherText) } } } + }; + + return makeEvent(encrypted, + olmAccount->identityKeys().curve25519); +} + +void Connection::sendSessionKeyToDevices( + const QString& roomId, const QByteArray& sessionId, + const QByteArray& sessionKey, const QMultiHash& devices, + int index) +{ + qDebug(E2EE) << "Sending room key to devices:" << sessionId + << sessionKey.toHex(); + QHash> hash; + for (const auto& [userId, deviceId] : asKeyValueRange(devices)) + if (!hasOlmSession(userId, deviceId)) { + hash[userId].insert(deviceId, "signed_curve25519"_ls); + qDebug(E2EE) << "Adding" << userId << deviceId + << "to keys to claim"; + } + + if (hash.isEmpty()) return; - } - d->saveSession(**session, theirIdentityKey); - d->olmSessions[theirIdentityKey].push_back(std::move(*session)); + + auto job = callApi(hash); + connect(job, &BaseJob::success, this, [job, this, roomId, sessionId, sessionKey, devices, index] { + UsersToDevicesToEvents usersToDevicesToEvents; + const auto oneTimeKeys = job->oneTimeKeys(); + for (const auto& [targetUserId, targetDeviceId] : + asKeyValueRange(devices)) { + if (!hasOlmSession(targetUserId, targetDeviceId) + && !d->createOlmSession( + targetUserId, targetDeviceId, + oneTimeKeys[targetUserId][targetDeviceId])) + continue; + + usersToDevicesToEvents[targetUserId][targetDeviceId] = + d->makeEventForSessionKey(roomId, targetUserId, targetDeviceId, + sessionId, sessionKey); + } + if (!usersToDevicesToEvents.empty()) { + sendToDevices(EncryptedEvent::TypeId, usersToDevicesToEvents); + QVector> receivedDevices; + receivedDevices.reserve(devices.size()); + for (const auto& [user, device] : asKeyValueRange(devices)) + receivedDevices.push_back( + { user, device, d->curveKeyForUserDevice(user, device) }); + + database()->setDevicesReceivedKey(roomId, receivedDevices, + sessionId, index); + } + }); } QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession( -- cgit v1.2.3 From 999ec716d5e2b03aceb562e730edf3939eb2578a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 30 May 2022 15:23:39 +0200 Subject: Emit loggedOut() after the access token is gone ...not before. --- lib/connection.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 7e36b3c9..b7f49546 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -398,7 +398,7 @@ public: //TODO error handling } - void removeAccessTokenFromKeychain() + void dropAccessToken() { qCDebug(MAIN) << "Removing access token from keychain for" << q->userId(); auto job = new QKeychain::DeletePasswordJob(qAppName()); @@ -411,6 +411,8 @@ public: pickleJob->setKey(q->userId() + "-Pickle"_ls); pickleJob->start(); //TODO error handling + + data->setToken({}); } }; @@ -727,10 +729,9 @@ void Connection::logout() || d->logoutJob->error() == BaseJob::ContentAccessError) { if (d->syncLoopConnection) disconnect(d->syncLoopConnection); - d->data->setToken({}); - emit loggedOut(); SettingsGroup("Accounts").remove(userId()); - d->removeAccessTokenFromKeychain(); + d->dropAccessToken(); + emit loggedOut(); deleteLater(); } else { // logout() somehow didn't proceed - restore the session state emit stateChanged(); -- cgit v1.2.3 From 078f5aab2e1eb1dea30828429069836509551b07 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 30 May 2022 15:24:02 +0200 Subject: Cleanup and reformatting --- lib/connection.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index b7f49546..102fb16d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -387,7 +387,7 @@ public: const QByteArray& sessionKey) const; #endif - void saveAccessTokenToKeychain() + void saveAccessTokenToKeychain() const { qCDebug(MAIN) << "Saving access token to keychain for" << q->userId(); auto job = new QKeychain::WritePasswordJob(qAppName()); @@ -593,11 +593,9 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) data->setDeviceId(loginJob->deviceId()); completeSetup(loginJob->userId()); saveAccessTokenToKeychain(); -#ifndef Quotient_E2EE_ENABLED - qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; -#else // Quotient_E2EE_ENABLED +#ifdef Quotient_E2EE_ENABLED database->clear(); -#endif // Quotient_E2EE_ENABLED +#endif }); connect(loginJob, &BaseJob::failure, q, [this, loginJob] { emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); @@ -654,9 +652,7 @@ void Connection::Private::completeSetup(const QString& mxId) olmAccount = std::make_unique(data->userId(), data->deviceId(), q); connect(olmAccount.get(), &QOlmAccount::needsSave, q, &Connection::saveOlmAccount); -#ifdef Quotient_E2EE_ENABLED loadSessions(); -#endif if (database->accountPickle().isEmpty()) { // create new account and save unpickle data -- cgit v1.2.3 From cd442611b19ec4a438d0847bf09b7bca99b494d3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 1 Jun 2022 08:05:21 +0200 Subject: Fix FTBFS after the merge --- lib/connection.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 102fb16d..7885718f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -376,7 +376,7 @@ public: const QByteArray& message) const; bool createOlmSession(const QString& targetUserId, const QString& targetDeviceId, - const QJsonObject& oneTimeKeyObject); + const OneTimeKeys &oneTimeKeyObject); QString curveKeyForUserDevice(const QString& userId, const QString& device) const; QString edKeyForUserDevice(const QString& userId, @@ -2306,7 +2306,7 @@ std::pair Connection::Private::olmEncryptMessage( bool Connection::Private::createOlmSession(const QString& targetUserId, const QString& targetDeviceId, - const QJsonObject& oneTimeKeyObject) + const OneTimeKeys& oneTimeKeyObject) { static QOlmUtility verifier; qDebug(E2EE) << "Creating a new session for" << targetUserId @@ -2316,17 +2316,23 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, << targetDeviceId; return false; } - auto signedOneTimeKey = oneTimeKeyObject.constBegin()->toObject(); + auto* signedOneTimeKey = + std::get_if(&*oneTimeKeyObject.begin()); + if (!signedOneTimeKey) { + qWarning(E2EE) << "No signed one time key for" << targetUserId + << targetDeviceId; + return false; + } // Verify contents of signedOneTimeKey - for that, drop `signatures` and // `unsigned` and then verify the object against the respective signature const auto signature = - signedOneTimeKey.take("signatures"_ls)[targetUserId]["ed25519:"_ls % targetDeviceId] - .toString() + signedOneTimeKey + ->signatures[targetUserId]["ed25519:"_ls % targetDeviceId] .toLatin1(); - signedOneTimeKey.remove("unsigned"_ls); if (!verifier.ed25519Verify( edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), - QJsonDocument(signedOneTimeKey).toJson(QJsonDocument::Compact), + QJsonDocument(toJson(SignedOneTimeKey { signedOneTimeKey->key, {} })) + .toJson(QJsonDocument::Compact), signature)) { qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId << targetDeviceId << ". Skipping this device."; @@ -2336,7 +2342,7 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, curveKeyForUserDevice(targetUserId, targetDeviceId); auto session = QOlmSession::createOutboundSession(olmAccount.get(), recipientCurveKey, - signedOneTimeKey["key"].toString()); + signedOneTimeKey->key); if (!session) { qCWarning(E2EE) << "Failed to create olm session for " << recipientCurveKey << session.error(); -- cgit v1.2.3 From d547e84c9335d9524ae7530be622d5ed2f0b1fb8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 8 Jun 2022 13:58:33 +0200 Subject: Save connection state while QCoreApplication is still there This reimplements #558 in a more reliable way. Deconstruction of AccountRegistry may (or may not, yay for static initialisation) occur after deconstruction of QCoreApplication, in which case an attempt to determine the directory for the state fails because it depends on the application object existence. --- lib/connection.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 7885718f..101bef89 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -611,6 +611,7 @@ void Connection::Private::completeSetup(const QString& mxId) << "by user" << data->userId() << "from device" << data->deviceId(); Accounts.add(q); + connect(qApp, &QCoreApplication::aboutToQuit, q, &Connection::saveState); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED -- cgit v1.2.3 From f779b235ddac990d17a9a8d8dd222b9e0e7abd49 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 17 Jun 2022 10:35:22 +0200 Subject: Make Connection::sendToDevices() an actual slot Although Qt 5 didn't complain about that, you could never really use sendToDevices() in its slot (or even invocable) capacity because Qt's meta-type system could not handle move-only UsersToDevicesToEvents. Qt 6 is more stringent; the build fails at trying to instantiate QMetaType for that type (with a rather unhelpful error message thrown by Clang, and more helpful but very verbose diagnostic from MSVC) because it does not provide a copy constructor. However, sendToDevice doesn't really need to have full-blown events in that parameter; just the content of the event is equally fine. This commit does exactly that: replaces UsersToDevicesToEvents with UsersToDevicesToContent that contains QJsonObject's instead of EventPtr's. The code around is updated accordingly. Also: factor out the key event JSON creation from makeMessageEventForSessionKey() because it's the same JSON for each target device; the function therefore is called encryptSessionKeyEvent() now. --- lib/connection.cpp | 74 +++++++++++++++++++++++------------------------------- 1 file changed, 32 insertions(+), 42 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 101bef89..3e44513b 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -381,10 +381,9 @@ public: const QString& device) const; QString edKeyForUserDevice(const QString& userId, const QString& device) const; - std::unique_ptr makeEventForSessionKey( - const QString& roomId, const QString& targetUserId, - const QString& targetDeviceId, const QByteArray& sessionId, - const QByteArray& sessionKey) const; + QJsonObject encryptSessionKeyEvent(QJsonObject payloadJson, + const QString& targetUserId, + const QString& targetDeviceId) const; #endif void saveAccessTokenToKeychain() const @@ -1365,17 +1364,10 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) } SendToDeviceJob* Connection::sendToDevices( - const QString& eventType, const UsersToDevicesToEvents& eventsMap) + const QString& eventType, const UsersToDevicesToContent& contents) { - QHash> json; - json.reserve(int(eventsMap.size())); - for (const auto& [userId, devicesToEvents] : eventsMap) { - auto& jsonUser = json[userId]; - for (const auto& [deviceId, event] : devicesToEvents) - jsonUser.insert(deviceId, event->contentJson()); - } return callApi(BackgroundRequest, eventType, - generateTxnId(), json); + generateTxnId(), contents); } SendMessageJob* Connection::sendMessage(const QString& roomId, @@ -2354,30 +2346,15 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, return true; } -std::unique_ptr Connection::Private::makeEventForSessionKey( - const QString& roomId, const QString& targetUserId, - const QString& targetDeviceId, const QByteArray& sessionId, - const QByteArray& sessionKey) const +QJsonObject Connection::Private::encryptSessionKeyEvent( + QJsonObject payloadJson, const QString& targetUserId, + const QString& targetDeviceId) const { - // Noisy but nice for debugging - // qDebug(E2EE) << "Creating the payload for" << data->userId() << device << - // sessionId << sessionKey.toHex(); - const auto event = makeEvent("m.megolm.v1.aes-sha2", roomId, - sessionId, sessionKey, - data->userId()); - auto payloadJson = event->fullJson(); payloadJson.insert("recipient"_ls, targetUserId); - payloadJson.insert(SenderKeyL, data->userId()); payloadJson.insert("recipient_keys"_ls, QJsonObject { { Ed25519Key, edKeyForUserDevice(targetUserId, targetDeviceId) } }); - payloadJson.insert("keys"_ls, - QJsonObject { - { Ed25519Key, - QString(olmAccount->identityKeys().ed25519) } }); - payloadJson.insert("sender_device"_ls, data->deviceId()); - const auto [type, cipherText] = olmEncryptMessage( targetUserId, targetDeviceId, QJsonDocument(payloadJson).toJson(QJsonDocument::Compact)); @@ -2387,8 +2364,8 @@ std::unique_ptr Connection::Private::makeEventForSessionKey( { "body"_ls, QString(cipherText) } } } }; - return makeEvent(encrypted, - olmAccount->identityKeys().curve25519); + return EncryptedEvent(encrypted, olmAccount->identityKeys().curve25519) + .contentJson(); } void Connection::sendSessionKeyToDevices( @@ -2409,11 +2386,21 @@ void Connection::sendSessionKeyToDevices( if (hash.isEmpty()) return; + auto keyEventJson = RoomKeyEvent(MegolmV1AesSha2AlgoKey, roomId, sessionId, + sessionKey, userId()) + .fullJson(); + keyEventJson.insert(SenderKeyL, userId()); + keyEventJson.insert("sender_device"_ls, deviceId()); + keyEventJson.insert( + "keys"_ls, + QJsonObject { + { Ed25519Key, QString(olmAccount()->identityKeys().ed25519) } }); + auto job = callApi(hash); - connect(job, &BaseJob::success, this, [job, this, roomId, sessionId, sessionKey, devices, index] { - UsersToDevicesToEvents usersToDevicesToEvents; - const auto oneTimeKeys = job->oneTimeKeys(); - for (const auto& [targetUserId, targetDeviceId] : + connect(job, &BaseJob::success, this, [job, this, roomId, sessionId, keyEventJson, devices, index] { + QHash> usersToDevicesToContent; + for (const auto oneTimeKeys = job->oneTimeKeys(); + const auto& [targetUserId, targetDeviceId] : asKeyValueRange(devices)) { if (!hasOlmSession(targetUserId, targetDeviceId) && !d->createOlmSession( @@ -2421,12 +2408,15 @@ void Connection::sendSessionKeyToDevices( oneTimeKeys[targetUserId][targetDeviceId])) continue; - usersToDevicesToEvents[targetUserId][targetDeviceId] = - d->makeEventForSessionKey(roomId, targetUserId, targetDeviceId, - sessionId, sessionKey); + // Noisy but nice for debugging +// qDebug(E2EE) << "Creating the payload for" << targetUserId +// << targetDeviceId << sessionId << sessionKey.toHex(); + usersToDevicesToContent[targetUserId][targetDeviceId] = + d->encryptSessionKeyEvent(keyEventJson, targetUserId, + targetDeviceId); } - if (!usersToDevicesToEvents.empty()) { - sendToDevices(EncryptedEvent::TypeId, usersToDevicesToEvents); + if (!usersToDevicesToContent.empty()) { + sendToDevices(EncryptedEvent::TypeId, usersToDevicesToContent); QVector> receivedDevices; receivedDevices.reserve(devices.size()); for (const auto& [user, device] : asKeyValueRange(devices)) -- cgit v1.2.3 From 2504e6e5f216e34fc9aabfda0c462b1b37620a5e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 17 Jun 2022 10:40:49 +0200 Subject: Further fix building with Qt 6 Also: build with Qt 6 first, so that it fails sooner. --- lib/connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 3e44513b..c390cc05 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -92,7 +92,7 @@ public: // state is Invited. The spec mandates to keep Invited room state // separately; specifically, we should keep objects for Invite and // Leave state of the same room if the two happen to co-exist. - QHash, Room*> roomMap; + QHash, Room*> roomMap; /// Mapping from serverparts to alias/room id mappings, /// as of the last sync QHash roomAliasMap; @@ -1707,7 +1707,7 @@ Room* Connection::provideRoom(const QString& id, Omittable joinState) Q_ASSERT_X(!id.isEmpty(), __FUNCTION__, "Empty room id"); // If joinState is empty, all joinState == comparisons below are false. - const auto roomKey = qMakePair(id, joinState == JoinState::Invite); + const std::pair roomKey { id, joinState == JoinState::Invite }; auto* room = d->roomMap.value(roomKey, nullptr); if (room) { // Leave is a special case because in transition (5a) (see the .h file) -- cgit v1.2.3 From 6b355d1aa87072143e09ea5269e8cf465318a64f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 18 Jun 2022 21:26:43 +0200 Subject: Drop pre-Qt 5.15 code --- lib/connection.cpp | 16 ---------------- 1 file changed, 16 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index c390cc05..2319a38a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -49,10 +49,6 @@ # include #endif -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) -# include -#endif - #include #include #include @@ -1826,16 +1822,10 @@ void Connection::saveRoomState(Room* r) const QFile outRoomFile { stateCacheDir().filePath( SyncData::fileNameForRoom(r->id())) }; if (outRoomFile.open(QFile::WriteOnly)) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) const auto data = d->cacheToBinary ? QCborValue::fromJsonValue(r->toJson()).toCbor() : QJsonDocument(r->toJson()).toJson(QJsonDocument::Compact); -#else - QJsonDocument json { r->toJson() }; - const auto data = d->cacheToBinary ? json.toBinaryData() - : json.toJson(QJsonDocument::Compact); -#endif outRoomFile.write(data.data(), data.size()); qCDebug(MAIN) << "Room state cache saved to" << outRoomFile.fileName(); } else { @@ -1905,15 +1895,9 @@ void Connection::saveState() const } #endif -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) const auto data = d->cacheToBinary ? QCborValue::fromJsonValue(rootObj).toCbor() : QJsonDocument(rootObj).toJson(QJsonDocument::Compact); -#else - QJsonDocument json { rootObj }; - const auto data = d->cacheToBinary ? json.toBinaryData() - : json.toJson(QJsonDocument::Compact); -#endif qCDebug(PROFILER) << "Cache for" << userId() << "generated in" << et; outFile.write(data.data(), data.size()); -- cgit v1.2.3 From 9f7a65b04c246de4c27b205ece778ede1ad7df7e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 09:18:09 +0200 Subject: Fix copy-pasta in signed one-time key JSON dumper --- lib/connection.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 2319a38a..13a35684 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2306,10 +2306,11 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, signedOneTimeKey ->signatures[targetUserId]["ed25519:"_ls % targetDeviceId] .toLatin1(); + const auto payloadObject = + toJson(SignedOneTimeKey { signedOneTimeKey->key, {} }); if (!verifier.ed25519Verify( edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), - QJsonDocument(toJson(SignedOneTimeKey { signedOneTimeKey->key, {} })) - .toJson(QJsonDocument::Compact), + QJsonDocument(payloadObject).toJson(QJsonDocument::Compact), signature)) { qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId << targetDeviceId << ". Skipping this device."; -- cgit v1.2.3 From 6ae41d68dcdb91e5ec4a3ea48a151daaa0765765 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 15:10:33 +0200 Subject: Rework SignedOneTimeKey as a QJsonObject wrapper Since this object has to be verified against a signature it also carries there's a rather specific procedure described in The Spec for that. That procedure basically assumes handling the signed one-time key object as a JSON object, not as a C++ object. And originally Quotient E2EE code was exactly like that (obtaining the right QJsonObject from the job result and handling it as specced) but then one enthusiastic developer (me) decided it's better to use a proper C++ structure - breaking the verification logic along the way. After a couple attempts to fix it, here we are again: SignedOneTimeKey is a proper QJsonObject, and even provides a method returning its JSON in the form prepared for verification (according to the spec). --- lib/connection.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 13a35684..690b3f6a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2303,14 +2303,10 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, // Verify contents of signedOneTimeKey - for that, drop `signatures` and // `unsigned` and then verify the object against the respective signature const auto signature = - signedOneTimeKey - ->signatures[targetUserId]["ed25519:"_ls % targetDeviceId] - .toLatin1(); - const auto payloadObject = - toJson(SignedOneTimeKey { signedOneTimeKey->key, {} }); + signedOneTimeKey->signature(targetUserId, targetDeviceId); if (!verifier.ed25519Verify( edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), - QJsonDocument(payloadObject).toJson(QJsonDocument::Compact), + signedOneTimeKey->toJsonForVerification(), signature)) { qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId << targetDeviceId << ". Skipping this device."; @@ -2320,7 +2316,7 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, curveKeyForUserDevice(targetUserId, targetDeviceId); auto session = QOlmSession::createOutboundSession(olmAccount.get(), recipientCurveKey, - signedOneTimeKey->key); + signedOneTimeKey->key()); if (!session) { qCWarning(E2EE) << "Failed to create olm session for " << recipientCurveKey << session.error(); -- cgit v1.2.3 From 7fdb1a8653863f580b2672faefc08fb372258df8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 15:56:03 +0200 Subject: Code cleanup and reformatting --- lib/connection.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 690b3f6a..701f78c2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2308,8 +2308,9 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, edKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(), signedOneTimeKey->toJsonForVerification(), signature)) { - qWarning(E2EE) << "Failed to verify one-time-key signature for" << targetUserId - << targetDeviceId << ". Skipping this device."; + qWarning(E2EE) << "Failed to verify one-time-key signature for" + << targetUserId << targetDeviceId + << ". Skipping this device."; return false; } const auto recipientCurveKey = -- cgit v1.2.3 From 78c1a2db7c42b7d2cd5a036a5ef19bb95c679b86 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 15 Jul 2022 14:31:01 +0200 Subject: Connection::user(): validate after lookup, not before If userMap only holds valid ids, there's no reason to spend time validating the sought id: if it's invalid, it won't be found. And lookups over a hash map are cheap. --- lib/connection.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 701f78c2..81151135 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1456,12 +1456,14 @@ User* Connection::user(const QString& uId) { if (uId.isEmpty()) return nullptr; + if (const auto v = d->userMap.value(uId, nullptr)) + return v; + // Before creating a user object, check that the user id is well-formed + // (it's faster to just do a lookup above before validation) if (!uId.startsWith('@') || serverPart(uId).isEmpty()) { qCCritical(MAIN) << "Malformed userId:" << uId; return nullptr; } - if (d->userMap.contains(uId)) - return d->userMap.value(uId); auto* user = userFactory()(this, uId); d->userMap.insert(uId, user); emit newUser(user); -- cgit v1.2.3 From 953d5a9d03b2a3ca439a79775a0c212965d91c20 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 29 Jul 2022 17:50:17 +0200 Subject: Moving eventCast() In a situation where you have an EventPtr that you want to place somewhere as an `event_ptr_tt` you have to carefully check that the stored event is actually of SomeMoreSpecificType and if it is, release() that event pointer, downcast, and re-wrap it into that new event_ptr_tt - or, as can be seen from the diff here, re-loadEvent() from JSON, which is simpler but inefficient. To help clients, and the library, eventCast() can now accept an rvalue smart pointer and do all the necessary things with it. --- lib/connection.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 81151135..6e04883e 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -977,22 +977,22 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) if (!toDeviceEvents.empty()) { qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; - visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { - if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { - qCDebug(E2EE) << "Unsupported algorithm" << event.id() - << "for event" << event.algorithm(); - return; - } - if (isKnownCurveKey(event.senderId(), event.senderKey())) { - handleEncryptedToDeviceEvent(event); - return; + for (auto&& tdEvt : toDeviceEvents) + if (auto&& event = eventCast(std::move(tdEvt))) { + if (event->algorithm() != OlmV1Curve25519AesSha2AlgoKey) { + qCDebug(E2EE) << "Unsupported algorithm" << event->id() + << "for event" << event->algorithm(); + return; + } + if (isKnownCurveKey(event->senderId(), event->senderKey())) { + handleEncryptedToDeviceEvent(*event); + return; + } + trackedUsers += event->senderId(); + outdatedUsers += event->senderId(); + encryptionUpdateRequired = true; + pendingEncryptedEvents.push_back(std::move(event)); } - trackedUsers += event.senderId(); - outdatedUsers += event.senderId(); - encryptionUpdateRequired = true; - pendingEncryptedEvents.push_back( - makeEvent(event.fullJson())); - }); } #endif } -- cgit v1.2.3 From deb6c1141af445de6f3f1fd5afc83ed2ac4def4b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 27 Jul 2022 20:10:03 +0200 Subject: Fix Connection::accountData<>() The template version has never worked, to the point where instantiating it would immediately lead to FTBFS. The new version returns an event pointer as a simpler fix that would make it usable - in particular, there's no more need to have separate Connection::Private::unpackAccountData(). To simplify the fix, eventCast() has been made more tolerating - passing nullptr to it is processed in an expected (no-op) way now. --- lib/connection.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 6e04883e..9e4444df 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -175,15 +175,6 @@ public: void consumeToDeviceEvents(Events&& toDeviceEvents); void consumeDevicesList(DevicesList&& devicesList); - template - EventT* unpackAccountData() const - { - const auto& eventIt = accountData.find(EventT::matrixTypeId()); - return eventIt == accountData.end() - ? nullptr - : weakPtrCast(eventIt->second); - } - void packAndSendAccountData(EventPtr&& event) { const auto eventType = event->matrixType(); @@ -1665,7 +1656,7 @@ bool Connection::isIgnored(const User* user) const IgnoredUsersList Connection::ignoredUsers() const { - const auto* event = d->unpackAccountData(); + const auto* event = accountData(); return event ? event->ignored_users() : IgnoredUsersList(); } -- cgit v1.2.3 From 1c94d1b41eb352b31b2dc915fea95e26f6138284 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 25 Aug 2022 19:28:10 +0200 Subject: KeyVerificationSession: cleanup - Use std::chrono for the timeout (it's more readable and less ambiguous) and make it a local variable - Only pass a Connection object once to constructors - Ensure buildability even without E2EE (key verification is disabled in that case) - Reorder #includes - Other cleanup following clang-tidy warnings --- lib/connection.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 3e1e556f..fbe365de 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -37,15 +37,17 @@ #ifdef Quotient_E2EE_ENABLED # include "database.h" +# include "keyverificationsession.h" + # include "e2ee/qolmaccount.h" # include "e2ee/qolminboundsession.h" # include "e2ee/qolmsession.h" # include "e2ee/qolmutility.h" # include "e2ee/qolmutils.h" -# include "events/keyverificationevent.h" -# include "keyverificationsession.h" +# include "events/keyverificationevent.h" #endif // Quotient_E2EE_ENABLED + #if QT_VERSION_MAJOR >= 6 # include #else @@ -988,7 +990,8 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) } switchOnType(*tdEvt, [this](const KeyVerificationRequestEvent& event) { - auto session = new KeyVerificationSession(q->userId(), event, q, false, q); + auto session = new KeyVerificationSession(q->userId(), + event, q, false); emit q->newKeyVerificationSession(session); }, [this](const KeyVerificationReadyEvent& event) { emit q->incomingKeyVerificationReady(event); @@ -1028,8 +1031,8 @@ void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& eve << "is not found at the connection" << q->objectName(); } }, [this](const KeyVerificationRequestEvent& event) { - auto session = new KeyVerificationSession(q->userId(), event, q, true, q); - emit q->newKeyVerificationSession(session); + emit q->newKeyVerificationSession( + new KeyVerificationSession(q->userId(), event, q, true)); }, [this](const KeyVerificationReadyEvent& event) { emit q->incomingKeyVerificationReady(event); }, [this](const KeyVerificationStartEvent& event) { @@ -2274,9 +2277,9 @@ QString Connection::Private::curveKeyForUserDevice(const QString& userId, } QString Connection::edKeyForUserDevice(const QString& userId, - const QString& device) const + const QString& deviceId) const { - return d->deviceKeys[userId][device].keys["ed25519:" % device]; + return d->deviceKeys[userId][deviceId].keys["ed25519:" % deviceId]; } bool Connection::Private::isKnownCurveKey(const QString& userId, @@ -2458,12 +2461,10 @@ void Connection::saveCurrentOutboundMegolmSession( session); } -#endif - void Connection::startKeyVerificationSession(const QString& deviceId) { - auto session = new KeyVerificationSession(userId(), deviceId, this, this); - Q_EMIT newKeyVerificationSession(session); + auto* const session = new KeyVerificationSession(userId(), deviceId, this); + emit newKeyVerificationSession(session); } void Connection::sendToDevice(const QString& userId, const QString& deviceId, @@ -2499,7 +2500,7 @@ void Connection::sendToDevice(const QString& userId, const QString& deviceId, { { userId, { { deviceId, event->contentJson() } } } }); } -bool Connection::isVerifiedSession(const QString& megolmSessionId) +bool Connection::isVerifiedSession(const QString& megolmSessionId) const { auto query = database()->prepareQuery("SELECT olmSessionId FROM inbound_megolm_sessions WHERE sessionId=:sessionId;"_ls); query.bindValue(":sessionId", megolmSessionId); @@ -2520,3 +2521,4 @@ bool Connection::isVerifiedSession(const QString& megolmSessionId) database()->execute(query); return query.next() && query.value("verified").toBool(); } +#endif -- cgit v1.2.3 From 2e1f179bf75da9705963be9305ab6db34afa4d6d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 20 Aug 2022 19:18:17 +0200 Subject: Connection::Private::assembleEncryptedContent() What was partially factored out before into encryptSessionKeyEvent() is now the complete algorithm converting any event json into encrypted content. --- lib/connection.cpp | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index fbe365de..19fc484a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -371,9 +371,9 @@ public: const OneTimeKeys &oneTimeKeyObject); QString curveKeyForUserDevice(const QString& userId, const QString& device) const; - QJsonObject encryptSessionKeyEvent(QJsonObject payloadJson, - const QString& targetUserId, - const QString& targetDeviceId) const; + QJsonObject assembleEncryptedContent(QJsonObject payloadJson, + const QString& targetUserId, + const QString& targetDeviceId) const; #endif void saveAccessTokenToKeychain() const @@ -2364,10 +2364,16 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, return true; } -QJsonObject Connection::Private::encryptSessionKeyEvent( +QJsonObject Connection::Private::assembleEncryptedContent( QJsonObject payloadJson, const QString& targetUserId, const QString& targetDeviceId) const { + payloadJson.insert(SenderKeyL, data->userId()); +// eventJson.insert("sender_device"_ls, data->deviceId()); + payloadJson.insert("keys"_ls, + QJsonObject{ + { Ed25519Key, + QString(olmAccount->identityKeys().ed25519) } }); payloadJson.insert("recipient"_ls, targetUserId); payloadJson.insert( "recipient_keys"_ls, @@ -2381,7 +2387,6 @@ QJsonObject Connection::Private::encryptSessionKeyEvent( QJsonObject { { "type"_ls, type }, { "body"_ls, QString(cipherText) } } } }; - return EncryptedEvent(encrypted, olmAccount->identityKeys().curve25519) .contentJson(); } @@ -2404,18 +2409,8 @@ void Connection::sendSessionKeyToDevices( if (hash.isEmpty()) return; - auto keyEventJson = RoomKeyEvent(MegolmV1AesSha2AlgoKey, roomId, sessionId, - sessionKey, userId()) - .fullJson(); - keyEventJson.insert(SenderKeyL, userId()); - keyEventJson.insert("sender_device"_ls, deviceId()); - keyEventJson.insert( - "keys"_ls, - QJsonObject { - { Ed25519Key, QString(olmAccount()->identityKeys().ed25519) } }); - auto job = callApi(hash); - connect(job, &BaseJob::success, this, [job, this, roomId, sessionId, keyEventJson, devices, index] { + connect(job, &BaseJob::success, this, [job, this, roomId, sessionId, sessionKey, devices, index] { QHash> usersToDevicesToContent; for (const auto oneTimeKeys = job->oneTimeKeys(); const auto& [targetUserId, targetDeviceId] : @@ -2429,10 +2424,14 @@ void Connection::sendSessionKeyToDevices( // Noisy but nice for debugging // qDebug(E2EE) << "Creating the payload for" << targetUserId // << targetDeviceId << sessionId << sessionKey.toHex(); + const auto keyEventJson = RoomKeyEvent(MegolmV1AesSha2AlgoKey, + roomId, sessionId, sessionKey) + .fullJson(); + usersToDevicesToContent[targetUserId][targetDeviceId] = - d->encryptSessionKeyEvent(keyEventJson, targetUserId, + d->assembleEncryptedContent(keyEventJson, targetUserId, targetDeviceId); - } + } if (!usersToDevicesToContent.empty()) { sendToDevices(EncryptedEvent::TypeId, usersToDevicesToContent); QVector> receivedDevices; -- cgit v1.2.3 From 376da43a29f3ebad807da2761e7a0c0b105587ec Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 25 Aug 2022 19:58:18 +0200 Subject: More code reorganisation - Common switchOnType() piece for key verification events is factored out into processIfVerificationEvent() - Bare event JSON removed from KeyVerificationSession into constructors of respective events - Connection::sendToDevice() uses assembleEncryptedContent() introduced in the previous commit - commonSupportedMethods() moved out to .cpp; error/string converters made static --- lib/connection.cpp | 127 +++++++++++++++++++++++------------------------------ 1 file changed, 55 insertions(+), 72 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 19fc484a..04cabf47 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -121,6 +121,7 @@ public: QHash oneTimeKeysCount; std::vector> pendingEncryptedEvents; void handleEncryptedToDeviceEvent(const EncryptedEvent& event); + bool processIfVerificationEvent(const Event &evt, bool encrypted); // A map from SenderKey to vector of InboundSession UnorderedMap> olmSessions; @@ -988,68 +989,71 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) pendingEncryptedEvents.push_back(std::move(event)); continue; } - switchOnType(*tdEvt, - [this](const KeyVerificationRequestEvent& event) { - auto session = new KeyVerificationSession(q->userId(), - event, q, false); - emit q->newKeyVerificationSession(session); - }, [this](const KeyVerificationReadyEvent& event) { - emit q->incomingKeyVerificationReady(event); - }, [this](const KeyVerificationStartEvent& event) { - emit q->incomingKeyVerificationStart(event); - }, [this](const KeyVerificationAcceptEvent& event) { - emit q->incomingKeyVerificationAccept(event); - }, [this](const KeyVerificationKeyEvent& event) { - emit q->incomingKeyVerificationKey(event); - }, [this](const KeyVerificationMacEvent& event) { - emit q->incomingKeyVerificationMac(event); - }, [this](const KeyVerificationDoneEvent& event) { - emit q->incomingKeyVerificationDone(event); - }, [this](const KeyVerificationCancelEvent& event) { - emit q->incomingKeyVerificationCancel(event); - }); + processIfVerificationEvent(*tdEvt, false); } } #endif } #ifdef Quotient_E2EE_ENABLED -void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& event) -{ - const auto [decryptedEvent, olmSessionId] = sessionDecryptMessage(event); - if(!decryptedEvent) { - qCWarning(E2EE) << "Failed to decrypt event" << event.id(); - return; - } - - switchOnType(*decryptedEvent, - [this, &event, olmSessionId = olmSessionId](const RoomKeyEvent& roomKeyEvent) { - if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { - detectedRoom->handleRoomKeyEvent(roomKeyEvent, event.senderId(), olmSessionId); - } else { - qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() - << "is not found at the connection" << q->objectName(); - } - }, [this](const KeyVerificationRequestEvent& event) { - emit q->newKeyVerificationSession( - new KeyVerificationSession(q->userId(), event, q, true)); +bool Connection::Private::processIfVerificationEvent(const Event& evt, + bool encrypted) +{ + return switchOnType(evt, + [this, encrypted](const KeyVerificationRequestEvent& event) { + auto session = + new KeyVerificationSession(q->userId(), event, q, encrypted); + emit q->newKeyVerificationSession(session); + return true; }, [this](const KeyVerificationReadyEvent& event) { emit q->incomingKeyVerificationReady(event); + return true; }, [this](const KeyVerificationStartEvent& event) { emit q->incomingKeyVerificationStart(event); + return true; }, [this](const KeyVerificationAcceptEvent& event) { emit q->incomingKeyVerificationAccept(event); + return true; }, [this](const KeyVerificationKeyEvent& event) { emit q->incomingKeyVerificationKey(event); + return true; }, [this](const KeyVerificationMacEvent& event) { emit q->incomingKeyVerificationMac(event); + return true; }, [this](const KeyVerificationDoneEvent& event) { emit q->incomingKeyVerificationDone(event); + return true; }, [this](const KeyVerificationCancelEvent& event) { emit q->incomingKeyVerificationCancel(event); - }, [](const Event& evt) { + return true; + }, false); +} + +void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& event) +{ + const auto [decryptedEvent, olmSessionId] = sessionDecryptMessage(event); + if(!decryptedEvent) { + qCWarning(E2EE) << "Failed to decrypt event" << event.id(); + return; + } + + if (processIfVerificationEvent(*decryptedEvent, true)) + return; + switchOnType(*decryptedEvent, + [this, &event, + olmSessionId = olmSessionId](const RoomKeyEvent& roomKeyEvent) { + if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { + detectedRoom->handleRoomKeyEvent(roomKeyEvent, event.senderId(), + olmSessionId); + } else { + qCDebug(E2EE) + << "Encrypted event room id" << roomKeyEvent.roomId() + << "is not found at the connection" << q->objectName(); + } + }, + [](const Event& evt) { qCWarning(E2EE) << "Skipping encrypted to_device event, type" - << evt.matrixType(); + << evt.matrixType(); }); } #endif @@ -2466,37 +2470,16 @@ void Connection::startKeyVerificationSession(const QString& deviceId) emit newKeyVerificationSession(session); } -void Connection::sendToDevice(const QString& userId, const QString& deviceId, - event_ptr_tt event, bool encrypted) -{ - if (encrypted) { - QJsonObject payloadJson = event->fullJson(); - payloadJson["recipient"] = userId; - payloadJson["sender"] = user()->id(); - QJsonObject recipientObject; - recipientObject["ed25519"] = edKeyForUserDevice(userId, deviceId); - payloadJson["recipient_keys"] = recipientObject; - QJsonObject senderObject; - senderObject["ed25519"] = QString(olmAccount()->identityKeys().ed25519); - payloadJson["keys"] = senderObject; - - auto cipherText = d->olmEncryptMessage( - userId, deviceId, - QJsonDocument(payloadJson).toJson(QJsonDocument::Compact)); - QJsonObject encryptedJson; - encryptedJson[d->curveKeyForUserDevice(userId, deviceId)] = - QJsonObject{ { "type", cipherText.first }, - { "body", QString(cipherText.second) }, - { "sender", this->userId() } }; - const auto& contentJson = - EncryptedEvent(encryptedJson, - olmAccount()->identityKeys().curve25519) - .contentJson(); - sendToDevices(EncryptedEvent::TypeId, - { { userId, { { deviceId, contentJson } } } }); - } else - sendToDevices(event->matrixType(), - { { userId, { { deviceId, event->contentJson() } } } }); +void Connection::sendToDevice(const QString& targetUserId, + const QString& targetDeviceId, Event event, + bool encrypted) +{ + const auto contentJson = + encrypted ? d->assembleEncryptedContent(event.fullJson(), targetUserId, + targetDeviceId) + : event.contentJson(); + sendToDevices(encrypted ? EncryptedEvent::TypeId : event.type(), + { { targetUserId, { { targetDeviceId, contentJson } } } }); } bool Connection::isVerifiedSession(const QString& megolmSessionId) const -- cgit v1.2.3 From 4ad2f6e165a4eb486155eae652e187dc4d6b7d99 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 25 Aug 2022 20:02:35 +0200 Subject: Cleanup --- lib/connection.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 9e4444df..98720f3b 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -6,11 +6,12 @@ #include "connection.h" +#include "accountregistry.h" #include "connectiondata.h" +#include "qt_connection_util.h" #include "room.h" #include "settings.h" #include "user.h" -#include "accountregistry.h" // NB: since Qt 6, moc_connection.cpp needs Room and User fully defined #include "moc_connection.cpp" @@ -20,10 +21,8 @@ #include "csapi/joining.h" #include "csapi/leaving.h" #include "csapi/logout.h" -#include "csapi/receipts.h" #include "csapi/room_send.h" #include "csapi/to_device.h" -#include "csapi/versions.h" #include "csapi/voip.h" #include "csapi/wellknown.h" #include "csapi/whoami.h" @@ -2184,8 +2183,8 @@ PicklingMode Connection::picklingMode() const void Connection::saveOlmAccount() { - qCDebug(E2EE) << "Saving olm account"; #ifdef Quotient_E2EE_ENABLED + qCDebug(E2EE) << "Saving olm account"; if (const auto expectedPickle = d->olmAccount->pickle(d->picklingMode)) d->database->setAccountPickle(*expectedPickle); else -- cgit v1.2.3 From a26147582ce8cbd6a5206aee4b59de98c9dfe9b6 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 27 Jul 2022 20:19:44 +0200 Subject: DEFINE_SIMPLE_EVENT: support custom JSON keys --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 79d7ae55..722829e8 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1701,7 +1701,7 @@ bool Connection::isIgnored(const User* user) const IgnoredUsersList Connection::ignoredUsers() const { const auto* event = accountData(); - return event ? event->ignored_users() : IgnoredUsersList(); + return event ? event->ignoredUsers() : IgnoredUsersList(); } void Connection::addToIgnoredUsers(const User* user) -- cgit v1.2.3 From 17cd3beaefa5501a902e08c7644e8cd97c9091a0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 12 Aug 2022 16:46:01 +0200 Subject: Streamline event types This commit introduces a few things to further reduce the boilerplate across event type definitions: - Event type is no more separately stored in Event and therefore no more passed to base event constructors. Until the previous commit, it was used by is() to quickly match the event type; with the new event metatype class, the same is achieved even quicker by comparing metatype pointers. - EventTemplate is a generalisation of StateEvent for all event types providing common constructor signatures and content() for (most) leaf event types. StateEvent therefore has become a partial specialisation of EventTemplate for types derived from StateEventBase; as the known client code base does not use it directly, a compatibility alias is not provided. Also, DEFINE_SIMPLE_EVENT now expands into a class deriving from EventTemplate. - On top of StateEvent->EventTemplate specialisation, KeyedStateEventBase and KeylessStateEventBase types are introduced with appropriate constructor signatures (with or without state_key, respectively) to allow `using` of them from derived event types. To facilitate writing of constraints, concepts for keyed and keyless state event types are also introduced; RoomStateView, e.g., makes use of those to provide appropriate method signatures. - typeId(), unknownEventTypeId(), UnknownEventTypeId are no more provided - they weren't used throughout the known code base (Quaternion, NeoChat), and the concept of "unknown event types" is hereby eliminated entirely. - RoomKeyEvent no more accepts senderId as a parameter; it has never been a good practice as the sender is assigned by Connection anyway. --- lib/connection.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 722829e8..471dc20d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -28,7 +28,6 @@ #include "csapi/whoami.h" #include "events/directchatevent.h" -#include "events/eventloader.h" #include "jobs/downloadfilejob.h" #include "jobs/mediathumbnailjob.h" #include "jobs/syncjob.h" -- cgit v1.2.3 From 575534e7cca310c6d6195ab16d482bf9dfba755e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 1 Aug 2022 18:09:35 +0200 Subject: Disallow direct events construction from JSON Direct construction (using makeEvent() or explicitly constructing an event) from JSON may create an event that has a type conflicting with that stored in JSON. There's no such problem with loadEvent(), even though it's considerably slower. Driven by the fact that almost nowhere in the code direct construction is used on checked JSON (one test is the only valid case), this commit moves all JSON-loading constructors to the protected section, thereby disabling usage of makeEvent() in JSON-loading capacity, and switches such cases across the library to loadEvent(). --- lib/connection.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 471dc20d..a33ace51 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2242,10 +2242,12 @@ void Connection::saveOlmAccount() #ifdef Quotient_E2EE_ENABLED QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) { - auto r = room(notification["room_id"].toString()); - auto event = makeEvent(notification["event"].toObject()); - const auto decrypted = r->decryptMessage(*event); - return decrypted ? decrypted->fullJson() : QJsonObject(); + if (auto r = room(notification["room_id"].toString())) + if (auto event = + loadEvent(notification["event"].toObject())) + if (const auto decrypted = r->decryptMessage(*event)) + return decrypted->fullJson(); + return QJsonObject(); } Database* Connection::database() const -- cgit v1.2.3 From bde38f86337d6f49b34b38016ab088d2f48ec371 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 1 Aug 2022 18:09:47 +0200 Subject: concept EventClass Constrain types to derive from Event (or the chosen class), where applicable. --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index a33ace51..d9268028 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -188,7 +188,7 @@ public: emit q->accountDataChanged(eventType); } - template + template void packAndSendAccountData(ContentT&& content) { packAndSendAccountData( -- cgit v1.2.3 From 66127730592eadf9ee717a53a521ac2ec14f1051 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 5 Sep 2022 15:20:58 +0200 Subject: sendToDevice: fix unintended slicing Ironically, this slicing would not break anything as all the necessary data are saved in the Event parent class; but the code is very fragile and scary. --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index d9268028..53c99969 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2471,7 +2471,7 @@ void Connection::startKeyVerificationSession(const QString& deviceId) } void Connection::sendToDevice(const QString& targetUserId, - const QString& targetDeviceId, Event event, + const QString& targetDeviceId, const Event& event, bool encrypted) { const auto contentJson = -- cgit v1.2.3 From 6abdd5358c9c5ed89cda5bc5e50a76af423b0634 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 11 Sep 2022 20:25:46 +0200 Subject: KeyVerificationEvent; KeyVerificationSession::handleEvent() Key verification events gain their own base type and KeyVerificationSession gets a single point of entry for all kinds of incoming events. This allows to drop a pile of `incoming*` signals in Connection and a stack of options inside switchOnType in processIfVerification(). KVS::handleEvent() also makes (some) allowed state transitions a bit clearer. --- lib/connection.cpp | 44 +++++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 27 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 53c99969..5003f40c 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -124,6 +124,7 @@ public: // A map from SenderKey to vector of InboundSession UnorderedMap> olmSessions; + QHash verificationSessions; #endif GetCapabilitiesJob* capabilitiesJob = nullptr; @@ -971,6 +972,8 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) qCDebug(E2EE) << "Consuming" << toDeviceEvents.size() << "to-device events"; for (auto&& tdEvt : toDeviceEvents) { + if (processIfVerificationEvent(*tdEvt, false)) + continue; if (auto&& event = eventCast(std::move(tdEvt))) { if (event->algorithm() != OlmV1Curve25519AesSha2AlgoKey) { qCDebug(E2EE) << "Unsupported algorithm" << event->id() @@ -985,9 +988,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) outdatedUsers += event->senderId(); encryptionUpdateRequired = true; pendingEncryptedEvents.push_back(std::move(event)); - continue; } - processIfVerificationEvent(*tdEvt, false); } } #endif @@ -998,33 +999,22 @@ bool Connection::Private::processIfVerificationEvent(const Event& evt, bool encrypted) { return switchOnType(evt, - [this, encrypted](const KeyVerificationRequestEvent& event) { - auto session = - new KeyVerificationSession(q->userId(), event, q, encrypted); - emit q->newKeyVerificationSession(session); - return true; - }, [this](const KeyVerificationReadyEvent& event) { - emit q->incomingKeyVerificationReady(event); - return true; - }, [this](const KeyVerificationStartEvent& event) { - emit q->incomingKeyVerificationStart(event); - return true; - }, [this](const KeyVerificationAcceptEvent& event) { - emit q->incomingKeyVerificationAccept(event); - return true; - }, [this](const KeyVerificationKeyEvent& event) { - emit q->incomingKeyVerificationKey(event); + [this, encrypted](const KeyVerificationRequestEvent& reqEvt) { + const auto sessionIter = verificationSessions.insert( + reqEvt.transactionId(), + new KeyVerificationSession(q->userId(), reqEvt, q, encrypted)); + emit q->newKeyVerificationSession(*sessionIter); return true; - }, [this](const KeyVerificationMacEvent& event) { - emit q->incomingKeyVerificationMac(event); - return true; - }, [this](const KeyVerificationDoneEvent& event) { - emit q->incomingKeyVerificationDone(event); - return true; - }, [this](const KeyVerificationCancelEvent& event) { - emit q->incomingKeyVerificationCancel(event); + }, + [this](const KeyVerificationEvent& kvEvt) { + if (auto* const session = + verificationSessions.value(kvEvt.transactionId())) { + session->handleEvent(kvEvt); + emit q->keyVerificationStateChanged(session, session->state()); + } return true; - }, false); + }, + false); } void Connection::Private::handleEncryptedToDeviceEvent(const EncryptedEvent& event) -- cgit v1.2.3 From d7030d88eee9a1ac285f00f3a252b63b9cd40a6c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 12 Sep 2022 08:27:38 +0200 Subject: Fix a leak in Connection::saveAccessTokenToKeychain() --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 53c99969..5de02b8c 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -379,7 +379,7 @@ public: { qCDebug(MAIN) << "Saving access token to keychain for" << q->userId(); auto job = new QKeychain::WritePasswordJob(qAppName()); - job->setAutoDelete(false); + job->setAutoDelete(true); job->setKey(q->userId()); job->setBinaryData(data->accessToken()); job->start(); -- cgit v1.2.3 From a2b155b10197a4fca4db2af59c2171f82a0eac2b Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 24 Sep 2022 12:15:54 +0200 Subject: Fix verification Contains two fixes: - When receiving the mac, we can also be in WAITINGFORVERIFICATION state - Ignore all KeyVerificationDone events; we don't do anything with them anyway and sometimes receive them after the session is destructed --- lib/connection.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 8ca76ceb..307c3840 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1006,6 +1006,10 @@ bool Connection::Private::processIfVerificationEvent(const Event& evt, emit q->newKeyVerificationSession(*sessionIter); return true; }, + [](const KeyVerificationDoneEvent& doneEvt) { + Q_UNUSED(doneEvt) + return true; + }, [this](const KeyVerificationEvent& kvEvt) { if (auto* const session = verificationSessions.value(kvEvt.transactionId())) { -- cgit v1.2.3 From 63d658e798c1ba29d080564db06a613a3d7d5df5 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:22:05 +0200 Subject: Update lib/connection.cpp Co-authored-by: Alexey Rusakov --- lib/connection.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 307c3840..1048884f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1006,8 +1006,7 @@ bool Connection::Private::processIfVerificationEvent(const Event& evt, emit q->newKeyVerificationSession(*sessionIter); return true; }, - [](const KeyVerificationDoneEvent& doneEvt) { - Q_UNUSED(doneEvt) + [](const KeyVerificationDoneEvent&) { return true; }, [this](const KeyVerificationEvent& kvEvt) { -- cgit v1.2.3 From 363a7e40e8aa12cb780b076cca8db4f47b70f4fa Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 26 Sep 2022 09:44:20 +0200 Subject: Replace QOlmError with OlmErrorCode QOlmError represents a subset of OlmErrorCode, and the associated fromString() function uses undocumented strings produced inside Olm; meanwhile OlmErrorCode is documented in its own header file. Each QOlm* class now has lastErrorCode() next to lastError() (that, from now, returns a textual representation straight from Olm, not QOlmError enum). Also: including olm/error.h in e2ee/e2ee.h required some rearrangement of the code to make sure non-E2EE configuration still builds. --- lib/connection.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 8ca76ceb..865dff79 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -270,8 +270,7 @@ public: return {}; } auto newSession = std::move(*newSessionResult); - auto error = olmAccount->removeOneTimeKeys(*newSession); - if (error) { + if (olmAccount->removeOneTimeKeys(*newSession) != OLM_SUCCESS) { qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); // Keep going though -- cgit v1.2.3 From bcc05aa1d52cae2b6d8e70bb6cf04fa49904687a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 21 Sep 2022 15:45:59 +0200 Subject: Cleanup across E2EE code Notably: - simplified unnecessarily verbose constructs; - formally aligned (no re-numeration was necessary) QOlmMessage::Type with corresponding OLM_ constants; - dropped QOlmSession::encryptMessageType() because it's very sensitive to the order of calling with QOlmSession::encrypt() (and encrypt() itself already calls it and returns the message type); - simplify the return type of pickle() calls that can only fail due to an internal error; - replace const QString& with QStringView or const QByteArray& where appropriate; - use '\0' where it was meant to be instead of '0'. --- lib/connection.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 865dff79..f38bb751 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -652,8 +652,7 @@ void Connection::Private::completeSetup(const QString& mxId) }); } else { // account already existing - auto pickle = database->accountPickle(); - olmAccount->unpickle(pickle, picklingMode); + olmAccount->unpickle(database->accountPickle(), picklingMode); } #endif // Quotient_E2EE_ENABLED emit q->stateChanged(); @@ -2300,14 +2299,13 @@ std::pair Connection::Private::olmEncryptMessage( { const auto& curveKey = curveKeyForUserDevice(userId, device); const auto& olmSession = olmSessions.at(curveKey).front(); - QOlmMessage::Type type = olmSession->encryptMessageType(); const auto result = olmSession->encrypt(message); if (const auto pickle = olmSession->pickle(picklingMode)) { database->updateOlmSession(curveKey, olmSession->sessionId(), *pickle); } else { qWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); } - return { type, result.toCiphertext() }; + return { result.type(), result.toCiphertext() }; } bool Connection::Private::createOlmSession(const QString& targetUserId, @@ -2343,7 +2341,7 @@ bool Connection::Private::createOlmSession(const QString& targetUserId, return false; } const auto recipientCurveKey = - curveKeyForUserDevice(targetUserId, targetDeviceId); + curveKeyForUserDevice(targetUserId, targetDeviceId).toLatin1(); auto session = QOlmSession::createOutboundSession(olmAccount.get(), recipientCurveKey, signedOneTimeKey->key()); -- cgit v1.2.3 From bc1ded73bedf593acda80b00eb7da32f688c4843 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 21 Sep 2022 16:11:39 +0200 Subject: RandomBuffer A convenient abstraction swallowing all the type casts and, more importantly, cleanup on destruction (previous code only cleaned up the buffer upon a successful call to Olm API but not upon an error). --- lib/connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index f38bb751..cd8ee727 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -614,7 +614,7 @@ void Connection::Private::completeSetup(const QString& mxId) loop.exec(); if (job.error() == QKeychain::Error::EntryNotFound) { - picklingMode = Encrypted { getRandom(128) }; + picklingMode = Encrypted { RandomBuffer(128) }; QKeychain::WritePasswordJob job(qAppName()); job.setAutoDelete(false); job.setKey(accountSettings.userId() + QStringLiteral("-Pickle")); -- cgit v1.2.3 From 2ff045d8b381bfbd64100d083f81b61c5fe87b23 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 21 Sep 2022 21:09:49 +0200 Subject: Wrap error reporting into facility macros Facility macros to report Olm errors: QOLM_INTERNAL_ERROR[_X], QOLM_FAIL_OR_LOG[_X] --- lib/connection.cpp | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index cd8ee727..0afbffbc 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -206,13 +206,9 @@ public: } void saveSession(const QOlmSession& session, const QString& senderKey) const { - if (auto pickleResult = session.pickle(picklingMode)) - q->database()->saveOlmSession(senderKey, session.sessionId(), - *pickleResult, - QDateTime::currentDateTime()); - else - qCWarning(E2EE) << "Failed to pickle olm session. Error" - << pickleResult.error(); + q->database()->saveOlmSession(senderKey, session.sessionId(), + session.pickle(picklingMode), + QDateTime::currentDateTime()); } template @@ -2219,11 +2215,7 @@ void Connection::saveOlmAccount() { #ifdef Quotient_E2EE_ENABLED qCDebug(E2EE) << "Saving olm account"; - if (const auto expectedPickle = d->olmAccount->pickle(d->picklingMode)) - d->database->setAccountPickle(*expectedPickle); - else - qCWarning(E2EE) << "Couldn't save Olm account pickle:" - << expectedPickle.error(); + d->database->setAccountPickle(d->olmAccount->pickle(d->picklingMode)); #endif } @@ -2300,11 +2292,8 @@ std::pair Connection::Private::olmEncryptMessage( const auto& curveKey = curveKeyForUserDevice(userId, device); const auto& olmSession = olmSessions.at(curveKey).front(); const auto result = olmSession->encrypt(message); - if (const auto pickle = olmSession->pickle(picklingMode)) { - database->updateOlmSession(curveKey, olmSession->sessionId(), *pickle); - } else { - qWarning(E2EE) << "Failed to pickle olm session: " << pickle.error(); - } + database->updateOlmSession(curveKey, olmSession->sessionId(), + olmSession->pickle(picklingMode)); return { result.type(), result.toCiphertext() }; } -- cgit v1.2.3 From d5c1afc536dde87f460ccadfcfd51444b5e9bb82 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 22 Sep 2022 14:01:01 +0200 Subject: Trample Sonar warnings --- lib/connection.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib/connection.cpp') diff --git a/lib/connection.cpp b/lib/connection.cpp index 0afbffbc..51c2062f 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -648,7 +648,9 @@ void Connection::Private::completeSetup(const QString& mxId) }); } else { // account already existing - olmAccount->unpickle(database->accountPickle(), picklingMode); + if (!olmAccount->unpickle(database->accountPickle(), picklingMode)) + qWarning(E2EE) + << "Could not unpickle Olm account, E2EE won't be available"; } #endif // Quotient_E2EE_ENABLED emit q->stateChanged(); -- cgit v1.2.3