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 +++++++++++++++++++++++------------------------------- lib/connection.h | 5 ++-- 2 files changed, 34 insertions(+), 45 deletions(-) (limited to 'lib') 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)) diff --git a/lib/connection.h b/lib/connection.h index 5b806350..b8246ecb 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -133,8 +133,7 @@ class QUOTIENT_API Connection : public QObject { Q_PROPERTY(bool canChangePassword READ canChangePassword NOTIFY capabilitiesLoaded) public: - using UsersToDevicesToEvents = - UnorderedMap>; + using UsersToDevicesToContent = QHash>; enum RoomVisibility { PublishRoom, @@ -689,7 +688,7 @@ public Q_SLOTS: ForgetRoomJob* forgetRoom(const QString& id); SendToDeviceJob* sendToDevices(const QString& eventType, - const UsersToDevicesToEvents& eventsMap); + const UsersToDevicesToContent& contents); /** \deprecated This method is experimental and may be removed any time */ SendMessageJob* sendMessage(const QString& roomId, const RoomEvent& event); -- 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. --- .github/workflows/ci.yml | 5 +++-- lib/accountregistry.h | 25 +++++++++++++------------ lib/avatar.cpp | 8 ++++---- lib/connection.cpp | 4 ++-- lib/room.cpp | 4 ++-- 5 files changed, 24 insertions(+), 22 deletions(-) (limited to 'lib') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab581238..f03af94b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: max-parallel: 1 matrix: os: [ ubuntu-20.04, macos-10.15 ] - qt-version: [ '5.15.2', '6.3.1' ] + qt-version: [ '6.3.1', '5.15.2' ] compiler: [ LLVM ] # Not using binary values here, to make the job captions more readable e2ee: [ '', e2ee ] @@ -105,7 +105,8 @@ jobs: -DBUILD_SHARED_LIBS=${{ runner.os == 'Linux' }} \ -DCMAKE_INSTALL_PREFIX=~/.local \ -DCMAKE_PREFIX_PATH=~/.local \ - -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON" + -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON \ + -DBUILD_WITH_QT6=${{ startsWith(matrix.qt-version, '6') }}" if [ '${{ matrix.static-analysis }}' == 'sonar' ]; then mkdir -p $HOME/.sonar diff --git a/lib/accountregistry.h b/lib/accountregistry.h index 38cfe6c6..9560688e 100644 --- a/lib/accountregistry.h +++ b/lib/accountregistry.h @@ -31,8 +31,9 @@ class QUOTIENT_API AccountRegistry : public QAbstractListModel, /// Can be used to inform the user or to show a login screen if size() == 0 and no accounts are loaded Q_PROPERTY(QStringList accountsLoading READ accountsLoading NOTIFY accountsLoadingChanged) public: - using const_iterator = QVector::const_iterator; - using const_reference = QVector::const_reference; + using vector_t = QVector; + using const_iterator = vector_t::const_iterator; + using const_reference = vector_t::const_reference; enum EventRoles { AccountRole = Qt::UserRole + 1, @@ -42,24 +43,24 @@ public: [[deprecated("Use Accounts variable instead")]] // static AccountRegistry& instance(); - // Expose most of QVector's const-API but only provide add() and drop() + // Expose most of vector_t's const-API but only provide add() and drop() // for changing it. In theory other changing operations could be supported // too; but then boilerplate begin/end*() calls has to be tucked into each // and this class gives no guarantees on the order of entries, so why care. - const QVector& accounts() const { return *this; } + const vector_t& accounts() const { return *this; } void add(Connection* a); void drop(Connection* a); - const_iterator begin() const { return QVector::begin(); } - const_iterator end() const { return QVector::end(); } - const_reference front() const { return QVector::front(); } - const_reference back() const { return QVector::back(); } + const_iterator begin() const { return vector_t::begin(); } + const_iterator end() const { return vector_t::end(); } + const_reference front() const { return vector_t::front(); } + const_reference back() const { return vector_t::back(); } bool isLoggedIn(const QString& userId) const; Connection* get(const QString& userId); - using QVector::isEmpty, QVector::empty; - using QVector::size, QVector::count, QVector::capacity; - using QVector::cbegin, QVector::cend, QVector::contains; + using vector_t::isEmpty, vector_t::empty; + using vector_t::size, vector_t::count, vector_t::capacity; + using vector_t::cbegin, vector_t::cend, vector_t::contains; // QAbstractItemModel interface implementation @@ -88,4 +89,4 @@ private: inline QUOTIENT_API AccountRegistry Accounts {}; inline AccountRegistry& AccountRegistry::instance() { return Accounts; } -} +} // namespace Quotient diff --git a/lib/avatar.cpp b/lib/avatar.cpp index 9304a3de..13de99bf 100644 --- a/lib/avatar.cpp +++ b/lib/avatar.cpp @@ -39,7 +39,7 @@ public: // The below are related to image caching, hence mutable mutable QImage _originalImage; - mutable std::vector> _scaledImages; + mutable std::vector> _scaledImages; mutable QSize _requestedSize; mutable enum { Unknown, Cache, Network, Banned } _imageSource = Unknown; mutable QPointer _thumbnailRequest = nullptr; @@ -124,9 +124,9 @@ QImage Avatar::Private::get(Connection* connection, QSize size, }); } - for (const auto& p : _scaledImages) - if (p.first == size) - return p.second; + for (const auto& [scaledSize, scaledImage] : _scaledImages) + if (scaledSize == size) + return scaledImage; auto result = _originalImage.isNull() ? QImage() : _originalImage.scaled(size, Qt::KeepAspectRatio, 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) diff --git a/lib/room.cpp b/lib/room.cpp index 284d19df..f692c354 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -118,7 +118,7 @@ public: // A map from evtId to a map of relation type to a vector of event // pointers. Not using QMultiHash, because we want to quickly return // a number of relations for a given event without enumerating them. - QHash, RelatedEvents> relations; + QHash, RelatedEvents> relations; QString displayname; Avatar avatar; QHash notifications; @@ -2687,7 +2687,7 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) } if (const auto* reaction = eventCast(oldEvent)) { const auto& targetEvtId = reaction->relation().eventId; - const QPair lookupKey { targetEvtId, EventRelation::AnnotationType }; + const std::pair lookupKey { targetEvtId, EventRelation::AnnotationType }; if (relations.contains(lookupKey)) { relations[lookupKey].removeOne(reaction); emit q->updatedEvent(targetEvtId); -- cgit v1.2.3