From f779b235ddac990d17a9a8d8dd222b9e0e7abd49 Mon Sep 17 00:00:00 2001
From: Alexey Rusakov <Kitsune-Ral@users.sf.net>
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(-)

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<EncryptedEvent> 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<QString, QHash<QString, QJsonObject>> 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<SendToDeviceJob>(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<EncryptedEvent> 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<RoomKeyEvent>("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<EncryptedEvent> Connection::Private::makeEventForSessionKey(
                         { "body"_ls, QString(cipherText) } } }
     };
 
-    return makeEvent<EncryptedEvent>(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<ClaimKeysJob>(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<QString, QHash<QString, QJsonObject>> 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<std::tuple<QString, QString, QString>> 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<QString, UnorderedMap<QString, EventPtr>>;
+    using UsersToDevicesToContent = QHash<QString, QHash<QString, QJsonObject>>;
 
     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