From 08e19449ad4e33b9ec3eb66c56501f1c4a977350 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 2 Aug 2020 19:31:22 +0200 Subject: Event::dumpTo: make protected, and RoomEvent override The override adds the event's origin timestamp --- lib/events/event.h | 2 +- lib/events/roomevent.cpp | 6 ++++++ lib/events/roomevent.h | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 6c8961ad..5b9f20b7 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -270,10 +270,10 @@ public: virtual bool isStateEvent() const { return false; } virtual bool isCallEvent() const { return false; } - virtual void dumpTo(QDebug dbg) const; protected: QJsonObject& editJson() { return _json; } + virtual void dumpTo(QDebug dbg) const; private: Type _type; diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index a59cd6e0..0a4332ad 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -115,6 +115,12 @@ void RoomEvent::addId(const QString& newId) Q_ASSERT(id() == newId); } +void RoomEvent::dumpTo(QDebug dbg) const +{ + Event::dumpTo(dbg); + dbg << " (made at " << originTimestamp().toString(Qt::ISODate) << ')'; +} + QJsonObject makeCallContentJson(const QString& callId, int version, QJsonObject content) { diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 621652cb..084cb524 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -87,6 +87,9 @@ public: */ void addId(const QString& newId); +protected: + void dumpTo(QDebug dbg) const override; + private: event_ptr_tt _redactedBecause; }; -- cgit v1.2.3 From 82bcc316d736bd04174876efde38827c34181c33 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 3 Aug 2020 19:10:10 +0200 Subject: Cleanup --- lib/connection.h | 6 ++++-- lib/events/roomcanonicalaliasevent.h | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'lib/events') diff --git a/lib/connection.h b/lib/connection.h index 48ea4f5e..258280a8 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -238,7 +238,8 @@ public: /** Get the list of rooms with the specified tag */ QVector roomsWithTag(const QString& tagName) const; - /** Mark the room as a direct chat with the user + /*! \brief Mark the room as a direct chat with the user + * * This function marks \p room as a direct chat with \p user. * Emits the signal synchronously, without waiting to complete * synchronisation with the server. @@ -247,7 +248,8 @@ public: */ void addToDirectChats(const Room* room, User* user); - /** Unmark the room from direct chats + /*! \brief Unmark the room from direct chats + * * This function removes the room id from direct chats either for * a specific \p user or for all users if \p user in nullptr. * The room id is used to allow removal of, e.g., ids of forgotten diff --git a/lib/events/roomcanonicalaliasevent.h b/lib/events/roomcanonicalaliasevent.h index 5d680de7..fadfece0 100644 --- a/lib/events/roomcanonicalaliasevent.h +++ b/lib/events/roomcanonicalaliasevent.h @@ -59,12 +59,14 @@ public: : StateEvent(typeId(), obj) { } - explicit RoomCanonicalAliasEvent(const QString& canonicalAlias, const QStringList& altAliases = {}) + explicit RoomCanonicalAliasEvent(const QString& canonicalAlias, + const QStringList& altAliases = {}) : StateEvent(typeId(), matrixTypeId(), QString(), canonicalAlias, altAliases) { } - explicit RoomCanonicalAliasEvent(QString&& canonicalAlias, QStringList&& altAliases = {}) + explicit RoomCanonicalAliasEvent(QString&& canonicalAlias, + QStringList&& altAliases = {}) : StateEvent(typeId(), matrixTypeId(), QString(), std::move(canonicalAlias), std::move(altAliases)) { } -- cgit v1.2.3 From 08b9adcf611e0307daa8297205eb9ed3af66c71e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 11 Aug 2020 22:53:58 +0200 Subject: Drop unneeded #include --- lib/events/accountdataevents.h | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index a55016d9..0f240aa1 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -18,7 +18,6 @@ #pragma once -#include "converters.h" #include "event.h" #include "eventcontent.h" -- cgit v1.2.3 From f4db6988bf2fd71f74ac851557d82c6f65cc89b1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 8 Nov 2020 18:57:44 +0100 Subject: More robust member profile data retrieval MemberEventContent: displayname and avatarUrl are now Omittables; CS API doesn't guarantee their presence (see also https://github.com/matrix-org/matrix-doc/issues/1375) but Quotient used to assume they are always there, causing #412. RoomMemberEvent: displayname() -> newDisplayName() and avatarUrl() -> newAvatarUrl(), to emphasise the actual semantics (and also the changed interface). The old signatures still work but are deprecated. Instead of roomMembername() (with weird camel-casing), three new methods in addition to safeMemberName() are introduced to Room: - memberName() - produces the "best known" display name for a given member; User::name() uses it to avoid the pitfall of #412. - disambiguatedMemberName() - this is what roomMembername() used to be; not recommended for direct use when UI is concerned. - safeMemberName() - remains as is, with the fix to the documentation that used to mislead that the function returns HTML-escaped content (it didn't, and doesn't). - htmlSafeMemberName() - does what safeMemberName() claimed to do. Respectively, memberNames() is deprecated in favor of safeMemberNames() and htmlSafeMemberNames(). The corresponding Q_PROPERTY uses safeMemberNames() now. Similar to memberName(), Room has got memberAvatarUrl() to spare User class from diving into Room state to find the member avatar URL. Closes #412. --- lib/events/roommemberevent.cpp | 28 +++++++++----- lib/events/roommemberevent.h | 18 ++++++--- lib/room.cpp | 85 +++++++++++++++++++++++++++++++++--------- lib/room.h | 47 ++++++++++++++++++----- lib/user.cpp | 10 ++--- 5 files changed, 139 insertions(+), 49 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 35cbdb3a..f6b29f7f 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -50,10 +50,13 @@ using namespace Quotient; MemberEventContent::MemberEventContent(const QJsonObject& json) : membership(fromJson(json["membership"_ls])) , isDirect(json["is_direct"_ls].toBool()) - , displayName(sanitized(json["displayname"_ls].toString())) - , avatarUrl(json["avatar_url"_ls].toString()) + , displayName(fromJson>(json["displayname"_ls])) + , avatarUrl(fromJson>(json["avatar_url"_ls])) , reason(json["reason"_ls].toString()) -{} +{ + if (displayName) + displayName = sanitized(*displayName); +} void MemberEventContent::fillJson(QJsonObject* o) const { @@ -62,9 +65,10 @@ void MemberEventContent::fillJson(QJsonObject* o) const "The key 'membership' must be explicit in MemberEventContent"); if (membership != MembershipType::Undefined) o->insert(QStringLiteral("membership"), membershipStrings[membership]); - o->insert(QStringLiteral("displayname"), displayName); - if (avatarUrl.isValid()) - o->insert(QStringLiteral("avatar_url"), avatarUrl.toString()); + if (displayName) + o->insert(QStringLiteral("displayname"), *displayName); + if (avatarUrl && avatarUrl->isValid()) + o->insert(QStringLiteral("avatar_url"), avatarUrl->toString()); if (!reason.isEmpty()) o->insert(QStringLiteral("reason"), reason); } @@ -111,12 +115,16 @@ bool RoomMemberEvent::isUnban() const bool RoomMemberEvent::isRename() const { - auto prevName = prevContent() ? prevContent()->displayName : QString(); - return displayName() != prevName; + auto prevName = prevContent() && prevContent()->displayName + ? *prevContent()->displayName + : QString(); + return newDisplayName() != prevName; } bool RoomMemberEvent::isAvatarUpdate() const { - auto prevAvatarUrl = prevContent() ? prevContent()->avatarUrl : QUrl(); - return avatarUrl() != prevAvatarUrl; + auto prevAvatarUrl = prevContent() && prevContent()->avatarUrl + ? *prevContent()->avatarUrl + : QUrl(); + return newAvatarUrl() != prevAvatarUrl; } diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index 783b8207..35fd69a9 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -24,7 +24,7 @@ namespace Quotient { class MemberEventContent : public EventContent::Base { public: - enum MembershipType : size_t { + enum MembershipType : unsigned char { Invite = 0, Join, Knock, @@ -38,8 +38,8 @@ public: MembershipType membership; bool isDirect = false; - QString displayName; - QUrl avatarUrl; + Omittable displayName; + Omittable avatarUrl; QString reason; protected: @@ -84,8 +84,16 @@ public: MembershipType membership() const { return content().membership; } QString userId() const { return fullJson()[StateKeyKeyL].toString(); } bool isDirect() const { return content().isDirect; } - QString displayName() const { return content().displayName; } - QUrl avatarUrl() const { return content().avatarUrl; } + Omittable newDisplayName() const { return content().displayName; } + Omittable newAvatarUrl() const { return content().avatarUrl; } + [[deprecated("Use newDisplayName() instead")]] QString displayName() const + { + return newDisplayName().value_or(QString()); + } + [[deprecated("Use newAvatarUrl() instead")]] QUrl avatarUrl() const + { + return newAvatarUrl().value_or(QUrl()); + } QString reason() const { return content().reason; } bool changesMembership() const; bool isBan() const; diff --git a/lib/room.cpp b/lib/room.cpp index f8e6e6ba..b564369c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1241,12 +1241,27 @@ QList Room::membersLeft() const { return d->membersLeft; } QList Room::users() const { return d->membersMap.values(); } -QStringList Room::memberNames() const +[[deprecated]] QStringList Room::memberNames() const +{ + return safeMemberNames(); +} + +QStringList Room::safeMemberNames() const +{ + QStringList res; + res.reserve(d->membersMap.size()); + for (auto u: std::as_const(d->membersMap)) + res.append(safeMemberName(u->id())); + + return res; +} + +QStringList Room::htmlSafeMemberNames() const { QStringList res; res.reserve(d->membersMap.size()); - for (auto u : qAsConst(d->membersMap)) - res.append(roomMembername(u)); + for (auto u: std::as_const(d->membersMap)) + res.append(htmlSafeMemberName(u->id())); return res; } @@ -1411,37 +1426,71 @@ Room::Private::moveEventsToTimeline(RoomEventsRange events, return insertedSize; } +QString Room::memberName(const QString& mxId) const +{ + // See https://github.com/matrix-org/matrix-doc/issues/1375 + const auto rme = getCurrentState(mxId); + return rme->newDisplayName() ? *rme->newDisplayName() + : rme->prevContent() ? rme->prevContent()->displayName.value_or(QString()) + : QString(); +} + QString Room::roomMembername(const User* u) const +{ + Q_ASSERT(u != nullptr); + return disambiguatedMemberName(u->id()); +} + +QString Room::roomMembername(const QString& userId) const +{ + return disambiguatedMemberName(userId); +} + +inline QString makeFullUserName(const QString& displayName, const QString& mxId) +{ + return displayName % " (" % mxId % ')'; +} + +QString Room::disambiguatedMemberName(const QString& mxId) const { // See the CS spec, section 11.2.2.3 - const auto username = u->name(this); + const auto username = memberName(mxId); if (username.isEmpty()) - return u->id(); + return mxId; auto namesakesIt = qAsConst(d->membersMap).find(username); // We expect a user to be a member of the room - but technically it is - // possible to invoke roomMemberName() even for non-members. In such case + // possible to invoke this function even for non-members. In such case // we return the full name, just in case. if (namesakesIt == d->membersMap.cend()) - return u->fullName(this); + return makeFullUserName(username, mxId); auto nextUserIt = namesakesIt; if (++nextUserIt == d->membersMap.cend() || nextUserIt.key() != username) return username; // No disambiguation necessary - return u->fullName(this); // Disambiguate fully + return makeFullUserName(username, mxId); // Disambiguate fully } -QString Room::roomMembername(const QString& userId) const +QString Room::safeMemberName(const QString& userId) const { - return roomMembername(user(userId)); + return sanitized(roomMembername(userId)); } -QString Room::safeMemberName(const QString& userId) const +QString Room::htmlSafeMemberName(const QString& userId) const { - return sanitized(roomMembername(userId)); + return safeMemberName(userId).toHtmlEscaped(); +} + +QUrl Room::memberAvatarUrl(const QString &mxId) const +{ + // See https://github.com/matrix-org/matrix-doc/issues/1375 + const auto rme = getCurrentState(mxId); + return rme->newAvatarUrl() ? *rme->newAvatarUrl() + : rme->prevContent() ? rme->prevContent()->avatarUrl.value_or(QUrl()) + : QUrl(); } void Room::updateData(SyncRoomData&& data, bool fromCache) @@ -2422,8 +2471,8 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) case MembershipType::Join: switch (rme.membership()) { case MembershipType::Join: // rename/avatar change or no-op - if (rme.displayName() != oldRme->displayName()) { - emit memberAboutToRename(u, rme.displayName()); + if (rme.newDisplayName()) { + emit memberAboutToRename(u, *rme.newDisplayName()); d->removeMemberFromMap(u->name(this), u); } break; @@ -2517,11 +2566,11 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) d->insertMemberIntoMap(u); emit userAdded(u); } else { - if (oldMemberEvent->displayName() != evt.displayName()) { + if (evt.newDisplayName()) { d->insertMemberIntoMap(u); emit memberRenamed(u); } - if (oldMemberEvent->avatarUrl() != evt.avatarUrl()) + if (evt.newAvatarUrl()) emit memberAvatarChanged(u); } break; @@ -2858,12 +2907,12 @@ MemberSorter Room::memberSorter() const { return MemberSorter(this); } bool MemberSorter::operator()(User* u1, User* u2) const { - return operator()(u1, room->roomMembername(u2)); + return operator()(u1, room->disambiguatedMemberName(u2->id())); } bool MemberSorter::operator()(User* u1, const QString& u2name) const { - auto n1 = room->roomMembername(u1); + auto n1 = room->disambiguatedMemberName(u1->id()); if (n1.startsWith('@')) n1.remove(0, 1); auto n2 = u2name.midRef(u2name.startsWith('@') ? 1 : 0); diff --git a/lib/room.h b/lib/room.h index 4be3ed2b..d7b5c516 100644 --- a/lib/room.h +++ b/lib/room.h @@ -101,7 +101,7 @@ class Room : public QObject { Q_PROPERTY(bool usesEncryption READ usesEncryption NOTIFY encryption) Q_PROPERTY(int timelineSize READ timelineSize NOTIFY addedMessages) - Q_PROPERTY(QStringList memberNames READ memberNames NOTIFY memberListChanged) + Q_PROPERTY(QStringList memberNames READ safeMemberNames NOTIFY memberListChanged) Q_PROPERTY(int memberCount READ memberCount NOTIFY memberListChanged) Q_PROPERTY(int joinedCount READ joinedCount NOTIFY memberListChanged) Q_PROPERTY(int invitedCount READ invitedCount NOTIFY memberListChanged) @@ -209,7 +209,10 @@ public: QList membersLeft() const; Q_INVOKABLE QList users() const; + [[deprecated("Use safeMemberNames() or htmlSafeMemberNames() instead")]] QStringList memberNames() const; + QStringList safeMemberNames() const; + QStringList htmlSafeMemberNames() const; [[deprecated("Use joinedCount(), invitedCount(), totalMemberCount()")]] int memberCount() const; int timelineSize() const; @@ -251,31 +254,55 @@ public: /** * \brief Check the join state of a given user in this room * - * \note Banned and invited users are not tracked for now (Leave + * \note Banned and invited users are not tracked separately for now (Leave * will be returned for them). * * \return Join if the user is a room member; Leave otherwise */ Q_INVOKABLE Quotient::JoinState memberJoinState(Quotient::User* user) const; - /** - * Get a disambiguated name for a given user in - * the context of the room + //! \brief Get a display name (without disambiguation) for the given member + //! + //! \sa safeMemberName, htmlSafeMemberName + Q_INVOKABLE QString memberName(const QString& mxId) const; + + /*! + * \brief Get a disambiguated name for the given user in the room context + * + * \deprecated use safeMemberName() instead */ Q_INVOKABLE QString roomMembername(const Quotient::User* u) const; - /** - * Get a disambiguated name for a user with this id in - * the context of the room + /*! + * \brief Get a disambiguated name for a user with this id in the room context + * + * \deprecated use safeMemberName() instead */ Q_INVOKABLE QString roomMembername(const QString& userId) const; - /** Get a display-safe member name in the context of this room + /*! + * \brief Get a disambiguated name for the member with the given MXID * - * Display-safe means HTML-safe + without RLO/LRO markers + * This function should only be used for non-UI code; consider using + * safeMemberName() or htmlSafeMemberName() for displayed strings. + */ + Q_INVOKABLE QString disambiguatedMemberName(const QString& mxId) const; + + /*! Get a display-safe member name in the context of this room + * + * Display-safe means disambiguated and without RLO/LRO markers * (see https://github.com/quotient-im/Quaternion/issues/545). */ Q_INVOKABLE QString safeMemberName(const QString& userId) const; + /*! Get an HTML-safe member name in the context of this room + * + * This function adds HTML escaping on top of safeMemberName() safeguards. + */ + Q_INVOKABLE QString htmlSafeMemberName(const QString& userId) const; + + //! \brief Get an avatar for the member with the given MXID + QUrl memberAvatarUrl(const QString& mxId) const; + const Timeline& messageEvents() const; const PendingEvents& pendingEvents() const; diff --git a/lib/user.cpp b/lib/user.cpp index ffa4efb9..45a9c121 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -88,8 +88,7 @@ int User::hue() const { return int(hueF() * 359); } QString User::name(const Room* room) const { - return room ? room->getCurrentState(id())->displayName() - : d->defaultName; + return room ? room->memberName(id()) : d->defaultName; } QString User::rawName(const Room* room) const { return name(room); } @@ -169,9 +168,8 @@ bool User::isIgnored() const { return connection()->isIgnored(this); } QString User::displayname(const Room* room) const { - return room ? room->roomMembername(this) - : d->defaultName.isEmpty() ? d->id - : d->defaultName; + return room ? room->safeMemberName(id()) + : d->defaultName.isEmpty() ? d->id : d->defaultName; } QString User::fullName(const Room* room) const @@ -187,7 +185,7 @@ const Avatar& User::avatarObject(const Room* room) const if (!room) return d->defaultAvatar; - const auto& url = room->getCurrentState(id())->avatarUrl(); + const auto& url = room->memberAvatarUrl(id()); const auto& mediaId = url.authority() + url.path(); return d->otherAvatars.try_emplace(mediaId, url).first->second; } -- cgit v1.2.3 From b27ef93df9b5147ab03c8a255918874b9f73c201 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 10 Nov 2020 13:07:28 +0100 Subject: MembershipType: drop warning on empty values This is a usual situation when a membership type is undefined; and the current code constructs _a lot_ of stub events by loading them from empty JSON. So just silence those warnings for now. --- lib/events/roommemberevent.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index f6b29f7f..be47e412 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -39,7 +39,8 @@ struct JsonConverter { if (membershipString == *it) return MembershipType(it - membershipStrings.begin()); - qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString; + if (!membershipString.isEmpty()) + qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString; return MembershipType::Undefined; } }; -- 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 +- lib/events/event.h | 4 ++++ lib/events/roomevent.cpp | 8 ++++---- lib/events/roomkeyevent.h | 2 +- lib/room.cpp | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) (limited to 'lib/events') 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() diff --git a/lib/events/event.h b/lib/events/event.h index 5b9f20b7..b12dc9ad 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -59,12 +59,16 @@ static const auto TypeKey = QStringLiteral("type"); static const auto BodyKey = QStringLiteral("body"); static const auto ContentKey = QStringLiteral("content"); static const auto EventIdKey = QStringLiteral("event_id"); +static const auto SenderKey = QStringLiteral("sender"); +static const auto RoomIdKey = QStringLiteral("room_id"); static const auto UnsignedKey = QStringLiteral("unsigned"); static const auto StateKeyKey = QStringLiteral("state_key"); static const auto TypeKeyL = "type"_ls; static const auto BodyKeyL = "body"_ls; static const auto ContentKeyL = "content"_ls; static const auto EventIdKeyL = "event_id"_ls; +static const auto SenderKeyL = "sender"_ls; +static const auto RoomIdKeyL = "room_id"_ls; static const auto UnsignedKeyL = "unsigned"_ls; static const auto RedactedCauseKeyL = "redacted_because"_ls; static const auto PrevContentKeyL = "prev_content"_ls; diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 0a4332ad..3d87ef18 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -51,12 +51,12 @@ QDateTime RoomEvent::originTimestamp() const QString RoomEvent::roomId() const { - return fullJson()["room_id"_ls].toString(); + return fullJson()[RoomIdKeyL].toString(); } QString RoomEvent::senderId() const { - return fullJson()["sender"_ls].toString(); + return fullJson()[SenderKeyL].toString(); } bool RoomEvent::isReplaced() const @@ -90,12 +90,12 @@ QString RoomEvent::stateKey() const void RoomEvent::setRoomId(const QString& roomId) { - editJson().insert(QStringLiteral("room_id"), roomId); + editJson().insert(RoomIdKey, roomId); } void RoomEvent::setSender(const QString& senderId) { - editJson().insert(QStringLiteral("sender"), senderId); + editJson().insert(SenderKey, senderId); } void RoomEvent::setTransactionId(const QString& txnId) diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 679cbf7c..3a781474 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -11,7 +11,7 @@ public: RoomKeyEvent(const QJsonObject& obj); QString algorithm() const { return content("algorithm"_ls); } - QString roomId() const { return content("room_id"_ls); } + QString roomId() const { return content(RoomIdKeyL); } QString sessionId() const { return content("session_id"_ls); } QString sessionKey() const { return content("session_key"_ls); } }; diff --git a/lib/room.cpp b/lib/room.cpp index e98116a9..57de4f0c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2064,8 +2064,8 @@ RoomEventPtr makeRedacted(const RoomEvent& target, { auto originalJson = target.originalJsonObject(); // clang-format off - static const QStringList keepKeys { EventIdKey, TypeKey, - QStringLiteral("room_id"), QStringLiteral("sender"), StateKeyKey, + static const QStringList keepKeys { + EventIdKey, TypeKey, RoomIdKey, SenderKey, StateKeyKey, QStringLiteral("hashes"), QStringLiteral("signatures"), QStringLiteral("depth"), QStringLiteral("prev_events"), QStringLiteral("prev_state"), QStringLiteral("auth_events"), -- cgit v1.2.3 From 8f4c7f67930be402836ca7a6266ba4277266a12d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 14 Nov 2020 20:01:36 +0100 Subject: Drop EventFactory RoomPowerLevelsEvent is not used in csapi/ classes so the factory is of no use either. --- lib/events/roompowerlevelsevent.h | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h index f0f7207f..b832230e 100644 --- a/lib/events/roompowerlevelsevent.h +++ b/lib/events/roompowerlevelsevent.h @@ -62,15 +62,5 @@ public: private: }; -template <> -class EventFactory { -public: - static event_ptr_tt make(const QJsonObject& json, - const QString&) - { - return makeEvent(json); - } -}; - REGISTER_EVENT_TYPE(RoomPowerLevelsEvent) } // namespace Quotient -- cgit v1.2.3 From c919c021be42228ff615e581a2f80e649c992807 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 14 Nov 2020 22:34:41 +0100 Subject: Cleanup --- lib/events/roomcreateevent.cpp | 4 ++-- lib/events/stateevent.h | 5 ++--- lib/room.cpp | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index c72b5bc2..0fc7d6b9 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -33,8 +33,8 @@ QString RoomCreateEvent::version() const RoomCreateEvent::Predecessor RoomCreateEvent::predecessor() const { const auto predJson = contentJson()["predecessor"_ls].toObject(); - return { fromJson(predJson["room_id"_ls]), - fromJson(predJson["event_id"_ls]) }; + return { fromJson(predJson[RoomIdKeyL]), + fromJson(predJson[EventIdKeyL]) }; } bool RoomCreateEvent::isUpgrade() const diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 710b4271..400858db 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -23,12 +23,11 @@ namespace Quotient { /// Make a minimal correct Matrix state event JSON -template -inline QJsonObject basicStateEventJson(StrT matrixType, +inline QJsonObject basicStateEventJson(const QString& matrixTypeId, const QJsonObject& content, const QString& stateKey = {}) { - return { { TypeKey, std::forward(matrixType) }, + return { { TypeKey, matrixTypeId }, { StateKeyKey, stateKey }, { ContentKey, content } }; } diff --git a/lib/room.cpp b/lib/room.cpp index 09e3dea2..3b5a80a4 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1284,7 +1284,7 @@ const StateEventBase* Room::getCurrentState(const QString& evtType, RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) { #ifndef Quotient_E2EE_ENABLED - Q_UNUSED(encryptedEvent); + Q_UNUSED(encryptedEvent) qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return {}; #else // Quotient_E2EE_ENABLED @@ -1309,8 +1309,8 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, const QString& senderKey) { #ifndef Quotient_E2EE_ENABLED - Q_UNUSED(roomKeyEvent); - Q_UNUSED(senderKey); + Q_UNUSED(roomKeyEvent) + Q_UNUSED(senderKey) qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return; #else // Quotient_E2EE_ENABLED -- cgit v1.2.3 From 5b1bfc102fccd4e57893b34bf2b0a14ba6a9f577 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 14 Nov 2020 22:35:31 +0100 Subject: Make StateEventBase Q_GADGET too To align with the two other base event classes (Event and RoomEvent). --- lib/events/stateevent.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/events') diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 400858db..20a85f83 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -33,6 +33,8 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, } class StateEventBase : public RoomEvent { + Q_GADGET + Q_PROPERTY(QString stateKey READ stateKey CONSTANT) public: using factory_t = EventFactory; @@ -128,3 +130,5 @@ private: std::unique_ptr> _prev; }; } // namespace Quotient +Q_DECLARE_METATYPE(Quotient::StateEventBase*) +Q_DECLARE_METATYPE(const Quotient::StateEventBase*) -- cgit v1.2.3 From 52cab4b11bdd48cd87e04c01b12c698ec4145e6d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 15 Nov 2020 21:51:51 +0100 Subject: Cleanup across event classes In particular: removed unnecessary #includes, deprecated and no more used constructs, replaced stored members with dynamic generation from JSON (TypingEvent and, especially promising for performance, ReceiptEvent) --- lib/events/callanswerevent.cpp | 5 ----- lib/events/callhangupevent.cpp | 5 ----- lib/events/callinviteevent.cpp | 5 ----- lib/events/encryptedevent.cpp | 3 --- lib/events/encryptionevent.h | 4 +--- lib/events/event.h | 12 ------------ lib/events/reactionevent.h | 3 --- lib/events/receiptevent.cpp | 12 +++++++----- lib/events/receiptevent.h | 10 ++-------- lib/events/roomcanonicalaliasevent.h | 4 ++-- lib/events/roomevent.cpp | 5 ++--- lib/events/roomkeyevent.h | 2 +- lib/events/roommemberevent.h | 10 ++-------- lib/events/typingevent.cpp | 8 ++------ lib/events/typingevent.h | 7 ++----- lib/room.cpp | 11 ++++++----- 16 files changed, 27 insertions(+), 79 deletions(-) (limited to 'lib/events') diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp index d6622b30..bf096534 100644 --- a/lib/events/callanswerevent.cpp +++ b/lib/events/callanswerevent.cpp @@ -18,11 +18,6 @@ #include "callanswerevent.h" -#include "event.h" -#include "logging.h" - -#include - /* m.call.answer { diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp index d41849c3..f2117f38 100644 --- a/lib/events/callhangupevent.cpp +++ b/lib/events/callhangupevent.cpp @@ -18,11 +18,6 @@ #include "callhangupevent.h" -#include "event.h" -#include "logging.h" - -#include - /* m.call.hangup { diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index 54faac8d..63f331de 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -18,11 +18,6 @@ #include "callinviteevent.h" -#include "event.h" -#include "logging.h" - -#include - /* m.call.invite { diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index dccfa540..117aae37 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -1,9 +1,6 @@ #include "encryptedevent.h" -#include "room.h" - using namespace Quotient; -using namespace QtOlm; EncryptedEvent::EncryptedEvent(const QJsonObject& ciphertext, const QString& senderKey) diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index cbd3ba4a..cbb6d786 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -48,6 +48,7 @@ public: DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent) using EncryptionType = EncryptionEventContent::EncryptionType; + Q_ENUM(EncryptionType) explicit EncryptionEvent(const QJsonObject& obj = {}) // TODO: apropriate // default value @@ -64,9 +65,6 @@ public: QString algorithm() const { return content().algorithm; } int rotationPeriodMs() const { return content().rotationPeriodMs; } int rotationPeriodMsgs() const { return content().rotationPeriodMsgs; } - -private: - Q_ENUM(EncryptionType) }; REGISTER_EVENT_TYPE(EncryptionEvent) diff --git a/lib/events/event.h b/lib/events/event.h index b12dc9ad..626a0229 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -21,10 +21,6 @@ #include "converters.h" #include "logging.h" -#ifdef ENABLE_EVENTTYPE_ALIAS -# define USE_EVENTTYPE_ALIAS 1 -#endif - namespace Quotient { // === event_ptr_tt<> and type casting facilities === @@ -45,14 +41,6 @@ inline TargetEventT* weakPtrCast(const event_ptr_tt& ptr) return static_cast(rawPtr(ptr)); } -/// Re-wrap a smart pointer to base into a smart pointer to derived -template -[[deprecated("Consider using eventCast() or visit() instead")]] -inline event_ptr_tt ptrCast(event_ptr_tt&& ptr) -{ - return std::unique_ptr(static_cast(ptr.release())); -} - // === Standard Matrix key names and basicEventJson() === static const auto TypeKey = QStringLiteral("type"); diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index 75c6528c..48b0bc6c 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -64,9 +64,6 @@ public: { return content(QStringLiteral("m.relates_to")); } - -private: - EventRelation _relation; }; REGISTER_EVENT_TYPE(ReactionEvent) diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp index bf050cb2..deb3c4e8 100644 --- a/lib/events/receiptevent.cpp +++ b/lib/events/receiptevent.cpp @@ -40,10 +40,11 @@ Example of a Receipt Event: using namespace Quotient; -ReceiptEvent::ReceiptEvent(const QJsonObject& obj) : Event(typeId(), obj) +EventsWithReceipts ReceiptEvent::eventsWithReceipts() const { + EventsWithReceipts result; const auto& contents = contentJson(); - _eventsWithReceipts.reserve(contents.size()); + result.reserve(contents.size()); for (auto eventIt = contents.begin(); eventIt != contents.end(); ++eventIt) { if (eventIt.key().isEmpty()) { qCWarning(EPHEMERAL) @@ -51,15 +52,16 @@ ReceiptEvent::ReceiptEvent(const QJsonObject& obj) : Event(typeId(), obj) qCDebug(EPHEMERAL) << "ReceiptEvent content follows:\n" << contents; continue; } - const QJsonObject reads = + const auto reads = eventIt.value().toObject().value("m.read"_ls).toObject(); QVector receipts; receipts.reserve(reads.size()); for (auto userIt = reads.begin(); userIt != reads.end(); ++userIt) { - const QJsonObject user = userIt.value().toObject(); + const auto user = userIt.value().toObject(); receipts.push_back( { userIt.key(), fromJson(user["ts"_ls]) }); } - _eventsWithReceipts.push_back({ eventIt.key(), std::move(receipts) }); + result.push_back({ eventIt.key(), std::move(receipts) }); } + return result; } diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index dd54a476..b7adea44 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -37,15 +37,9 @@ using EventsWithReceipts = QVector; class ReceiptEvent : public Event { public: DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent) - explicit ReceiptEvent(const QJsonObject& obj); + explicit ReceiptEvent(const QJsonObject& obj) : Event(typeId(), obj) {} - const EventsWithReceipts& eventsWithReceipts() const - { - return _eventsWithReceipts; - } - -private: - EventsWithReceipts _eventsWithReceipts; + EventsWithReceipts eventsWithReceipts() const; }; REGISTER_EVENT_TYPE(ReceiptEvent) } // namespace Quotient diff --git a/lib/events/roomcanonicalaliasevent.h b/lib/events/roomcanonicalaliasevent.h index fadfece0..4a21b7cc 100644 --- a/lib/events/roomcanonicalaliasevent.h +++ b/lib/events/roomcanonicalaliasevent.h @@ -61,13 +61,13 @@ public: explicit RoomCanonicalAliasEvent(const QString& canonicalAlias, const QStringList& altAliases = {}) - : StateEvent(typeId(), matrixTypeId(), QString(), + : StateEvent(typeId(), matrixTypeId(), {}, canonicalAlias, altAliases) { } explicit RoomCanonicalAliasEvent(QString&& canonicalAlias, QStringList&& altAliases = {}) - : StateEvent(typeId(), matrixTypeId(), QString(), + : StateEvent(typeId(), matrixTypeId(), {}, std::move(canonicalAlias), std::move(altAliases)) { } diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 3d87ef18..a2dbc07d 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -34,9 +34,8 @@ RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, RoomEvent::RoomEvent(Type type, const QJsonObject& json) : Event(type, json) { - const auto unsignedData = json[UnsignedKeyL].toObject(); - const auto redaction = unsignedData[RedactedCauseKeyL]; - if (redaction.isObject()) + if (const auto redaction = unsignedJson()[RedactedCauseKeyL]; + redaction.isObject()) _redactedBecause = makeEvent(redaction.toObject()); } diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 3a781474..b8cd2eae 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -8,7 +8,7 @@ class RoomKeyEvent : public Event public: DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent) - RoomKeyEvent(const QJsonObject& obj); + explicit RoomKeyEvent(const QJsonObject& obj); QString algorithm() const { return content("algorithm"_ls); } QString roomId() const { return content(RoomIdKeyL); } diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index 35fd69a9..cebaaf10 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -54,13 +54,10 @@ public: DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent) using MembershipType = MemberEventContent::MembershipType; + Q_ENUM(MembershipType) explicit RoomMemberEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} - [[deprecated("Use RoomMemberEvent(userId, contentArgs) instead")]] - RoomMemberEvent(MemberEventContent&& c) - : StateEvent(typeId(), matrixTypeId(), QString(), c) - {} template RoomMemberEvent(const QString& userId, ArgTs&&... contentArgs) : StateEvent(typeId(), matrixTypeId(), userId, @@ -82,7 +79,7 @@ public: {} MembershipType membership() const { return content().membership; } - QString userId() const { return fullJson()[StateKeyKeyL].toString(); } + QString userId() const { return stateKey(); } bool isDirect() const { return content().isDirect; } Omittable newDisplayName() const { return content().displayName; } Omittable newAvatarUrl() const { return content().avatarUrl; } @@ -104,9 +101,6 @@ public: bool isLeave() const; bool isRename() const; bool isAvatarUpdate() const; - -private: - Q_ENUM(MembershipType) }; template <> diff --git a/lib/events/typingevent.cpp b/lib/events/typingevent.cpp index 0c5fc6ba..e102fc79 100644 --- a/lib/events/typingevent.cpp +++ b/lib/events/typingevent.cpp @@ -18,13 +18,9 @@ #include "typingevent.h" -#include - using namespace Quotient; -TypingEvent::TypingEvent(const QJsonObject& obj) : Event(typeId(), obj) +QStringList TypingEvent::users() const { - const auto& array = contentJson()["user_ids"_ls].toArray(); - for (const auto& user : array) - _users.push_back(user.toString()); + return fromJson(contentJson()["user_ids"_ls]); } diff --git a/lib/events/typingevent.h b/lib/events/typingevent.h index 1cf4e69d..97e1f9cc 100644 --- a/lib/events/typingevent.h +++ b/lib/events/typingevent.h @@ -25,12 +25,9 @@ class TypingEvent : public Event { public: DEFINE_EVENT_TYPEID("m.typing", TypingEvent) - TypingEvent(const QJsonObject& obj); + explicit TypingEvent(const QJsonObject& obj) : Event(typeId(), obj) {} - const QStringList& users() const { return _users; } - -private: - QStringList _users; + QStringList users() const; }; REGISTER_EVENT_TYPE(TypingEvent) } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index 3b5a80a4..7ac3463e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2073,7 +2073,7 @@ RoomEventPtr makeRedacted(const RoomEvent& target, QStringLiteral("membership") }; // clang-format on - std::vector> keepContentKeysMap { + std::vector> keepContentKeysMap { { RoomMemberEvent::typeId(), { QStringLiteral("membership") } }, { RoomCreateEvent::typeId(), { QStringLiteral("creator") } }, { RoomPowerLevelsEvent::typeId(), @@ -2621,7 +2621,7 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) et.start(); if (auto* evt = eventCast(event)) { d->usersTyping.clear(); - for (const QString& userId : qAsConst(evt->users())) { + for (const auto& userId : evt->users()) { auto u = user(userId); if (memberJoinState(u) == JoinState::Join) d->usersTyping.append(u); @@ -2633,7 +2633,8 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) } if (auto* evt = eventCast(event)) { int totalReceipts = 0; - for (const auto& p : qAsConst(evt->eventsWithReceipts())) { + const auto& eventsWithReceipts = evt->eventsWithReceipts(); + for (const auto& p : eventsWithReceipts) { totalReceipts += p.receipts.size(); { if (p.receipts.size() == 1) @@ -2669,11 +2670,11 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) } } } - if (evt->eventsWithReceipts().size() > 3 || totalReceipts > 10 + if (eventsWithReceipts.size() > 3 || totalReceipts > 10 || et.nsecsElapsed() >= profilerMinNsecs()) qCDebug(PROFILER) << "*** Room::processEphemeralEvent(receipts):" - << evt->eventsWithReceipts().size() << "event(s) with" + << eventsWithReceipts.size() << "event(s) with" << totalReceipts << "receipt(s)," << et; } return changes; -- cgit v1.2.3 From dda813899fdb4a520dc83e10c17c1923712a8f7d Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 19 Nov 2020 14:56:09 +0100 Subject: Fix Q_ASSERT failure on sending messages Changes in e81117fb exposed a flaw in EncryptionEvent causing assertion failure when this event is default-initialised (i.e. no encryption). --- lib/events/encryptionevent.cpp | 19 +++++-------------- lib/room.cpp | 8 +++++++- 2 files changed, 12 insertions(+), 15 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 073303b0..f1bde621 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -1,21 +1,14 @@ -// -// Created by rusakov on 26/09/2017. -// Contributed by andreev on 27/06/2019. -// - #include "encryptionevent.h" -#include "converters.h" #include "e2ee.h" -#include "logging.h" #include +namespace Quotient { static const std::array encryptionStrings = { - { Quotient::MegolmV1AesSha2AlgoKey } + { MegolmV1AesSha2AlgoKey } }; -namespace Quotient { template <> struct JsonConverter { static EncryptionType load(const QJsonValue& jv) @@ -26,7 +19,8 @@ struct JsonConverter { if (encryptionString == *it) return EncryptionType(it - encryptionStrings.begin()); - qCWarning(EVENTS) << "Unknown EncryptionType: " << encryptionString; + if (!encryptionString.isEmpty()) + qCWarning(EVENTS) << "Unknown EncryptionType: " << encryptionString; return EncryptionType::Undefined; } }; @@ -35,7 +29,7 @@ struct JsonConverter { using namespace Quotient; EncryptionEventContent::EncryptionEventContent(const QJsonObject& json) - : encryption(fromJson(json["algorithm"_ls])) + : encryption(fromJson(json[AlgorithmKeyL])) , algorithm(sanitized(json[AlgorithmKeyL].toString())) , rotationPeriodMs(json[RotationPeriodMsKeyL].toInt(604800000)) , rotationPeriodMsgs(json[RotationPeriodMsgsKeyL].toInt(100)) @@ -44,9 +38,6 @@ EncryptionEventContent::EncryptionEventContent(const QJsonObject& json) void EncryptionEventContent::fillJson(QJsonObject* o) const { Q_ASSERT(o); - Q_ASSERT_X( - encryption != EncryptionType::Undefined, __FUNCTION__, - "The key 'algorithm' must be explicit in EncryptionEventContent"); if (encryption != EncryptionType::Undefined) o->insert(AlgorithmKey, algorithm); o->insert(RotationPeriodMsKey, rotationPeriodMs); diff --git a/lib/room.cpp b/lib/room.cpp index 7ac3463e..1af294a7 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2580,8 +2580,14 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) // clang-format off } , [this, oldEncEvt = static_cast(oldStateEvent)]( - const EncryptionEvent&) { + const EncryptionEvent& ee) { // clang-format on + if (ee.algorithm().isEmpty()) { + qWarning(STATE) + << "The encryption event for room" << objectName() + << "doesn't have 'algorithm' specified - ignoring"; + return NoChange; + } if (oldEncEvt && oldEncEvt->encryption() != EncryptionEventContent::Undefined) { qCWarning(STATE) << "The room is already encrypted but a new" -- cgit v1.2.3 From 17d6e00597fdc1c8f25808735cbc728c4a6b3506 Mon Sep 17 00:00:00 2001 From: David Faure Date: Thu, 26 Nov 2020 22:32:55 +0100 Subject: Enable QT_NO_URL_CAST_FROM_STRING and QT_STRICT_ITERATORS. * QT_NO_URL_CAST_FROM_STRING makes it clearer where QUrls are created from QStrings (which incurs a parsing cost). * QT_STRICT_ITERATORS helps detecting where begin()/end() is used instead of cbegin()/cend(). KDE developers have verified that the generated assembly code is identical. --- CMakeLists.txt | 2 +- lib/connection.cpp | 4 ++-- lib/events/eventcontent.cpp | 2 +- lib/events/eventcontent.h | 2 +- lib/events/roommemberevent.cpp | 2 +- lib/jobs/basejob.cpp | 2 +- lib/room.cpp | 2 +- lib/ssosession.cpp | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d5d286d..4bb2bd31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,7 +257,7 @@ file(GLOB_RECURSE api_SRCS ${add_CONFIGURE_DEPENDS} ${FULL_CSAPI_DIR}/*.cpp) set(tests_SRCS tests/quotest.cpp) add_library(${PROJECT_NAME} ${lib_SRCS} ${api_SRCS}) -target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_JAVA_STYLE_ITERATORS) +target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_JAVA_STYLE_ITERATORS QT_NO_URL_CAST_FROM_STRING QT_NO_CAST_TO_ASCII) if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.16.0" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL GNU) # https://bugzilla.redhat.com/show_bug.cgi?id=1721553 target_precompile_headers(${PROJECT_NAME} PRIVATE lib/converters.h) diff --git a/lib/connection.cpp b/lib/connection.cpp index e84b8080..2a86d2eb 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -931,8 +931,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.constEnd() && it.key() == u; ++it) { const auto& roomId = *it; if (auto r = room(roomId, JoinState::Join)) { Q_ASSERT(r->id() == roomId); diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 802d8176..0cb9e292 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -89,7 +89,7 @@ void ImageInfo::fillInfoJson(QJsonObject* infoJson) const } Thumbnail::Thumbnail(const QJsonObject& infoJson) - : ImageInfo(infoJson["thumbnail_url"_ls].toString(), + : ImageInfo(QUrl(infoJson["thumbnail_url"_ls].toString()), infoJson["thumbnail_info"_ls].toObject()) {} diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 0d4c047e..9c167d4b 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -189,7 +189,7 @@ namespace EventContent { using InfoT::InfoT; explicit UrlBasedContent(const QJsonObject& json) : TypedBase(json) - , InfoT(json["url"].toString(), json["info"].toObject(), + , InfoT(QUrl(json["url"].toString()), json["info"].toObject(), json["filename"].toString()) { // A small hack to facilitate links creation in QML. diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index be47e412..913bde74 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -52,7 +52,7 @@ MemberEventContent::MemberEventContent(const QJsonObject& json) : membership(fromJson(json["membership"_ls])) , isDirect(json["is_direct"_ls].toBool()) , displayName(fromJson>(json["displayname"_ls])) - , avatarUrl(fromJson>(json["avatar_url"_ls])) + , avatarUrl(fromJson>(json["avatar_url"_ls])) , reason(json["reason"_ls].toString()) { if (displayName) diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 1f0e84ba..422931ee 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -534,7 +534,7 @@ BaseJob::Status BaseJob::prepareError() } if (errCode == "M_CONSENT_NOT_GIVEN") { - d->errorUrl = errorJson.value("consent_uri"_ls).toString(); + d->errorUrl = QUrl(errorJson.value("consent_uri"_ls).toString()); return { UserConsentRequiredError }; } if (errCode == "M_UNSUPPORTED_ROOM_VERSION" diff --git a/lib/room.cpp b/lib/room.cpp index 1af294a7..a309cd24 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1947,7 +1947,7 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, }); connect(job, &BaseJob::success, this, [this, id, localFilename, job] { d->fileTransfers[id].status = FileTransferInfo::Completed; - emit fileTransferCompleted(id, localFilename, job->contentUri()); + emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri())); }); connect(job, &BaseJob::failure, this, std::bind(&Private::failedTransfer, d, id, job->errorString())); diff --git a/lib/ssosession.cpp b/lib/ssosession.cpp index be701204..3c6ec48b 100644 --- a/lib/ssosession.cpp +++ b/lib/ssosession.cpp @@ -70,7 +70,7 @@ SsoSession::~SsoSession() QUrl SsoSession::ssoUrl() const { return d->ssoUrl; } -QUrl SsoSession::callbackUrl() const { return d->callbackUrl; } +QUrl SsoSession::callbackUrl() const { return QUrl(d->callbackUrl); } void SsoSession::Private::processCallback() { -- 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 --- LICENSES/LGPL-2.1-or-later.txt | 462 +++++++++++++++++++++++++++++++++++ lib/avatar.cpp | 16 +- lib/avatar.h | 16 +- lib/connection.cpp | 16 +- lib/connection.h | 16 +- lib/connectiondata.cpp | 16 +- lib/connectiondata.h | 16 +- lib/converters.cpp | 16 +- lib/converters.h | 16 +- lib/eventitem.cpp | 16 +- lib/eventitem.h | 16 +- lib/events/accountdataevents.h | 16 +- lib/events/callanswerevent.cpp | 16 +- lib/events/callanswerevent.h | 16 +- lib/events/callcandidatesevent.cpp | 16 +- lib/events/callcandidatesevent.h | 16 +- lib/events/callhangupevent.cpp | 16 +- lib/events/callhangupevent.h | 16 +- lib/events/callinviteevent.cpp | 16 +- lib/events/callinviteevent.h | 16 +- lib/events/directchatevent.cpp | 16 +- lib/events/directchatevent.h | 16 +- lib/events/encryptionevent.h | 16 +- lib/events/event.cpp | 16 +- lib/events/event.h | 16 +- lib/events/eventcontent.cpp | 16 +- lib/events/eventcontent.h | 16 +- lib/events/eventloader.h | 16 +- lib/events/reactionevent.cpp | 16 +- lib/events/reactionevent.h | 16 +- lib/events/receiptevent.cpp | 16 +- lib/events/receiptevent.h | 16 +- lib/events/redactionevent.h | 16 +- lib/events/roomavatarevent.h | 16 +- lib/events/roomcanonicalaliasevent.h | 16 +- lib/events/roomcreateevent.cpp | 16 +- lib/events/roomcreateevent.h | 16 +- lib/events/roomevent.cpp | 16 +- lib/events/roomevent.h | 16 +- lib/events/roommemberevent.cpp | 16 +- lib/events/roommemberevent.h | 16 +- lib/events/roommessageevent.cpp | 16 +- lib/events/roommessageevent.h | 16 +- lib/events/roomtombstoneevent.cpp | 16 +- lib/events/roomtombstoneevent.h | 16 +- lib/events/simplestateevents.h | 16 +- lib/events/stateevent.cpp | 16 +- lib/events/stateevent.h | 16 +- lib/events/typingevent.cpp | 16 +- lib/events/typingevent.h | 16 +- lib/jobs/basejob.cpp | 16 +- lib/jobs/basejob.h | 16 +- lib/jobs/mediathumbnailjob.cpp | 16 +- lib/jobs/mediathumbnailjob.h | 16 +- lib/jobs/postreadmarkersjob.h | 16 +- lib/jobs/requestdata.h | 16 +- lib/jobs/syncjob.cpp | 16 +- lib/jobs/syncjob.h | 16 +- lib/joinstate.h | 16 +- lib/logging.cpp | 16 +- lib/logging.h | 16 +- lib/networkaccessmanager.cpp | 16 +- lib/networkaccessmanager.h | 16 +- lib/networksettings.cpp | 16 +- lib/networksettings.h | 16 +- lib/qt_connection_util.h | 16 +- lib/room.cpp | 16 +- lib/room.h | 16 +- lib/settings.h | 16 +- lib/syncdata.cpp | 16 +- lib/syncdata.h | 16 +- lib/user.cpp | 16 +- lib/user.h | 16 +- lib/util.cpp | 16 +- lib/util.h | 16 +- 75 files changed, 610 insertions(+), 1036 deletions(-) create mode 100644 LICENSES/LGPL-2.1-or-later.txt (limited to 'lib/events') diff --git a/LICENSES/LGPL-2.1-or-later.txt b/LICENSES/LGPL-2.1-or-later.txt new file mode 100644 index 00000000..aaaba168 --- /dev/null +++ b/LICENSES/LGPL-2.1-or-later.txt @@ -0,0 +1,462 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as +the successor of the GNU Library Public License, version 2, hence the version +number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software Foundation +and other authors who decide to use it. You can use it too, but we suggest +you first think carefully about whether this license or the ordinary General +Public License is the better strategy to use in any particular case, based +on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. +Our General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you +wish); that you receive source code or can get it if you want it; that you +can change the software and use pieces of it in new free programs; and that +you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors +to deny you these rights or to ask you to surrender these rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +other code with the library, you must provide complete object files to the +recipients, so that they can relink them with the library after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, +and (2) we offer you this license, which gives you legal permission to copy, +distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone +else and passed on, the recipients should know that what they have is not +the original version, so that the original author's reputation will not be +affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free +program. We wish to make sure that a company cannot effectively restrict the +users of a free program by obtaining a restrictive license from a patent holder. +Therefore, we insist that any patent license obtained for a version of the +library must be consistent with the full freedom of use specified in this +license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public License, +applies to certain designated libraries, and is quite different from the ordinary +General Public License. We use this license for certain libraries in order +to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared +library, the combination of the two is legally speaking a combined work, a +derivative of the original library. The ordinary General Public License therefore +permits such linking only if the entire combination fits its criteria of freedom. +The Lesser General Public License permits more lax criteria for linking other +code with the library. + +We call this license the "Lesser" General Public License because it does Less +to protect the user's freedom than the ordinary General Public License. It +also provides other free software developers Less of an advantage over competing +non-free programs. These disadvantages are the reason we use the ordinary +General Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the +widest possible use of a certain library, so that it becomes a de-facto standard. +To achieve this, non-free programs must be allowed to use the library. A more +frequent case is that a free library does the same job as widely used non-free +libraries. In this case, there is little to gain by limiting the free library +to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free software. For +example, permission to use the GNU C Library in non-free programs enables +many more people to use the whole GNU operating system, as well as its variant, +the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a modified +version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, whereas the latter must be combined with the library in +order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program +which contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Lesser General +Public License (also called "this License"). Each licensee is addressed as +"you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (1) uses at run time a copy of the library +already present on the user's computer system, rather than copying library +functions into the executable, and (2) will operate properly with a modified +version of the library, if the user installs one, as long as the modified +version is interface-compatible with the version that the work was made with. + +c) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +d) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +e) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the materials to be distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed +on you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of +this License. If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Lesser General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to address +new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE +OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES +OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + + one line to give the library's name and an idea of what it does. + Copyright (C) year name of author + +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 Also add information +on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in +the library `Frob' (a library for tweaking knobs) written +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 +Ty Coon, President of Vice +That's all there is to it! diff --git a/lib/avatar.cpp b/lib/avatar.cpp index c65aa25c..4548be02 100644 --- a/lib/avatar.cpp +++ b/lib/avatar.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 "avatar.h" diff --git a/lib/avatar.h b/lib/avatar.h index 7a566bfa..111f565d 100644 --- a/lib/avatar.h +++ b/lib/avatar.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 */ #pragma once 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" diff --git a/lib/connection.h b/lib/connection.h index 07ae9f29..a32d0801 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -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 */ #pragma once diff --git a/lib/connectiondata.cpp b/lib/connectiondata.cpp index d57363d0..25ab775a 100644 --- a/lib/connectiondata.cpp +++ b/lib/connectiondata.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 "connectiondata.h" diff --git a/lib/connectiondata.h b/lib/connectiondata.h index 000099d1..a3b2d39b 100644 --- a/lib/connectiondata.h +++ b/lib/connectiondata.h @@ -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 */ #pragma once diff --git a/lib/converters.cpp b/lib/converters.cpp index 9f570087..0df880a0 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 "converters.h" diff --git a/lib/converters.h b/lib/converters.h index 81d7b6d8..d4f19b60 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/eventitem.cpp b/lib/eventitem.cpp index 2e2b11c0..9c47e50d 100644 --- a/lib/eventitem.cpp +++ b/lib/eventitem.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 "eventitem.h" diff --git a/lib/eventitem.h b/lib/eventitem.h index 7b2c3c44..ae3d5762 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 0f240aa1..d0abf577 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp index bf096534..f3d0a9a0 100644 --- a/lib/events/callanswerevent.cpp +++ b/lib/events/callanswerevent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard + * SPDX-FileCopyrightText: 2017 Marius Gripsgard * - * 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 "callanswerevent.h" diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index 2709882b..d7214468 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard + * SPDX-FileCopyrightText: 2017 Marius Gripsgard * - * 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 */ #pragma once diff --git a/lib/events/callcandidatesevent.cpp b/lib/events/callcandidatesevent.cpp index 24f0dd46..9b765064 100644 --- a/lib/events/callcandidatesevent.cpp +++ b/lib/events/callcandidatesevent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard + * SPDX-FileCopyrightText: 2017 Marius Gripsgard * - * 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 "callcandidatesevent.h" diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h index e224f048..ae3bb150 100644 --- a/lib/events/callcandidatesevent.h +++ b/lib/events/callcandidatesevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard + * SPDX-FileCopyrightText: 2017 Marius Gripsgard * - * 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 */ #pragma once diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp index f2117f38..45b84cd4 100644 --- a/lib/events/callhangupevent.cpp +++ b/lib/events/callhangupevent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard + * SPDX-FileCopyrightText: 2017 Marius Gripsgard * - * 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 "callhangupevent.h" diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index 5d73fb62..432f72f5 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard + * SPDX-FileCopyrightText: 2017 Marius Gripsgard * - * 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 */ #pragma once diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index 63f331de..86478ada 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard + * SPDX-FileCopyrightText: 2017 Marius Gripsgard * - * 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 "callinviteevent.h" diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index b067a492..304c89ac 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Marius Gripsgard + * SPDX-FileCopyrightText: 2017 Marius Gripsgard * - * 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 */ #pragma once diff --git a/lib/events/directchatevent.cpp b/lib/events/directchatevent.cpp index b4027e16..39d11072 100644 --- a/lib/events/directchatevent.cpp +++ b/lib/events/directchatevent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 "directchatevent.h" diff --git a/lib/events/directchatevent.h b/lib/events/directchatevent.h index bb091c5c..373e36dc 100644 --- a/lib/events/directchatevent.h +++ b/lib/events/directchatevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index cbb6d786..3431ddd8 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 96e33864..6014183e 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.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 "event.h" diff --git a/lib/events/event.h b/lib/events/event.h index 626a0229..e9d42333 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -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 */ #pragma once diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 802d8176..d7b109f7 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 "eventcontent.h" diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 0d4c047e..e0e4a5db 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index ebb96441..0d95daf5 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/reactionevent.cpp b/lib/events/reactionevent.cpp index 003c8ead..9b43e372 100644 --- a/lib/events/reactionevent.cpp +++ b/lib/events/reactionevent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2019 Kitsune Ral + * SPDX-FileCopyrightText: 2019 Kitsune Ral * - * 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 "reactionevent.h" diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index 48b0bc6c..09166b24 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2019 Kitsune Ral + * SPDX-FileCopyrightText: 2019 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp index deb3c4e8..b6f0fcdd 100644 --- a/lib/events/receiptevent.cpp +++ b/lib/events/receiptevent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2016 Felix Rohrbach + * SPDX-FileCopyrightText: 2016 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 */ /* diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index b7adea44..ec297a6c 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2016 Felix Rohrbach + * SPDX-FileCopyrightText: 2016 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 */ #pragma once diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index 3b3af18e..320db6f2 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index c2100eaa..649412e8 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/roomcanonicalaliasevent.h b/lib/events/roomcanonicalaliasevent.h index 4a21b7cc..eda94d2d 100644 --- a/lib/events/roomcanonicalaliasevent.h +++ b/lib/events/roomcanonicalaliasevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2020 QMatrixClient project + * SPDX-FileCopyrightText: 2020 QMatrixClient project * - * 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 */ #pragma once diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index 0fc7d6b9..3d9ec4a3 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2019 QMatrixClient project + * SPDX-FileCopyrightText: 2019 QMatrixClient project * - * 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 "roomcreateevent.h" diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index 91aefe9e..8328d38a 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2019 QMatrixClient project + * SPDX-FileCopyrightText: 2019 QMatrixClient project * - * 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 */ #pragma once diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index a2dbc07d..2b6ac2be 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 "roomevent.h" diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 084cb524..3fafecfd 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index be47e412..6f5d5a52 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.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 "roommemberevent.h" diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index cebaaf10..b7a7c9df 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -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 */ #pragma once diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 616a034f..19d460b8 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.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 "roommessageevent.h" diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 2501d097..ebc9d564 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -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 */ #pragma once diff --git a/lib/events/roomtombstoneevent.cpp b/lib/events/roomtombstoneevent.cpp index f93eb60d..163e1d3a 100644 --- a/lib/events/roomtombstoneevent.cpp +++ b/lib/events/roomtombstoneevent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2019 QMatrixClient project + * SPDX-FileCopyrightText: 2019 QMatrixClient project * - * 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 "roomtombstoneevent.h" diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h index 2c2f0663..8d50aba0 100644 --- a/lib/events/roomtombstoneevent.h +++ b/lib/events/roomtombstoneevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2019 QMatrixClient project + * SPDX-FileCopyrightText: 2019 QMatrixClient project * - * 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 */ #pragma once diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index cde5b0fd..58ba3b5a 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 5909e8a6..7bde12bb 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 "stateevent.h" diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 20a85f83..0db37767 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/events/typingevent.cpp b/lib/events/typingevent.cpp index e102fc79..7d3f71e5 100644 --- a/lib/events/typingevent.cpp +++ b/lib/events/typingevent.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 "typingevent.h" diff --git a/lib/events/typingevent.h b/lib/events/typingevent.h index 97e1f9cc..8ca4f8e4 100644 --- a/lib/events/typingevent.h +++ b/lib/events/typingevent.h @@ -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 */ #pragma once diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 3fa1cd94..e6dc9f82 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.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 "basejob.h" diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index a0b89ef7..c2d42f49 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -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 */ #pragma once diff --git a/lib/jobs/mediathumbnailjob.cpp b/lib/jobs/mediathumbnailjob.cpp index a69f00e9..fbea8797 100644 --- a/lib/jobs/mediathumbnailjob.cpp +++ b/lib/jobs/mediathumbnailjob.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2016 Felix Rohrbach + * SPDX-FileCopyrightText: 2016 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 "mediathumbnailjob.h" diff --git a/lib/jobs/mediathumbnailjob.h b/lib/jobs/mediathumbnailjob.h index e6d39085..cb55a0b0 100644 --- a/lib/jobs/mediathumbnailjob.h +++ b/lib/jobs/mediathumbnailjob.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2016 Felix Rohrbach + * SPDX-FileCopyrightText: 2016 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 */ #pragma once diff --git a/lib/jobs/postreadmarkersjob.h b/lib/jobs/postreadmarkersjob.h index 5a4d942c..ba965de9 100644 --- a/lib/jobs/postreadmarkersjob.h +++ b/lib/jobs/postreadmarkersjob.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/jobs/requestdata.h b/lib/jobs/requestdata.h index 9cb5ecaf..2a227646 100644 --- a/lib/jobs/requestdata.h +++ b/lib/jobs/requestdata.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/jobs/syncjob.cpp b/lib/jobs/syncjob.cpp index 9087fe50..beb0a535 100644 --- a/lib/jobs/syncjob.cpp +++ b/lib/jobs/syncjob.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2016 Felix Rohrbach + * SPDX-FileCopyrightText: 2016 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 "syncjob.h" diff --git a/lib/jobs/syncjob.h b/lib/jobs/syncjob.h index bf139a7b..a7d10ed8 100644 --- a/lib/jobs/syncjob.h +++ b/lib/jobs/syncjob.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2016 Felix Rohrbach + * SPDX-FileCopyrightText: 2016 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 */ #pragma once diff --git a/lib/joinstate.h b/lib/joinstate.h index 31c2b6a7..1a7b1add 100644 --- a/lib/joinstate.h +++ b/lib/joinstate.h @@ -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 */ #pragma once diff --git a/lib/logging.cpp b/lib/logging.cpp index c346fbf1..c285821c 100644 --- a/lib/logging.cpp +++ b/lib/logging.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Elvis Angelaccio + * SPDX-FileCopyrightText: 2017 Elvis Angelaccio * - * 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 "logging.h" diff --git a/lib/logging.h b/lib/logging.h index ce4131bb..77e0dad3 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index e8aa85df..43a8287a 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 "networkaccessmanager.h" diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index a678b80f..6075767a 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/networksettings.cpp b/lib/networksettings.cpp index 40ecba11..db16034a 100644 --- a/lib/networksettings.cpp +++ b/lib/networksettings.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 "networksettings.h" diff --git a/lib/networksettings.h b/lib/networksettings.h index 2399cf5f..31602734 100644 --- a/lib/networksettings.h +++ b/lib/networksettings.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2017 Kitsune Ral + * SPDX-FileCopyrightText: 2017 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index 699735d4..158d7a40 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2019 Kitsune Ral + * SPDX-FileCopyrightText: 2019 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/room.cpp b/lib/room.cpp index a19624d8..155f5cd9 100644 --- a/lib/room.cpp +++ b/lib/room.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 "room.h" diff --git a/lib/room.h b/lib/room.h index 7eee022c..c9205e9c 100644 --- a/lib/room.h +++ b/lib/room.h @@ -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 */ #pragma once diff --git a/lib/settings.h b/lib/settings.h index c45764a6..badabec2 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2016 Kitsune Ral + * SPDX-FileCopyrightText: 2016 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index e6472e18..f67ab6c7 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 "syncdata.h" diff --git a/lib/syncdata.h b/lib/syncdata.h index 67d04557..d9868e46 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 */ #pragma once diff --git a/lib/user.cpp b/lib/user.cpp index c399ce88..9c2b76b5 100644 --- a/lib/user.cpp +++ b/lib/user.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 "user.h" diff --git a/lib/user.h b/lib/user.h index 19f57c30..d5c892ed 100644 --- a/lib/user.h +++ b/lib/user.h @@ -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 */ #pragma once diff --git a/lib/util.cpp b/lib/util.cpp index 36015d7a..14492ba6 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2018 Kitsune Ral + * SPDX-FileCopyrightText: 2018 Kitsune Ral * - * 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 "util.h" diff --git a/lib/util.h b/lib/util.h index 8c92df74..12e903ac 100644 --- a/lib/util.h +++ b/lib/util.h @@ -1,19 +1,7 @@ /****************************************************************************** - * Copyright (C) 2016 Kitsune Ral + * SPDX-FileCopyrightText: 2016 Kitsune Ral * - * 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 */ #pragma once -- cgit v1.2.3 From 78cfde52d8f3ff04a07031a87a0c7218a3b0079f Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 27 Dec 2020 18:07:30 +0100 Subject: more :) --- lib/events/encryptedevent.cpp | 4 ++++ lib/events/encryptedevent.h | 4 ++++ lib/events/redactionevent.cpp | 4 ++++ lib/quotient_common.h | 4 ++++ 4 files changed, 16 insertions(+) (limited to 'lib/events') diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 117aae37..dc9eaf2d 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "encryptedevent.h" using namespace Quotient; diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 235b2aa4..9de08b00 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once #include "e2ee.h" diff --git a/lib/events/redactionevent.cpp b/lib/events/redactionevent.cpp index bf467718..5889773c 100644 --- a/lib/events/redactionevent.cpp +++ b/lib/events/redactionevent.cpp @@ -1 +1,5 @@ +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// +// SPDX-License-Identifier: CC0-1.0 + #include "redactionevent.h" diff --git a/lib/quotient_common.h b/lib/quotient_common.h index bb05af05..e2384f12 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// +// SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once #include -- cgit v1.2.3 From 639f1d482633a7adb72164c56e3b5ea429db96a5 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 27 Dec 2020 23:19:12 +0100 Subject: event.h: Minor tweaks around visit<> --- lib/events/event.h | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 626a0229..309ebddf 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -298,7 +298,7 @@ using Events = EventsArray; // === is<>(), eventCast<>() and visit<>() === -template +template inline bool is(const Event& e) { return e.type() == typeId(); @@ -309,7 +309,7 @@ inline bool isUnknown(const Event& e) return e.type() == unknownEventTypeId(); } -template +template inline auto eventCast(const BasePtrT& eptr) -> decltype(static_cast(&*eptr)) { @@ -319,7 +319,7 @@ inline auto eventCast(const BasePtrT& eptr) } // A single generic catch-all visitor -template +template inline auto visit(const BaseEventT& event, FnT&& visitor) -> decltype(visitor(event)) { @@ -327,18 +327,17 @@ inline auto visit(const BaseEventT& event, FnT&& visitor) } namespace _impl { - template - constexpr auto needs_downcast() - { - return !std::is_convertible_v>; - } + template + inline constexpr auto needs_downcast = + std::is_base_of_v>> + && !std::is_same_v>>; } // A single type-specific void visitor -template -inline std::enable_if_t<_impl::needs_downcast() +template +inline auto visit(const BaseT& event, FnT&& visitor) + -> std::enable_if_t<_impl::needs_downcast && std::is_void_v>> -visit(const BaseEventT& event, FnT&& visitor) { using event_type = fn_arg_t; if (is>(event)) @@ -347,10 +346,10 @@ visit(const BaseEventT& event, FnT&& visitor) // A single type-specific non-void visitor with an optional default value // non-voidness is guarded by defaultValue type -template -inline std::enable_if_t<_impl::needs_downcast(), fn_return_t> -visit(const BaseEventT& event, FnT&& visitor, - fn_return_t&& defaultValue = {}) +template +inline auto visit(const BaseT& event, FnT&& visitor, + fn_return_t&& defaultValue = {}) + -> std::enable_if_t<_impl::needs_downcast, fn_return_t> { using event_type = fn_arg_t; if (is>(event)) @@ -359,9 +358,10 @@ visit(const BaseEventT& event, FnT&& visitor, } // A chain of 2 or more visitors -template -inline fn_return_t visit(const BaseEventT& event, FnT1&& visitor1, - FnT2&& visitor2, FnTs&&... visitors) +template +inline std::common_type_t, fn_return_t> visit( + const BaseT& event, FnT1&& visitor1, FnT2&& visitor2, + FnTs&&... visitors) { using event_type1 = fn_arg_t; if (is>(event)) @@ -374,8 +374,8 @@ inline fn_return_t visit(const BaseEventT& event, FnT1&& visitor1, // over a range of event pointers template inline auto visitEach(RangeT&& events, FnTs&&... visitors) - -> std::enable_if_t, Event>> + -> std::enable_if_t(visitors)...))>> { for (auto&& evtPtr: events) visit(*evtPtr, std::forward(visitors)...); -- 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 ++--- lib/connection.h | 30 ++++++++++++++++++++++++++---- lib/events/event.cpp | 3 +++ 3 files changed, 31 insertions(+), 7 deletions(-) (limited to 'lib/events') 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); }); } diff --git a/lib/connection.h b/lib/connection.h index 07ae9f29..2f638448 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -504,13 +504,35 @@ public Q_SLOTS: /** Determine and set the homeserver from MXID */ void resolveServer(const QString& mxid); + /** \brief Log in using a username and password pair + * + * Before logging in, this method checks if the homeserver is valid and + * supports the password login flow. If the homeserver is invalid but + * a full user MXID is provided, this method calls resolveServer() using + * this MXID. + * + * \sa resolveServer, resolveError, loginError + */ void loginWithPassword(const QString& userId, const QString& password, const QString& initialDeviceName, const QString& deviceId = {}); + /** \brief Log in using a login token + * + * One usual case for this method is the final stage of logging in via SSO. + * Unlike loginWithPassword() and assumeIdentity(), this method cannot + * resolve the server from the user name because the full user MXID is + * encoded in the login token. Callers should ensure the homeserver + * sanity in advance. + */ void loginWithToken(const QByteArray& loginToken, const QString& initialDeviceName, const QString& deviceId = {}); - void assumeIdentity(const QString& userId, const QString& accessToken, + /** \brief Use an existing access token to connect to the homeserver + * + * Similar to loginWithPassword(), this method checks that the homeserver + * URL is valid and tries to resolve it from the MXID in case it is not. + */ + void assumeIdentity(const QString& mxId, const QString& accessToken, const QString& deviceId); /*! \deprecated Use loginWithPassword instead */ void connectToServer(const QString& userId, const QString& password, @@ -662,9 +684,9 @@ Q_SIGNALS: * This was a signal resulting from a successful resolveServer(). * Since Connection now provides setHomeserver(), the HS URL * may change even without resolveServer() invocation. Use - * homeserverChanged() instead of resolved(). You can also use - * connectToServer and connectWithToken without the HS URL set in - * advance (i.e. without calling resolveServer), as they now trigger + * loginFLowsChanged() instead of resolved(). You can also use + * loginWith*() and assumeIdentity() without the HS URL set in + * advance (i.e. without calling resolveServer), as they trigger * server name resolution from MXID if the server URL is not valid. */ void resolved(); diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 96e33864..7b34114d 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -61,11 +61,14 @@ QString Event::matrixType() const { return fullJson()[TypeKeyL].toString(); } QByteArray Event::originalJson() const { return QJsonDocument(_json).toJson(); } +// On const below: this is to catch accidental attempts to change event JSON +// NOLINTNEXTLINE(readability-const-return-type) const QJsonObject Event::contentJson() const { return fullJson()[ContentKeyL].toObject(); } +// NOLINTNEXTLINE(readability-const-return-type) const QJsonObject Event::unsignedJson() const { return fullJson()[UnsignedKeyL].toObject(); -- cgit v1.2.3 From e5bf7c2fa64716de0b75a67acc5f8620e8dc1704 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Mon, 28 Dec 2020 18:22:29 +0100 Subject: Add support for sticker events --- lib/events/stickerevent.cpp | 26 ++++++++++++++++++++++++++ lib/events/stickerevent.h | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 lib/events/stickerevent.cpp create mode 100644 lib/events/stickerevent.h (limited to 'lib/events') diff --git a/lib/events/stickerevent.cpp b/lib/events/stickerevent.cpp new file mode 100644 index 00000000..ea4dff3f --- /dev/null +++ b/lib/events/stickerevent.cpp @@ -0,0 +1,26 @@ +// SDPX-FileCopyrightText: 2020 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "stickerevent.h" + +using namespace Quotient; + +StickerEvent::StickerEvent(const QJsonObject &obj) + : RoomEvent(typeId(), obj) + , m_imageContent(EventContent::ImageContent(obj["content"_ls].toObject())) +{} + +QString StickerEvent::body() const +{ + return content("body"_ls); +} + +const EventContent::ImageContent &StickerEvent::image() const +{ + return m_imageContent; +} + +QUrl StickerEvent::url() const +{ + return m_imageContent.url; +} diff --git a/lib/events/stickerevent.h b/lib/events/stickerevent.h new file mode 100644 index 00000000..93671086 --- /dev/null +++ b/lib/events/stickerevent.h @@ -0,0 +1,38 @@ +// SDPX-FileCopyrightText: 2020 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "roomevent.h" +#include "eventcontent.h" + +namespace Quotient { + +/// Sticker messages are specialised image messages that are displayed without +/// controls (e.g. no "download" link, or light-box view on click, as would be +/// displayed for for m.image events). +class StickerEvent : public RoomEvent +{ +public: + DEFINE_EVENT_TYPEID("m.sticker", StickerEvent) + + explicit StickerEvent(const QJsonObject &obj); + + /// \brief A textual representation or associated description of the + /// sticker image. + /// + /// This could be the alt text of the original image, or a message to + /// accompany and further describe the sticker. + QString body() const; + + /// \brief Metadata about the image referred to in url including a + /// thumbnail representation. + const EventContent::ImageContent &image() const; + + /// \brief The URL to the sticker image. This must be a valid mxc:// URI. + QUrl url() const; +private: + EventContent::ImageContent m_imageContent; +}; +REGISTER_EVENT_TYPE(StickerEvent) +} // namespace Quotient -- cgit v1.2.3 From 2174e1980fd2cf5407ba8cd7cabb85d74d242ed2 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 28 Dec 2020 18:57:07 +0100 Subject: event.h: Fix breakage of AppVeyor CI The breakage was caused by 639f1d48. --- lib/events/event.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 309ebddf..9f2f4f91 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -327,8 +327,9 @@ inline auto visit(const BaseEventT& event, FnT&& visitor) } namespace _impl { + // Using bool instead of auto below because auto apparently upsets MSVC template - inline constexpr auto needs_downcast = + inline constexpr bool needs_downcast = std::is_base_of_v>> && !std::is_same_v>>; } -- 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. --- .clang-format | 8 ++++---- CONTRIBUTING.md | 14 ++++++++++++-- gtad/data.h.mustache | 4 ++++ gtad/operation.cpp.mustache | 4 ++++ gtad/operation.h.mustache | 4 ++++ lib/avatar.cpp | 7 ++----- lib/avatar.h | 9 +++------ lib/connection.cpp | 10 +++++----- lib/connection.h | 9 ++++----- lib/connectiondata.cpp | 8 +++----- lib/connectiondata.h | 8 +++----- lib/converters.cpp | 7 ++----- lib/converters.h | 7 ++----- lib/e2ee.h | 1 - lib/encryptionmanager.cpp | 1 - lib/encryptionmanager.h | 1 - lib/eventitem.cpp | 7 ++----- lib/eventitem.h | 7 ++----- lib/events/accountdataevents.h | 7 ++----- lib/events/callanswerevent.cpp | 8 +++----- lib/events/callanswerevent.h | 8 +++----- lib/events/callcandidatesevent.cpp | 8 +++----- lib/events/callcandidatesevent.h | 9 ++++----- lib/events/callhangupevent.cpp | 1 + lib/events/callhangupevent.h | 8 +++----- lib/events/callinviteevent.cpp | 8 +++----- lib/events/callinviteevent.h | 8 +++----- lib/events/directchatevent.cpp | 7 ++----- lib/events/directchatevent.h | 7 ++----- lib/events/encryptedevent.cpp | 1 - lib/events/encryptedevent.h | 1 - lib/events/encryptionevent.cpp | 4 ++++ lib/events/encryptionevent.h | 8 +++----- lib/events/event.cpp | 7 ++----- lib/events/event.h | 7 ++----- lib/events/eventcontent.cpp | 7 ++----- lib/events/eventcontent.h | 7 ++----- lib/events/eventloader.h | 7 ++----- lib/events/reactionevent.cpp | 7 ++----- lib/events/reactionevent.h | 7 ++----- lib/events/receiptevent.cpp | 7 ++----- lib/events/receiptevent.h | 7 ++----- lib/events/redactionevent.cpp | 5 ----- lib/events/redactionevent.h | 7 ++----- lib/events/roomavatarevent.h | 7 ++----- lib/events/roomcanonicalaliasevent.h | 8 +++----- lib/events/roomcreateevent.cpp | 7 ++----- lib/events/roomcreateevent.h | 7 ++----- lib/events/roomevent.cpp | 7 ++----- lib/events/roomevent.h | 7 ++----- lib/events/roomkeyevent.cpp | 3 +++ lib/events/roomkeyevent.h | 3 +++ lib/events/roommemberevent.cpp | 8 +++----- lib/events/roommemberevent.h | 9 ++++----- lib/events/roommessageevent.cpp | 9 ++++----- lib/events/roommessageevent.h | 9 ++++----- lib/events/roompowerlevelsevent.cpp | 3 +++ lib/events/roompowerlevelsevent.h | 3 +++ lib/events/roomtombstoneevent.cpp | 7 ++----- lib/events/roomtombstoneevent.h | 7 ++----- lib/events/simplestateevents.h | 7 ++----- lib/events/stateevent.cpp | 7 ++----- lib/events/stateevent.h | 7 ++----- lib/events/typingevent.cpp | 7 ++----- lib/events/typingevent.h | 7 ++----- lib/jobs/basejob.cpp | 8 +++----- lib/jobs/basejob.h | 8 +++----- lib/jobs/downloadfilejob.cpp | 3 +++ lib/jobs/downloadfilejob.h | 3 +++ lib/jobs/mediathumbnailjob.cpp | 7 ++----- lib/jobs/mediathumbnailjob.h | 7 ++----- lib/jobs/requestdata.cpp | 3 +++ lib/jobs/requestdata.h | 7 ++----- lib/jobs/syncjob.cpp | 7 ++----- lib/jobs/syncjob.h | 7 ++----- lib/joinstate.h | 7 ++----- lib/logging.cpp | 8 +++----- lib/logging.h | 8 +++----- lib/networkaccessmanager.cpp | 7 ++----- lib/networkaccessmanager.h | 7 ++----- lib/networksettings.cpp | 7 ++----- lib/networksettings.h | 7 ++----- lib/qt_connection_util.h | 7 ++----- lib/quotient_common.h | 1 - lib/room.cpp | 13 ++++++++----- lib/room.h | 13 ++++++++----- lib/settings.cpp | 3 +++ lib/settings.h | 7 ++----- lib/ssosession.cpp | 3 +++ lib/ssosession.h | 3 +++ lib/syncdata.cpp | 7 ++----- lib/syncdata.h | 7 ++----- lib/uri.cpp | 3 +++ lib/uri.h | 3 +++ lib/uriresolver.cpp | 3 +++ lib/uriresolver.h | 3 +++ lib/user.cpp | 8 +++----- lib/user.h | 8 +++----- lib/util.cpp | 8 +++----- lib/util.h | 8 +++----- tests/quotest.cpp | 2 ++ 101 files changed, 266 insertions(+), 378 deletions(-) delete mode 100644 lib/events/redactionevent.cpp (limited to 'lib/events') diff --git a/.clang-format b/.clang-format index 4df5ae84..4510d46e 100644 --- a/.clang-format +++ b/.clang-format @@ -1,7 +1,7 @@ -# Copyright (C) 2019 Project Quotient -# -# You may use this file under the terms of the LGPL-2.1 license -# See the file LICENSE from this package for details. +# SPDX-FileCopyrightText: 2019 Kitsune Ral +# SPDX-FileCopyrightText: 2019 Marc Deop + +# SPDX-License-Identifier: LGPL-2.1-or-later # This is the clang-format configuration style to be used by libQuotient. # Inspired by: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bda004df..f09b1529 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,8 +96,18 @@ All new contributed material that is not executable, including all text when not --> Any components proposed for reuse should have a license that permits releasing -a derivative work under *LGPL v2.1 or later* or LGPL v3. Moreover, the license of -a proposed component should be approved by OSI, no exceptions. +a derivative work under *LGPL v3 or later* (that includes licenses permitting +*LGPL v2.1 or later* but not *LGPL v2.1 only*). In any case, the component +should be redistributable under a license from +[the list approved by OSI](https://opensource.org/licenses), no exceptions. + +We use [SPDX](https://spdx.dev) conventions for copyright statements. Please +follow them when making a sizable contribution: add your name and year to +the top of the file. New files should begin with the following preamble: +```cpp +// SPDX-FileCopyrightText: 2021 Your Name +// SPDX-License-Identifier: LGPL-2.1-or-later +``` ## Vulnerability reporting (security issues) - see [SECURITY.md](./SECURITY.md) diff --git a/gtad/data.h.mustache b/gtad/data.h.mustache index 32ea85ee..a2193380 100644 --- a/gtad/data.h.mustache +++ b/gtad/data.h.mustache @@ -1,3 +1,7 @@ +{{! +SPDX-FileCopyrightText: 2020 Kitsune Ral +SPDX-License-Identifier: LGPL-2.1-or-later +}} {{>preamble}} #pragma once diff --git a/gtad/operation.cpp.mustache b/gtad/operation.cpp.mustache index 3c3396e9..1d0ae476 100644 --- a/gtad/operation.cpp.mustache +++ b/gtad/operation.cpp.mustache @@ -1,3 +1,7 @@ +{{! +SPDX-FileCopyrightText: 2020 Kitsune Ral +SPDX-License-Identifier: LGPL-2.1-or-later +}} {{>preamble}} #include "{{filenameBase}}.h" diff --git a/gtad/operation.h.mustache b/gtad/operation.h.mustache index 36963b9a..135eee55 100644 --- a/gtad/operation.h.mustache +++ b/gtad/operation.h.mustache @@ -1,3 +1,7 @@ +{{! +SPDX-FileCopyrightText: 2020 Kitsune Ral +SPDX-License-Identifier: LGPL-2.1-or-later +}} {{>preamble}} #pragma once diff --git a/lib/avatar.cpp b/lib/avatar.cpp index 0573df5d..77648562 100644 --- a/lib/avatar.cpp +++ b/lib/avatar.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "avatar.h" diff --git a/lib/avatar.h b/lib/avatar.h index 111f565d..be125c17 100644 --- a/lib/avatar.h +++ b/lib/avatar.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once @@ -46,4 +43,4 @@ private: }; } // namespace Quotient /// \deprecated Use namespace Quotient instead -namespace QMatrixClient = Quotient; \ No newline at end of file +namespace QMatrixClient = Quotient; 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" diff --git a/lib/connection.h b/lib/connection.h index f3d7d725..4f949641 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -1,8 +1,7 @@ -/****************************************************************************** - * 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 Alexey Andreyev +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/connectiondata.cpp b/lib/connectiondata.cpp index 25ab775a..e54d909b 100644 --- a/lib/connectiondata.cpp +++ b/lib/connectiondata.cpp @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "connectiondata.h" diff --git a/lib/connectiondata.h b/lib/connectiondata.h index a3b2d39b..7dd96f26 100644 --- a/lib/connectiondata.h +++ b/lib/connectiondata.h @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/converters.cpp b/lib/converters.cpp index 0df880a0..4338e8ed 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "converters.h" diff --git a/lib/converters.h b/lib/converters.h index d4f19b60..e07b6ee4 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/e2ee.h b/lib/e2ee.h index 5f1857b6..4044aa02 100644 --- a/lib/e2ee.h +++ b/lib/e2ee.h @@ -1,6 +1,5 @@ // SPDX-FileCopyrightText: 2019 Alexey Andreyev // SPDX-FileCopyrightText: 2019 Kitsune Ral -// // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 8d241eb2..37f3b7c3 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -1,6 +1,5 @@ // SPDX-FileCopyrightText: 2019 Alexey Andreyev // SPDX-FileCopyrightText: 2019 Kitsune Ral -// // SPDX-License-Identifier: LGPL-2.1-or-later #ifdef Quotient_E2EE_ENABLED diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h index 0f507337..714f95fd 100644 --- a/lib/encryptionmanager.h +++ b/lib/encryptionmanager.h @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: 2019 Alexey Andreyev -// // SPDX-License-Identifier: LGPL-2.1-or-later #ifdef Quotient_E2EE_ENABLED diff --git a/lib/eventitem.cpp b/lib/eventitem.cpp index 9c47e50d..4f1595bc 100644 --- a/lib/eventitem.cpp +++ b/lib/eventitem.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "eventitem.h" diff --git a/lib/eventitem.h b/lib/eventitem.h index 2d3d9ef6..1986ba77 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index d0abf577..8cea0ec8 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp index f3d0a9a0..be83d9d0 100644 --- a/lib/events/callanswerevent.cpp +++ b/lib/events/callanswerevent.cpp @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Marius Gripsgard - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Marius Gripsgard +// SPDX-FileCopyrightText: 2018 Josip Delic +// SPDX-License-Identifier: LGPL-2.1-or-later #include "callanswerevent.h" diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index d7214468..6132cb44 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Marius Gripsgard - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Marius Gripsgard +// SPDX-FileCopyrightText: 2018 Josip Delic +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/callcandidatesevent.cpp b/lib/events/callcandidatesevent.cpp index 9b765064..b87c8e9b 100644 --- a/lib/events/callcandidatesevent.cpp +++ b/lib/events/callcandidatesevent.cpp @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Marius Gripsgard - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Marius Gripsgard +// SPDX-FileCopyrightText: 2018 Josip Delic +// SPDX-License-Identifier: LGPL-2.1-or-later #include "callcandidatesevent.h" diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h index ae3bb150..b9de7556 100644 --- a/lib/events/callcandidatesevent.h +++ b/lib/events/callcandidatesevent.h @@ -1,8 +1,7 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Marius Gripsgard - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Marius Gripsgard +// SPDX-FileCopyrightText: 2018 Josip Delic +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp index 45b84cd4..43bc4db0 100644 --- a/lib/events/callhangupevent.cpp +++ b/lib/events/callhangupevent.cpp @@ -1,5 +1,6 @@ /****************************************************************************** * SPDX-FileCopyrightText: 2017 Marius Gripsgard + * SPDX-FileCopyrightText: 2018 Josip Delic * * SPDX-License-Identifier: LGPL-2.1-or-later */ diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index 432f72f5..24382ac2 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Marius Gripsgard - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Marius Gripsgard +// SPDX-FileCopyrightText: 2018 Josip Delic +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index 86478ada..5ea54662 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Marius Gripsgard - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Marius Gripsgard +// SPDX-FileCopyrightText: 2018 Josip Delic +// SPDX-License-Identifier: LGPL-2.1-or-later #include "callinviteevent.h" diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index 304c89ac..d3454c4f 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Marius Gripsgard - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Marius Gripsgard +// SPDX-FileCopyrightText: 2018 Josip Delic +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/directchatevent.cpp b/lib/events/directchatevent.cpp index 39d11072..0ee1f7b0 100644 --- a/lib/events/directchatevent.cpp +++ b/lib/events/directchatevent.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "directchatevent.h" diff --git a/lib/events/directchatevent.h b/lib/events/directchatevent.h index 373e36dc..e2143779 100644 --- a/lib/events/directchatevent.h +++ b/lib/events/directchatevent.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index dc9eaf2d..0290f973 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: 2019 Alexey Andreyev -// // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 9de08b00..eb7123eb 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: 2019 Alexey Andreyev -// // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index f1bde621..490a5e8a 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "encryptionevent.h" #include "e2ee.h" diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 3431ddd8..f9bbab12 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 97edb4e0..3d66ab55 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "event.h" diff --git a/lib/events/event.h b/lib/events/event.h index c5752a7a..f8f8311d 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 18b1b94b..b249b160 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "eventcontent.h" diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index e247adbf..60d1f7b7 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index 0d95daf5..978668f2 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/reactionevent.cpp b/lib/events/reactionevent.cpp index 9b43e372..b53fffd6 100644 --- a/lib/events/reactionevent.cpp +++ b/lib/events/reactionevent.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2019 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "reactionevent.h" diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index 09166b24..777905f2 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2019 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp index b6f0fcdd..4185d92d 100644 --- a/lib/events/receiptevent.cpp +++ b/lib/events/receiptevent.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2016 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later /* Example of a Receipt Event: diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index ec297a6c..4feec9ea 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2016 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/redactionevent.cpp b/lib/events/redactionevent.cpp deleted file mode 100644 index 5889773c..00000000 --- a/lib/events/redactionevent.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// -// SPDX-License-Identifier: CC0-1.0 - -#include "redactionevent.h" diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index 320db6f2..ed560331 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index 649412e8..a4257895 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/roomcanonicalaliasevent.h b/lib/events/roomcanonicalaliasevent.h index eda94d2d..bb8654e5 100644 --- a/lib/events/roomcanonicalaliasevent.h +++ b/lib/events/roomcanonicalaliasevent.h @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2020 QMatrixClient project - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2020 Ram Nad +// SPDX-FileCopyrightText: 2020 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index 3d9ec4a3..6558bade 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2019 QMatrixClient project - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "roomcreateevent.h" diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index 8328d38a..05e623ed 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2019 QMatrixClient project - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 2b6ac2be..4fec9d2b 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "roomevent.h" diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 3fafecfd..fea509c0 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/roomkeyevent.cpp b/lib/events/roomkeyevent.cpp index 66580430..332be3f7 100644 --- a/lib/events/roomkeyevent.cpp +++ b/lib/events/roomkeyevent.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "roomkeyevent.h" using namespace Quotient; diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index b8cd2eae..14e80324 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once #include "event.h" diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index d093286c..9634ca3a 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-FileCopyrightText: 2019 Karol Kosek +// SPDX-License-Identifier: LGPL-2.1-or-later #include "roommemberevent.h" diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index b7a7c9df..f2fbe689 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -1,8 +1,7 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-FileCopyrightText: 2019 Karol Kosek +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 19d460b8..14824277 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -1,8 +1,7 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-FileCopyrightText: 2017 Roman Plášil +// SPDX-License-Identifier: LGPL-2.1-or-later #include "roommessageevent.h" diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index ebc9d564..8303ce4e 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -1,8 +1,7 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-FileCopyrightText: 2017 Roman Plášil +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/roompowerlevelsevent.cpp b/lib/events/roompowerlevelsevent.cpp index 0a401752..8d262ddf 100644 --- a/lib/events/roompowerlevelsevent.cpp +++ b/lib/events/roompowerlevelsevent.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2019 Black Hat +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "roompowerlevelsevent.h" #include diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h index b832230e..0346fc0d 100644 --- a/lib/events/roompowerlevelsevent.h +++ b/lib/events/roompowerlevelsevent.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2019 Black Hat +// SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once #include "eventcontent.h" diff --git a/lib/events/roomtombstoneevent.cpp b/lib/events/roomtombstoneevent.cpp index 163e1d3a..080d269c 100644 --- a/lib/events/roomtombstoneevent.cpp +++ b/lib/events/roomtombstoneevent.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2019 QMatrixClient project - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "roomtombstoneevent.h" diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h index 8d50aba0..30e53738 100644 --- a/lib/events/roomtombstoneevent.h +++ b/lib/events/roomtombstoneevent.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2019 QMatrixClient project - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 58ba3b5a..d6261a8f 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 7bde12bb..42fc9054 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "stateevent.h" diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 0db37767..1415f709 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/events/typingevent.cpp b/lib/events/typingevent.cpp index 7d3f71e5..e97e978f 100644 --- a/lib/events/typingevent.cpp +++ b/lib/events/typingevent.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "typingevent.h" diff --git a/lib/events/typingevent.h b/lib/events/typingevent.h index 8ca4f8e4..7456100a 100644 --- a/lib/events/typingevent.h +++ b/lib/events/typingevent.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index a0c88581..48c2996d 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "basejob.h" diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 3165edd3..ca91a781 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 0011a97c..0b0531ad 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "downloadfilejob.h" #include diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index e00fd9e4..0752af89 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once #include "csapi/content-repo.h" diff --git a/lib/jobs/mediathumbnailjob.cpp b/lib/jobs/mediathumbnailjob.cpp index fbea8797..7dbf4ab3 100644 --- a/lib/jobs/mediathumbnailjob.cpp +++ b/lib/jobs/mediathumbnailjob.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2016 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "mediathumbnailjob.h" diff --git a/lib/jobs/mediathumbnailjob.h b/lib/jobs/mediathumbnailjob.h index cb55a0b0..3183feb1 100644 --- a/lib/jobs/mediathumbnailjob.h +++ b/lib/jobs/mediathumbnailjob.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2016 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/jobs/requestdata.cpp b/lib/jobs/requestdata.cpp index cec15954..047e2920 100644 --- a/lib/jobs/requestdata.cpp +++ b/lib/jobs/requestdata.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "requestdata.h" #include diff --git a/lib/jobs/requestdata.h b/lib/jobs/requestdata.h index 2a227646..4958e0f9 100644 --- a/lib/jobs/requestdata.h +++ b/lib/jobs/requestdata.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/jobs/syncjob.cpp b/lib/jobs/syncjob.cpp index beb0a535..59a34ef3 100644 --- a/lib/jobs/syncjob.cpp +++ b/lib/jobs/syncjob.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2016 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "syncjob.h" diff --git a/lib/jobs/syncjob.h b/lib/jobs/syncjob.h index a7d10ed8..830a7c71 100644 --- a/lib/jobs/syncjob.h +++ b/lib/jobs/syncjob.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2016 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/joinstate.h b/lib/joinstate.h index 1a7b1add..805ce73a 100644 --- a/lib/joinstate.h +++ b/lib/joinstate.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/logging.cpp b/lib/logging.cpp index 3f757393..af229684 100644 --- a/lib/logging.cpp +++ b/lib/logging.cpp @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Elvis Angelaccio - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Elvis Angelaccio +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "logging.h" diff --git a/lib/logging.h b/lib/logging.h index 21d05d8b..432ed16f 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Elvis Angelaccio +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 43a8287a..a94ead34 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "networkaccessmanager.h" diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index 6075767a..47729a1b 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/networksettings.cpp b/lib/networksettings.cpp index db16034a..ce46ce5f 100644 --- a/lib/networksettings.cpp +++ b/lib/networksettings.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "networksettings.h" diff --git a/lib/networksettings.h b/lib/networksettings.h index 31602734..df11a9c8 100644 --- a/lib/networksettings.h +++ b/lib/networksettings.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2017 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index 158d7a40..c6fa037a 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2019 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/quotient_common.h b/lib/quotient_common.h index e2384f12..22fdbe94 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: 2019 Kitsune Ral -// // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/room.cpp b/lib/room.cpp index bc89464d..2e8641aa 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1,8 +1,11 @@ -/****************************************************************************** - * 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: 2017 Marius Gripsgard +// SPDX-FileCopyrightText: 2018 Josip Delic +// SPDX-FileCopyrightText: 2018 Black Hat +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// SPDX-FileCopyrightText: 2020 Ram Nad +// SPDX-License-Identifier: LGPL-2.1-or-later #include "room.h" diff --git a/lib/room.h b/lib/room.h index c9205e9c..a8275ce9 100644 --- a/lib/room.h +++ b/lib/room.h @@ -1,8 +1,11 @@ -/****************************************************************************** - * 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: 2017 Marius Gripsgard +// SPDX-FileCopyrightText: 2018 Josip Delic +// SPDX-FileCopyrightText: 2018 Black Hat +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// SPDX-FileCopyrightText: 2020 Ram Nad +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/settings.cpp b/lib/settings.cpp index dd086d9c..703f4320 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "settings.h" #include "logging.h" diff --git a/lib/settings.h b/lib/settings.h index badabec2..84c54802 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2016 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/ssosession.cpp b/lib/ssosession.cpp index 3c6ec48b..a1d27504 100644 --- a/lib/ssosession.cpp +++ b/lib/ssosession.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2020 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "ssosession.h" #include "connection.h" diff --git a/lib/ssosession.h b/lib/ssosession.h index 5845cd4d..72dd60c4 100644 --- a/lib/ssosession.h +++ b/lib/ssosession.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2020 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once #include diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index f67ab6c7..adcba5cd 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "syncdata.h" diff --git a/lib/syncdata.h b/lib/syncdata.h index d9868e46..e69bac17 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -1,8 +1,5 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/uri.cpp b/lib/uri.cpp index e0912eb6..4b171e79 100644 --- a/lib/uri.cpp +++ b/lib/uri.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2020 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "uri.h" #include "logging.h" diff --git a/lib/uri.h b/lib/uri.h index 270766dd..d8b892b6 100644 --- a/lib/uri.h +++ b/lib/uri.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2020 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once #include "quotient_common.h" diff --git a/lib/uriresolver.cpp b/lib/uriresolver.cpp index 27360bcc..287e0552 100644 --- a/lib/uriresolver.cpp +++ b/lib/uriresolver.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2020 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + #include "uriresolver.h" #include "connection.h" diff --git a/lib/uriresolver.h b/lib/uriresolver.h index 428ce04c..f290e58b 100644 --- a/lib/uriresolver.h +++ b/lib/uriresolver.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2020 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + #pragma once #include "uri.h" diff --git a/lib/user.cpp b/lib/user.cpp index 9c2b76b5..bed7be3a 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "user.h" diff --git a/lib/user.h b/lib/user.h index d5c892ed..f831865e 100644 --- a/lib/user.h +++ b/lib/user.h @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2015 Felix Rohrbach - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2015 Felix Rohrbach +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/lib/util.cpp b/lib/util.cpp index 14492ba6..904bfd5a 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2018 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// SPDX-License-Identifier: LGPL-2.1-or-later #include "util.h" diff --git a/lib/util.h b/lib/util.h index 7547a75a..cb0ff44a 100644 --- a/lib/util.h +++ b/lib/util.h @@ -1,8 +1,6 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2016 Kitsune Ral - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once diff --git a/tests/quotest.cpp b/tests/quotest.cpp index 98c01cfc..5098bc02 100644 --- a/tests/quotest.cpp +++ b/tests/quotest.cpp @@ -1,3 +1,5 @@ +// SPDX-FileCopyrightText: 2016 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later #include "connection.h" #include "room.h" -- cgit v1.2.3 From 17e62b85cae99d8485be44f90f0622e4ba843fa0 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sun, 10 Jan 2021 23:26:57 +0100 Subject: Add more properties to CallCandidateEvent --- lib/events/callcandidatesevent.h | 11 +++++++++ tests/callcandidateseventtest.cpp | 47 +++++++++++++++++++++++++++++++++++++++ tests/callcandidateseventtest.h | 13 +++++++++++ 3 files changed, 71 insertions(+) create mode 100644 tests/callcandidateseventtest.cpp create mode 100644 tests/callcandidateseventtest.h (limited to 'lib/events') diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h index b9de7556..c2ccac3b 100644 --- a/lib/events/callcandidatesevent.h +++ b/lib/events/callcandidatesevent.h @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2017 Marius Gripsgard // SPDX-FileCopyrightText: 2018 Josip Delic // SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-FileCopyrightText: 2020 Carl Schwan // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once @@ -26,6 +27,16 @@ public: { return content("candidates"_ls); } + + QString callId() const + { + return content("call_id"); + } + + int version() const + { + return content("version"); + } }; REGISTER_EVENT_TYPE(CallCandidatesEvent) diff --git a/tests/callcandidateseventtest.cpp b/tests/callcandidateseventtest.cpp new file mode 100644 index 00000000..27108fdd --- /dev/null +++ b/tests/callcandidateseventtest.cpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2020 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "events/callcandidatesevent.h" +#include "callcandidateseventtest.h" + +void TestCallCandidatesEvent::fromJson() +{ + auto documemt = QJsonDocument::fromJson(R"({ + "age": 242352, + "content": { + "call_id": "12345", + "candidates": [ + { + "candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156 43670 typ host generation 0", + "sdpMLineIndex": 0, + "sdpMid": "audio" + } + ], + "version": 0 + }, + "event_id": "$WLGTSEFSEF:localhost", + "origin_server_ts": 1431961217939, + "room_id": "!Cuyf34gef24t:localhost", + "sender": "@example:localhost", + "type": "m.call.candidates" + })"); + + QVERIFY(documemt.isObject()); + + auto object = documemt.object(); + + Quotient::CallCandidatesEvent callCandidatesEvent(object); + + QCOMPARE(callCandidatesEvent.version(), 0); + QCOMPARE(callCandidatesEvent.callId(), "12345"); + QCOMPARE(callCandidatesEvent.candidates().count(), 1); + + const QJsonObject &candidate = callCandidatesEvent.candidates().at(0).toObject(); + QCOMPARE(candidate.value("sdpMid").toString(), "audio"); + QCOMPARE(candidate.value("sdpMLineIndex").toInt(), 0); + QCOMPARE(candidate.value("candidate").toString(), "candidate:863018703 1 udp 2122260223 10.9.64.156 43670 typ host generation 0"); +} + +QTEST_MAIN(TestCallCandidatesEvent) +#include "callcandidateseventtest.moc" diff --git a/tests/callcandidateseventtest.h b/tests/callcandidateseventtest.h new file mode 100644 index 00000000..b81c9c9b --- /dev/null +++ b/tests/callcandidateseventtest.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2020 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include + +class TestCallCandidatesEvent : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void fromJson(); +}; -- cgit v1.2.3 From b850edadde2299b122a5cd17da85e943430e43b7 Mon Sep 17 00:00:00 2001 From: Roland Pallai Date: Thu, 28 Jan 2021 14:58:11 +0100 Subject: Fix rich replies json format (transmit) With this patch it looks like: "m.relates_to": { "m.in_reply_to": { "event_id": "$another:event.com" } } instead of: "m.relates_to": { "event_id": "$another:event.com", "rel_type": "m.in_reply_to" }, So it fits the specification by now. https://matrix.org/docs/spec/client_server/r0.6.1#rich-replies --- lib/events/roommessageevent.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 14824277..3fccb380 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -325,7 +325,10 @@ void TextContent::fillJson(QJsonObject* json) const } if (relatesTo) { json->insert(QStringLiteral("m.relates_to"), - QJsonObject { { "rel_type", relatesTo->type }, { EventIdKey, relatesTo->eventId } }); + relatesTo->type == RelatesTo::ReplyTypeId() ? + QJsonObject { { relatesTo->type, QJsonObject{ { EventIdKey, relatesTo->eventId } } } } : + QJsonObject { { "rel_type", relatesTo->type }, { EventIdKey, relatesTo->eventId } } + ); if (relatesTo->type == RelatesTo::ReplacementTypeId()) { QJsonObject newContentJson; if (mimeType.inherits("text/html")) { -- cgit v1.2.3 From df6b2d31ec8f2f5890826719e960f450a4968f22 Mon Sep 17 00:00:00 2001 From: Roland Pallai Date: Thu, 28 Jan 2021 15:05:33 +0100 Subject: Fix rich edits (transmit) The new formatted_body was not included into new content on edit due to badly constructed json. --- lib/events/roommessageevent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 14824277..4a733772 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -329,8 +329,8 @@ void TextContent::fillJson(QJsonObject* json) const if (relatesTo->type == RelatesTo::ReplacementTypeId()) { QJsonObject newContentJson; if (mimeType.inherits("text/html")) { - json->insert(FormatKey, HtmlContentTypeId); - json->insert(FormattedBodyKey, body); + newContentJson.insert(FormatKey, HtmlContentTypeId); + newContentJson.insert(FormattedBodyKey, body); } json->insert(QStringLiteral("m.new_content"), newContentJson); } -- cgit v1.2.3 From ec60e76d585813ea54a22b58d6fabc52f113e4b1 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 21 Feb 2021 20:47:36 +0100 Subject: Update a comment that still mentions Riot (cherry picked from commit b25785d294669f2bab7dcd1e3cd1fba61991fe46) --- lib/events/roommessageevent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index a9d9754f..31c0fd9e 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -300,7 +300,7 @@ TextContent::TextContent(const QJsonObject& json) const auto actualJson = isReplacement(relatesTo) ? json.value("m.new_content"_ls).toObject() : json; - // Special-casing the custom matrix.org's (actually, Riot's) way + // Special-casing the custom matrix.org's (actually, Element's) way // of sending HTML messages. if (actualJson["format"_ls].toString() == HtmlContentTypeId) { mimeType = HtmlMimeType; -- cgit v1.2.3 From a95d5e83b4d0ab3a9bf2adef1ae4246adc317d04 Mon Sep 17 00:00:00 2001 From: Arnav Rawat Date: Thu, 11 Mar 2021 16:09:55 -0600 Subject: Support for pinned messages Fixes issue #188 --- lib/events/simplestateevents.h | 17 +++++++++++++++++ lib/room.cpp | 14 ++++++++++++++ lib/room.h | 2 ++ libQuotient.kdev4 | 4 ++++ 4 files changed, 37 insertions(+) create mode 100644 libQuotient.kdev4 (limited to 'lib/events') diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index d6261a8f..f22f313d 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -71,4 +71,21 @@ public: QStringList aliases() const { return content().value; } }; REGISTER_EVENT_TYPE(RoomAliasesEvent) + +class RoomPinnedEvent + : public StateEvent> +{ +public: + DEFINE_EVENT_TYPEID("m.room.pinned_messages", RoomPinnedEvent) + + explicit RoomPinnedEvent(const QJsonObject& json) + : StateEvent(typeId(), json, QStringLiteral("pinned")) + { } + explicit RoomPinnedEvent(const QStringList& roomEvents) + : StateEvent(typeId(), matrixTypeId(), {}, + QStringLiteral("pinned"), roomEvents) + { } + QStringList pinnedEvents() const { return content().value; } +}; +REGISTER_EVENT_TYPE(RoomPinnedEvent) } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index fadcea17..4f7f7ca9 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -557,6 +557,16 @@ QString Room::canonicalAlias() const QString Room::displayName() const { return d->displayname; } +QList Room::pinnedEvents() const +{ + QStringList events = d->getCurrentState()->pinnedEvents(); + QList pinnedEvents; + QStringList::iterator i; + for (i = events.begin(); i != events.end(); ++i) + pinnedEvents.append(findInTimeline(*i)->event()); + return pinnedEvents; +} + void Room::refreshDisplayName() { d->updateDisplayname(); } QString Room::topic() const @@ -1830,6 +1840,10 @@ void Room::setCanonicalAlias(const QString& newAlias) d->requestSetState(newAlias, altAliases()); } +void Room::setPinnedMessages(const QStringList& events) +{ + d->requestSetState(events); +} void Room::setLocalAliases(const QStringList& aliases) { d->requestSetState(canonicalAlias(), aliases); diff --git a/lib/room.h b/lib/room.h index a8275ce9..60eadacc 100644 --- a/lib/room.h +++ b/lib/room.h @@ -191,6 +191,7 @@ public: QStringList altAliases() const; QStringList aliases() const; QString displayName() const; + QList pinnedEvents() const; QString topic() const; QString avatarMediaId() const; QUrl avatarUrl() const; @@ -566,6 +567,7 @@ public Q_SLOTS: SetRoomStateWithKeyJob* setState(const StateEventBase& evt) const; void setName(const QString& newName); void setCanonicalAlias(const QString& newAlias); + void setPinnedMessages(const QStringList& events); /// Set room aliases on the user's current server void setLocalAliases(const QStringList& aliases); void setTopic(const QString& newTopic); diff --git a/libQuotient.kdev4 b/libQuotient.kdev4 new file mode 100644 index 00000000..2afdb72d --- /dev/null +++ b/libQuotient.kdev4 @@ -0,0 +1,4 @@ +[Project] +CreatedFrom=CMakeLists.txt +Manager=KDevCMakeManager +Name=libQuotient -- cgit v1.2.3 From 17bf4d180297c7e87363e179b8afa79ddb15dca7 Mon Sep 17 00:00:00 2001 From: Arnav Rawat Date: Tue, 25 May 2021 14:01:51 -0500 Subject: Fixes --- lib/events/simplestateevents.h | 18 +----------------- lib/room.cpp | 16 ++++++++++++---- lib/room.h | 9 +++++++-- 3 files changed, 20 insertions(+), 23 deletions(-) (limited to 'lib/events') diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index f22f313d..c977cb6e 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -55,6 +55,7 @@ namespace EventContent { DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic) +DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", QStringList, pinnedEvents) class RoomAliasesEvent : public StateEvent> { @@ -71,21 +72,4 @@ public: QStringList aliases() const { return content().value; } }; REGISTER_EVENT_TYPE(RoomAliasesEvent) - -class RoomPinnedEvent - : public StateEvent> -{ -public: - DEFINE_EVENT_TYPEID("m.room.pinned_messages", RoomPinnedEvent) - - explicit RoomPinnedEvent(const QJsonObject& json) - : StateEvent(typeId(), json, QStringLiteral("pinned")) - { } - explicit RoomPinnedEvent(const QStringList& roomEvents) - : StateEvent(typeId(), matrixTypeId(), {}, - QStringLiteral("pinned"), roomEvents) - { } - QStringList pinnedEvents() const { return content().value; } -}; -REGISTER_EVENT_TYPE(RoomPinnedEvent) } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index ed07868b..2a9cc0d8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -557,13 +557,17 @@ QString Room::canonicalAlias() const QString Room::displayName() const { return d->displayname; } -QList Room::pinnedEvents() const +QStringList Room::pinnedEventIds() const { + return d->getCurrentState()->pinnedEvents(); +} + +QVector< const Quotient::RoomEvent* > Quotient::Room::pinnedEvents() const { QStringList events = d->getCurrentState()->pinnedEvents(); - QList pinnedEvents; + QVector pinnedEvents; QStringList::iterator i; for (i = events.begin(); i != events.end(); ++i) { - auto timelineItem = findInTimeline(*i); + auto timelineItem = findInTimeline(*i); if (timelineItem != historyEdge()) pinnedEvents.append(timelineItem->event()); } @@ -1843,7 +1847,7 @@ void Room::setCanonicalAlias(const QString& newAlias) d->requestSetState(newAlias, altAliases()); } -void Room::setPinnedMessages(const QStringList& events) +void Room::setPinnedEvents(const QStringList& events) { d->requestSetState(events); } @@ -2610,6 +2614,10 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return AliasesChange; // clang-format off } + , [this] (const RoomPinnedEvent&) { + emit pinnedEventsChanged(); + return OtherChange; + } , [] (const RoomTopicEvent&) { return TopicChange; } diff --git a/lib/room.h b/lib/room.h index 60eadacc..23c0c846 100644 --- a/lib/room.h +++ b/lib/room.h @@ -85,6 +85,8 @@ class Room : public QObject { Q_PROPERTY(QStringList altAliases READ altAliases NOTIFY namesChanged) Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged) Q_PROPERTY(QString displayName READ displayName NOTIFY displaynameChanged) + Q_PROPERTY(QStringList pinnedEventIds READ pinnedEventIds WRITE setPinnedEvents + NOTIFY pinnedEventsChanged) Q_PROPERTY(QString topic READ topic NOTIFY topicChanged) Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false) @@ -191,7 +193,9 @@ public: QStringList altAliases() const; QStringList aliases() const; QString displayName() const; - QList pinnedEvents() const; + QStringList pinnedEventIds() const; + // Returns events available locally, use pinnedEventIds() for full list + QVector pinnedEvents() const; QString topic() const; QString avatarMediaId() const; QUrl avatarUrl() const; @@ -567,7 +571,7 @@ public Q_SLOTS: SetRoomStateWithKeyJob* setState(const StateEventBase& evt) const; void setName(const QString& newName); void setCanonicalAlias(const QString& newAlias); - void setPinnedMessages(const QStringList& events); + void setPinnedEvents(const QStringList& events); /// Set room aliases on the user's current server void setLocalAliases(const QStringList& aliases); void setTopic(const QString& newTopic); @@ -663,6 +667,7 @@ Q_SIGNALS: void namesChanged(Quotient::Room* room); void displaynameAboutToChange(Quotient::Room* room); void displaynameChanged(Quotient::Room* room, QString oldName); + void pinnedEventsChanged(); void topicChanged(); void avatarChanged(); void userAdded(Quotient::User* user); -- cgit v1.2.3 From 67ea5b45701e6bd5bf244039dc60a134d67a4cab Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 13 Jun 2021 14:08:52 +0200 Subject: Disable the piece depending on Qt Multimedia for Qt 6 Waiting for the Multimedia arrival in Qt 6.2. --- lib/events/roommessageevent.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 31c0fd9e..3f6e475d 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -10,7 +10,9 @@ #include #include #include -#include +#if QT_VERSION_MAJOR < 6 +# include +#endif using namespace Quotient; using namespace EventContent; @@ -149,7 +151,11 @@ TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) // done by starting to play the file. Left for a future implementation. if (mimeTypeName.startsWith("video/")) return new VideoContent(localUrl, file.size(), mimeType, +#if QT_VERSION_MAJOR < 6 QMediaResource(localUrl).resolution(), +#else + {}, +#endif file.fileName()); if (mimeTypeName.startsWith("audio/")) -- cgit v1.2.3 From beb3a135a336dca654d967b88ea06a7457fbbec1 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 12 Jun 2021 22:31:25 +0200 Subject: EncryptionEvent: fix "too perfect forwarding" Now that QMetaType introspects into types, it reveals hidden problems (which is very nice of it). --- lib/events/encryptionevent.h | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/events') diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index f9bbab12..65ee4187 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -40,6 +40,7 @@ public: // default value : StateEvent(typeId(), obj) {} + EncryptionEvent(EncryptionEvent&&) = delete; template EncryptionEvent(ArgTs&&... contentArgs) : StateEvent(typeId(), matrixTypeId(), QString(), -- cgit v1.2.3 From 9a5fa623c17f5644da7cdb459ade86bc8e1cdbf3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 16 Jul 2021 20:02:00 +0200 Subject: Officially drop Qt Multimedia with Qt 6 Closes #483. --- CMakeLists.txt | 14 +++++++------- lib/events/roommessageevent.cpp | 6 ++---- lib/events/roommessageevent.h | 4 ++++ 3 files changed, 13 insertions(+), 11 deletions(-) (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 555ffa96..285862df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,14 +75,14 @@ set(CMAKE_AUTOMOC ON) option(BUILD_WITH_QT6 "Build Quotient with Qt 6 (EXPERIMENTAL)" OFF) if (BUILD_WITH_QT6) - find_package(Qt6 6.2 REQUIRED Core Network Gui Test) # TODO: Multimedia - set(Qt Qt6) - qt6_wrap_cpp(lib_SRCS lib/quotient_common.h) + set(QtMinVersion "6.0") else() - find_package(Qt5 5.12 REQUIRED Core Network Gui Multimedia Test) - set(Qt Qt5) + set(QtMinVersion "5.12") + set(QtExtraModules "Multimedia") # See #483 endif() -get_filename_component($Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) +string(REGEX REPLACE "^(.).*" "Qt\\1" Qt ${QtMinVersion}) # makes "Qt5" or "Qt6" +find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModules}) +get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") if (${PROJECT_NAME}_ENABLE_E2EE) @@ -299,7 +299,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE) set(FIND_DEPS "find_dependency(QtOlm)") # For QuotientConfig.cmake.in endif() target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) -if (Qt STREQUAL Qt5) # Qt 6 hasn't got Multimedia component as yet +if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) endif() diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 3f6e475d..71f85363 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -135,6 +135,7 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, MsgType msgType, : RoomMessageEvent(plainBody, msgTypeToJson(msgType), content) {} +#if QT_VERSION_MAJOR < 6 TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) { auto filePath = file.absoluteFilePath(); @@ -151,11 +152,7 @@ TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) // done by starting to play the file. Left for a future implementation. if (mimeTypeName.startsWith("video/")) return new VideoContent(localUrl, file.size(), mimeType, -#if QT_VERSION_MAJOR < 6 QMediaResource(localUrl).resolution(), -#else - {}, -#endif file.fileName()); if (mimeTypeName.startsWith("audio/")) @@ -172,6 +169,7 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, : rawMsgTypeForFile(file), contentFromFile(file, asGenericFile)) {} +#endif RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj), _content(nullptr) diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 8303ce4e..7bcda2ba 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -42,8 +42,12 @@ public: explicit RoomMessageEvent(const QString& plainBody, MsgType msgType = MsgType::Text, EventContent::TypedBase* content = nullptr); +#if QT_VERSION_MAJOR < 6 + [[deprecated("Create an EventContent object on the client side" + " and pass it to other constructors")]] // explicit RoomMessageEvent(const QString& plainBody, const QFileInfo& file, bool asGenericFile = false); +#endif explicit RoomMessageEvent(const QJsonObject& obj); MsgType msgtype() const; -- cgit v1.2.3 From 004ebf8d5ba095ca1b11e30d86cedc2ff8c0cfe7 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 18:22:28 +0200 Subject: Room::postFile(): adjust to the changed RoomMessageEvent API 9a5fa623 dropped one of RoomMessageEvent constructors for Qt 6 in order to address #483 - breaking the build with Qt 6 along the way, as Room::postFile() relied on that constructor. This commit changes Room::postFile() in turn, deprecating the current signature and adding a new one that accepts an EventContent object rather than a path to a file. In order to achieve that, FileInfo and ImageInfo classes have gained new constructors that accept QFileInfo instead of the legacy series of parameters, streamlining usage of EventContent structures. --- lib/events/eventcontent.cpp | 51 +++++++++++++++------ lib/events/eventcontent.h | 23 ++++++---- lib/events/roomavatarevent.h | 4 +- lib/room.cpp | 105 ++++++++++++++++++++++++++----------------- lib/room.h | 5 +++ quotest/quotest.cpp | 9 ++-- 6 files changed, 128 insertions(+), 69 deletions(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index b249b160..1f28f195 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -5,10 +5,13 @@ #include "converters.h" #include "util.h" +#include "logging.h" #include +#include using namespace Quotient::EventContent; +using std::move; QJsonObject Base::toJson() const { @@ -17,22 +20,37 @@ QJsonObject Base::toJson() const return o; } -FileInfo::FileInfo(const QUrl& u, qint64 payloadSize, const QMimeType& mimeType, - const QString& originalFilename) +FileInfo::FileInfo(const QFileInfo &fi) + : mimeType(QMimeDatabase().mimeTypeForFile(fi)) + , url(QUrl::fromLocalFile(fi.filePath())) + , payloadSize(fi.size()) + , originalName(fi.fileName()) +{ + Q_ASSERT(fi.isFile()); +} + +FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, + QString originalFilename) : mimeType(mimeType) - , url(u) + , url(move(u)) , payloadSize(payloadSize) - , originalName(originalFilename) -{} + , originalName(move(originalFilename)) +{ + if (!isValid()) + qCWarning(MESSAGES) + << "To client developers: using FileInfo(QUrl, qint64, ...) " + "constructor for non-mxc resources is deprecated since Quotient " + "0.7; for local resources, use FileInfo(QFileInfo) instead"; +} -FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename) +FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, + QString originalFilename) : originalInfoJson(infoJson) , mimeType( QMimeDatabase().mimeTypeForName(infoJson["mimetype"_ls].toString())) - , url(u) + , url(move(mxcUrl)) , payloadSize(fromJson(infoJson["size"_ls])) - , originalName(originalFilename) + , originalName(move(originalFilename)) { if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); @@ -53,14 +71,19 @@ void FileInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert(QStringLiteral("mimetype"), mimeType.name()); } -ImageInfo::ImageInfo(const QUrl& u, qint64 fileSize, QMimeType mimeType, - const QSize& imageSize, const QString& originalFilename) - : FileInfo(u, fileSize, mimeType, originalFilename), imageSize(imageSize) +ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) + : FileInfo(fi), imageSize(imageSize) +{} + +ImageInfo::ImageInfo(const QUrl& mxcUrl, qint64 fileSize, const QMimeType& type, + QSize imageSize, const QString& originalFilename) + : FileInfo(mxcUrl, fileSize, type, originalFilename) + , imageSize(imageSize) {} -ImageInfo::ImageInfo(const QUrl& u, const QJsonObject& infoJson, +ImageInfo::ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, const QString& originalFilename) - : FileInfo(u, infoJson, originalFilename) + : FileInfo(mxcUrl, infoJson, originalFilename) , imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt()) {} diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 60d1f7b7..78c5b287 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -10,7 +10,8 @@ #include #include #include -#include + +class QFileInfo; namespace Quotient { namespace EventContent { @@ -73,11 +74,13 @@ namespace EventContent { */ class FileInfo { public: - explicit FileInfo(const QUrl& u, qint64 payloadSize = -1, + FileInfo() = default; + explicit FileInfo(const QFileInfo& fi); + explicit FileInfo(QUrl mxcUrl, qint64 payloadSize = -1, const QMimeType& mimeType = {}, - const QString& originalFilename = {}); - FileInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}); + QString originalFilename = {}); + FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, + QString originalFilename = {}); bool isValid() const; @@ -113,10 +116,12 @@ namespace EventContent { */ class ImageInfo : public FileInfo { public: - explicit ImageInfo(const QUrl& u, qint64 fileSize = -1, - QMimeType mimeType = {}, const QSize& imageSize = {}, + ImageInfo() = default; + explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); + explicit ImageInfo(const QUrl& mxcUrl, qint64 fileSize = -1, + const QMimeType& type = {}, QSize imageSize = {}, const QString& originalFilename = {}); - ImageInfo(const QUrl& u, const QJsonObject& infoJson, + ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, const QString& originalFilename = {}); void fillInfoJson(QJsonObject* infoJson) const; @@ -134,7 +139,7 @@ namespace EventContent { */ class Thumbnail : public ImageInfo { public: - Thumbnail() : ImageInfo(QUrl()) {} // To allow empty thumbnails + Thumbnail() = default; // Allow empty thumbnails Thumbnail(const QJsonObject& infoJson); Thumbnail(const ImageInfo& info) : ImageInfo(info) {} using ImageInfo::ImageInfo; diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index a4257895..3fa11a0f 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -20,12 +20,12 @@ public: : StateEvent(typeId(), matrixTypeId(), QString(), avatar) {} // A replica of EventContent::ImageInfo constructor - explicit RoomAvatarEvent(const QUrl& u, qint64 fileSize = -1, + explicit RoomAvatarEvent(const QUrl& mxcUrl, qint64 fileSize = -1, QMimeType mimeType = {}, const QSize& imageSize = {}, const QString& originalFilename = {}) : RoomAvatarEvent(EventContent::ImageContent { - u, fileSize, mimeType, imageSize, originalFilename }) + mxcUrl, fileSize, mimeType, imageSize, originalFilename }) {} QUrl url() const { return content().url; } diff --git a/lib/room.cpp b/lib/room.cpp index 0984bd96..6b729b8f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -306,6 +306,8 @@ public: return sendEvent(makeEvent(std::forward(eventArgs)...)); } + QString doPostFile(RoomEventPtr &&msgEvent, const QUrl &localUrl); + RoomEvent* addAsPending(RoomEventPtr&& event); QString doSendEvent(const RoomEvent* pEvent); @@ -1756,57 +1758,80 @@ QString Room::postReaction(const QString& eventId, const QString& key) return d->sendEvent(EventRelation::annotate(eventId, key)); } -QString Room::postFile(const QString& plainText, const QUrl& localPath, - bool asGenericFile) +QString Room::Private::doPostFile(RoomEventPtr&& msgEvent, const QUrl& localUrl) { - QFileInfo localFile { localPath.toLocalFile() }; - Q_ASSERT(localFile.isFile()); - - const auto txnId = - d->addAsPending( - makeEvent(plainText, localFile, asGenericFile)) - ->transactionId(); + const auto txnId = addAsPending(move(msgEvent))->transactionId(); // Remote URL will only be known after upload; fill in the local path // to enable the preview while the event is pending. - uploadFile(txnId, localPath); + q->uploadFile(txnId, localUrl); // Below, the upload job is used as a context object to clean up connections - const auto& transferJob = d->fileTransfers.value(txnId).job; - connect(this, &Room::fileTransferCompleted, transferJob, - [this, txnId](const QString& id, const QUrl&, const QUrl& mxcUri) { - if (id == txnId) { - auto it = findPendingEvent(txnId); - if (it != d->unsyncedEvents.end()) { - it->setFileUploaded(mxcUri); - emit pendingEventChanged( - int(it - d->unsyncedEvents.begin())); - d->doSendEvent(it->get()); - } else { - // Normally in this situation we should instruct - // the media server to delete the file; alas, there's no - // API specced for that. - qCWarning(MAIN) << "File uploaded to" << mxcUri - << "but the event referring to it was " - "cancelled"; - } + const auto& transferJob = fileTransfers.value(txnId).job; + connect(q, &Room::fileTransferCompleted, transferJob, + [this, txnId](const QString& tId, const QUrl&, const QUrl& mxcUri) { + if (tId != txnId) + return; + + const auto it = q->findPendingEvent(txnId); + if (it != unsyncedEvents.end()) { + it->setFileUploaded(mxcUri); + emit q->pendingEventChanged( + int(it - unsyncedEvents.begin())); + doSendEvent(it->get()); + } else { + // Normally in this situation we should instruct + // the media server to delete the file; alas, there's no + // API specced for that. + qCWarning(MAIN) << "File uploaded to" << mxcUri + << "but the event referring to it was " + "cancelled"; } }); - connect(this, &Room::fileTransferCancelled, transferJob, - [this, txnId](const QString& id) { - if (id == txnId) { - auto it = findPendingEvent(txnId); - if (it != d->unsyncedEvents.end()) { - const auto idx = int(it - d->unsyncedEvents.begin()); - emit pendingEventAboutToDiscard(idx); - // See #286 on why iterator may not be valid here. - d->unsyncedEvents.erase(d->unsyncedEvents.begin() + idx); - emit pendingEventDiscarded(); - } - } + connect(q, &Room::fileTransferCancelled, transferJob, + [this, txnId](const QString& tId) { + if (tId != txnId) + return; + + const auto it = q->findPendingEvent(txnId); + if (it == unsyncedEvents.end()) + return; + + const auto idx = int(it - unsyncedEvents.begin()); + emit q->pendingEventAboutToDiscard(idx); + // See #286 on why `it` may not be valid here. + unsyncedEvents.erase(unsyncedEvents.begin() + idx); + emit q->pendingEventDiscarded(); }); return txnId; } +QString Room::postFile(const QString& plainText, + EventContent::TypedBase* content) +{ + Q_ASSERT(content != nullptr && content->fileInfo() != nullptr); + const auto* const fileInfo = content->fileInfo(); + Q_ASSERT(fileInfo != nullptr); + QFileInfo localFile { fileInfo->url.toLocalFile() }; + Q_ASSERT(localFile.isFile()); + + return d->doPostFile( + makeEvent( + plainText, RoomMessageEvent::rawMsgTypeForFile(localFile), content), + fileInfo->url); +} + +#if QT_VERSION_MAJOR < 6 +QString Room::postFile(const QString& plainText, const QUrl& localPath, + bool asGenericFile) +{ + QFileInfo localFile { localPath.toLocalFile() }; + Q_ASSERT(localFile.isFile()); + return d->doPostFile(makeEvent(plainText, localFile, + asGenericFile), + localPath); +} +#endif + QString Room::postEvent(RoomEvent* event) { return d->sendEvent(RoomEventPtr(event)); diff --git a/lib/room.h b/lib/room.h index 26d0121e..d71bff9c 100644 --- a/lib/room.h +++ b/lib/room.h @@ -549,8 +549,13 @@ public Q_SLOTS: QString postHtmlText(const QString& plainText, const QString& html); /// Send a reaction on a given event with a given key QString postReaction(const QString& eventId, const QString& key); + + QString postFile(const QString& plainText, EventContent::TypedBase* content); +#if QT_VERSION_MAJOR < 6 + /// \deprecated Use postFile(QString, MessageEventType, EventContent) instead QString postFile(const QString& plainText, const QUrl& localPath, bool asGenericFile = false); +#endif /** Post a pre-created room message event * * Takes ownership of the event, deleting it once the matching one diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index a0914c72..5646d54d 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -397,15 +397,16 @@ TEST_IMPL(sendFile) } tf->write("Test"); tf->close(); + QFileInfo tfi { *tf }; // QFileInfo::fileName brings only the file name; QFile::fileName brings // the full path - const auto tfName = QFileInfo(*tf).fileName(); + const auto tfName = tfi.fileName(); clog << "Sending file " << tfName.toStdString() << endl; - const auto txnId = - targetRoom->postFile("Test file", QUrl::fromLocalFile(tf->fileName())); + const auto txnId = targetRoom->postFile( + "Test file", new EventContent::FileContent(tfi)); if (!validatePendingEvent(txnId)) { clog << "Invalid pending event right after submitting" << endl; - delete tf; + tf->deleteLater(); FAIL_TEST(); } -- cgit v1.2.3 From 86fe0a8f0682c0122439d53cc96b4b742a69ffcf Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 18:24:39 +0200 Subject: Make EventContent::TypedBase() constructor protected TypedBase is an abstract class; constructing it doesn't make sense. But even if it were not abstract, it's not supposed to be instantiated. --- lib/events/eventcontent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 78c5b287..b3a5f280 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -153,13 +153,13 @@ namespace EventContent { class TypedBase : public Base { public: - explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} virtual QMimeType type() const = 0; virtual const FileInfo* fileInfo() const { return nullptr; } virtual FileInfo* fileInfo() { return nullptr; } virtual const Thumbnail* thumbnailInfo() const { return nullptr; } protected: + explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} using Base::Base; }; -- cgit v1.2.3 From 6d24915e4bdd56dbdace8358297ee9d2d9aa83a0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 18:53:57 +0200 Subject: Revert previous commit Q_DECLARE_METATYPE is really unhappy about types without a public default constructor. --- lib/events/eventcontent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index b3a5f280..78c5b287 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -153,13 +153,13 @@ namespace EventContent { class TypedBase : public Base { public: + explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} virtual QMimeType type() const = 0; virtual const FileInfo* fileInfo() const { return nullptr; } virtual FileInfo* fileInfo() { return nullptr; } virtual const Thumbnail* thumbnailInfo() const { return nullptr; } protected: - explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} using Base::Base; }; -- cgit v1.2.3 From 110190d48a80a471e6d10d048602390b35e7ed07 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 18:24:39 +0200 Subject: Re-apply the previous commit and actually fix the breakage Ok, it was stupid to delete #include in 004ebf8d and then to expect that Qt macros would still work, given that I don't use QObject. In my defense I can only say that with Qt 6 it still compiled. --- lib/events/eventcontent.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 78c5b287..1d81bd72 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -10,6 +10,7 @@ #include #include #include +#include class QFileInfo; @@ -153,13 +154,13 @@ namespace EventContent { class TypedBase : public Base { public: - explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} virtual QMimeType type() const = 0; virtual const FileInfo* fileInfo() const { return nullptr; } virtual FileInfo* fileInfo() { return nullptr; } virtual const Thumbnail* thumbnailInfo() const { return nullptr; } protected: + explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} using Base::Base; }; -- 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. --- CMakeLists.txt | 6 +--- lib/connection.cpp | 4 +-- lib/connection.h | 1 - lib/events/roommemberevent.cpp | 65 ++++++++++++++++++++-------------------- lib/events/roommemberevent.h | 26 ++++++++-------- lib/joinstate.h | 32 -------------------- lib/quotient_common.cpp | 45 ++++++++++++++++++++++++++++ lib/quotient_common.h | 67 +++++++++++++++++++++++++++++++++++++----- lib/room.cpp | 49 ++++++++++++++++-------------- lib/room.h | 9 +++++- lib/syncdata.h | 2 +- lib/user.cpp | 2 +- 12 files changed, 190 insertions(+), 118 deletions(-) delete mode 100644 lib/joinstate.h create mode 100644 lib/quotient_common.cpp (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index deb50aea..49105389 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,11 +122,7 @@ endif () # Set up source files list(APPEND lib_SRCS - # This .h is special in that it declares a Q_NAMESPACE but has no .cpp - # where staticMetaObject for that namespace would be defined; passing it - # to add_library (see below) puts it on the automoc radar, producing - # a compilation unit with the needed definition. - lib/quotient_common.h + lib/quotient_common.cpp lib/networkaccessmanager.cpp lib/connectiondata.cpp 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(); } diff --git a/lib/connection.h b/lib/connection.h index 0d22d01f..a7a071f3 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -6,7 +6,6 @@ #pragma once #include "ssosession.h" -#include "joinstate.h" #include "qt_connection_util.h" #include "quotient_common.h" diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 9634ca3a..8a6bddd8 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -7,27 +7,26 @@ #include "converters.h" #include "logging.h" -#include - -static const std::array membershipStrings = { - { QStringLiteral("invite"), QStringLiteral("join"), QStringLiteral("knock"), - QStringLiteral("leave"), QStringLiteral("ban") } -}; +#include namespace Quotient { template <> -struct JsonConverter { - static MembershipType load(const QJsonValue& jv) +struct JsonConverter { + static Membership load(const QJsonValue& jv) { - const auto& membershipString = jv.toString(); - for (auto it = membershipStrings.begin(); it != membershipStrings.end(); - ++it) - if (membershipString == *it) - return MembershipType(it - membershipStrings.begin()); - - if (!membershipString.isEmpty()) - qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString; - return MembershipType::Undefined; + const auto& ms = jv.toString(); + if (ms.isEmpty()) + { + qCWarning(EVENTS) << "Empty member state:" << ms; + return Membership::Invalid; + } + const auto it = + std::find(MembershipStrings.begin(), MembershipStrings.end(), ms); + if (it != MembershipStrings.end()) + return Membership(1U << (it - MembershipStrings.begin())); + + qCWarning(EVENTS) << "Unknown Membership value: " << ms; + return Membership::Invalid; } }; } // namespace Quotient @@ -35,7 +34,7 @@ struct JsonConverter { using namespace Quotient; MemberEventContent::MemberEventContent(const QJsonObject& json) - : membership(fromJson(json["membership"_ls])) + : membership(fromJson(json["membership"_ls])) , isDirect(json["is_direct"_ls].toBool()) , displayName(fromJson>(json["displayname"_ls])) , avatarUrl(fromJson>(json["avatar_url"_ls])) @@ -48,10 +47,12 @@ MemberEventContent::MemberEventContent(const QJsonObject& json) void MemberEventContent::fillJson(QJsonObject* o) const { Q_ASSERT(o); - Q_ASSERT_X(membership != MembershipType::Undefined, __FUNCTION__, - "The key 'membership' must be explicit in MemberEventContent"); - if (membership != MembershipType::Undefined) - o->insert(QStringLiteral("membership"), membershipStrings[membership]); + if (membership != Membership::Invalid) + o->insert( + QStringLiteral("membership"), + MembershipStrings[qCountTrailingZeroBits( + std::underlying_type_t(membership)) + + 1]); if (displayName) o->insert(QStringLiteral("displayname"), *displayName); if (avatarUrl && avatarUrl->isValid()) @@ -67,37 +68,37 @@ bool RoomMemberEvent::changesMembership() const bool RoomMemberEvent::isInvite() const { - return membership() == MembershipType::Invite && changesMembership(); + return membership() == Membership::Invite && changesMembership(); } bool RoomMemberEvent::isRejectedInvite() const { - return membership() == MembershipType::Leave && prevContent() - && prevContent()->membership == MembershipType::Invite; + return membership() == Membership::Leave && prevContent() + && prevContent()->membership == Membership::Invite; } bool RoomMemberEvent::isJoin() const { - return membership() == MembershipType::Join && changesMembership(); + return membership() == Membership::Join && changesMembership(); } bool RoomMemberEvent::isLeave() const { - return membership() == MembershipType::Leave && prevContent() + return membership() == Membership::Leave && prevContent() && prevContent()->membership != membership() - && prevContent()->membership != MembershipType::Ban - && prevContent()->membership != MembershipType::Invite; + && prevContent()->membership != Membership::Ban + && prevContent()->membership != Membership::Invite; } bool RoomMemberEvent::isBan() const { - return membership() == MembershipType::Ban && changesMembership(); + return membership() == Membership::Ban && changesMembership(); } bool RoomMemberEvent::isUnban() const { - return membership() == MembershipType::Leave && prevContent() - && prevContent()->membership == MembershipType::Ban; + return membership() == Membership::Leave && prevContent() + && prevContent()->membership == Membership::Ban; } bool RoomMemberEvent::isRename() const diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index f2fbe689..f3047159 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -7,23 +7,21 @@ #include "eventcontent.h" #include "stateevent.h" +#include "quotient_common.h" namespace Quotient { class MemberEventContent : public EventContent::Base { public: - enum MembershipType : unsigned char { - Invite = 0, - Join, - Knock, - Leave, - Ban, - Undefined - }; + using MembershipType + [[deprecated("Use Quotient::Membership instead")]] = Membership; - explicit MemberEventContent(MembershipType mt = Join) : membership(mt) {} + explicit MemberEventContent(Membership ms = Membership::Join) + : membership(ms) + {} explicit MemberEventContent(const QJsonObject& json); - MembershipType membership; + Membership membership; + /// (Only for invites) Whether the invite is to a direct chat bool isDirect = false; Omittable displayName; Omittable avatarUrl; @@ -33,15 +31,15 @@ protected: void fillJson(QJsonObject* o) const override; }; -using MembershipType = MemberEventContent::MembershipType; +using MembershipType [[deprecated("Use Membership instead")]] = Membership; class RoomMemberEvent : public StateEvent { Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent) - using MembershipType = MemberEventContent::MembershipType; - Q_ENUM(MembershipType) + using MembershipType + [[deprecated("Use Quotient::Membership instead")]] = Membership; explicit RoomMemberEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} @@ -65,7 +63,7 @@ public: : StateEvent(type, fullJson) {} - MembershipType membership() const { return content().membership; } + Membership membership() const { return content().membership; } QString userId() const { return stateKey(); } bool isDirect() const { return content().isDirect; } Omittable newDisplayName() const { return content().displayName; } diff --git a/lib/joinstate.h b/lib/joinstate.h deleted file mode 100644 index 805ce73a..00000000 --- a/lib/joinstate.h +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-FileCopyrightText: 2016 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include - -#include - -namespace Quotient { -enum class JoinState : unsigned int { - Join = 0x1, - Invite = 0x2, - Leave = 0x4, -}; - -Q_DECLARE_FLAGS(JoinStates, JoinState) - -// We cannot use Q_ENUM outside of a Q_OBJECT and besides, we want -// to use strings that match respective JSON keys. -static const std::array JoinStateStrings { { "join", "invite", - "leave" } }; - -inline const char* toCString(JoinState js) -{ - size_t state = size_t(js), index = 0; - while (state >>= 1u) - ++index; - return JoinStateStrings[index]; -} -} // namespace Quotient -Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::JoinStates) diff --git a/lib/quotient_common.cpp b/lib/quotient_common.cpp new file mode 100644 index 00000000..805070d1 --- /dev/null +++ b/lib/quotient_common.cpp @@ -0,0 +1,45 @@ +#include "quotient_common.h" + +#include + +using namespace Quotient; + +template +inline QDebug suppressScopeAndDump(QDebug dbg, Enum e) +{ + // Suppress "Quotient::" prefix + QDebugStateSaver _dss(dbg); + dbg.setVerbosity(QDebug::MinimumVerbosity); + return qt_QMetaEnum_debugOperator(dbg, std::underlying_type_t(e), + qt_getEnumMetaObject(e), + qt_getEnumName(e)); +} + +template +inline QDebug suppressScopeAndDump(QDebug dbg, const QFlags& f) +{ + // Suppress "Quotient::" prefix + QDebugStateSaver _dss(dbg); + dbg.setVerbosity(QDebug::MinimumVerbosity); + return qt_QMetaEnum_flagDebugOperator_helper(dbg, f); +} + +QDebug operator<<(QDebug dbg, Membership m) +{ + return suppressScopeAndDump(dbg, m); +} + +QDebug operator<<(QDebug dbg, MembershipMask mm) +{ + return suppressScopeAndDump(dbg, mm) << ")"; +} + +QDebug operator<<(QDebug dbg, JoinState js) +{ + return suppressScopeAndDump(dbg, js); +} + +QDebug operator<<(QDebug dbg, JoinStates jss) +{ + return suppressScopeAndDump(dbg, jss) << ")"; +} diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 22fdbe94..789128cf 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -8,15 +8,62 @@ namespace Quotient { Q_NAMESPACE -/** Enumeration with flags defining the network job running policy - * So far only background/foreground flags are available. - * - * \sa Connection::callApi, Connection::run - */ +// TODO: code like this should be generated from the CS API definition + +//! \brief Membership states +//! +//! These are used for member events. The names here are case-insensitively +//! equal to state names used on the wire. +//! \sa MemberEventContent, RoomMemberEvent +enum class Membership : unsigned int { + // Specific power-of-2 values (1,2,4,...) are important here as syncdata.cpp + // depends on that, as well as Join being the first in line + Invalid = 0x0, + Join = 0x1, + Leave = 0x2, + Invite = 0x4, + Knock = 0x8, + Ban = 0x10, + Undefined = Invalid +}; +Q_DECLARE_FLAGS(MembershipMask, Membership) +Q_FLAG_NS(MembershipMask) + +constexpr inline std::array MembershipStrings = { + // The order MUST be the same as the order in the original enum + "join", "leave", "invite", "knock", "ban" +}; + +//! \brief Local user join-state names +//! +//! This represents a subset of Membership values that may arrive as the local +//! user's state grouping for the sync response. +//! \sa SyncData +enum class JoinState : std::underlying_type_t { + Invalid = std::underlying_type_t(Membership::Invalid), + Join = std::underlying_type_t(Membership::Join), + Leave = std::underlying_type_t(Membership::Leave), + Invite = std::underlying_type_t(Membership::Invite), + Knock = std::underlying_type_t(Membership::Knock), +}; +Q_DECLARE_FLAGS(JoinStates, JoinState) +Q_FLAG_NS(JoinStates) + +constexpr inline std::array JoinStateStrings { + MembershipStrings[0], MembershipStrings[1], MembershipStrings[2], + MembershipStrings[3] /* same as MembershipStrings, sans "ban" */ +}; + +//! \brief Network job running policy flags +//! +//! So far only background/foreground flags are available. +//! \sa Connection::callApi, Connection::run enum RunningPolicy { ForegroundRequest = 0x0, BackgroundRequest = 0x1 }; Q_ENUM_NS(RunningPolicy) +//! \brief The result of URI resolution using UriResolver +//! \sa UriResolver enum UriResolveResult : short { StillResolving = -1, UriResolved = 0, @@ -28,5 +75,11 @@ enum UriResolveResult : short { Q_ENUM_NS(UriResolveResult) } // namespace Quotient -/// \deprecated Use namespace Quotient instead -namespace QMatrixClient = Quotient; +Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) +Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::JoinStates) + +class QDebug; +QDebug operator<<(QDebug dbg, Quotient::Membership m); +QDebug operator<<(QDebug dbg, Quotient::MembershipMask m); +QDebug operator<<(QDebug dbg, Quotient::JoinState js); +QDebug operator<<(QDebug dbg, Quotient::JoinStates js); diff --git a/lib/room.cpp b/lib/room.cpp index 6b729b8f..54c67c2f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -460,7 +460,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) emit baseStateLoaded(); return this == r; // loadedRoomState fires only once per room }); - qCDebug(STATE) << "New" << toCString(initialJoinState) << "Room:" << id; + qCDebug(STATE) << "New" << initialJoinState << "Room:" << id; } Room::~Room() { delete d; } @@ -603,6 +603,11 @@ JoinState Room::memberJoinState(User* user) const : JoinState::Leave; } +Membership Room::memberState(User* user) const +{ + return d->getCurrentState(user->id())->membership(); +} + JoinState Room::joinState() const { return d->joinState; } void Room::setJoinState(JoinState state) @@ -611,8 +616,8 @@ void Room::setJoinState(JoinState state) if (state == oldState) return; d->joinState = state; - qCDebug(STATE) << "Room" << id() << "changed state: " << int(oldState) - << "->" << int(state); + qCDebug(STATE) << "Room" << id() << "changed state: " << oldState + << "->" << state; emit changed(Change::JoinStateChange); emit joinStateChanged(oldState, state); } @@ -2513,16 +2518,16 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return false; // Stay low and hope for the best... } const auto prevMembership = oldRme ? oldRme->membership() - : MembershipType::Leave; + : Membership::Leave; switch (prevMembership) { - case MembershipType::Invite: + case Membership::Invite: if (rme.membership() != prevMembership) { d->usersInvited.removeOne(u); Q_ASSERT(!d->usersInvited.contains(u)); } break; - case MembershipType::Join: - if (rme.membership() == MembershipType::Join) { + case Membership::Join: + if (rme.membership() == Membership::Join) { // rename/avatar change or no-op if (rme.newDisplayName()) { emit memberAboutToRename(u, *rme.newDisplayName()); @@ -2536,7 +2541,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return false; } } else { - if (rme.membership() == MembershipType::Invite) + if (rme.membership() == Membership::Invite) qCWarning(MAIN) << "Membership change from Join to Invite:" << rme; // whatever the new membership, it's no more Join @@ -2544,16 +2549,16 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) emit userRemoved(u); } break; - case MembershipType::Ban: - case MembershipType::Knock: - case MembershipType::Leave: - if (rme.membership() == MembershipType::Invite - || rme.membership() == MembershipType::Join) { + case Membership::Ban: + case Membership::Knock: + case Membership::Leave: + if (rme.membership() == Membership::Invite + || rme.membership() == Membership::Join) { d->membersLeft.removeOne(u); Q_ASSERT(!d->membersLeft.contains(u)); } break; - case MembershipType::Undefined: + case Membership::Undefined: ; // A warning will be dropped in the post-processing block below } return true; @@ -2636,10 +2641,10 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) static_cast(oldStateEvent); const auto prevMembership = oldMemberEvent ? oldMemberEvent->membership() - : MembershipType::Leave; + : Membership::Leave; switch (evt.membership()) { - case MembershipType::Join: - if (prevMembership != MembershipType::Join) { + case Membership::Join: + if (prevMembership != Membership::Join) { d->insertMemberIntoMap(u); emit userAdded(u); } else { @@ -2651,19 +2656,19 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) emit memberAvatarChanged(u); } break; - case MembershipType::Invite: + case Membership::Invite: if (!d->usersInvited.contains(u)) d->usersInvited.push_back(u); if (u == localUser() && evt.isDirect()) connection()->addToDirectChats(this, user(evt.senderId())); break; - case MembershipType::Knock: - case MembershipType::Ban: - case MembershipType::Leave: + case Membership::Knock: + case Membership::Ban: + case Membership::Leave: if (!d->membersLeft.contains(u)) d->membersLeft.append(u); break; - case MembershipType::Undefined: + case Membership::Undefined: qCWarning(MEMBERS) << "Ignored undefined membership type"; } return MembersChange; diff --git a/lib/room.h b/lib/room.h index d71bff9c..cdbfe58f 100644 --- a/lib/room.h +++ b/lib/room.h @@ -11,7 +11,7 @@ #include "connection.h" #include "eventitem.h" -#include "joinstate.h" +#include "quotient_common.h" #include "csapi/message_pagination.h" @@ -245,6 +245,8 @@ public: /** * \brief Check the join state of a given user in this room * + * \deprecated Use memberState and check against a mask + * * \note Banned and invited users are not tracked separately for now (Leave * will be returned for them). * @@ -252,6 +254,11 @@ public: */ Q_INVOKABLE Quotient::JoinState memberJoinState(Quotient::User* user) const; + //! \brief Check the join state of a given user in this room + //! + //! \return the given user's state with respect to the room + Q_INVOKABLE Quotient::Membership memberState(User* user) const; + //! \brief Get a display name (without disambiguation) for the given member //! //! \sa safeMemberName, htmlSafeMemberName diff --git a/lib/syncdata.h b/lib/syncdata.h index e69bac17..0153bfd6 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -3,7 +3,7 @@ #pragma once -#include "joinstate.h" +#include "quotient_common.h" #include "events/stateevent.h" diff --git a/lib/user.cpp b/lib/user.cpp index 6cc9e161..c97e33a4 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -123,7 +123,7 @@ void User::rename(const QString& newName, const Room* r) } // #481: take the current state and update it with the new name auto evtC = r->getCurrentState(id())->content(); - Q_ASSERT_X(evtC.membership == MembershipType::Join, __FUNCTION__, + Q_ASSERT_X(evtC.membership == Membership::Join, __FUNCTION__, "Attempt to rename a user that's not a room member"); evtC.displayName = sanitized(newName); r->setState(id(), move(evtC)); -- cgit v1.2.3 From e4a08bc431be9a2b680a4cd70f2ceda07c99b7bf Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 18 Jul 2021 20:10:35 +0200 Subject: Fix bit rot in comments --- lib/events/eventcontent.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 1d81bd72..40ec3a49 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -49,13 +49,14 @@ namespace EventContent { // but specific aggregation structure is altered. See doc comments to // each type for the list of available attributes. - // A quick classes inheritance structure follows: + // A quick classes inheritance structure follows (the definitions are + // spread across eventcontent.h and roommessageevent.h): // FileInfo - // FileContent : UrlBasedContent - // AudioContent : UrlBasedContent + // FileContent : UrlWithThumbnailContent + // AudioContent : PlayableContent> // ImageInfo : FileInfo + imageSize attribute - // ImageContent : UrlBasedContent - // VideoContent : UrlBasedContent + // ImageContent : UrlWithThumbnailContent + // VideoContent : PlayableContent> /** * A base/mixin class for structures representing an "info" object for -- cgit v1.2.3 From e0d9125de7ac132c2a54152015687abbe5e73193 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 30 Jul 2021 08:10:48 +0200 Subject: Room: drop 0.6 deprecations; deprecate RoomAliasEvent Namely memberCount(), localAliases(), remoteAliases(), timelineEdge(). --- lib/events/simplestateevents.h | 6 ++++-- lib/room.cpp | 30 +++++------------------------- lib/room.h | 14 ++------------ 3 files changed, 11 insertions(+), 39 deletions(-) (limited to 'lib/events') diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index d6261a8f..3bac54e6 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -56,8 +56,10 @@ namespace EventContent { DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic) -class RoomAliasesEvent - : public StateEvent> { +class [[deprecated( + "m.room.aliases events are deprecated by the Matrix spec; use" + " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases")]] // +RoomAliasesEvent : public StateEvent> { public: DEFINE_EVENT_TYPEID("m.room.aliases", RoomAliasesEvent) explicit RoomAliasesEvent(const QJsonObject& obj) diff --git a/lib/room.cpp b/lib/room.cpp index 10e827d7..f223e0b9 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -540,21 +540,6 @@ QStringList Room::altAliases() const return d->getCurrentState()->altAliases(); } -QStringList Room::localAliases() const -{ - return d->getCurrentState( - connection()->domain()) - ->aliases(); -} - -QStringList Room::remoteAliases() const -{ - QStringList result; - for (const auto& s : std::as_const(d->aliasServers)) - result += d->getCurrentState(s)->aliases(); - return result; -} - QString Room::canonicalAlias() const { return d->getCurrentState()->alias(); @@ -793,8 +778,6 @@ Room::Timeline::const_iterator Room::syncEdge() const return d->timeline.cend(); } -Room::rev_iter_t Room::timelineEdge() const { return historyEdge(); } - TimelineItem::index_t Room::minTimelineIndex() const { return d->timeline.empty() ? 0 : d->timeline.front().index(); @@ -813,7 +796,7 @@ bool Room::isValidIndex(TimelineItem::index_t timelineIndex) const Room::rev_iter_t Room::findInTimeline(TimelineItem::index_t index) const { - return timelineEdge() + return historyEdge() - (isValidIndex(index) ? index - minTimelineIndex() + 1 : 0); } @@ -1279,8 +1262,6 @@ QStringList Room::htmlSafeMemberNames() const return res; } -int Room::memberCount() const { return d->membersMap.size(); } - int Room::timelineSize() const { return int(d->timeline.size()); } bool Room::usesEncryption() const @@ -2153,8 +2134,7 @@ RoomEventPtr makeRedacted(const RoomEvent& target, { QStringLiteral("ban"), QStringLiteral("events"), QStringLiteral("events_default"), QStringLiteral("kick"), QStringLiteral("redact"), QStringLiteral("state_default"), - QStringLiteral("users"), QStringLiteral("users_default") } }, - { RoomAliasesEvent::typeId(), { QStringLiteral("aliases") } } + QStringLiteral("users"), QStringLiteral("users_default") } } // , { RoomJoinRules::typeId(), { QStringLiteral("join_rule") } } // , { RoomHistoryVisibility::typeId(), // { QStringLiteral("history_visibility") } } @@ -2743,7 +2723,7 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) << p.receipts.size() << "users"; } const auto newMarker = findInTimeline(p.evtId); - if (newMarker != timelineEdge()) { + if (newMarker != historyEdge()) { for (const Receipt& r : p.receipts) { if (r.userId == connection()->userId()) continue; // FIXME, #185 @@ -2763,7 +2743,7 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) continue; // FIXME, #185 auto u = user(r.userId); if (memberJoinState(u) == JoinState::Join - && readMarker(u) == timelineEdge()) + && readMarker(u) == historyEdge()) changes |= d->setLastReadEvent(u, p.evtId); } } @@ -2791,7 +2771,7 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event) qCDebug(STATE) << "Server-side read marker at" << readEventId; d->serverReadMarker = readEventId; const auto newMarker = findInTimeline(readEventId); - changes |= newMarker != timelineEdge() + changes |= newMarker != historyEdge() ? d->markMessagesAsRead(newMarker) : d->setLastReadEvent(localUser(), readEventId); } diff --git a/lib/room.h b/lib/room.h index a1cb6261..0ab18ef7 100644 --- a/lib/room.h +++ b/lib/room.h @@ -93,7 +93,6 @@ class Room : public QObject { Q_PROPERTY(int timelineSize READ timelineSize NOTIFY addedMessages) Q_PROPERTY(QStringList memberNames READ safeMemberNames NOTIFY memberListChanged) - Q_PROPERTY(int memberCount READ memberCount NOTIFY memberListChanged) Q_PROPERTY(int joinedCount READ joinedCount NOTIFY memberListChanged) Q_PROPERTY(int invitedCount READ invitedCount NOTIFY memberListChanged) Q_PROPERTY(int totalMemberCount READ totalMemberCount NOTIFY memberListChanged) @@ -179,14 +178,9 @@ public: Room* successor(JoinStates statesFilter = JoinState::Invite | JoinState::Join) const; QString name() const; - /// Room aliases defined on the current user's server - /// \sa remoteAliases, setLocalAliases - [[deprecated("Use aliases()")]] QStringList localAliases() const; - /// Room aliases defined on other servers - /// \sa localAliases - [[deprecated("Use aliases()")]] QStringList remoteAliases() const; QString canonicalAlias() const; QStringList altAliases() const; + //! Get a list of both canonical and alternative aliases QStringList aliases() const; QString displayName() const; QString topic() const; @@ -202,8 +196,6 @@ public: QStringList memberNames() const; QStringList safeMemberNames() const; QStringList htmlSafeMemberNames() const; - [[deprecated("Use joinedCount(), invitedCount(), totalMemberCount()")]] - int memberCount() const; int timelineSize() const; bool usesEncryption() const; RoomEventPtr decryptMessage(const EncryptedEvent& encryptedEvent); @@ -320,8 +312,6 @@ public: * arrived event; same as messageEvents().cend() */ Timeline::const_iterator syncEdge() const; - /// \deprecated Use historyEdge instead - rev_iter_t timelineEdge() const; Q_INVOKABLE Quotient::TimelineItem::index_t minTimelineIndex() const; Q_INVOKABLE Quotient::TimelineItem::index_t maxTimelineIndex() const; Q_INVOKABLE bool @@ -387,7 +377,7 @@ public: * events (non-redacted message events from users other than local) * are counted. * - * In a case when readMarker() == timelineEdge() (the local read + * In a case when readMarker() == historyEdge() (the local read * marker is beyond the local timeline) only the bottom limit of * the unread messages number can be estimated (and even that may * be slightly off due to, e.g., redactions of events not loaded -- cgit v1.2.3 From 7ee1681d7640b7e7683f7bb40bf768704a48832c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 30 Jul 2021 08:22:13 +0200 Subject: Clean up after the previous commit RoomAliasesEvent is no more even registered (meaning that the library will load m.room.aliases as unknown state events); quotest code updated to use historyEdge() instead of timelineEdge(). --- lib/events/simplestateevents.h | 1 - quotest/quotest.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 3bac54e6..cf1bfbba 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -72,5 +72,4 @@ public: QString server() const { return stateKey(); } QStringList aliases() const { return content().value; } }; -REGISTER_EVENT_TYPE(RoomAliasesEvent) } // namespace Quotient diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 5646d54d..ec7d4dcb 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -557,7 +557,7 @@ bool TestSuite::checkRedactionOutcome(const QByteArray& thisTest, // redacted at the next sync, or the nearest sync completes with // the unredacted event but the next one brings redaction. auto it = targetRoom->findInTimeline(evtIdToRedact); - if (it == targetRoom->timelineEdge()) + if (it == targetRoom->historyEdge()) return false; // Waiting for the next sync if ((*it)->isRedacted()) { -- cgit v1.2.3 From ea1e849f617f62b3d209b2019e0daa3c6bed50f0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 2 Aug 2021 09:02:31 +0200 Subject: More doc-comments --- lib/events/roomevent.h | 4 ++++ lib/events/roommessageevent.h | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) (limited to 'lib/events') diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index fea509c0..3abd56c0 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -37,6 +37,10 @@ public: } QString roomId() const; QString senderId() const; + //! \brief Determine whether the event has been replaced + //! + //! \return true if this event has been overridden by another event + //! with `"rel_type": "m.replace"`; false otherwise bool isReplaced() const; QString replacedBy() const; bool isRedacted() const { return bool(_redactedBecause); } diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 7bcda2ba..88d3b74c 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -62,9 +62,26 @@ public: _content.data()); } QMimeType mimeType() const; + //! \brief Determine whether the message has text content + //! + //! \return true, if the message type is one of m.text, m.notice, m.emote, + //! or the message type is unspecified (in which case plainBody() + //! can still be examined); false otherwise bool hasTextContent() const; + //! \brief Determine whether the message has a file/attachment + //! + //! \return true, if the message has a data structure corresponding to + //! a file (such as m.file or m.audio); false otherwise bool hasFileContent() const; + //! \brief Determine whether the message has a thumbnail + //! + //! \return true, if the message has a data structure corresponding to + //! a thumbnail (the message type may be one for visual content, + //! such as m.image, or generic binary content, i.e. m.file); + //! false otherwise bool hasThumbnail() const; + //! \brief Obtain id of an event replaced by the current one + //! \sa RoomEvent::isReplaced, RoomEvent::replacedBy QString replacedEvent() const; static QString rawMsgTypeForUrl(const QUrl& url); -- 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 --------------------- lib/connection.h | 45 --------------------------------------------- lib/connectiondata.cpp | 12 ------------ lib/connectiondata.h | 4 ---- lib/events/stateevent.h | 4 ---- lib/jobs/basejob.h | 2 -- lib/user.cpp | 4 ---- lib/user.h | 18 +----------------- 8 files changed, 1 insertion(+), 109 deletions(-) (limited to 'lib/events') 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; diff --git a/lib/connection.h b/lib/connection.h index a7a071f3..ecbb1a19 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -138,15 +138,6 @@ public: explicit Connection(const QUrl& server, QObject* parent = nullptr); ~Connection() override; - /// Get all Invited and Joined rooms - /*! - * \return a hashmap from a composite key - room name and whether - * it's an Invite rather than Join - to room pointers - * \sa allRooms, rooms, roomsWithTag - */ - [[deprecated("Use allRooms(), roomsWithTag() or rooms(joinStates) instead")]] - QHash, Room*> roomMap() const; - /// Get all rooms known within this Connection /*! * This includes Invite, Join and Leave rooms, in no particular order. @@ -524,30 +515,12 @@ public Q_SLOTS: */ void assumeIdentity(const QString& mxId, const QString& accessToken, const QString& deviceId); - /*! \deprecated Use loginWithPassword instead */ - void connectToServer(const QString& userId, const QString& password, - const QString& initialDeviceName, - const QString& deviceId = {}) - { - loginWithPassword(userId, password, initialDeviceName, deviceId); - } - /*! \deprecated - * Use assumeIdentity() if you have an access token or - * loginWithToken() if you have a login token. - */ - void connectWithToken(const QString& userId, const QString& accessToken, - const QString& deviceId) - { - assumeIdentity(userId, accessToken, deviceId); - } /// Explicitly request capabilities from the server void reloadCapabilities(); /// Find out if capabilites are still loading from the server bool loadingCapabilities() const; - /** @deprecated Use stopSync() instead */ - void disconnectFromServer() { stopSync(); } void logout(); void sync(int timeout = -1); @@ -662,24 +635,7 @@ public Q_SLOTS: /** \deprecated Do not use this directly, use Room::leaveRoom() instead */ virtual LeaveRoomJob* leaveRoom(Room* room); - // Old API that will be abolished any time soon. DO NOT USE. - - /** @deprecated Use callApi() or Room::postReceipt() instead - */ - virtual PostReceiptJob* postReceipt(Room* room, RoomEvent* event); - Q_SIGNALS: - /** - * @deprecated - * This was a signal resulting from a successful resolveServer(). - * Since Connection now provides setHomeserver(), the HS URL - * may change even without resolveServer() invocation. Use - * loginFLowsChanged() instead of resolved(). You can also use - * loginWith*() and assumeIdentity() without the HS URL set in - * advance (i.e. without calling resolveServer), as they trigger - * server name resolution from MXID if the server URL is not valid. - */ - void resolved(); void resolveError(QString error); void homeserverChanged(QUrl baseUrl); @@ -687,7 +643,6 @@ Q_SIGNALS: void capabilitiesLoaded(); void connected(); - void reconnected(); //< \deprecated Use connected() instead void loggedOut(); /** Login data or state have changed * diff --git a/lib/connectiondata.cpp b/lib/connectiondata.cpp index e54d909b..87ad4577 100644 --- a/lib/connectiondata.cpp +++ b/lib/connectiondata.cpp @@ -118,18 +118,6 @@ void ConnectionData::setToken(QByteArray token) d->accessToken = std::move(token); } -void ConnectionData::setHost(QString host) -{ - d->baseUrl.setHost(host); - qCDebug(MAIN) << "updated baseUrl to" << d->baseUrl; -} - -void ConnectionData::setPort(int port) -{ - d->baseUrl.setPort(port); - qCDebug(MAIN) << "updated baseUrl to" << d->baseUrl; -} - const QString& ConnectionData::deviceId() const { return d->deviceId; } const QString& ConnectionData::userId() const { return d->userId; } diff --git a/lib/connectiondata.h b/lib/connectiondata.h index 7dd96f26..e16a2dac 100644 --- a/lib/connectiondata.h +++ b/lib/connectiondata.h @@ -31,10 +31,6 @@ public: void setBaseUrl(QUrl baseUrl); void setToken(QByteArray accessToken); - [[deprecated("Use setBaseUrl() instead")]] - void setHost(QString host); - [[deprecated("Use setBaseUrl() instead")]] - void setPort(int port); void setDeviceId(const QString& deviceId); void setUserId(const QString& userId); void setNeedsToken(const QString& requestName); diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 1415f709..bc414a5f 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -100,10 +100,6 @@ public: visitor(_content); editJson()[ContentKeyL] = _content.toJson(); } - [[deprecated("Use prevContent instead")]] const ContentT* prev_content() const - { - return prevContent(); - } const ContentT* prevContent() const { return _prev ? &_prev->content : nullptr; diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 7750fb8b..835cd822 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -52,8 +52,6 @@ public: IncorrectRequestError = IncorrectRequest, IncorrectResponse, IncorrectResponseError = IncorrectResponse, - JsonParseError //< \deprecated Use IncorrectResponse instead - = IncorrectResponse, TooManyRequests, TooManyRequestsError = TooManyRequests, RateLimited = TooManyRequests, diff --git a/lib/user.cpp b/lib/user.cpp index a4abed37..60920a59 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -93,8 +93,6 @@ QString User::name(const Room* room) const return room ? room->memberName(id()) : d->defaultName; } -QString User::rawName(const Room* room) const { return name(room); } - void User::rename(const QString& newName) { const auto actualNewName = sanitized(newName); @@ -185,8 +183,6 @@ QString User::fullName(const Room* room) const return displayName.isEmpty() ? id() : (displayName % " (" % id() % ')'); } -QString User::bridged() const { return {}; } - const Avatar& User::avatarObject(const Room* room) const { if (!room) diff --git a/lib/user.h b/lib/user.h index 4ff62951..78b72bf2 100644 --- a/lib/user.h +++ b/lib/user.h @@ -22,7 +22,6 @@ class User : public QObject { Q_PROPERTY(QString name READ name NOTIFY defaultNameChanged) Q_PROPERTY(QString displayName READ displayname NOTIFY defaultNameChanged STORED false) Q_PROPERTY(QString fullName READ fullName NOTIFY defaultNameChanged STORED false) - Q_PROPERTY(QString bridgeName READ bridged NOTIFY defaultNameChanged STORED false) Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY defaultAvatarChanged STORED false) Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY defaultAvatarChanged) public: @@ -40,18 +39,10 @@ public: * This may be empty if the user didn't choose the name or cleared * it. If the user is bridged, the bridge postfix (such as '(IRC)') * is stripped out. No disambiguation for the room is done. - * \sa displayName, rawName + * \sa displayName */ QString name(const Room* room = nullptr) const; - /** Get the user name along with the bridge postfix - * This function is similar to name() but appends the bridge postfix - * (such as '(IRC)') to the user name. No disambiguation is done. - * \sa name, displayName - */ - [[deprecated("Bridge postfixes exist no more, use name() instead")]] - QString rawName(const Room* room = nullptr) const; - /** Get the displayed user name * When \p room is null, this method returns result of name() if * the name is non-empty; otherwise it returns user id. @@ -70,13 +61,6 @@ public: */ QString fullName(const Room* room = nullptr) const; - /** - * Returns the name of bridge the user is connected from or empty. - */ - [[deprecated("Bridged status is no more supported; this always returns" - " an empty string")]] - QString bridged() const; - /** Whether the user is a guest * As of now, the function relies on the convention used in Synapse * that guests and only guests have all-numeric IDs. This may or -- cgit v1.2.3 From fd42d1dbd29800ef53ab9997c948f39a92aa8bff Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 22 Aug 2021 20:23:42 +0200 Subject: RoomEvent: drop timestamp() Use originTimestamp(); the corresponding Q_PROPERTY was not renamed (in error) so it is now. --- lib/events/roomevent.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 3abd56c0..3174764f 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -14,7 +14,9 @@ class RedactionEvent; class RoomEvent : public Event { Q_GADGET Q_PROPERTY(QString id READ id) - Q_PROPERTY(QDateTime timestamp READ timestamp CONSTANT) + //! \deprecated Use originTimestamp instead + Q_PROPERTY(QDateTime timestamp READ originTimestamp CONSTANT) + Q_PROPERTY(QDateTime originTimestamp READ originTimestamp CONSTANT) Q_PROPERTY(QString roomId READ roomId CONSTANT) Q_PROPERTY(QString senderId READ senderId CONSTANT) Q_PROPERTY(QString redactionReason READ redactionReason) @@ -32,9 +34,6 @@ public: QString id() const; QDateTime originTimestamp() const; - [[deprecated("Use originTimestamp()")]] QDateTime timestamp() const { - return originTimestamp(); - } QString roomId() const; QString senderId() const; //! \brief Determine whether the event has been replaced -- cgit v1.2.3 From 0209be8305aa38722a3d25593ae71fbb3ac05e52 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 11 Sep 2021 16:18:35 +0200 Subject: Add convenience function for activating encryption and fix EncryptionEvent constructor --- lib/events/encryptionevent.cpp | 8 ++++++++ lib/events/encryptionevent.h | 4 +--- lib/room.cpp | 9 +++++++++ lib/room.h | 6 ++++++ 4 files changed, 24 insertions(+), 3 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 490a5e8a..aa05a96e 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -39,6 +39,14 @@ EncryptionEventContent::EncryptionEventContent(const QJsonObject& json) , rotationPeriodMsgs(json[RotationPeriodMsgsKeyL].toInt(100)) {} +EncryptionEventContent::EncryptionEventContent(EncryptionType et) + : encryption(et) +{ + if(encryption != Undefined) { + algorithm = encryptionStrings[encryption]; + } +} + void EncryptionEventContent::fillJson(QJsonObject* o) const { Q_ASSERT(o); diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 65ee4187..14439fcc 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -12,9 +12,7 @@ class EncryptionEventContent : public EventContent::Base { public: enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined }; - explicit EncryptionEventContent(EncryptionType et = Undefined) - : encryption(et) - {} + explicit EncryptionEventContent(EncryptionType et = Undefined); explicit EncryptionEventContent(const QJsonObject& json); EncryptionType encryption; diff --git a/lib/room.cpp b/lib/room.cpp index c6cca2ea..ced313a4 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -3009,3 +3009,12 @@ bool MemberSorter::operator()(User* u1, QStringView u2name) const return n1.localeAwareCompare(n2) < 0; } + +void Room::activateEncryption() +{ + if(usesEncryption()) { + qCWarning(E2EE) << "Room" << objectName() << "is already encrypted"; + return; + } + setState(EncryptionEventContent::MegolmV1AesSha2); +} diff --git a/lib/room.h b/lib/room.h index 4e55dbaf..c18d0b36 100644 --- a/lib/room.h +++ b/lib/room.h @@ -604,6 +604,12 @@ public Q_SLOTS: void answerCall(const QString& callId, const QString& sdp); void hangupCall(const QString& callId); + /** + * Activates encryption for this room. + * Warning: Cannot be undone + */ + void activateEncryption(); + Q_SIGNALS: /// Initial set of state events has been loaded /** -- cgit v1.2.3 From 59c9ca720093f2931c2eee1c0d5806d7e2e0c85f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 13 Sep 2021 16:05:46 +0200 Subject: Add room types to RoomCreateEvent --- lib/events/roomcreateevent.cpp | 21 +++++++++++++++++++++ lib/events/roomcreateevent.h | 2 ++ lib/quotient_common.h | 10 ++++++++++ 3 files changed, 33 insertions(+) (limited to 'lib/events') diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index 6558bade..ff93041c 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -5,6 +5,22 @@ using namespace Quotient; +template <> +struct Quotient::JsonConverter { + static RoomType load(const QJsonValue& jv) + { + const auto& roomTypeString = jv.toString(); + for (auto it = RoomTypeStrings.begin(); it != RoomTypeStrings.end(); + ++it) + if (roomTypeString == *it) + return RoomType(it - RoomTypeStrings.begin()); + + if (!roomTypeString.isEmpty()) + qCWarning(EVENTS) << "Unknown Room Type: " << roomTypeString; + return RoomType::Undefined; + } +}; + bool RoomCreateEvent::isFederated() const { return fromJson(contentJson()["m.federate"_ls]); @@ -26,3 +42,8 @@ bool RoomCreateEvent::isUpgrade() const { return contentJson().contains("predecessor"_ls); } + +RoomType RoomCreateEvent::roomType() const +{ + return fromJson(contentJson()["type"_ls]); +} diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index 05e623ed..b3ad287c 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -4,6 +4,7 @@ #pragma once #include "stateevent.h" +#include "quotient_common.h" namespace Quotient { class RoomCreateEvent : public StateEventBase { @@ -24,6 +25,7 @@ public: QString version() const; Predecessor predecessor() const; bool isUpgrade() const; + RoomType roomType() const; }; REGISTER_EVENT_TYPE(RoomCreateEvent) } // namespace Quotient diff --git a/lib/quotient_common.h b/lib/quotient_common.h index bb2e6a6b..4444a111 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -88,6 +88,16 @@ enum UriResolveResult : short { }; Q_ENUM_NS(UriResolveResult) +enum RoomType { + Space, + Undefined, +}; +Q_ENUM_NS(RoomType); + +constexpr inline auto RoomTypeStrings = make_array( + "m.space" +); + } // namespace Quotient Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::JoinStates) -- cgit v1.2.3 From fe9425f313e7c172095ff9355743427337b7ea78 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 13 Sep 2021 19:14:19 +0200 Subject: Add the encryptedfile to the eventcontent --- lib/events/encryptedfile.h | 88 +++++++++++++++++++++++++++++++++++++++++ lib/events/eventcontent.cpp | 20 +++++++--- lib/events/eventcontent.h | 17 ++++++-- lib/events/roomavatarevent.h | 2 +- lib/events/roommessageevent.cpp | 8 ++-- 5 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 lib/events/encryptedfile.h (limited to 'lib/events') diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h new file mode 100644 index 00000000..24ac9de1 --- /dev/null +++ b/lib/events/encryptedfile.h @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPl-2.1-or-later + +#pragma once + +#include "converters.h" + +namespace Quotient { +/** + * JSON Web Key object as specified in + * https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes + * The only currently relevant member is `k`, the rest needs to be set to the defaults specified in the spec. + */ +struct JWK +{ + Q_GADGET + Q_PROPERTY(QString kty MEMBER kty CONSTANT) + Q_PROPERTY(QStringList keyOps MEMBER keyOps CONSTANT) + Q_PROPERTY(QString alg MEMBER alg CONSTANT) + Q_PROPERTY(QString k MEMBER k CONSTANT) + Q_PROPERTY(bool ext MEMBER ext CONSTANT) + +public: + QString kty; + QStringList keyOps; + QString alg; + QString k; + bool ext; +}; + +struct EncryptedFile +{ + Q_GADGET + Q_PROPERTY(QUrl url MEMBER url CONSTANT) + Q_PROPERTY(JWK key MEMBER key CONSTANT) + Q_PROPERTY(QString iv MEMBER iv CONSTANT) + Q_PROPERTY(QHash hashes MEMBER hashes CONSTANT) + Q_PROPERTY(QString v MEMBER v CONSTANT) + +public: + QUrl url; + JWK key; + QString iv; + QHash hashes; + QString v; +}; + +template <> +struct JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const EncryptedFile& pod) + { + addParam<>(jo, QStringLiteral("url"), pod.url); + addParam<>(jo, QStringLiteral("key"), pod.key); + addParam<>(jo, QStringLiteral("iv"), pod.iv); + addParam<>(jo, QStringLiteral("hashes"), pod.hashes); + addParam<>(jo, QStringLiteral("v"), pod.v); + } + static void fillFrom(const QJsonObject& jo, EncryptedFile& pod) + { + fromJson(jo.value("url"_ls), pod.url); + fromJson(jo.value("key"_ls), pod.key); + fromJson(jo.value("iv"_ls), pod.iv); + fromJson(jo.value("hashes"_ls), pod.hashes); + fromJson(jo.value("v"_ls), pod.v); + } +}; + +template <> +struct JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const JWK& pod) + { + addParam<>(jo, QStringLiteral("kty"), pod.kty); + addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); + addParam<>(jo, QStringLiteral("alg"), pod.alg); + addParam<>(jo, QStringLiteral("k"), pod.k); + addParam<>(jo, QStringLiteral("ext"), pod.ext); + } + static void fillFrom(const QJsonObject& jo, JWK& pod) + { + fromJson(jo.value("kty"_ls), pod.kty); + fromJson(jo.value("key_ops"_ls), pod.keyOps); + fromJson(jo.value("alg"_ls), pod.alg); + fromJson(jo.value("k"_ls), pod.k); + fromJson(jo.value("ext"_ls), pod.ext); + } +}; +} // namespace Quotient diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 1f28f195..22878d4c 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -30,11 +30,12 @@ FileInfo::FileInfo(const QFileInfo &fi) } FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, - QString originalFilename) + Omittable file, QString originalFilename) : mimeType(mimeType) , url(move(u)) , payloadSize(payloadSize) , originalName(move(originalFilename)) + , file(file) { if (!isValid()) qCWarning(MESSAGES) @@ -44,6 +45,7 @@ FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, } FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, + const Omittable &file, QString originalFilename) : originalInfoJson(infoJson) , mimeType( @@ -51,7 +53,11 @@ FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, , url(move(mxcUrl)) , payloadSize(fromJson(infoJson["size"_ls])) , originalName(move(originalFilename)) + , file(file) { + if(url.isEmpty() && file.has_value()) { + url = file->url; + } if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); } @@ -76,14 +82,15 @@ ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) {} ImageInfo::ImageInfo(const QUrl& mxcUrl, qint64 fileSize, const QMimeType& type, - QSize imageSize, const QString& originalFilename) - : FileInfo(mxcUrl, fileSize, type, originalFilename) + QSize imageSize, const Omittable &file, const QString& originalFilename) + : FileInfo(mxcUrl, fileSize, type, file, originalFilename) , imageSize(imageSize) {} ImageInfo::ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, + const Omittable &file, const QString& originalFilename) - : FileInfo(mxcUrl, infoJson, originalFilename) + : FileInfo(mxcUrl, infoJson, file, originalFilename) , imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt()) {} @@ -96,9 +103,10 @@ void ImageInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert(QStringLiteral("h"), imageSize.height()); } -Thumbnail::Thumbnail(const QJsonObject& infoJson) +Thumbnail::Thumbnail(const QJsonObject& infoJson, const Omittable &file) : ImageInfo(QUrl(infoJson["thumbnail_url"_ls].toString()), - infoJson["thumbnail_info"_ls].toObject()) + infoJson["thumbnail_info"_ls].toObject(), + file) {} void Thumbnail::fillInfoJson(QJsonObject* infoJson) const diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 40ec3a49..f609a603 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -12,6 +12,8 @@ #include #include +#include "encryptedfile.h" + class QFileInfo; namespace Quotient { @@ -80,8 +82,10 @@ namespace EventContent { explicit FileInfo(const QFileInfo& fi); explicit FileInfo(QUrl mxcUrl, qint64 payloadSize = -1, const QMimeType& mimeType = {}, + Omittable file = none, QString originalFilename = {}); FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, + const Omittable &file, QString originalFilename = {}); bool isValid() const; @@ -103,6 +107,7 @@ namespace EventContent { QUrl url; qint64 payloadSize; QString originalName; + Omittable file = none; }; template @@ -122,8 +127,10 @@ namespace EventContent { explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); explicit ImageInfo(const QUrl& mxcUrl, qint64 fileSize = -1, const QMimeType& type = {}, QSize imageSize = {}, + const Omittable &file = none, const QString& originalFilename = {}); ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, + const Omittable &encryptedFile, const QString& originalFilename = {}); void fillInfoJson(QJsonObject* infoJson) const; @@ -142,7 +149,7 @@ namespace EventContent { class Thumbnail : public ImageInfo { public: Thumbnail() = default; // Allow empty thumbnails - Thumbnail(const QJsonObject& infoJson); + Thumbnail(const QJsonObject& infoJson, const Omittable &file = none); Thumbnail(const ImageInfo& info) : ImageInfo(info) {} using ImageInfo::ImageInfo; @@ -182,7 +189,7 @@ namespace EventContent { explicit UrlBasedContent(const QJsonObject& json) : TypedBase(json) , InfoT(QUrl(json["url"].toString()), json["info"].toObject(), - json["filename"].toString()) + fromJson>(json["file"]), json["filename"].toString()) { // A small hack to facilitate links creation in QML. originalJson.insert("mediaId", InfoT::mediaId()); @@ -196,7 +203,11 @@ namespace EventContent { void fillJson(QJsonObject* json) const override { Q_ASSERT(json); - json->insert("url", InfoT::url.toString()); + if (!InfoT::file.has_value()) { + json->insert("url", InfoT::url.toString()); + } else { + json->insert("file", Quotient::toJson(*InfoT::file)); + } if (!InfoT::originalName.isEmpty()) json->insert("filename", InfoT::originalName); json->insert("info", toInfoJson(*this)); diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index 3fa11a0f..8618ba31 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -25,7 +25,7 @@ public: const QSize& imageSize = {}, const QString& originalFilename = {}) : RoomAvatarEvent(EventContent::ImageContent { - mxcUrl, fileSize, mimeType, imageSize, originalFilename }) + mxcUrl, fileSize, mimeType, imageSize, none, originalFilename }) {} QUrl url() const { return content().url; } diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 71f85363..9b46594e 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -145,21 +145,21 @@ TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) auto mimeTypeName = mimeType.name(); if (mimeTypeName.startsWith("image/")) return new ImageContent(localUrl, file.size(), mimeType, - QImageReader(filePath).size(), + QImageReader(filePath).size(), none, file.fileName()); // duration can only be obtained asynchronously and can only be reliably // done by starting to play the file. Left for a future implementation. if (mimeTypeName.startsWith("video/")) return new VideoContent(localUrl, file.size(), mimeType, - QMediaResource(localUrl).resolution(), + QMediaResource(localUrl).resolution(), none, file.fileName()); if (mimeTypeName.startsWith("audio/")) - return new AudioContent(localUrl, file.size(), mimeType, + return new AudioContent(localUrl, file.size(), mimeType, none, file.fileName()); } - return new FileContent(localUrl, file.size(), mimeType, file.fileName()); + return new FileContent(localUrl, file.size(), mimeType, none, file.fileName()); } RoomMessageEvent::RoomMessageEvent(const QString& plainBody, -- cgit v1.2.3 From fc0fdf2ed6006c11ffd47675fabb1232721c5e7d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 12 Oct 2021 09:26:09 +0200 Subject: RoomMemberEvent::is*(): fix comparison against Omittable Closes #514. --- lib/events/roommemberevent.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 8a6bddd8..469dbb32 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -103,16 +103,14 @@ bool RoomMemberEvent::isUnban() const bool RoomMemberEvent::isRename() const { - auto prevName = prevContent() && prevContent()->displayName - ? *prevContent()->displayName - : QString(); - return newDisplayName() != prevName; + return prevContent() && prevContent()->displayName + ? newDisplayName() != *prevContent()->displayName + : newDisplayName().has_value(); } bool RoomMemberEvent::isAvatarUpdate() const { - auto prevAvatarUrl = prevContent() && prevContent()->avatarUrl - ? *prevContent()->avatarUrl - : QUrl(); - return newAvatarUrl() != prevAvatarUrl; + return prevContent() && prevContent()->avatarUrl + ? newAvatarUrl() != *prevContent()->avatarUrl + : newAvatarUrl().has_value(); } -- 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 -- lib/events/event.h | 3 +-- lib/util.h | 28 ++++++++++++++++++++++++---- 3 files changed, 25 insertions(+), 8 deletions(-) (limited to 'lib/events') 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; diff --git a/lib/events/event.h b/lib/events/event.h index f8f8311d..78853ced 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -76,8 +76,7 @@ public: private: EventTypeRegistry() = default; - Q_DISABLE_COPY(EventTypeRegistry) - DISABLE_MOVE(EventTypeRegistry) + Q_DISABLE_COPY_MOVE(EventTypeRegistry) static EventTypeRegistry& get() { diff --git a/lib/util.h b/lib/util.h index 9c146100..13efb94b 100644 --- a/lib/util.h +++ b/lib/util.h @@ -12,10 +12,30 @@ #include #include -// Along the lines of Q_DISABLE_COPY - the upstream version comes in Qt 5.13 -#define DISABLE_MOVE(_ClassName) \ - _ClassName(_ClassName&&) Q_DECL_EQ_DELETE; \ - _ClassName& operator=(_ClassName&&) Q_DECL_EQ_DELETE; +#ifndef Q_DISABLE_MOVE +// Q_DISABLE_MOVE was introduced in Q_VERSION_CHECK(5,13,0) +# define Q_DISABLE_MOVE(_ClassName) \ + _ClassName(_ClassName&&) Q_DECL_EQ_DELETE; \ + _ClassName& operator=(_ClassName&&) Q_DECL_EQ_DELETE; +#endif + +#ifndef Q_DISABLE_COPY_MOVE +#define Q_DISABLE_COPY_MOVE(Class) \ + Q_DISABLE_COPY(Class) \ + Q_DISABLE_MOVE(Class) +#endif + +#define DISABLE_MOVE(_ClassName) \ +static_assert(false, "Use Q_DISABLE_MOVE instead; Quotient enables it across all used versions of Qt"); + +#ifndef QT_IGNORE_DEPRECATIONS +// QT_IGNORE_DEPRECATIONS was introduced in Q_VERSION_CHECK(5,15,0) +# define QT_IGNORE_DEPRECATIONS(statement) \ + QT_WARNING_PUSH \ + QT_WARNING_DISABLE_DEPRECATED \ + statement \ + QT_WARNING_POP +#endif namespace Quotient { /// An equivalent of std::hash for QTypes to enable std::unordered_map -- cgit v1.2.3 From f2bf3f203965c51824e8681427798f7a09784ce3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 11 Nov 2021 22:18:18 +0100 Subject: Make ReceiptEvent constructible from content Makes the Room::P::toJson() code more readable. --- lib/converters.h | 7 +++++-- lib/events/receiptevent.cpp | 29 +++++++++++++++++++++++++---- lib/events/receiptevent.h | 5 +++-- lib/room.cpp | 27 +++++++-------------------- 4 files changed, 40 insertions(+), 28 deletions(-) (limited to 'lib/events') diff --git a/lib/converters.h b/lib/converters.h index 2df18b03..d9a68bfb 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -134,7 +134,10 @@ struct JsonConverter : public TrivialJsonDumper { template <> struct JsonConverter { - static auto dump(const QDateTime& val) = delete; // not provided yet + static auto dump(const QDateTime& val) + { + return val.isValid() ? val.toMSecsSinceEpoch() : QJsonValue(); + } static auto load(const QJsonValue& jv) { return QDateTime::fromMSecsSinceEpoch(fromJson(jv), Qt::UTC); @@ -143,7 +146,7 @@ struct JsonConverter { template <> struct JsonConverter { - static auto dump(const QDate& val) = delete; // not provided yet + static auto dump(const QDate& val) { return toJson(QDateTime(val)); } static auto load(const QJsonValue& jv) { return fromJson(jv).date(); diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp index 4185d92d..72dbf2e3 100644 --- a/lib/events/receiptevent.cpp +++ b/lib/events/receiptevent.cpp @@ -25,6 +25,27 @@ Example of a Receipt Event: using namespace Quotient; +// The library loads the event-ids-to-receipts JSON map into a vector because +// map lookups are not used and vectors are massively faster. Same goes for +// de-/serialization of ReceiptsForEvent::receipts. +// (XXX: would this be generally preferred across CS API JSON maps?..) +QJsonObject toJson(const EventsWithReceipts& ewrs) +{ + QJsonObject json; + for (const auto& e : ewrs) { + QJsonObject receiptsJson; + for (const auto& r : e.receipts) + receiptsJson.insert(r.userId, + QJsonObject { { "ts"_ls, toJson(r.timestamp) } }); + json.insert(e.evtId, QJsonObject { { "m.read"_ls, receiptsJson } }); + } + return json; +} + +ReceiptEvent::ReceiptEvent(const EventsWithReceipts &ewrs) + : Event(typeId(), matrixTypeId(), toJson(ewrs)) +{} + EventsWithReceipts ReceiptEvent::eventsWithReceipts() const { EventsWithReceipts result; @@ -39,14 +60,14 @@ EventsWithReceipts ReceiptEvent::eventsWithReceipts() const } const auto reads = eventIt.value().toObject().value("m.read"_ls).toObject(); - QVector receipts; - receipts.reserve(reads.size()); + QVector usersAtEvent; + usersAtEvent.reserve(reads.size()); for (auto userIt = reads.begin(); userIt != reads.end(); ++userIt) { const auto user = userIt.value().toObject(); - receipts.push_back( + usersAtEvent.push_back( { userIt.key(), fromJson(user["ts"_ls]) }); } - result.push_back({ eventIt.key(), std::move(receipts) }); + result.push_back({ eventIt.key(), std::move(usersAtEvent) }); } return result; } diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index 4feec9ea..9683deef 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -9,19 +9,20 @@ #include namespace Quotient { -struct Receipt { +struct UserTimestamp { QString userId; QDateTime timestamp; }; struct ReceiptsForEvent { QString evtId; - QVector receipts; + QVector receipts; }; using EventsWithReceipts = QVector; class ReceiptEvent : public Event { public: DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent) + explicit ReceiptEvent(const EventsWithReceipts& ewrs); explicit ReceiptEvent(const QJsonObject& obj) : Event(typeId(), obj) {} EventsWithReceipts eventsWithReceipts() const; diff --git a/lib/room.cpp b/lib/room.cpp index 4d11878f..58950aac 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2813,7 +2813,7 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) // are not supposed to move backwards. Otherwise, blindly store // the event id for this user and update the read marker when/if // the event is fetched later on. - for (const Receipt& r : p.receipts) + for (const auto& r : p.receipts) if (isMember(r.userId)) { d->setLastReadReceipt(user(r.userId), newMarker, { p.evtId, r.timestamp }); @@ -3021,30 +3021,17 @@ QJsonObject Room::Private::toJson() const { QStringLiteral("events"), accountDataEvents } }); } - if (const auto& readReceiptEventId = - lastReadReceipts.value(q->localUser()).eventId; - !readReceiptEventId.isEmpty()) // + if (const auto& readReceipt = q->lastReadReceipt(connection->userId()); + !readReceipt.eventId.isEmpty()) // { - // Okay, that's a mouthful; but basically, it's simply placing an m.read - // event in the 'ephemeral' section of the cached sync payload. - // See also receiptevent.* and m.read example in the spec. - // Only the local user's read receipt is saved - others' are really - // considered ephemeral but this one is useful in understanding where - // the user is in the timeline before any history is loaded. result.insert( QStringLiteral("ephemeral"), QJsonObject { { QStringLiteral("events"), - QJsonArray { QJsonObject { - { TypeKey, ReceiptEvent::matrixTypeId() }, - { ContentKey, - QJsonObject { - { readReceiptEventId, - QJsonObject { - { QStringLiteral("m.read"), - QJsonObject { - { connection->userId(), - QJsonObject {} } } } } } } } } } } }); + QJsonArray { ReceiptEvent({ { readReceipt.eventId, + { { connection->userId(), + readReceipt.timestamp } } } }) + .fullJson() } } }); } QJsonObject unreadNotifObj { { SyncRoomData::UnreadCountKey, -- cgit v1.2.3 From 2d1cf137d7380a15673826bce00e71461fbc7446 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 19 Nov 2021 12:46:00 +0100 Subject: Cleanup --- lib/events/roommemberevent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 469dbb32..b0bc7bcb 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -17,7 +17,7 @@ struct JsonConverter { const auto& ms = jv.toString(); if (ms.isEmpty()) { - qCWarning(EVENTS) << "Empty member state:" << ms; + qCWarning(EVENTS) << "Empty membership state"; return Membership::Invalid; } const auto it = -- cgit v1.2.3 From f6155d62740a88b020273ba623c816f7b9805772 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 26 Nov 2021 13:44:39 +0100 Subject: Drop Q_GADGET from most uncopyable classes; other minor cleanup Q_GADGET is generally used to enable two things outside of QObject: Q_PROPERTY/Q_INVOKABLE and Q_ENUM/Q_FLAG. While the latter can be used in its own right in QML, the former requires Q_GADGET instances to be passed to QML by value, which is not really possible with uncopyable/unassignable classes. Bottom line is that Q_PROPERTY in anything derived from Quotient::Event is not viable, making Q_GADGET macro useless unless there's a Q_ENUM/Q_FLAG (as is the case with RoomMessageEvent, e.g.). --- lib/eventitem.h | 5 +---- lib/events/encryptedevent.h | 2 -- lib/events/event.h | 3 --- lib/events/roomevent.h | 41 +++++++++++++---------------------------- lib/events/roommessageevent.h | 4 ---- lib/events/stateevent.h | 2 -- lib/quotient_common.h | 1 - 7 files changed, 14 insertions(+), 44 deletions(-) (limited to 'lib/events') diff --git a/lib/eventitem.h b/lib/eventitem.h index a70a3c3e..0ab1a01d 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -9,7 +9,6 @@ #include namespace Quotient { -class StateEventBase; namespace EventStatus { Q_NAMESPACE @@ -31,8 +30,7 @@ namespace EventStatus { Replaced = 0x10, //< The event has been replaced Hidden = 0x100, //< The event should not be shown in the timeline }; - Q_DECLARE_FLAGS(Status, Code) - Q_FLAG_NS(Status) + Q_ENUM_NS(Code) } // namespace EventStatus class EventItemBase { @@ -106,7 +104,6 @@ inline const CallEventBase* EventItemBase::viewAs() const } class PendingEventItem : public EventItemBase { - Q_GADGET public: using EventItemBase::EventItemBase; diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index eb7123eb..598829cd 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -7,7 +7,6 @@ #include "roomevent.h" namespace Quotient { -class Room; /* * While the specification states: * @@ -27,7 +26,6 @@ class Room; * one and doesn't add new restrictions, just provides additional features. */ class EncryptedEvent : public RoomEvent { - Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.encrypted", EncryptedEvent) diff --git a/lib/events/event.h b/lib/events/event.h index 78853ced..2ed5de5d 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -195,9 +195,6 @@ inline auto registerEventType() // === Event === class Event { - Q_GADGET - Q_PROPERTY(Type type READ type CONSTANT) - Q_PROPERTY(QJsonObject contentJson READ contentJson CONSTANT) public: using Type = event_type_t; using factory_t = EventFactory; diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 3174764f..a6cd84ca 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -12,16 +12,6 @@ class RedactionEvent; /** This class corresponds to m.room.* events */ class RoomEvent : public Event { - Q_GADGET - Q_PROPERTY(QString id READ id) - //! \deprecated Use originTimestamp instead - Q_PROPERTY(QDateTime timestamp READ originTimestamp CONSTANT) - Q_PROPERTY(QDateTime originTimestamp READ originTimestamp CONSTANT) - Q_PROPERTY(QString roomId READ roomId CONSTANT) - Q_PROPERTY(QString senderId READ senderId CONSTANT) - Q_PROPERTY(QString redactionReason READ redactionReason) - Q_PROPERTY(bool isRedacted READ isRedacted) - Q_PROPERTY(QString transactionId READ transactionId WRITE setTransactionId) public: using factory_t = EventFactory; @@ -51,28 +41,23 @@ public: QString transactionId() const; QString stateKey() const; + //! \brief Fill the pending event object with the room id void setRoomId(const QString& roomId); + //! \brief Fill the pending event object with the sender id void setSender(const QString& senderId); - - /** - * Sets the transaction id for locally created events. This should be - * done before the event is exposed to any code using the respective - * Q_PROPERTY. - * - * \param txnId - transaction id, normally obtained from - * Connection::generateTxnId() - */ + //! \brief Fill the pending event object with the transaction id + //! \param txnId - transaction id, normally obtained from + //! Connection::generateTxnId() void setTransactionId(const QString& txnId); - /** - * Sets event id for locally created events - * - * When a new event is created locally, it has no server id yet. - * This function allows to add the id once the confirmation from - * the server is received. There should be no id set previously - * in the event. It's the responsibility of the code calling addId() - * to notify clients that use Q_PROPERTY(id) about its change - */ + //! \brief Add an event id to locally created events after they are sent + //! + //! When a new event is created locally, it has no id; the homeserver + //! assigns it once the event is sent. This function allows to add the id + //! once the confirmation from the server is received. There should be no id + //! set previously in the event. It's the responsibility of the code calling + //! addId() to notify clients about the change; there's no signal or + //! callback for that in RoomEvent. void addId(const QString& newId); protected: diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 88d3b74c..56597ddc 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -18,10 +18,6 @@ namespace MessageEventContent = EventContent; // Back-compatibility */ class RoomMessageEvent : public RoomEvent { Q_GADGET - Q_PROPERTY(QString msgType READ rawMsgtype CONSTANT) - Q_PROPERTY(QString plainBody READ plainBody CONSTANT) - Q_PROPERTY(QMimeType mimeType READ mimeType STORED false CONSTANT) - Q_PROPERTY(const EventContent::TypedBase* content READ content CONSTANT) public: DEFINE_EVENT_TYPEID("m.room.message", RoomMessageEvent) diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index bc414a5f..b0aa9907 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -18,8 +18,6 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, } class StateEventBase : public RoomEvent { - Q_GADGET - Q_PROPERTY(QString stateKey READ stateKey CONSTANT) public: using factory_t = EventFactory; diff --git a/lib/quotient_common.h b/lib/quotient_common.h index d91c3d17..0e3e2a40 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -90,7 +90,6 @@ constexpr inline auto JoinStateStrings = make_array( //! So far only background/foreground flags are available. //! \sa Connection::callApi, Connection::run enum RunningPolicy { ForegroundRequest = 0x0, BackgroundRequest = 0x1 }; - Q_ENUM_NS(RunningPolicy) //! \brief The result of URI resolution using UriResolver -- cgit v1.2.3 From f4a0acf818c4c89d132b2ec96d47c5817b106149 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 26 Nov 2021 13:46:58 +0100 Subject: Drop #include "logging.h" from event.h Makes compilation a tad lighter. --- lib/events/callanswerevent.cpp | 1 + lib/events/callhangupevent.cpp | 1 + lib/events/callinviteevent.cpp | 1 + lib/events/encryptedevent.cpp | 11 ++++++++--- lib/events/encryptedevent.h | 14 ++++---------- lib/events/encryptionevent.cpp | 1 + lib/events/event.cpp | 11 ++++++++--- lib/events/event.h | 5 +++-- lib/events/reactionevent.cpp | 1 + lib/events/roomcreateevent.cpp | 1 + lib/events/roomkeyevent.cpp | 1 + lib/syncdata.cpp | 1 + 12 files changed, 31 insertions(+), 18 deletions(-) (limited to 'lib/events') diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp index be83d9d0..24144585 100644 --- a/lib/events/callanswerevent.cpp +++ b/lib/events/callanswerevent.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "callanswerevent.h" +#include "logging.h" /* m.call.answer diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp index 43bc4db0..537ace75 100644 --- a/lib/events/callhangupevent.cpp +++ b/lib/events/callhangupevent.cpp @@ -6,6 +6,7 @@ */ #include "callhangupevent.h" +#include "logging.h" /* m.call.hangup diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index 5ea54662..ce4fb101 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "callinviteevent.h" +#include "logging.h" /* m.call.invite diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 0290f973..ebf733ac 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" +#include "logging.h" using namespace Quotient; @@ -25,8 +26,12 @@ EncryptedEvent::EncryptedEvent(QByteArray ciphertext, const QString& senderKey, }) {} -EncryptedEvent::EncryptedEvent(const QJsonObject& obj) - : RoomEvent(typeId(), obj) +QString EncryptedEvent::algorithm() const { - qCDebug(E2EE) << "Encrypted event from" << senderId(); + const auto algo = content(AlgorithmKeyL); + if (!SupportedAlgorithms.contains(algo)) { + qCWarning(MAIN) << "The EncryptedEvent's algorithm" << algo + << "is not supported"; + } + return algo; } diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 598829cd..d2b71785 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -37,17 +37,11 @@ public: /* In case with Megolm, device_id and session_id are required */ explicit EncryptedEvent(QByteArray ciphertext, const QString& senderKey, const QString& deviceId, const QString& sessionId); - explicit EncryptedEvent(const QJsonObject& obj); + explicit EncryptedEvent(const QJsonObject& obj) + : RoomEvent(typeId(), obj) + {} - QString algorithm() const - { - QString algo = content(AlgorithmKeyL); - if (!SupportedAlgorithms.contains(algo)) { - qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo - << "is not supported"; - } - return algo; - } + QString algorithm() const; QByteArray ciphertext() const { return content(CiphertextKeyL).toLatin1(); diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index aa05a96e..9eb48844 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptionevent.h" +#include "logging.h" #include "e2ee.h" diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 3d66ab55..305dd454 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -9,15 +9,20 @@ using namespace Quotient; +void Quotient::logFactorySetup(event_mtype_t eventTypeId) +{ + qCDebug(EVENTS) << "Adding factory method for" << eventTypeId; +} + event_type_t EventTypeRegistry::initializeTypeId(event_mtype_t matrixTypeId) { const auto id = get().eventTypes.size(); get().eventTypes.push_back(matrixTypeId); if (strncmp(matrixTypeId, "", 1) == 0) - qDebug(EVENTS) << "Initialized unknown event type with id" << id; + qCDebug(EVENTS) << "Initialized unknown event type with id" << id; else - qDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id" - << id; + qCDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id" + << id; return id; } diff --git a/lib/events/event.h b/lib/events/event.h index 2ed5de5d..e9963eae 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -4,7 +4,6 @@ #pragma once #include "converters.h" -#include "logging.h" namespace Quotient { // === event_ptr_tt<> and type casting facilities === @@ -162,6 +161,8 @@ private: } }; +void logFactorySetup(event_mtype_t eventTypeId); + /** Add a type to its default factory * Adds a standard factory method (via makeEvent<>) for a given * type to EventT::factory_t factory class so that it can be @@ -174,7 +175,7 @@ private: template inline auto setupFactory() { - qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId(); + logFactorySetup(EventT::matrixTypeId()); return EventT::factory_t::addMethod([](const QJsonObject& json, const QString& jsonMatrixType) { return EventT::matrixTypeId() == jsonMatrixType ? makeEvent(json) diff --git a/lib/events/reactionevent.cpp b/lib/events/reactionevent.cpp index b53fffd6..bb62f0e0 100644 --- a/lib/events/reactionevent.cpp +++ b/lib/events/reactionevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "reactionevent.h" +#include "logging.h" using namespace Quotient; diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index ff93041c..e119696f 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "roomcreateevent.h" +#include "logging.h" using namespace Quotient; diff --git a/lib/events/roomkeyevent.cpp b/lib/events/roomkeyevent.cpp index 332be3f7..dbcb35bd 100644 --- a/lib/events/roomkeyevent.cpp +++ b/lib/events/roomkeyevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "roomkeyevent.h" +#include "logging.h" using namespace Quotient; diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index 396e77eb..f9fabdea 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -4,6 +4,7 @@ #include "syncdata.h" #include "events/eventloader.h" +#include "logging.h" #include #include -- cgit v1.2.3 From b3d62050befbb1c526f03e4356f3263d197c45f2 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 26 Nov 2021 14:02:49 +0100 Subject: Event: deprecate originalJson[Object]() The "original JSON" wording is misleading: the returned JSON can be and is routinely edited as a part of event construction, redaction, editing. Also, originalJson() name is misleading in that it returns a stringified (in a very specific way) JSON and not an object. You have to call fullJson() to get the object, and originalJsonObject(), confusingly, returns exactly the same thing but as a value rather than as a reference. The original intention of keeping originalJsonObject() was to make it Q_INVOKABLE or use it as an accessor for a Q_PROPERTY. unfortunately, this was never really practical as discussed in the previous commit. All that implies that clients have to handle passing event JSON to QML themselves, in the form they prefer (as an object or a string). The added complexity is negligible though; on the other hand, there's added flexibility in, e.g., choosing a compact instead of default JSON layout or even generate a highlighted JSON representation. --- lib/events/event.h | 3 +++ lib/room.cpp | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index e9963eae..52e3d025 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -210,7 +210,10 @@ public: Type type() const { return _type; } QString matrixType() const; + [[deprecated("Use fullJson() and stringify it with QJsonDocument::toJson() " + "or by other means")]] QByteArray originalJson() const; + [[deprecated("Use fullJson() instead")]] // QJsonObject originalJsonObject() const { return fullJson(); } const QJsonObject& fullJson() const { return _json; } diff --git a/lib/room.cpp b/lib/room.cpp index 3090cb7b..aa00025c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1589,7 +1589,8 @@ void Room::Private::removeMemberFromMap(User* u) inline auto makeErrorStr(const Event& e, QByteArray msg) { - return msg.append("; event dump follows:\n").append(e.originalJson()); + return msg.append("; event dump follows:\n") + .append(QJsonDocument(e.fullJson()).toJson()); } Room::Timeline::size_type @@ -2363,7 +2364,7 @@ void Room::Private::dropDuplicateEvents(RoomEvents& events) const RoomEventPtr makeRedacted(const RoomEvent& target, const RedactionEvent& redaction) { - auto originalJson = target.originalJsonObject(); + auto originalJson = target.fullJson(); // clang-format off static const QStringList keepKeys { EventIdKey, TypeKey, RoomIdKey, SenderKey, StateKeyKey, @@ -2409,7 +2410,7 @@ RoomEventPtr makeRedacted(const RoomEvent& target, originalJson.insert(ContentKey, content); } auto unsignedData = originalJson.take(UnsignedKeyL).toObject(); - unsignedData[RedactedCauseKeyL] = redaction.originalJsonObject(); + unsignedData[RedactedCauseKeyL] = redaction.fullJson(); originalJson.insert(QStringLiteral("unsigned"), unsignedData); return loadEvent(originalJson); @@ -2479,7 +2480,7 @@ RoomEventPtr makeReplaced(const RoomEvent& target, if (!targetReply.empty()) { newContent["m.relates_to"] = targetReply; } - auto originalJson = target.originalJsonObject(); + auto originalJson = target.fullJson(); originalJson[ContentKeyL] = newContent; auto unsignedData = originalJson.take(UnsignedKeyL).toObject(); -- cgit v1.2.3 From 4ba795556721a88d2ac258d9095a46de21d77011 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 26 Nov 2021 16:07:25 +0100 Subject: Revert "Drop #include "logging.h" from event.h" Doesn't really help build times, instead breaking the build on older Qt. --- lib/events/callanswerevent.cpp | 1 - lib/events/callhangupevent.cpp | 1 - lib/events/callinviteevent.cpp | 1 - lib/events/encryptedevent.cpp | 11 +++-------- lib/events/encryptedevent.h | 14 ++++++++++---- lib/events/encryptionevent.cpp | 1 - lib/events/event.cpp | 11 +++-------- lib/events/event.h | 5 ++--- lib/events/reactionevent.cpp | 1 - lib/events/roomcreateevent.cpp | 1 - lib/events/roomkeyevent.cpp | 1 - lib/syncdata.cpp | 1 - 12 files changed, 18 insertions(+), 31 deletions(-) (limited to 'lib/events') diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp index 24144585..be83d9d0 100644 --- a/lib/events/callanswerevent.cpp +++ b/lib/events/callanswerevent.cpp @@ -3,7 +3,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "callanswerevent.h" -#include "logging.h" /* m.call.answer diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp index 537ace75..43bc4db0 100644 --- a/lib/events/callhangupevent.cpp +++ b/lib/events/callhangupevent.cpp @@ -6,7 +6,6 @@ */ #include "callhangupevent.h" -#include "logging.h" /* m.call.hangup diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index ce4fb101..5ea54662 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -3,7 +3,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "callinviteevent.h" -#include "logging.h" /* m.call.invite diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index ebf733ac..0290f973 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" -#include "logging.h" using namespace Quotient; @@ -26,12 +25,8 @@ EncryptedEvent::EncryptedEvent(QByteArray ciphertext, const QString& senderKey, }) {} -QString EncryptedEvent::algorithm() const +EncryptedEvent::EncryptedEvent(const QJsonObject& obj) + : RoomEvent(typeId(), obj) { - const auto algo = content(AlgorithmKeyL); - if (!SupportedAlgorithms.contains(algo)) { - qCWarning(MAIN) << "The EncryptedEvent's algorithm" << algo - << "is not supported"; - } - return algo; + qCDebug(E2EE) << "Encrypted event from" << senderId(); } diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index d2b71785..598829cd 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -37,11 +37,17 @@ public: /* In case with Megolm, device_id and session_id are required */ explicit EncryptedEvent(QByteArray ciphertext, const QString& senderKey, const QString& deviceId, const QString& sessionId); - explicit EncryptedEvent(const QJsonObject& obj) - : RoomEvent(typeId(), obj) - {} + explicit EncryptedEvent(const QJsonObject& obj); - QString algorithm() const; + QString algorithm() const + { + QString algo = content(AlgorithmKeyL); + if (!SupportedAlgorithms.contains(algo)) { + qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo + << "is not supported"; + } + return algo; + } QByteArray ciphertext() const { return content(CiphertextKeyL).toLatin1(); diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 9eb48844..aa05a96e 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -3,7 +3,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptionevent.h" -#include "logging.h" #include "e2ee.h" diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 305dd454..3d66ab55 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -9,20 +9,15 @@ using namespace Quotient; -void Quotient::logFactorySetup(event_mtype_t eventTypeId) -{ - qCDebug(EVENTS) << "Adding factory method for" << eventTypeId; -} - event_type_t EventTypeRegistry::initializeTypeId(event_mtype_t matrixTypeId) { const auto id = get().eventTypes.size(); get().eventTypes.push_back(matrixTypeId); if (strncmp(matrixTypeId, "", 1) == 0) - qCDebug(EVENTS) << "Initialized unknown event type with id" << id; + qDebug(EVENTS) << "Initialized unknown event type with id" << id; else - qCDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id" - << id; + qDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id" + << id; return id; } diff --git a/lib/events/event.h b/lib/events/event.h index 52e3d025..89efb7f8 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -4,6 +4,7 @@ #pragma once #include "converters.h" +#include "logging.h" namespace Quotient { // === event_ptr_tt<> and type casting facilities === @@ -161,8 +162,6 @@ private: } }; -void logFactorySetup(event_mtype_t eventTypeId); - /** Add a type to its default factory * Adds a standard factory method (via makeEvent<>) for a given * type to EventT::factory_t factory class so that it can be @@ -175,7 +174,7 @@ void logFactorySetup(event_mtype_t eventTypeId); template inline auto setupFactory() { - logFactorySetup(EventT::matrixTypeId()); + qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId(); return EventT::factory_t::addMethod([](const QJsonObject& json, const QString& jsonMatrixType) { return EventT::matrixTypeId() == jsonMatrixType ? makeEvent(json) diff --git a/lib/events/reactionevent.cpp b/lib/events/reactionevent.cpp index bb62f0e0..b53fffd6 100644 --- a/lib/events/reactionevent.cpp +++ b/lib/events/reactionevent.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "reactionevent.h" -#include "logging.h" using namespace Quotient; diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index e119696f..ff93041c 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "roomcreateevent.h" -#include "logging.h" using namespace Quotient; diff --git a/lib/events/roomkeyevent.cpp b/lib/events/roomkeyevent.cpp index dbcb35bd..332be3f7 100644 --- a/lib/events/roomkeyevent.cpp +++ b/lib/events/roomkeyevent.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "roomkeyevent.h" -#include "logging.h" using namespace Quotient; diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index f9fabdea..396e77eb 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -4,7 +4,6 @@ #include "syncdata.h" #include "events/eventloader.h" -#include "logging.h" #include #include -- cgit v1.2.3 From 0425fd280e7eee7a4c9bcf18f79910f181322c42 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 27 Nov 2021 20:14:53 +0100 Subject: Event::content() -> contentPart() There's a clash between Event::content() (a template function) and RoomMessageEvent::content() (plain member). Out of these two, the name more fits to the RME's member function - strictly speaking, Event::content() retrieves a part of content, and so is renamed. In addition, contentPart() defaults to QJsonValue now, which is pretty intuitive (the function returns values from a JSON object) and allows to implement more elaborate logic such as if (const auto v = contentPart<>("key"_ls); v.isObject()) { // foo } else if (v.isString()) { // bar } else { // boo } --- lib/events/accountdataevents.h | 2 +- lib/events/callanswerevent.h | 4 ++-- lib/events/callcandidatesevent.h | 6 +++--- lib/events/callinviteevent.h | 4 ++-- lib/events/encryptedevent.h | 12 ++++++------ lib/events/event.h | 15 +++++++++++---- lib/events/reactionevent.h | 2 +- lib/events/redactionevent.h | 2 +- lib/events/roomcreateevent.cpp | 8 ++++---- lib/events/roomevent.h | 4 ++-- lib/events/roomkeyevent.h | 8 ++++---- lib/events/roommessageevent.cpp | 4 ++-- lib/events/roomtombstoneevent.cpp | 4 ++-- lib/events/stickerevent.cpp | 2 +- lib/events/typingevent.cpp | 2 +- lib/room.cpp | 4 ++-- 16 files changed, 45 insertions(+), 38 deletions(-) (limited to 'lib/events') diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 8cea0ec8..7715d3b8 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -63,7 +63,7 @@ using TagsMap = QHash; {} \ auto _ContentKey() const \ { \ - return content(#_ContentKey##_ls); \ + return contentPart(#_ContentKey##_ls); \ } \ }; \ REGISTER_EVENT_TYPE(_Name) \ diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index 6132cb44..4c01c941 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -19,11 +19,11 @@ public: int lifetime() const { - return content("lifetime"_ls); + return contentPart("lifetime"_ls); } // FIXME: Omittable<>? QString sdp() const { - return contentJson()["answer"_ls].toObject().value("sdp"_ls).toString(); + return contentPart("answer"_ls).value("sdp"_ls).toString(); } }; diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h index c2ccac3b..74c38f2c 100644 --- a/lib/events/callcandidatesevent.h +++ b/lib/events/callcandidatesevent.h @@ -25,17 +25,17 @@ public: QJsonArray candidates() const { - return content("candidates"_ls); + return contentPart("candidates"_ls); } QString callId() const { - return content("call_id"); + return contentPart("call_id"); } int version() const { - return content("version"); + return contentPart("version"); } }; diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index d3454c4f..80b7d651 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -18,11 +18,11 @@ public: int lifetime() const { - return content("lifetime"_ls); + return contentPart("lifetime"_ls); } // FIXME: Omittable<>? QString sdp() const { - return contentJson()["offer"_ls].toObject().value("sdp"_ls).toString(); + return contentPart("offer"_ls).value("sdp"_ls).toString(); } }; diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 598829cd..de89a7c6 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -41,7 +41,7 @@ public: QString algorithm() const { - QString algo = content(AlgorithmKeyL); + QString algo = contentPart(AlgorithmKeyL); if (!SupportedAlgorithms.contains(algo)) { qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo << "is not supported"; @@ -50,17 +50,17 @@ public: } QByteArray ciphertext() const { - return content(CiphertextKeyL).toLatin1(); + return contentPart(CiphertextKeyL).toLatin1(); } QJsonObject ciphertext(const QString& identityKey) const { - return content(CiphertextKeyL).value(identityKey).toObject(); + return contentPart(CiphertextKeyL).value(identityKey).toObject(); } - QString senderKey() const { return content(SenderKeyKeyL); } + QString senderKey() const { return contentPart(SenderKeyKeyL); } /* device_id and session_id are required with Megolm */ - QString deviceId() const { return content(DeviceIdKeyL); } - QString sessionId() const { return content(SessionIdKeyL); } + QString deviceId() const { return contentPart(DeviceIdKeyL); } + QString sessionId() const { return contentPart(SessionIdKeyL); } }; REGISTER_EVENT_TYPE(EncryptedEvent) diff --git a/lib/events/event.h b/lib/events/event.h index 89efb7f8..024e45ef 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -224,18 +224,25 @@ public: const QJsonObject contentJson() const; const QJsonObject unsignedJson() const; - template - T content(const QString& key) const + template + const T contentPart(const QString& key) const { return fromJson(contentJson()[key]); } - template - T content(QLatin1String key) const + template + const T contentPart(QLatin1String key) const { return fromJson(contentJson()[key]); } + template + [[deprecated("Use contentPart() to get a part of the event content")]] // + T content(const QString& key) const + { + return contentPart(key); + } + friend QDebug operator<<(QDebug dbg, const Event& e) { QDebugStateSaver _dss { dbg }; diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index 777905f2..5a2b98c4 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -47,7 +47,7 @@ public: explicit ReactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) {} EventRelation relation() const { - return content(QStringLiteral("m.relates_to")); + return contentPart("m.relates_to"_ls); } }; REGISTER_EVENT_TYPE(ReactionEvent) diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index ed560331..be20bf52 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -17,7 +17,7 @@ public: { return fullJson()["redacts"_ls].toString(); } - QString reason() const { return contentJson()["reason"_ls].toString(); } + QString reason() const { return contentPart("reason"_ls); } }; REGISTER_EVENT_TYPE(RedactionEvent) } // namespace Quotient diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index ff93041c..bb6de648 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -23,17 +23,17 @@ struct Quotient::JsonConverter { bool RoomCreateEvent::isFederated() const { - return fromJson(contentJson()["m.federate"_ls]); + return contentPart("m.federate"_ls); } QString RoomCreateEvent::version() const { - return fromJson(contentJson()["room_version"_ls]); + return contentPart("room_version"_ls); } RoomCreateEvent::Predecessor RoomCreateEvent::predecessor() const { - const auto predJson = contentJson()["predecessor"_ls].toObject(); + const auto predJson = contentPart("predecessor"_ls); return { fromJson(predJson[RoomIdKeyL]), fromJson(predJson[EventIdKeyL]) }; } @@ -45,5 +45,5 @@ bool RoomCreateEvent::isUpgrade() const RoomType RoomCreateEvent::roomType() const { - return fromJson(contentJson()["type"_ls]); + return contentPart("type"_ls); } diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index a6cd84ca..7f13f6f2 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -78,8 +78,8 @@ public: ~CallEventBase() override = default; bool isCallEvent() const override { return true; } - QString callId() const { return content("call_id"_ls); } - int version() const { return content("version"_ls); } + QString callId() const { return contentPart("call_id"_ls); } + int version() const { return contentPart("version"_ls); } }; } // namespace Quotient Q_DECLARE_METATYPE(Quotient::RoomEvent*) diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 14e80324..d021fbec 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -13,10 +13,10 @@ public: explicit RoomKeyEvent(const QJsonObject& obj); - QString algorithm() const { return content("algorithm"_ls); } - QString roomId() const { return content(RoomIdKeyL); } - QString sessionId() const { return content("session_id"_ls); } - QString sessionKey() const { return content("session_key"_ls); } + QString algorithm() const { return contentPart("algorithm"_ls); } + QString roomId() const { return contentPart(RoomIdKeyL); } + QString sessionId() const { return contentPart("session_id"_ls); } + QString sessionKey() const { return contentPart("session_key"_ls); } }; REGISTER_EVENT_TYPE(RoomKeyEvent) } // namespace Quotient diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 9b46594e..2b7b4166 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -204,12 +204,12 @@ RoomMessageEvent::MsgType RoomMessageEvent::msgtype() const QString RoomMessageEvent::rawMsgtype() const { - return contentJson()[MsgTypeKeyL].toString(); + return contentPart(MsgTypeKeyL); } QString RoomMessageEvent::plainBody() const { - return contentJson()[BodyKeyL].toString(); + return contentPart(BodyKeyL); } QMimeType RoomMessageEvent::mimeType() const diff --git a/lib/events/roomtombstoneevent.cpp b/lib/events/roomtombstoneevent.cpp index 080d269c..2c3492d6 100644 --- a/lib/events/roomtombstoneevent.cpp +++ b/lib/events/roomtombstoneevent.cpp @@ -7,10 +7,10 @@ using namespace Quotient; QString RoomTombstoneEvent::serverMessage() const { - return fromJson(contentJson()["body"_ls]); + return contentPart("body"_ls); } QString RoomTombstoneEvent::successorRoomId() const { - return fromJson(contentJson()["replacement_room"_ls]); + return contentPart("replacement_room"_ls); } diff --git a/lib/events/stickerevent.cpp b/lib/events/stickerevent.cpp index ea4dff3f..628fd154 100644 --- a/lib/events/stickerevent.cpp +++ b/lib/events/stickerevent.cpp @@ -12,7 +12,7 @@ StickerEvent::StickerEvent(const QJsonObject &obj) QString StickerEvent::body() const { - return content("body"_ls); + return contentPart("body"_ls); } const EventContent::ImageContent &StickerEvent::image() const diff --git a/lib/events/typingevent.cpp b/lib/events/typingevent.cpp index e97e978f..7e5d7ee6 100644 --- a/lib/events/typingevent.cpp +++ b/lib/events/typingevent.cpp @@ -7,5 +7,5 @@ using namespace Quotient; QStringList TypingEvent::users() const { - return fromJson(contentJson()["user_ids"_ls]); + return contentPart("user_ids"_ls); } diff --git a/lib/room.cpp b/lib/room.cpp index fac24e5e..8f430ccd 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2507,8 +2507,8 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) RoomEventPtr makeReplaced(const RoomEvent& target, const RoomMessageEvent& replacement) { - const auto &targetReply = target.contentJson()["m.relates_to"].toObject(); - auto newContent = replacement.contentJson().value("m.new_content"_ls).toObject(); + const auto& targetReply = target.contentPart("m.relates_to"); + auto newContent = replacement.contentPart("m.new_content"_ls); if (!targetReply.empty()) { newContent["m.relates_to"] = targetReply; } -- cgit v1.2.3 From ce4e878040946b963b30c4768847e2be24dd77fa Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 27 Nov 2021 22:53:02 +0100 Subject: basicEventJson(): dismiss with the template Given that QJsonObject only accepts QStrings in the list constructor, the template is useless cruft. --- lib/events/event.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 024e45ef..733fadd8 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -48,11 +48,10 @@ static const auto PrevContentKeyL = "prev_content"_ls; static const auto StateKeyKeyL = "state_key"_ls; /// Make a minimal correct Matrix event JSON -template -inline QJsonObject basicEventJson(StrT matrixType, const QJsonObject& content) +inline QJsonObject basicEventJson(const QString& matrixType, + const QJsonObject& content) { - return { { TypeKey, std::forward(matrixType) }, - { ContentKey, content } }; + return { { TypeKey, matrixType }, { ContentKey, content } }; } // === Event types and event types registry === -- cgit v1.2.3 From ab91944cc71a72699d0168b7c472326e59319477 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 28 Nov 2021 11:19:08 +0100 Subject: Event::unsignedPart() Similar to contentPart() - apparently there are enough places across the code that would benefit from it. --- lib/events/event.h | 21 +++++++++++---------- lib/events/roomevent.cpp | 10 +++++----- lib/events/stateevent.cpp | 10 +++++----- 3 files changed, 21 insertions(+), 20 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 733fadd8..e45fecca 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -221,18 +221,11 @@ public: // different types, we're implementing it per-event type. const QJsonObject contentJson() const; - const QJsonObject unsignedJson() const; - template - const T contentPart(const QString& key) const + template + const T contentPart(KeyT&& key) const { - return fromJson(contentJson()[key]); - } - - template - const T contentPart(QLatin1String key) const - { - return fromJson(contentJson()[key]); + return fromJson(contentJson()[std::forward(key)]); } template @@ -242,6 +235,14 @@ public: return contentPart(key); } + const QJsonObject unsignedJson() const; + + template + const T unsignedPart(KeyT&& key) const + { + return fromJson(unsignedJson()[std::forward(key)]); + } + friend QDebug operator<<(QDebug dbg, const Event& e) { QDebugStateSaver _dss { dbg }; diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 4fec9d2b..fb921af6 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -19,7 +19,7 @@ RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, RoomEvent::RoomEvent(Type type, const QJsonObject& json) : Event(type, json) { - if (const auto redaction = unsignedJson()[RedactedCauseKeyL]; + if (const auto redaction = unsignedPart(RedactedCauseKeyL); redaction.isObject()) _redactedBecause = makeEvent(redaction.toObject()); } @@ -45,14 +45,14 @@ QString RoomEvent::senderId() const bool RoomEvent::isReplaced() const { - return unsignedJson()["m.relations"_ls].toObject().contains("m.replace"); + return unsignedPart("m.relations"_ls).contains("m.replace"); } QString RoomEvent::replacedBy() const { // clang-format off - return unsignedJson()["m.relations"_ls].toObject() - .value("m.replace").toObject() + return unsignedPart("m.relations"_ls) + .value("m.replace"_ls).toObject() .value(EventIdKeyL).toString(); // clang-format on } @@ -64,7 +64,7 @@ QString RoomEvent::redactionReason() const QString RoomEvent::transactionId() const { - return unsignedJson()["transaction_id"_ls].toString(); + return unsignedPart("transaction_id"_ls); } QString RoomEvent::stateKey() const diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 42fc9054..efe011a0 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -28,22 +28,22 @@ StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, bool StateEventBase::repeatsState() const { - const auto prevContentJson = unsignedJson().value(PrevContentKeyL); + const auto prevContentJson = unsignedPart(PrevContentKeyL); return fullJson().value(ContentKeyL) == prevContentJson; } QString StateEventBase::replacedState() const { - return unsignedJson().value("replaces_state"_ls).toString(); + return unsignedPart("replaces_state"_ls); } void StateEventBase::dumpTo(QDebug dbg) const { if (!stateKey().isEmpty()) dbg << '<' << stateKey() << "> "; - if (unsignedJson().contains(PrevContentKeyL)) - dbg << QJsonDocument(unsignedJson()[PrevContentKeyL].toObject()) - .toJson(QJsonDocument::Compact) + if (const auto prevContentJson = unsignedPart(PrevContentKeyL); + !prevContentJson.isEmpty()) + dbg << QJsonDocument(prevContentJson).toJson(QJsonDocument::Compact) << " -> "; RoomEvent::dumpTo(dbg); } -- cgit v1.2.3 From ca1ba482b50c41425bd0a540c7bb68406d10e552 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 28 Nov 2021 16:42:41 +0100 Subject: Don't std::move when the callee doesn't support it In both fixed cases the callee accepts a const reference, which makes std::move() useless. Static analyzers apparently missed them because the cases are inside a macro. --- lib/events/accountdataevents.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/events') diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 7715d3b8..e5101d20 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -55,11 +55,11 @@ using TagsMap = QHash; public: \ using content_type = _ContentType; \ DEFINE_EVENT_TYPEID(_TypeId, _Name) \ - explicit _Name(QJsonObject obj) : Event(typeId(), std::move(obj)) {} \ - explicit _Name(_ContentType content) \ + explicit _Name(const QJsonObject& obj) : Event(typeId(), obj) {} \ + explicit _Name(const content_type& content) \ : Event(typeId(), matrixTypeId(), \ - QJsonObject { { QStringLiteral(#_ContentKey), \ - toJson(std::move(content)) } }) \ + QJsonObject { \ + { QStringLiteral(#_ContentKey), toJson(content) } }) \ {} \ auto _ContentKey() const \ { \ -- cgit v1.2.3 From 8a769cddcd1a063dd9400518ff65c0b1f1aec1b4 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 28 Nov 2021 16:53:51 +0100 Subject: Comment on const return types in event.h Proper linters recognise that the returned types are not primitive, while people might still be confused a bit. --- lib/events/event.cpp | 3 --- lib/events/event.h | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 3d66ab55..96be717c 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -46,14 +46,11 @@ QString Event::matrixType() const { return fullJson()[TypeKeyL].toString(); } QByteArray Event::originalJson() const { return QJsonDocument(_json).toJson(); } -// On const below: this is to catch accidental attempts to change event JSON -// NOLINTNEXTLINE(readability-const-return-type) const QJsonObject Event::contentJson() const { return fullJson()[ContentKeyL].toObject(); } -// NOLINTNEXTLINE(readability-const-return-type) const QJsonObject Event::unsignedJson() const { return fullJson()[UnsignedKeyL].toObject(); diff --git a/lib/events/event.h b/lib/events/event.h index e45fecca..998a386c 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -220,6 +220,9 @@ public: // a "content" object; but since its structure is different for // different types, we're implementing it per-event type. + // NB: const return types below are meant to catch accidental attempts + // to change event JSON (e.g., consider contentJson()["inexistentKey"]). + const QJsonObject contentJson() const; template -- cgit v1.2.3 From 9f71b2a79fba7c5d5ce09ebfdd482c8c470203d9 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 28 Jan 2021 21:59:20 +0100 Subject: Remove duplicated file --- lib/e2ee.h | 35 ----------------------------------- lib/encryptionmanager.cpp | 2 +- lib/events/encryptedevent.h | 2 +- lib/events/encryptionevent.cpp | 2 +- lib/room.cpp | 2 +- 5 files changed, 4 insertions(+), 39 deletions(-) delete mode 100644 lib/e2ee.h (limited to 'lib/events') diff --git a/lib/e2ee.h b/lib/e2ee.h deleted file mode 100644 index 4044aa02..00000000 --- a/lib/e2ee.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "util.h" - -#include - -namespace Quotient { -inline const auto CiphertextKeyL = "ciphertext"_ls; -inline const auto SenderKeyKeyL = "sender_key"_ls; -inline const auto DeviceIdKeyL = "device_id"_ls; -inline const auto SessionIdKeyL = "session_id"_ls; - -inline const auto AlgorithmKeyL = "algorithm"_ls; -inline const auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; -inline const auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls; - -inline const auto AlgorithmKey = QStringLiteral("algorithm"); -inline const auto RotationPeriodMsKey = QStringLiteral("rotation_period_ms"); -inline const auto RotationPeriodMsgsKey = - QStringLiteral("rotation_period_msgs"); - -inline const auto Ed25519Key = QStringLiteral("ed25519"); -inline const auto Curve25519Key = QStringLiteral("curve25519"); -inline const auto SignedCurve25519Key = QStringLiteral("signed_curve25519"); -inline const auto OlmV1Curve25519AesSha2AlgoKey = - QStringLiteral("m.olm.v1.curve25519-aes-sha2"); -inline const auto MegolmV1AesSha2AlgoKey = - QStringLiteral("m.megolm.v1.aes-sha2"); -inline const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoKey, - MegolmV1AesSha2AlgoKey }; -} // namespace Quotient diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 37f3b7c3..569d369a 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -6,7 +6,7 @@ #include "encryptionmanager.h" #include "connection.h" -#include "e2ee.h" +#include "crypto/e2ee.h" #include "csapi/keys.h" diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index de89a7c6..1d7ea913 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -3,7 +3,7 @@ #pragma once -#include "e2ee.h" +#include "crypto/e2ee.h" #include "roomevent.h" namespace Quotient { diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index aa05a96e..d7bb953a 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -4,7 +4,7 @@ #include "encryptionevent.h" -#include "e2ee.h" +#include "crypto/e2ee.h" #include diff --git a/lib/room.cpp b/lib/room.cpp index 6e6d7f11..0c9af2b9 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -12,7 +12,7 @@ #include "avatar.h" #include "connection.h" #include "converters.h" -#include "e2ee.h" +#include "crypto/e2ee.h" #include "syncdata.h" #include "user.h" #include "eventstats.h" -- cgit v1.2.3 From 5f3e33e1c15be19f09d83a0d6f44d551021a9d44 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 5 Feb 2021 18:45:30 +0100 Subject: Implement key verification events --- CMakeLists.txt | 1 + lib/events/keyverificationevent.cpp | 193 ++++++++++++++++++++++++++++++++++++ lib/events/keyverificationevent.h | 167 +++++++++++++++++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 lib/events/keyverificationevent.cpp create mode 100644 lib/events/keyverificationevent.h (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 5601a281..92a9b213 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,7 @@ list(APPEND lib_SRCS lib/events/encryptedevent.cpp lib/events/roomkeyevent.cpp lib/events/stickerevent.cpp + lib/events/keyverificationevent.cpp lib/jobs/requestdata.cpp lib/jobs/basejob.cpp lib/jobs/syncjob.cpp diff --git a/lib/events/keyverificationevent.cpp b/lib/events/keyverificationevent.cpp new file mode 100644 index 00000000..938b3bde --- /dev/null +++ b/lib/events/keyverificationevent.cpp @@ -0,0 +1,193 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "keyverificationevent.h" + +using namespace Quotient; + +KeyVerificationRequestEvent::KeyVerificationRequestEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationRequestEvent::fromDevice() const +{ + return contentJson()["from_device"_ls].toString(); +} + +QString KeyVerificationRequestEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QStringList KeyVerificationRequestEvent::methods() const +{ + QStringList methods; + for (const auto &method : contentJson()["methods"].toArray()) { + methods.append(method.toString()); + } + return methods; +} + +uint64_t KeyVerificationRequestEvent::timestamp() const +{ + return contentJson()["timestamp"_ls].toDouble(); +} + +KeyVerificationStartEvent::KeyVerificationStartEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationStartEvent::fromDevice() const +{ + return contentJson()["from_device"_ls].toString(); +} + +QString KeyVerificationStartEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationStartEvent::method() const +{ + return contentJson()["method"_ls].toString(); +} + +Omittable KeyVerificationStartEvent::nextMethod() const +{ + auto next = contentJson()["method"_ls]; + if (next.isUndefined()) { + return std::nullopt; + } + return next.toString(); +} + +QStringList KeyVerificationStartEvent::keyAgreementProtocols() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + QStringList protocols; + for (const auto &proto : contentJson()["key_agreement_protocols"_ls].toArray()) { + protocols.append(proto.toString()); + } + return protocols; +} + +QStringList KeyVerificationStartEvent::hashes() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + QStringList hashes; + for (const auto &hashItem : contentJson()["hashes"_ls].toArray()) { + hashes.append(hashItem.toString()); + } + return hashes; +} + +QStringList KeyVerificationStartEvent::messageAuthenticationCodes() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + + QStringList codes; + for (const auto &code : contentJson()["message_authentication_codes"_ls].toArray()) { + codes.append(code.toString()); + } + return codes; +} + +QString KeyVerificationStartEvent::shortAuthenticationString() const +{ + return contentJson()["short_authentification_string"_ls].toString(); +} + +KeyVerificationAcceptEvent::KeyVerificationAcceptEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationAcceptEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationAcceptEvent::method() const +{ + return contentJson()["method"_ls].toString(); +} + +QString KeyVerificationAcceptEvent::keyAgreementProtocol() const +{ + return contentJson()["key_agreement_protocol"_ls].toString(); +} + +QString KeyVerificationAcceptEvent::hashData() const +{ + return contentJson()["hash"_ls].toString(); +} + +QStringList KeyVerificationAcceptEvent::shortAuthenticationString() const +{ + QStringList strings; + for (const auto &authenticationString : contentJson()["short_authentification_string"].toArray()) { + strings.append(authenticationString.toString()); + } + return strings; +} + +QString KeyVerificationAcceptEvent::commitement() const +{ + return contentJson()["commitement"].toString(); +} + +KeyVerificationCancelEvent::KeyVerificationCancelEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationCancelEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationCancelEvent::reason() const +{ + return contentJson()["reason"_ls].toString(); +} + +QString KeyVerificationCancelEvent::code() const +{ + return contentJson()["code"_ls].toString(); +} + +KeyVerificationKeyEvent::KeyVerificationKeyEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationKeyEvent::transactionId() const +{ + return contentJson()["transaction_id"_ls].toString(); +} + +QString KeyVerificationKeyEvent::key() const +{ + return contentJson()["key"_ls].toString(); +} + +KeyVerificationMacEvent::KeyVerificationMacEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationMacEvent::transactionId() const +{ + return contentJson()["transaction_id"].toString(); +} + +QString KeyVerificationMacEvent::keys() const +{ + return contentJson()["keys"].toString(); +} + +QHash KeyVerificationMacEvent::mac() const +{ + QHash macs; + const auto macObj = contentJson()["mac"_ls].toObject(); + for (auto mac = macObj.constBegin(); mac != macObj.constEnd(); mac++) { + macs.insert(mac.key(), mac.value().toString()); + } + return macs; +} diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h new file mode 100644 index 00000000..13e7dcdd --- /dev/null +++ b/lib/events/keyverificationevent.h @@ -0,0 +1,167 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "event.h" + +namespace Quotient { + +/// Requests a key verification with another user's devices. +/// Typically sent as a to-device event. +class KeyVerificationRequestEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.request", KeyVerificationRequestEvent) + + explicit KeyVerificationRequestEvent(const QJsonObject& obj); + + /// The device ID which is initiating the request. + QString fromDevice() const; + + /// An opaque identifier for the verification request. Must + /// be unique with respect to the devices involved. + QString transactionId() const; + + /// The verification methods supported by the sender. + QStringList methods() const; + + /// The POSIX timestamp in milliseconds for when the request was + /// made. If the request is in the future by more than 5 minutes or + /// more than 10 minutes in the past, the message should be ignored + /// by the receiver. + uint64_t timestamp() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationRequestEvent) + +/// Begins a key verification process. +class KeyVerificationStartEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent) + + explicit KeyVerificationStartEvent(const QJsonObject &obj); + + /// The device ID which is initiating the process. + QString fromDevice() const; + + /// An opaque identifier for the verification request. Must + /// be unique with respect to the devices involved. + QString transactionId() const; + + /// The verification method to use. + QString method() const; + + /// Optional method to use to verify the other user's key with. + Omittable nextMethod() const; + + // SAS.V1 methods + + /// The key agreement protocols the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList keyAgreementProtocols() const; + + /// The hash methods the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList hashes() const; + + /// The message authentication codes that the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList messageAuthenticationCodes() const; + + /// The SAS methods the sending device (and the sending device's + /// user) understands. + /// \note Only exist if method is m.sas.v1 + QString shortAuthenticationString() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationStartEvent) + +/// Accepts a previously sent m.key.verification.start message. +/// Typically sent as a to-device event. +class KeyVerificationAcceptEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.accept", KeyVerificationAcceptEvent) + + explicit KeyVerificationAcceptEvent(const QJsonObject& obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The verification method to use. Must be 'm.sas.v1'. + QString method() const; + + /// The key agreement protocol the device is choosing to use, out of + /// the options in the m.key.verification.start message. + QString keyAgreementProtocol() const; + + /// The hash method the device is choosing to use, out of the + /// options in the m.key.verification.start message. + QString hashData() const; + + /// The message authentication code the device is choosing to use, out + /// of the options in the m.key.verification.start message. + QString messageAuthenticationCode() const; + + /// The SAS methods both devices involved in the verification process understand. + QStringList shortAuthenticationString() const; + + /// The hash (encoded as unpadded base64) of the concatenation of the + /// device's ephemeral public key (encoded as unpadded base64) and the + /// canonical JSON representation of the m.key.verification.start message. + QString commitement() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent) + +class KeyVerificationCancelEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent) + + explicit KeyVerificationCancelEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// A human readable description of the code. The client should only + /// rely on this string if it does not understand the code. + QString reason() const; + + /// The error code for why the process/request was cancelled by the user. + QString code() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationCancelEvent) + +/// Sends the ephemeral public key for a device to the partner device. +/// Typically sent as a to-device event. +class KeyVerificationKeyEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent) + + explicit KeyVerificationKeyEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The device's ephemeral public key, encoded as unpadded base64. + QString key() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationKeyEvent) + +/// Sends the MAC of a device's key to the partner device. +class KeyVerificationMacEvent : public Event { + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent) + + explicit KeyVerificationMacEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The device's ephemeral public key, encoded as unpadded base64. + QString keys() const; + + QHash mac() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationMacEvent) +} // namespace Quotient -- 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 --- CMakeLists.txt | 4 +++- lib/connection.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++-- lib/connection.h | 5 +++++ lib/encryptionmanager.cpp | 4 ++-- lib/events/eventcontent.cpp | 1 + lib/room.cpp | 5 ++--- 6 files changed, 59 insertions(+), 8 deletions(-) (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d5f08af..3977a9d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,8 @@ if (${PROJECT_NAME}_ENABLE_E2EE) endif() endif() +find_package(Qt${QT_MAJOR_VERSION}Keychain REQUIRED) + # Set up source files list(APPEND lib_SRCS lib/quotient_common.h @@ -325,7 +327,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE) find_dependency(OpenSSL)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) +target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${QTKEYCHAIN_LIBRARIES}) if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) 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 diff --git a/lib/connection.h b/lib/connection.h index c351f93e..e5cec34b 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -21,6 +21,10 @@ #include +#ifdef Quotient_E2EE_ENABLED +#include "crypto/e2ee.h" +#endif + Q_DECLARE_METATYPE(Quotient::GetLoginFlowsJob::LoginFlow) namespace Quotient { @@ -650,6 +654,7 @@ public Q_SLOTS: #ifdef Quotient_E2EE_ENABLED void encryptionUpdate(Room *room); + PicklingMode picklingMode() const; #endif Q_SIGNALS: /// \brief Initial server resolution has failed diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index e8cc7b3a..5c1750c9 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -71,7 +71,7 @@ public: } for(const auto &senderKey : json["sessions"].toObject().keys()) { auto pickle = json["sessions"].toObject()[senderKey].toString(); - auto sessionResult = QOlmSession::unpickle(pickle.toLatin1(), Unencrypted{}); + auto sessionResult = QOlmSession::unpickle(pickle.toLatin1(), static_cast(q->parent())->picklingMode()); if(std::holds_alternative(sessionResult)) { qCWarning(E2EE) << "Failed to unpickle olm session"; continue; @@ -97,7 +97,7 @@ public: { QJsonObject sessionsJson; for (const auto &session : sessions) { - auto pickleResult = session.second->pickle(Unencrypted{}); + auto pickleResult = session.second->pickle(static_cast(q->parent())->picklingMode()); if(std::holds_alternative(pickleResult)) { qCWarning(E2EE) << "Failed to pickle session"; continue; diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 22878d4c..d4cb43ff 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -75,6 +75,7 @@ void FileInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert(QStringLiteral("size"), payloadSize); if (mimeType.isValid()) infoJson->insert(QStringLiteral("mimetype"), mimeType.name()); + //TODO add encryptedfile } ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) diff --git a/lib/room.cpp b/lib/room.cpp index a1354fc5..b60a23f2 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -394,7 +394,7 @@ public: auto pickle = s.toObject()["pickle"].toString().toLatin1(); auto senderKey = s.toObject()["sender_key"].toString(); auto sessionId = s.toObject()["session_id"].toString(); - auto sessionResult = QOlmInboundGroupSession::unpickle(pickle, Unencrypted{}); + auto sessionResult = QOlmInboundGroupSession::unpickle(pickle, connection->picklingMode()); if(std::holds_alternative(sessionResult)) { qCWarning(E2EE) << "Failed to unpickle olm session"; continue; @@ -421,7 +421,7 @@ public: { QJsonArray sessionsJson; for (const auto &session : groupSessions) { - auto pickleResult = session.second->pickle(Unencrypted{}); + auto pickleResult = session.second->pickle(connection->picklingMode()); sessionsJson += QJsonObject { {QStringLiteral("sender_key"), session.first.first}, {QStringLiteral("session_id"), session.first.second}, @@ -2659,7 +2659,6 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) //TODO should this be done before dropDuplicateEvents? for(long unsigned int i = 0; i < events.size(); i++) { if(auto* encrypted = eventCast(events[i])) { - qDebug() << "Encrypted Event"; auto decrypted = q->decryptMessage(*encrypted); if(decrypted) { events[i] = std::move(decrypted); -- cgit v1.2.3 From 3128df9daa196b2cf3cdb8e029e22d79c397ff66 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 27 Nov 2021 01:34:44 +0100 Subject: Apply even more suggestions --- lib/events/encryptedevent.cpp | 22 ++++++++++++++++++ lib/events/encryptedevent.h | 1 + lib/room.cpp | 53 ++++++++++++++----------------------------- 3 files changed, 40 insertions(+), 36 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 0290f973..c9257584 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" +#include "roommessageevent.h" using namespace Quotient; @@ -30,3 +31,24 @@ EncryptedEvent::EncryptedEvent(const QJsonObject& obj) { qCDebug(E2EE) << "Encrypted event from" << senderId(); } + +RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const +{ + auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); + eventObject["event_id"] = id(); + eventObject["sender"] = senderId(); + eventObject["origin_server_ts"] = originTimestamp().toMSecsSinceEpoch(); + if(contentJson().contains("m.relates_to")) { + auto relates = contentJson()["m.relates_to"].toObject(); + auto content = eventObject["content"].toObject(); + content["m.relates_to"] = relates; + eventObject["content"] = content; + } + if(unsignedJson().contains("redacts")) { + auto redacts = unsignedJson()["redacts"].toString(); + auto unsign = eventObject["unsigned"].toObject(); + unsign["redacts"] = redacts; + eventObject["unsigned"] = unsign; + } + return makeEvent(eventObject); +} diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 1d7ea913..28398827 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -61,6 +61,7 @@ public: /* device_id and session_id are required with Megolm */ QString deviceId() const { return contentPart(DeviceIdKeyL); } QString sessionId() const { return contentPart(SessionIdKeyL); } + RoomEventPtr createDecrypted(const QString &decrypted) const; }; REGISTER_EVENT_TYPE(EncryptedEvent) diff --git a/lib/room.cpp b/lib/room.cpp index e143747b..07ffd0cd 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -366,9 +366,7 @@ public: // A map from to QHash, QPair> groupSessionIndexRecord; // TODO: cache - // A map from senderKey to a map of sessionId to InboundGroupSession - // Not using QMultiHash, because we want to quickly return - // a number of relations for a given event without enumerating them. + // A map from (senderKey, sessionId) to InboundGroupSession UnorderedMap, std::unique_ptr> groupSessions; void loadMegOlmSessions() { @@ -399,7 +397,7 @@ public: qCWarning(E2EE) << "Failed to unpickle olm session"; continue; } - groupSessions[qMakePair(senderKey, sessionId)] = std::move(std::get>(sessionResult)); + groupSessions[{senderKey, sessionId}] = std::move(std::get>(sessionResult)); } } void saveMegOlmSessions() { @@ -438,7 +436,7 @@ public: bool addInboundGroupSession(QString senderKey, QString sessionId, QString sessionKey) { - if (groupSessions.find(qMakePair(senderKey, sessionId)) != groupSessions.end()) { + if (groupSessions.find({senderKey, sessionId}) != groupSessions.end()) { qCWarning(E2EE) << "Inbound Megolm session" << sessionId << "with senderKey" << senderKey << "already exists"; return false; @@ -462,8 +460,7 @@ public: const QString& eventId, QDateTime timestamp) { - QPair senderSessionPairKey = - qMakePair(senderKey, sessionId); + const auto senderSessionPairKey = qMakePair(senderKey, sessionId); auto groupSessionIt = groupSessions.find(senderSessionPairKey); if (groupSessionIt == groupSessions.end()) { qCWarning(E2EE) << "Unable to decrypt event" << eventId @@ -1540,36 +1537,20 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return {}; #else // Quotient_E2EE_ENABLED - if (encryptedEvent.algorithm() == MegolmV1AesSha2AlgoKey) { - QString decrypted = d->groupSessionDecryptMessage( - encryptedEvent.ciphertext(), encryptedEvent.senderKey(), - encryptedEvent.sessionId(), encryptedEvent.id(), - encryptedEvent.originTimestamp()); - if (decrypted.isEmpty()) { - qCWarning(E2EE) << "Encrypted message is empty"; - return {}; - } - auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); - eventObject["event_id"] = encryptedEvent.id(); - eventObject["sender"] = encryptedEvent.senderId(); - eventObject["origin_server_ts"] = encryptedEvent.originTimestamp().toMSecsSinceEpoch(); - if(encryptedEvent.contentJson().contains("m.relates_to")) { - auto relates = encryptedEvent.contentJson()["m.relates_to"].toObject(); - auto content = eventObject["content"].toObject(); - content["m.relates_to"] = relates; - eventObject["content"] = content; - } - if(encryptedEvent.unsignedJson().contains("redacts")) { - auto redacts = encryptedEvent.unsignedJson()["redacts"].toString(); - auto unsign = eventObject["unsigned"].toObject(); - unsign["redacts"] = redacts; - eventObject["unsigned"] = unsign; - } - return makeEvent(eventObject); + if (encryptedEvent.algorithm() != MegolmV1AesSha2AlgoKey) { + qWarning(E2EE) << "Algorithm of the encrypted event with id" + << encryptedEvent.id() << "is not decryptable by the current device"; + return {}; } - qCDebug(E2EE) << "Algorithm of the encrypted event with id" - << encryptedEvent.id() << "is not decryptable by the current device"; - return {}; + QString decrypted = d->groupSessionDecryptMessage( + encryptedEvent.ciphertext(), encryptedEvent.senderKey(), + encryptedEvent.sessionId(), encryptedEvent.id(), + encryptedEvent.originTimestamp()); + if (decrypted.isEmpty()) { + qCWarning(E2EE) << "Encrypted message is empty"; + return {}; + } + return encryptedEvent.createDecrypted(decrypted); #endif // Quotient_E2EE_ENABLED } -- 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 --- autotests/testolmsession.cpp | 2 +- lib/connection.cpp | 3 +-- lib/encryptionmanager.cpp | 2 +- lib/events/encryptedevent.cpp | 12 +++++------- lib/jobs/downloadfilejob.cpp | 2 +- 5 files changed, 9 insertions(+), 12 deletions(-) (limited to 'lib/events') diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 750b804e..00d76d4e 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -30,7 +30,7 @@ std::pair createSessionPair() throw "Wrong first message type received, can't create session"; } auto inbound = std::get(accountB.createInboundSession(preKey)); - return std::make_pair(std::move(inbound), std::move(outbound)); + return { std::move(inbound), std::move(outbound) }; } void TestOlmSession::olmOutboundSessionCreation() 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(); diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index ed6ad20b..5c106e12 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -80,7 +80,7 @@ public: } } void saveSessions() { - QFile outFile { static_cast(q->parent())->e2eeDataDir() + QStringLiteral("/olmsessions.json") }; + QFile outFile { static_cast(q->parent())->e2eeDataDir() % "/olmsessions.json" }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index c9257584..2e0d7387 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -38,17 +38,15 @@ RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const eventObject["event_id"] = id(); eventObject["sender"] = senderId(); eventObject["origin_server_ts"] = originTimestamp().toMSecsSinceEpoch(); - if(contentJson().contains("m.relates_to")) { - auto relates = contentJson()["m.relates_to"].toObject(); + if (const auto relatesToJson = contentPart("m.relates_to"_ls); !relatesToJson.isUndefined()) { auto content = eventObject["content"].toObject(); - content["m.relates_to"] = relates; + content["m.relates_to"] = relatesToJson.toObject(); eventObject["content"] = content; } - if(unsignedJson().contains("redacts")) { - auto redacts = unsignedJson()["redacts"].toString(); + if (const auto redactsJson = unsignedPart("redacts"_ls); !redactsJson.isUndefined()) { auto unsign = eventObject["unsigned"].toObject(); - unsign["redacts"] = redacts; + unsign["redacts"] = redactsJson.toString(); eventObject["unsigned"] = unsign; } - return makeEvent(eventObject); + return loadEvent(eventObject); } diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 0b4cf6d2..2eea9d59 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -8,7 +8,7 @@ #include #ifdef Quotient_E2EE_ENABLED -# include +# include # include "encryptionmanager.h" # include "events/encryptedfile.h" #endif -- 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 ++-- lib/events/event.h | 58 ++++++++++++++++++++++++++++++----------------------- lib/room.cpp | 9 +++++---- quotest/quotest.cpp | 4 ++-- 4 files changed, 42 insertions(+), 33 deletions(-) (limited to 'lib/events') 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); diff --git a/lib/events/event.h b/lib/events/event.h index 998a386c..ce737280 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -290,7 +290,7 @@ using Events = EventsArray; } \ // End of macro -// === is<>(), eventCast<>() and visit<>() === +// === is<>(), eventCast<>() and switchOnType<>() === template inline bool is(const Event& e) @@ -312,12 +312,12 @@ inline auto eventCast(const BasePtrT& eptr) : nullptr; } -// A single generic catch-all visitor +// A trivial generic catch-all "switch" template -inline auto visit(const BaseEventT& event, FnT&& visitor) - -> decltype(visitor(event)) +inline auto switchOnType(const BaseEventT& event, FnT&& fn) + -> decltype(fn(event)) { - return visitor(event); + return fn(event); } namespace _impl { @@ -328,52 +328,60 @@ namespace _impl { && !std::is_same_v>>; } -// A single type-specific void visitor +// A trivial type-specific "switch" for a void function template -inline auto visit(const BaseT& event, FnT&& visitor) +inline auto switchOnType(const BaseT& event, FnT&& fn) -> std::enable_if_t<_impl::needs_downcast && std::is_void_v>> { using event_type = fn_arg_t; if (is>(event)) - visitor(static_cast(event)); + fn(static_cast(event)); } -// A single type-specific non-void visitor with an optional default value -// non-voidness is guarded by defaultValue type +// A trivial type-specific "switch" for non-void functions with an optional +// default value; non-voidness is guarded by defaultValue type template -inline auto visit(const BaseT& event, FnT&& visitor, - fn_return_t&& defaultValue = {}) +inline auto switchOnType(const BaseT& event, FnT&& fn, + fn_return_t&& defaultValue = {}) -> std::enable_if_t<_impl::needs_downcast, fn_return_t> { using event_type = fn_arg_t; if (is>(event)) - return visitor(static_cast(event)); - return std::forward>(defaultValue); + return fn(static_cast(event)); + return std::move(defaultValue); } -// A chain of 2 or more visitors +// A switch for a chain of 2 or more functions template -inline std::common_type_t, fn_return_t> visit( - const BaseT& event, FnT1&& visitor1, FnT2&& visitor2, - FnTs&&... visitors) +inline std::common_type_t, fn_return_t> +switchOnType(const BaseT& event, FnT1&& fn1, FnT2&& fn2, FnTs&&... fns) { using event_type1 = fn_arg_t; if (is>(event)) - return visitor1(static_cast(event)); - return visit(event, std::forward(visitor2), - std::forward(visitors)...); + return fn1(static_cast(event)); + return switchOnType(event, std::forward(fn2), + std::forward(fns)...); } -// A facility overload that calls void-returning visit() on each event +template +[[deprecated("The new name for visit() is switchOnType()")]] // +inline std::common_type_t...> +visit(const BaseT& event, FnTs&&... fns) +{ + return switchOnType(event, std::forward(fns)...); +} + + // A facility overload that calls void-returning switchOnType() on each event // over a range of event pointers +// TODO: replace with ranges::for_each once all standard libraries have it template -inline auto visitEach(RangeT&& events, FnTs&&... visitors) +inline auto visitEach(RangeT&& events, FnTs&&... fns) -> std::enable_if_t(visitors)...))>> + decltype(switchOnType(**begin(events), std::forward(fns)...))>> { for (auto&& evtPtr: events) - visit(*evtPtr, std::forward(visitors)...); + switchOnType(*evtPtr, std::forward(fns)...); } } // namespace Quotient Q_DECLARE_METATYPE(Quotient::Event*) diff --git a/lib/room.cpp b/lib/room.cpp index 4d60570d..b3438e08 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2779,7 +2779,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) auto& curStateEvent = d->currentState[{ e.matrixType(), e.stateKey() }]; // Prepare for the state change // clang-format off - const bool proceed = visit(e + const bool proceed = switchOnType(e , [this, curStateEvent](const RoomMemberEvent& rme) { // clang-format on auto* oldRme = static_cast(curStateEvent); @@ -2877,7 +2877,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) // Update internal structures as per the change and work out the return value // clang-format off - const auto result = visit(e + const auto result = switchOnType(e , [] (const RoomNameEvent&) { return Change::Name; } @@ -2885,7 +2885,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) // clang-format on setObjectName(cae.alias().isEmpty() ? d->id : cae.alias()); const auto* oldCae = - static_cast(oldStateEvent); + static_cast(oldStateEvent); QStringList previousAltAliases {}; if (oldCae) { previousAltAliases = oldCae->altAliases(); @@ -2897,7 +2897,8 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) if (!cae.alias().isEmpty()) newAliases.push_front(cae.alias()); - connection()->updateRoomAliases(id(), previousAltAliases, newAliases); + connection()->updateRoomAliases(id(), previousAltAliases, + newAliases); return Change::Aliases; // clang-format off } diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index d006c7fb..764d5dfd 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -498,8 +498,8 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, clog << "File event " << txnId.toStdString() << " arrived in the timeline" << endl; - // This part tests visit() - return visit( + // This part tests switchOnType() + return switchOnType( *evt, [&](const RoomMessageEvent& e) { // TODO: check #366 once #368 is implemented -- cgit v1.2.3 From dc08fb9dfd474023084de9ce86f29f177ca52fdc Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Dec 2021 15:24:44 +0100 Subject: Improve function_traits<>; split out from util.* Quotient::function_traits<> did not support member functions in a proper way (i.e. the way std::invoke_result<> treats them, with the function's owning class represented as the first parameter). Now that I gained the skill and understanding in function_traits<> somewhat wicked machinery, I could properly support member functions. Overloads and generic lambdas are not supported but maybe we'll get to those one day. --- CMakeLists.txt | 2 ++ lib/events/event.h | 1 + lib/function_traits.cpp | 53 +++++++++++++++++++++++++++ lib/function_traits.h | 93 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/qt_connection_util.h | 2 +- lib/util.cpp | 32 ----------------- lib/util.h | 52 --------------------------- 7 files changed, 150 insertions(+), 85 deletions(-) create mode 100644 lib/function_traits.cpp create mode 100644 lib/function_traits.h (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index aa3b9c98..ca92699c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,6 +125,8 @@ endif () # Set up source files list(APPEND lib_SRCS lib/quotient_common.h + lib/function_traits.h + lib/function_traits.cpp lib/networkaccessmanager.cpp lib/connectiondata.cpp lib/connection.cpp diff --git a/lib/events/event.h b/lib/events/event.h index ce737280..4d4bb16b 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -5,6 +5,7 @@ #include "converters.h" #include "logging.h" +#include "function_traits.h" namespace Quotient { // === event_ptr_tt<> and type casting facilities === diff --git a/lib/function_traits.cpp b/lib/function_traits.cpp new file mode 100644 index 00000000..4ff427e4 --- /dev/null +++ b/lib/function_traits.cpp @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "function_traits.h" + +// Tests for function_traits<> + +using namespace Quotient; + +int f_(); +static_assert(std::is_same, int>::value, + "Test fn_return_t<>"); + +void f1_(int, float); +static_assert(std::is_same, float>::value, + "Test fn_arg_t<>"); + +struct Fo { + int operator()(); + static constexpr auto l = [] { return 0.0f; }; + bool memFn(); + void constMemFn() const&; + double field; + const double field2; +}; +static_assert(std::is_same_v, int>, + "Test return type of function object"); +static_assert(std::is_same_v, float>, + "Test return type of lambda"); +static_assert(std::is_same_v, Fo>, + "Test first argument type of member function"); +static_assert(std::is_same_v, bool>, + "Test return type of member function"); +static_assert(std::is_same_v, const Fo&>, + "Test first argument type of const member function"); +static_assert(std::is_void_v>, + "Test return type of const member function"); +static_assert(std::is_same_v, double&>, + "Test return type of a class member"); +static_assert(std::is_same_v, const double&>, + "Test return type of a const class member"); + +struct Fo1 { + void operator()(int); +}; +static_assert(std::is_same, int>(), + "Test fn_arg_t defaulting to first argument"); + +template +static void ft(const std::vector&); +static_assert( + std::is_same)>, const std::vector&>(), + "Test function templates"); diff --git a/lib/function_traits.h b/lib/function_traits.h new file mode 100644 index 00000000..83b8e425 --- /dev/null +++ b/lib/function_traits.h @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include + +namespace Quotient { + +namespace _impl { + template + struct fn_traits {}; +} + +/// Determine traits of an arbitrary function/lambda/functor +/*! + * Doesn't work with generic lambdas and function objects that have + * operator() overloaded. + * \sa + * https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda#7943765 + */ +template +struct function_traits + : public _impl::fn_traits> {}; + +// Specialisation for a function +template +struct function_traits { + using return_type = ReturnT; + using arg_types = std::tuple; + // See also the comment for wrap_in_function() in qt_connection_util.h + using function_type = std::function; +}; + +namespace _impl { + template + struct fn_object_traits; + + // Specialisation for a lambda function + template + struct fn_object_traits + : function_traits {}; + + // Specialisation for a const lambda function + template + struct fn_object_traits + : function_traits {}; + + // Specialisation for function objects with (non-overloaded) operator() + // (this includes non-generic lambdas) + template + struct fn_traits + : public fn_object_traits {}; + + // Specialisation for a member function in a non-functor class + template + struct fn_traits + : function_traits {}; + + // Specialisation for a const member function + template + struct fn_traits + : function_traits {}; + + // Specialisation for a constref member function + template + struct fn_traits + : function_traits {}; + + // Specialisation for a prvalue member function + template + struct fn_traits + : function_traits {}; + + // Specialisation for a pointer-to-member + template + struct fn_traits + : function_traits {}; + + // Specialisation for a const pointer-to-member + template + struct fn_traits + : function_traits {}; +} // namespace _impl + +template +using fn_return_t = typename function_traits::return_type; + +template +using fn_arg_t = + std::tuple_element_t::arg_types>; + +} // namespace Quotient diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index 46294499..86593cc8 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -3,7 +3,7 @@ #pragma once -#include "util.h" +#include "function_traits.h" #include diff --git a/lib/util.cpp b/lib/util.cpp index 2dfb09a6..03ebf325 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -135,35 +135,3 @@ int Quotient::patchVersion() { return Quotient_VERSION_PATCH; } - -// Tests for function_traits<> - -using namespace Quotient; - -int f_(); -static_assert(std::is_same, int>::value, - "Test fn_return_t<>"); - -void f1_(int, QString); -static_assert(std::is_same, QString>::value, - "Test fn_arg_t<>"); - -struct Fo { - int operator()(); - static constexpr auto l = [] { return 0.0f; }; -}; -static_assert(std::is_same, int>::value, - "Test return type of function object"); -static_assert(std::is_same, float>::value, - "Test return type of lambda"); - -struct Fo1 { - void operator()(int); -}; -static_assert(std::is_same, int>(), - "Test fn_arg_t defaulting to first argument"); - -template -static QString ft(T&&); -static_assert(std::is_same)>, QString&&>(), - "Test function templates"); diff --git a/lib/util.h b/lib/util.h index 13efb94b..97f0ecbc 100644 --- a/lib/util.h +++ b/lib/util.h @@ -189,58 +189,6 @@ inline auto merge(T1& lhs, const Omittable& rhs) return true; } -namespace _impl { - template - struct fn_traits {}; -} - -/// Determine traits of an arbitrary function/lambda/functor -/*! - * Doesn't work with generic lambdas and function objects that have - * operator() overloaded. - * \sa - * https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda#7943765 - */ -template -struct function_traits - : public _impl::fn_traits> {}; - -// Specialisation for a function -template -struct function_traits { - using return_type = ReturnT; - using arg_types = std::tuple; - // Doesn't (and there's no plan to make it) work for "classic" - // member functions (i.e. outside of functors). - // See also the comment for wrap_in_function() below - using function_type = std::function; -}; - -namespace _impl { - // Specialisation for function objects with (non-overloaded) operator() - // (this includes non-generic lambdas) - template - struct fn_traits - : public fn_traits {}; - - // Specialisation for a member function - template - struct fn_traits - : function_traits {}; - - // Specialisation for a const member function - template - struct fn_traits - : function_traits {}; -} // namespace _impl - -template -using fn_return_t = typename function_traits::return_type; - -template -using fn_arg_t = - std::tuple_element_t::arg_types>; - inline constexpr auto operator"" _ls(const char* s, std::size_t size) { return QLatin1String(s, int(size)); -- cgit v1.2.3 From 776d05bf98a5dd9e484d5a0e651c71fa95498689 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 3 Dec 2021 21:36:32 +0100 Subject: Cleanup; drop an unused RoomAliasesEvent constructor Also, RoomAliasesEvent is to be completely gone after 0.7. --- lib/events/accountdataevents.h | 5 ++--- lib/events/simplestateevents.h | 12 ++---------- 2 files changed, 4 insertions(+), 13 deletions(-) (limited to 'lib/events') diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index e5101d20..9cf77be3 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -4,7 +4,6 @@ #pragma once #include "event.h" -#include "eventcontent.h" namespace Quotient { constexpr const char* FavouriteTag = "m.favourite"; @@ -16,12 +15,12 @@ struct TagRecord { order_type order; - TagRecord(order_type order = none) : order(std::move(order)) {} + TagRecord(order_type order = none) : order(order) {} bool operator<(const TagRecord& other) const { // Per The Spec, rooms with no order should be after those with order, - // against optional<>::operator<() convention. + // against std::optional<>::operator<() convention. return order && (!other.order || *order < *other.order); } }; diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index cf1bfbba..9ce78609 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -8,8 +8,7 @@ namespace Quotient { namespace EventContent { template - class SimpleContent { - public: + struct SimpleContent { using value_type = T; // The constructor is templated to enable perfect forwarding @@ -25,11 +24,8 @@ namespace EventContent { return { { key, Quotient::toJson(value) } }; } - public: T value; - - protected: - QString key; + const QString key; }; } // namespace EventContent @@ -65,10 +61,6 @@ public: explicit RoomAliasesEvent(const QJsonObject& obj) : StateEvent(typeId(), obj, QStringLiteral("aliases")) {} - RoomAliasesEvent(const QString& server, const QStringList& aliases) - : StateEvent(typeId(), matrixTypeId(), server, - QStringLiteral("aliases"), aliases) - {} QString server() const { return stateKey(); } QStringList aliases() const { return content().value; } }; -- 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 --- CMakeLists.txt | 5 +- lib/connection.cpp | 23 ++-- lib/crypto/e2ee.h | 6 + lib/crypto/qolminboundsession.cpp | 2 +- lib/crypto/qolminboundsession.h | 2 +- lib/crypto/qolmsession.h | 3 - lib/database.cpp | 240 ++++++++++++++++++++++++++++++++++++++ lib/database.h | 46 ++++++++ lib/encryptionmanager.cpp | 96 +++------------ lib/events/encryptedevent.cpp | 1 + lib/logging.h | 1 + lib/room.cpp | 82 ++----------- 12 files changed, 335 insertions(+), 172 deletions(-) create mode 100644 lib/database.cpp create mode 100644 lib/database.h (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index dbb43f89..9f886094 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,7 +83,7 @@ else() set(QtExtraModules "Multimedia") # See #483 endif() string(REGEX REPLACE "^(.).*" "Qt\\1" Qt ${QtMinVersion}) # makes "Qt5" or "Qt6" -find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModules}) +find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test Sql ${QtExtraModules}) get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") @@ -133,6 +133,7 @@ list(APPEND lib_SRCS lib/eventitem.cpp lib/accountregistry.cpp lib/mxcreply.cpp + lib/database.cpp lib/events/event.cpp lib/events/roomevent.cpp lib/events/stateevent.cpp @@ -327,7 +328,7 @@ if (${PROJECT_NAME}_ENABLE_E2EE) find_dependency(OpenSSL)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${QTKEYCHAIN_LIBRARIES}) +target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${Qt}::Sql ${QTKEYCHAIN_LIBRARIES}) if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) 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 } diff --git a/lib/crypto/e2ee.h b/lib/crypto/e2ee.h index 2d280185..41cd2878 100644 --- a/lib/crypto/e2ee.h +++ b/lib/crypto/e2ee.h @@ -49,6 +49,12 @@ struct Encrypted { using PicklingMode = std::variant; +class QOlmSession; +using QOlmSessionPtr = std::unique_ptr; + +class QOlmInboundGroupSession; +using QOlmInboundGroupSessionPtr = std::unique_ptr; + template struct overloaded : Ts... { using Ts::operator()...; }; template overloaded(Ts...) -> overloaded; diff --git a/lib/crypto/qolminboundsession.cpp b/lib/crypto/qolminboundsession.cpp index beaf3299..31d699f1 100644 --- a/lib/crypto/qolminboundsession.cpp +++ b/lib/crypto/qolminboundsession.cpp @@ -72,7 +72,7 @@ QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const return pickledBuf; } -std::variant, QOlmError> QOlmInboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +std::variant, QOlmError> QOlmInboundGroupSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) { QByteArray pickledBuf = pickled; const auto groupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); diff --git a/lib/crypto/qolminboundsession.h b/lib/crypto/qolminboundsession.h index 36ab4942..362e42ba 100644 --- a/lib/crypto/qolminboundsession.h +++ b/lib/crypto/qolminboundsession.h @@ -27,7 +27,7 @@ public: QByteArray pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. - static std::variant, QOlmError> unpickle(QByteArray &picked, const PicklingMode &mode); + static std::variant, QOlmError> unpickle(const QByteArray &picked, const PicklingMode &mode); //! Decrypts ciphertext received for this group session. std::variant, QOlmError> decrypt(const QByteArray &message); //! Export the base64-encoded ratchet key for this session, at the given index, diff --git a/lib/crypto/qolmsession.h b/lib/crypto/qolmsession.h index 7a040b3d..711ca66b 100644 --- a/lib/crypto/qolmsession.h +++ b/lib/crypto/qolmsession.h @@ -73,7 +73,4 @@ private: static std::variant, QOlmError> createInbound(QOlmAccount *account, const QOlmMessage& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); OlmSession* m_session; }; - -using QOlmSessionPtr = std::unique_ptr; - } //namespace Quotient diff --git a/lib/database.cpp b/lib/database.cpp new file mode 100644 index 00000000..153aab31 --- /dev/null +++ b/lib/database.cpp @@ -0,0 +1,240 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "database.h" + +#include +#include +#include +#include +#include +#include + +#include "crypto/e2ee.h" +#include "crypto/qolmsession.h" +#include "crypto/qolminboundsession.h" + +//TODO: delete room specific data when leaving room + +using namespace Quotient; +Database::Database() +{ + QSqlDatabase::addDatabase(QStringLiteral("QSQLITE")); + QString databasePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir(databasePath).mkpath(databasePath); + QSqlDatabase::database().setDatabaseName(databasePath + QStringLiteral("/database.db3")); + QSqlDatabase::database().open(); + + switch(version()) { + case 0: migrateTo1(); + } +} + +int Database::version() +{ + auto query = execute(QStringLiteral("PRAGMA user_version;")); + if (query.next()) { + bool ok; + int value = query.value(0).toInt(&ok); + qDebug() << "Database version" << value; + if (ok) + return value; + } else { + qCritical() << "Failed to check database version"; + } + return -1; +} + +QSqlQuery Database::execute(const QString &queryString) +{ + auto query = QSqlDatabase::database().exec(queryString); + if (query.lastError().type() != QSqlError::NoError) { + qCritical() << "Failed to execute query"; + qCritical() << query.lastQuery(); + qCritical() << query.lastError(); + } + return query; +} + +QSqlQuery Database::execute(QSqlQuery &query) +{ + if (!query.exec()) { + qCritical() << "Failed to execute query"; + qCritical() << query.lastQuery(); + qCritical() << query.lastError(); + } + return query; +} + +void Database::transaction() +{ + QSqlDatabase::database().transaction(); +} + +void Database::commit() +{ + QSqlDatabase::database().commit(); +} + +void Database::migrateTo1() +{ + qDebug() << "Migrating database to version 1"; + transaction(); + execute(QStringLiteral("CREATE TABLE Accounts (matrixId TEXT UNIQUE, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE OlmSessions (matrixId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE InboundMegolmSessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE OutboundMegolmSessions (matrixId TEXT, roomId TEXT, senderKey TEXT, sessionId TEXT, pickle TEXT);")); + execute(QStringLiteral("CREATE TABLE GroupSessionIndexRecord (matrixId TEXT, roomId TEXT, sessionId TEXT, i INTEGER, eventId TEXT, ts INTEGER);")); + execute(QStringLiteral("PRAGMA user_version = 1;")); + commit(); +} + +QByteArray Database::accountPickle(const QString &id) +{ + QSqlQuery query; + query.prepare(QStringLiteral("SELECT pickle FROM Accounts WHERE matrixId=:matrixId;")); + query.bindValue(":matrixId", id); + execute(query); + if (query.next()) { + return query.value(QStringLiteral("pickle")).toByteArray(); + } + return {}; +} + +void Database::setAccountPickle(const QString &id, const QByteArray &pickle) +{ + QSqlQuery query; + query.prepare(QStringLiteral("INSERT INTO Accounts(matrixId, pickle) VALUES(:matrixId, :pickle) ON CONFLICT (matrixId) DO UPDATE SET pickle=:pickle WHERE matrixId=:matrixId;")); + query.bindValue(":matrixId", id); + query.bindValue(":pickle", pickle); + transaction(); + execute(query); + commit(); +} + +void Database::clear(const QString &id) +{ + QSqlQuery query; + query.prepare(QStringLiteral("DELETE FROM Accounts(matrixId, pickle) WHERE matrixId=:matrixId;")); + query.bindValue(":matrixId", id); + + QSqlQuery sessionsQuery; + sessionsQuery.prepare(QStringLiteral("DELETE FROM OlmSessions WHERE matrixId=:matrixId;")); + sessionsQuery.bindValue(":matrixId", id); + + QSqlQuery megolmSessionsQuery; + megolmSessionsQuery.prepare(QStringLiteral("DELETE FROM InboundMegolmSessions WHERE matrixId=:matrixId;")); + megolmSessionsQuery.bindValue(":matrixId", id); + + QSqlQuery groupSessionIndexRecordQuery; + groupSessionIndexRecordQuery.prepare(QStringLiteral("DELETE FROM GroupSessionIndexRecord WHERE matrixId=:matrixId;")); + groupSessionIndexRecordQuery.bindValue(":matrixId", matrixId); + + transaction(); + execute(query); + execute(sessionsQuery); + execute(megolmSessionsQuery); + execute(groupSessionIndexRecordQuery); + commit(); + +} + +void Database::saveOlmSession(const QString& matrixId, const QString& senderKey, const QString& sessionId, const QByteArray &pickle) +{ + QSqlQuery query; + query.prepare(QStringLiteral("INSERT INTO OlmSessions(matrixId, senderKey, sessionId, pickle) VALUES(:matrixId, :senderKey, :sessionId, :pickle);")); + query.bindValue(":matrixId", matrixId); + query.bindValue(":senderKey", senderKey); + query.bindValue(":sessionId", sessionId); + query.bindValue(":pickle", pickle); + transaction(); + execute(query); + commit(); +} + +UnorderedMap> Database::loadOlmSessions(const QString& matrixId, const PicklingMode& picklingMode) +{ + QSqlQuery query; + query.prepare(QStringLiteral("SELECT * FROM OlmSessions WHERE matrixId=:matrixId;")); + query.bindValue(":matrixId", matrixId); + transaction(); + execute(query); + commit(); + UnorderedMap> sessions; + while (query.next()) { + auto session = QOlmSession::unpickle(query.value("pickle").toByteArray(), picklingMode); + if (std::holds_alternative(session)) { + qCWarning(E2EE) << "Failed to unpickle olm session"; + continue; + } + sessions[query.value("senderKey").toString()].push_back(std::move(std::get(session))); + } + return sessions; +} + +UnorderedMap, QOlmInboundGroupSessionPtr> Database::loadMegolmSessions(const QString& matrixId, const QString& roomId, const PicklingMode& picklingMode) +{ + QSqlQuery query; + query.prepare(QStringLiteral("SELECT * FROM InboundMegolmSessions WHERE matrixId=:matrixId AND roomId=:roomId;")); + query.bindValue(":matrixId", matrixId); + query.bindValue(":roomId", roomId); + transaction(); + execute(query); + commit(); + UnorderedMap, QOlmInboundGroupSessionPtr> sessions; + while (query.next()) { + auto session = QOlmInboundGroupSession::unpickle(query.value("pickle").toByteArray(), picklingMode); + if (std::holds_alternative(session)) { + qCWarning(E2EE) << "Failed to unpickle megolm session"; + continue; + } + sessions[{query.value("senderKey").toString(), query.value("sessionId").toString()}] = std::move(std::get(session)); + } + return sessions; +} + +void Database::saveMegolmSession(const QString& matrixId, const QString& roomId, const QString& senderKey, const QString& sessionId, const QByteArray& pickle) +{ + QSqlQuery query; + query.prepare(QStringLiteral("INSERT INTO InboundMegolmSessions(matrixId, roomId, senderKey, sessionId, pickle) VALUES(:matrixId, :roomId, :senderKey, :sessionId, :pickle);")); + query.bindValue(":matrixId", matrixId); + query.bindValue(":roomId", roomId); + query.bindValue(":senderKey", senderKey); + query.bindValue(":sessionId", sessionId); + query.bindValue(":pickle", pickle); + transaction(); + execute(query); + commit(); +} + +void Database::addGroupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts) +{ + QSqlQuery query; + query.prepare("INSERT INTO GroupSessionIndexRecord(matrixId, roomId, sessionId, i, eventId, ts) VALUES(:matrixId, :roomId, :sessionId, :index, :eventId, :ts);"); + query.bindValue(":matrixId", matrixId); + query.bindValue(":roomId", roomId); + query.bindValue(":sessionId", sessionId); + query.bindValue(":index", index); + query.bindValue(":eventId", eventId); + query.bindValue(":ts", ts); + transaction(); + execute(query); + commit(); +} + +QPair Database::groupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, qint64 index) +{ + QSqlQuery query; + query.prepare(QStringLiteral("SELECT * FROM GroupSessionIndexRecord WHERE matrixId=:matrixId AND roomId=:roomId AND sessionId=:sessionId AND i=:index;")); + query.bindValue(":matrixId", matrixId); + query.bindValue(":roomId", roomId); + query.bindValue(":sessionId", sessionId); + query.bindValue(":index", index); + transaction(); + execute(query); + commit(); + if (!query.next()) { + return {}; + } + return {query.value("eventId").toString(), query.value("ts").toLongLong()}; +} diff --git a/lib/database.h b/lib/database.h new file mode 100644 index 00000000..ed356820 --- /dev/null +++ b/lib/database.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2021 Tobias Fella +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include + +#include "crypto/e2ee.h" + +namespace Quotient { +class Database : public QObject +{ + Q_OBJECT + +public: + static Database &instance() + { + static Database _instance; + return _instance; + } + + int version(); + void transaction(); + void commit(); + QSqlQuery execute(const QString &queryString); + QSqlQuery execute(QSqlQuery &query); + + QByteArray accountPickle(const QString &id); + void setAccountPickle(const QString &id, const QByteArray &pickle); + void clear(const QString &id); + void saveOlmSession(const QString& matrixId, const QString& senderKey, const QString& sessionId, const QByteArray &pickle); + UnorderedMap> loadOlmSessions(const QString& matrixId, const PicklingMode& picklingMode); + UnorderedMap, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& matrixId, const QString& roomId, const PicklingMode& picklingMode); + void saveMegolmSession(const QString& matrixId, const QString& roomId, const QString& senderKey, const QString& sessionKey, const QByteArray& pickle); + void addGroupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts); + QPair groupSessionIndexRecord(const QString& matrixId, const QString& roomId, const QString& sessionId, qint64 index); + + +private: + Database(); + + void migrateTo1(); +}; +} diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 5c106e12..e5fa978f 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -8,6 +8,7 @@ #include "connection.h" #include "crypto/e2ee.h" #include "events/encryptedfile.h" +#include "database.h" #include "csapi/keys.h" @@ -37,90 +38,28 @@ public: EncryptionManager* q; - // A map from senderKey to InboundSession - UnorderedMap sessions; - void updateDeviceKeys( - const QHash>& deviceKeys) - { - for (auto userId : deviceKeys.keys()) { - for (auto deviceId : deviceKeys.value(userId).keys()) { - auto info = deviceKeys.value(userId).value(deviceId); - // TODO: ed25519Verify, etc - } - } - } + // A map from SenderKey to vector of InboundSession + UnorderedMap> sessions; + void loadSessions() { - QFile file { static_cast(q->parent())->e2eeDataDir() % "/olmsessions.json" }; - if(!file.exists() || !file.open(QIODevice::ReadOnly)) { - qCDebug(E2EE) << "No sessions cache exists."; - 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) << "Sessions cache is empty"; - return; - } - for(const auto &senderKey : json["sessions"].toObject().keys()) { - auto pickle = json["sessions"].toObject()[senderKey].toString(); - auto sessionResult = QOlmSession::unpickle(pickle.toLatin1(), static_cast(q->parent())->picklingMode()); - if(std::holds_alternative(sessionResult)) { - qCWarning(E2EE) << "Failed to unpickle olm session"; - continue; - } - sessions[senderKey] = std::move(std::get(sessionResult)); - } + sessions = Database::instance().loadOlmSessions(static_cast(q->parent())->userId(), static_cast(q->parent())->picklingMode()); } - void saveSessions() { - QFile outFile { static_cast(q->parent())->e2eeDataDir() % "/olmsessions.json" }; - if (!outFile.open(QFile::WriteOnly)) { - qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" - << outFile.errorString(); - qCWarning(E2EE) << "Failed to write olm sessions"; + void saveSession(QOlmSessionPtr& session, const QString &senderKey) { + auto pickleResult = session->pickle(static_cast(q->parent())->picklingMode()); + if (std::holds_alternative(pickleResult)) { + qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); return; } - - QJsonObject rootObj { - { QStringLiteral("cache_version"), - QJsonObject { - { QStringLiteral("major"), 1 }, - { QStringLiteral("minor"), 0 } } } - }; - { - QJsonObject sessionsJson; - for (const auto &session : sessions) { - auto pickleResult = session.second->pickle(static_cast(q->parent())->picklingMode()); - if(std::holds_alternative(pickleResult)) { - qCWarning(E2EE) << "Failed to pickle session"; - continue; - } - sessionsJson[session.first] = QString(std::get(pickleResult)); - } - rootObj.insert(QStringLiteral("sessions"), sessionsJson); - } - - const auto data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact); - - outFile.write(data.data(), data.size()); - qCDebug(E2EE) << "Sessions saved to" << outFile.fileName(); + Database::instance().saveOlmSession(static_cast(q->parent())->userId(), 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 : sessions) { - const auto matches = session.second->matchesInboundSessionFrom(senderKey, message); + for(auto& session : sessions[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.second->decrypt(message); - saveSessions(); + const auto result = session->decrypt(message); if(std::holds_alternative(result)) { return std::get(result); } else { @@ -141,8 +80,8 @@ public: qWarning(E2EE) << "Failed to remove one time key for session" << newSession->sessionId(); } const auto result = newSession->decrypt(message); - sessions[senderKey] = std::move(newSession); - saveSessions(); + saveSession(newSession, senderKey); + sessions[senderKey].push_back(std::move(newSession)); if(std::holds_alternative(result)) { return std::get(result); } else { @@ -153,10 +92,9 @@ public: QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) { Q_ASSERT(message.type() == QOlmMessage::General); - for(auto& session : sessions) { - const auto result = session.second->decrypt(message); + for(auto& session : sessions[senderKey]) { + const auto result = session->decrypt(message); if(std::holds_alternative(result)) { - saveSessions(); return std::get(result); } } diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 2e0d7387..1b5e4441 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -3,6 +3,7 @@ #include "encryptedevent.h" #include "roommessageevent.h" +#include "events/eventloader.h" using namespace Quotient; diff --git a/lib/logging.h b/lib/logging.h index 5bf050a9..fc0a4c99 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -19,6 +19,7 @@ Q_DECLARE_LOGGING_CATEGORY(SYNCJOB) Q_DECLARE_LOGGING_CATEGORY(THUMBNAILJOB) Q_DECLARE_LOGGING_CATEGORY(NETWORK) Q_DECLARE_LOGGING_CATEGORY(PROFILER) +Q_DECLARE_LOGGING_CATEGORY(DATABASE) namespace Quotient { // QDebug manipulators diff --git a/lib/room.cpp b/lib/room.cpp index e4fe2fb8..8181f16a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -70,6 +70,8 @@ #include "crypto/qolminboundsession.h" #endif // Quotient_E2EE_ENABLED +#include "database.h" + using namespace Quotient; using namespace std::placeholders; using std::move; @@ -363,75 +365,11 @@ public: bool isLocalUser(const User* u) const { return u == q->localUser(); } #ifdef Quotient_E2EE_ENABLED - // A map from to - QHash, QPair> - groupSessionIndexRecord; // TODO: cache // A map from (senderKey, sessionId) to InboundGroupSession - UnorderedMap, std::unique_ptr> groupSessions; + UnorderedMap, QOlmInboundGroupSessionPtr> groupSessions; void loadMegOlmSessions() { - QFile file { connection->e2eeDataDir() + QStringLiteral("/%1.json").arg(id) }; - if(!file.exists() || !file.open(QIODevice::ReadOnly)) { - qCDebug(E2EE) << "No megolm sessions cache exists."; - 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(E2EE) << "Megolm sessions cache is empty"; - return; - } - for(const auto &s : json["sessions"].toArray()) { - auto pickle = s.toObject()["pickle"].toString().toLatin1(); - auto senderKey = s.toObject()["sender_key"].toString(); - auto sessionId = s.toObject()["session_id"].toString(); - auto sessionResult = QOlmInboundGroupSession::unpickle(pickle, connection->picklingMode()); - if(std::holds_alternative(sessionResult)) { - qCWarning(E2EE) << "Failed to unpickle olm session"; - continue; - } - groupSessions[{senderKey, sessionId}] = std::move(std::get>(sessionResult)); - } - } - void saveMegOlmSessions() { - QFile outFile { connection->e2eeDataDir() + QStringLiteral("/%1.json").arg(id)}; - if (!outFile.open(QFile::WriteOnly)) { - qCWarning(E2EE) << "Error opening" << outFile.fileName() << ":" - << outFile.errorString(); - qCWarning(E2EE) << "Failed to write megolm sessions"; - return; - } - - QJsonObject rootObj { - { QStringLiteral("cache_version"), - QJsonObject { - { QStringLiteral("major"), 1 }, - { QStringLiteral("minor"), 0 } } } - }; - { - QJsonArray sessionsJson; - for (const auto &session : groupSessions) { - auto pickleResult = session.second->pickle(connection->picklingMode()); - sessionsJson += QJsonObject { - {QStringLiteral("sender_key"), session.first.first}, - {QStringLiteral("session_id"), session.first.second}, - {QStringLiteral("pickle"), QString(pickleResult)} - }; - } - rootObj.insert(QStringLiteral("sessions"), sessionsJson); - } - - const auto data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact); - - outFile.write(data.data(), data.size()); - qCDebug(E2EE) << "Megolm sessions saved to" << outFile.fileName(); + groupSessions = Database::instance().loadMegolmSessions(q->localUser()->id(), q->id(), q->connection()->picklingMode()); } bool addInboundGroupSession(QString senderKey, QString sessionId, QString sessionKey) @@ -449,8 +387,8 @@ public: return false; } qCWarning(E2EE) << "Adding inbound session"; + Database::instance().saveMegolmSession(q->localUser()->id(), q->id(), senderKey, sessionId, megolmSession->pickle(q->connection()->picklingMode())); groupSessions[{senderKey, sessionId}] = std::move(megolmSession); - saveMegOlmSessions(); return true; } @@ -476,17 +414,15 @@ public: return QString(); } const auto& [content, index] = std::get>(decryptResult); - const auto& [recordEventId, ts] = groupSessionIndexRecord.value({senderSession->sessionId(), index}); - if (eventId.isEmpty()) { - groupSessionIndexRecord.insert({senderSession->sessionId(), index}, {recordEventId, timestamp}); + const auto& [recordEventId, ts] = Database::instance().groupSessionIndexRecord(q->localUser()->id(), q->id(), senderSession->sessionId(), index); + if (recordEventId.isEmpty()) { + Database::instance().addGroupSessionIndexRecord(q->localUser()->id(), q->id(), senderSession->sessionId(), index, eventId, timestamp.toMSecsSinceEpoch()); } else { - if ((eventId != recordEventId) || (ts != timestamp)) { + if ((eventId != recordEventId) || (ts != timestamp.toMSecsSinceEpoch())) { qCWarning(E2EE) << "Detected a replay attack on event" << eventId; return QString(); } } - //TODO is this necessary? - saveMegOlmSessions(); return content; } #endif // Quotient_E2EE_ENABLED -- 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" --- CMakeLists.txt | 18 +- autotests/testgroupsession.cpp | 6 +- autotests/testolmaccount.cpp | 4 +- autotests/testolmsession.cpp | 2 +- autotests/testolmutility.cpp | 4 +- lib/connection.cpp | 4 +- lib/connection.h | 2 +- lib/converters.cpp | 2 +- lib/crypto/e2ee.h | 132 --------------- lib/crypto/qolmaccount.cpp | 330 ------------------------------------- lib/crypto/qolmaccount.h | 123 -------------- lib/crypto/qolmerrors.cpp | 22 --- lib/crypto/qolmerrors.h | 28 ---- lib/crypto/qolminboundsession.cpp | 153 ----------------- lib/crypto/qolminboundsession.h | 48 ------ lib/crypto/qolmmessage.cpp | 35 ---- lib/crypto/qolmmessage.h | 41 ----- lib/crypto/qolmoutboundsession.cpp | 128 -------------- lib/crypto/qolmoutboundsession.h | 54 ------ lib/crypto/qolmsession.cpp | 253 ---------------------------- lib/crypto/qolmsession.h | 76 --------- lib/crypto/qolmutility.cpp | 63 ------- lib/crypto/qolmutility.h | 45 ----- lib/crypto/qolmutils.cpp | 24 --- lib/crypto/qolmutils.h | 15 -- lib/database.cpp | 6 +- lib/database.h | 2 +- lib/e2ee/e2ee.h | 132 +++++++++++++++ lib/e2ee/qolmaccount.cpp | 330 +++++++++++++++++++++++++++++++++++++ lib/e2ee/qolmaccount.h | 123 ++++++++++++++ lib/e2ee/qolmerrors.cpp | 22 +++ lib/e2ee/qolmerrors.h | 28 ++++ lib/e2ee/qolminboundsession.cpp | 153 +++++++++++++++++ lib/e2ee/qolminboundsession.h | 48 ++++++ lib/e2ee/qolmmessage.cpp | 35 ++++ lib/e2ee/qolmmessage.h | 41 +++++ lib/e2ee/qolmoutboundsession.cpp | 128 ++++++++++++++ lib/e2ee/qolmoutboundsession.h | 54 ++++++ lib/e2ee/qolmsession.cpp | 253 ++++++++++++++++++++++++++++ lib/e2ee/qolmsession.h | 76 +++++++++ lib/e2ee/qolmutility.cpp | 63 +++++++ lib/e2ee/qolmutility.h | 45 +++++ lib/e2ee/qolmutils.cpp | 24 +++ lib/e2ee/qolmutils.h | 15 ++ lib/encryptionmanager.cpp | 12 +- lib/events/encryptedevent.h | 2 +- lib/events/encryptionevent.cpp | 2 +- lib/room.cpp | 8 +- 48 files changed, 1607 insertions(+), 1607 deletions(-) delete mode 100644 lib/crypto/e2ee.h delete mode 100644 lib/crypto/qolmaccount.cpp delete mode 100644 lib/crypto/qolmaccount.h delete mode 100644 lib/crypto/qolmerrors.cpp delete mode 100644 lib/crypto/qolmerrors.h delete mode 100644 lib/crypto/qolminboundsession.cpp delete mode 100644 lib/crypto/qolminboundsession.h delete mode 100644 lib/crypto/qolmmessage.cpp delete mode 100644 lib/crypto/qolmmessage.h delete mode 100644 lib/crypto/qolmoutboundsession.cpp delete mode 100644 lib/crypto/qolmoutboundsession.h delete mode 100644 lib/crypto/qolmsession.cpp delete mode 100644 lib/crypto/qolmsession.h delete mode 100644 lib/crypto/qolmutility.cpp delete mode 100644 lib/crypto/qolmutility.h delete mode 100644 lib/crypto/qolmutils.cpp delete mode 100644 lib/crypto/qolmutils.h create mode 100644 lib/e2ee/e2ee.h create mode 100644 lib/e2ee/qolmaccount.cpp create mode 100644 lib/e2ee/qolmaccount.h create mode 100644 lib/e2ee/qolmerrors.cpp create mode 100644 lib/e2ee/qolmerrors.h create mode 100644 lib/e2ee/qolminboundsession.cpp create mode 100644 lib/e2ee/qolminboundsession.h create mode 100644 lib/e2ee/qolmmessage.cpp create mode 100644 lib/e2ee/qolmmessage.h create mode 100644 lib/e2ee/qolmoutboundsession.cpp create mode 100644 lib/e2ee/qolmoutboundsession.h create mode 100644 lib/e2ee/qolmsession.cpp create mode 100644 lib/e2ee/qolmsession.h create mode 100644 lib/e2ee/qolmutility.cpp create mode 100644 lib/e2ee/qolmutility.h create mode 100644 lib/e2ee/qolmutils.cpp create mode 100644 lib/e2ee/qolmutils.h (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f886094..a84a70fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,15 +164,15 @@ list(APPEND lib_SRCS ) if (${PROJECT_NAME}_ENABLE_E2EE) list(APPEND lib_SRCS - lib/crypto/qolmaccount.cpp - lib/crypto/qolmsession.cpp - lib/crypto/qolminboundsession.cpp - lib/crypto/qolmoutboundsession.cpp - lib/crypto/qolmutils.cpp - lib/crypto/qolmutility.cpp - lib/crypto/qolmerrors.cpp - lib/crypto/qolmsession.cpp - lib/crypto/qolmmessage.cpp + lib/e2ee/qolmaccount.cpp + lib/e2ee/qolmsession.cpp + lib/e2ee/qolminboundsession.cpp + lib/e2ee/qolmoutboundsession.cpp + lib/e2ee/qolmutils.cpp + lib/e2ee/qolmutility.cpp + lib/e2ee/qolmerrors.cpp + lib/e2ee/qolmsession.cpp + lib/e2ee/qolmmessage.cpp lib/encryptionmanager.cpp ) endif() diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index ea1bb4a9..afd5ef81 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -3,9 +3,9 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testgroupsession.h" -#include "crypto/qolminboundsession.h" -#include "crypto/qolmoutboundsession.h" -#include "crypto/qolmutils.h" +#include "e2ee/qolminboundsession.h" +#include "e2ee/qolmoutboundsession.h" +#include "e2ee/qolmutils.h" using namespace Quotient; diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 4fd129b5..22c457aa 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -4,8 +4,8 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testolmaccount.h" -#include -#include +#include +#include #include #include #include diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 00d76d4e..41baf8e3 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "crypto/qolmsession.h" +#include "e2ee/qolmsession.h" #include "testolmsession.h" using namespace Quotient; diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index 2eec7e00..bbf3a055 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -3,8 +3,8 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testolmutility.h" -#include "crypto/qolmaccount.h" -#include "crypto/qolmutility.h" +#include "e2ee/qolmaccount.h" +#include "e2ee/qolmutility.h" using namespace Quotient; 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) diff --git a/lib/connection.h b/lib/connection.h index d2347d1d..3a12ec39 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -22,7 +22,7 @@ #include #ifdef Quotient_E2EE_ENABLED -#include "crypto/e2ee.h" +#include "e2ee/e2ee.h" #endif Q_DECLARE_METATYPE(Quotient::GetLoginFlowsJob::LoginFlow) diff --git a/lib/converters.cpp b/lib/converters.cpp index a3ac44c5..4136940f 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -4,7 +4,7 @@ #include "converters.h" #include -#include "crypto/e2ee.h" +#include "e2ee/e2ee.h" QJsonValue Quotient::JsonConverter::dump(const QVariant& v) { diff --git a/lib/crypto/e2ee.h b/lib/crypto/e2ee.h deleted file mode 100644 index 41cd2878..00000000 --- a/lib/crypto/e2ee.h +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-FileCopyrightText: 2021 Carl Schwan -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include -#include -#include "converters.h" -#include - -#include -#include -#include -#include - -#include "util.h" - -namespace Quotient { - -inline const auto CiphertextKeyL = "ciphertext"_ls; -inline const auto SenderKeyKeyL = "sender_key"_ls; -inline const auto DeviceIdKeyL = "device_id"_ls; -inline const auto SessionIdKeyL = "session_id"_ls; - -inline const auto AlgorithmKeyL = "algorithm"_ls; -inline const auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; -inline const auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls; - -inline const auto AlgorithmKey = QStringLiteral("algorithm"); -inline const auto RotationPeriodMsKey = QStringLiteral("rotation_period_ms"); -inline const auto RotationPeriodMsgsKey = - QStringLiteral("rotation_period_msgs"); - -inline const auto Ed25519Key = QStringLiteral("ed25519"); -inline const auto Curve25519Key = QStringLiteral("curve25519"); -inline const auto SignedCurve25519Key = QStringLiteral("signed_curve25519"); -inline const auto OlmV1Curve25519AesSha2AlgoKey = - QStringLiteral("m.olm.v1.curve25519-aes-sha2"); -inline const auto MegolmV1AesSha2AlgoKey = - QStringLiteral("m.megolm.v1.aes-sha2"); -inline const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoKey, - MegolmV1AesSha2AlgoKey }; -struct Unencrypted {}; -struct Encrypted { - QByteArray key; -}; - -using PicklingMode = std::variant; - -class QOlmSession; -using QOlmSessionPtr = std::unique_ptr; - -class QOlmInboundGroupSession; -using QOlmInboundGroupSessionPtr = std::unique_ptr; - -template struct overloaded : Ts... { using Ts::operator()...; }; -template overloaded(Ts...) -> overloaded; - -struct IdentityKeys -{ - QByteArray curve25519; - QByteArray ed25519; -}; - -//! Struct representing the one-time keys. -struct OneTimeKeys -{ - QMap> keys; - - //! Get the HashMap containing the curve25519 one-time keys. - QMap curve25519() const; - - //! Get a reference to the hashmap corresponding to given key type. - std::optional> get(QString keyType) const; -}; - -//! Struct representing the signed one-time keys. -class SignedOneTimeKey -{ -public: - SignedOneTimeKey() = default; - SignedOneTimeKey(const SignedOneTimeKey &) = default; - SignedOneTimeKey &operator=(const SignedOneTimeKey &) = default; - //! Required. The unpadded Base64-encoded 32-byte Curve25519 public key. - QString key; - - //! Required. Signatures of the key object. - //! The signature is calculated using the process described at Signing JSON. - QHash> signatures; -}; - - -template <> -struct JsonObjectConverter { - static void fillFrom(const QJsonObject& jo, - SignedOneTimeKey& result) - { - fromJson(jo.value("key"_ls), result.key); - fromJson(jo.value("signatures"_ls), result.signatures); - } - - static void dumpTo(QJsonObject &jo, const SignedOneTimeKey &result) - { - addParam<>(jo, QStringLiteral("key"), result.key); - addParam<>(jo, QStringLiteral("signatures"), result.signatures); - } -}; - -bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); - -template -class asKeyValueRange -{ -public: - asKeyValueRange(T &data) - : m_data{data} - { - } - - auto begin() { return m_data.keyValueBegin(); } - - auto end() { return m_data.keyValueEnd(); } - -private: - T &m_data; -}; - -} // namespace Quotient - -Q_DECLARE_METATYPE(Quotient::SignedOneTimeKey) diff --git a/lib/crypto/qolmaccount.cpp b/lib/crypto/qolmaccount.cpp deleted file mode 100644 index 5c9f5db4..00000000 --- a/lib/crypto/qolmaccount.cpp +++ /dev/null @@ -1,330 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "qolmaccount.h" -#include "connection.h" -#include "csapi/keys.h" -#include "crypto/qolmutils.h" -#include "crypto/qolmutility.h" -#include -#include -#include -#include - -using namespace Quotient; - -QMap OneTimeKeys::curve25519() const -{ - return keys[QStringLiteral("curve25519")]; -} - -std::optional> OneTimeKeys::get(QString keyType) const -{ - if (!keys.contains(keyType)) { - return std::nullopt; - } - return keys[keyType]; -} - -bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) -{ - return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519; -} - -// Convert olm error to enum -QOlmError lastError(OlmAccount *account) { - const std::string error_raw = olm_account_last_error(account); - - return fromString(error_raw); -} - -QByteArray getRandom(size_t bufferSize) -{ - QByteArray buffer(bufferSize, '0'); - std::generate(buffer.begin(), buffer.end(), std::rand); - return buffer; -} - -QOlmAccount::QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent) - : QObject(parent) - , m_userId(userId) - , m_deviceId(deviceId) -{ -} - -QOlmAccount::~QOlmAccount() -{ - olm_clear_account(m_account); - delete[](reinterpret_cast(m_account)); -} - -void QOlmAccount::createNewAccount() -{ - m_account = olm_account(new uint8_t[olm_account_size()]); - size_t randomSize = olm_create_account_random_length(m_account); - QByteArray randomData = getRandom(randomSize); - const auto error = olm_create_account(m_account, randomData.data(), randomSize); - if (error == olm_error()) { - throw lastError(m_account); - } - Q_EMIT needsSave(); -} - -void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) -{ - m_account = olm_account(new uint8_t[olm_account_size()]); - const QByteArray key = toKey(mode); - const auto error = olm_unpickle_account(m_account, key.data(), key.length(), pickled.data(), pickled.size()); - if (error == olm_error()) { - qCWarning(E2EE) << "Failed to unpickle olm account"; - //TODO: Do something that is not dying - // Probably log the user out since we have no way of getting to the keys - //throw lastError(m_account); - } -} - -std::variant QOlmAccount::pickle(const PicklingMode &mode) -{ - const QByteArray key = toKey(mode); - const size_t pickleLength = olm_pickle_account_length(m_account); - QByteArray pickleBuffer(pickleLength, '0'); - const auto error = olm_pickle_account(m_account, key.data(), - key.length(), pickleBuffer.data(), pickleLength); - if (error == olm_error()) { - return lastError(m_account); - } - return pickleBuffer; -} - -IdentityKeys QOlmAccount::identityKeys() const -{ - const size_t keyLength = olm_account_identity_keys_length(m_account); - QByteArray keyBuffer(keyLength, '0'); - const auto error = olm_account_identity_keys(m_account, keyBuffer.data(), keyLength); - if (error == olm_error()) { - throw lastError(m_account); - } - const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object(); - return IdentityKeys { - key.value(QStringLiteral("curve25519")).toString().toUtf8(), - key.value(QStringLiteral("ed25519")).toString().toUtf8() - }; -} - -QByteArray QOlmAccount::sign(const QByteArray &message) const -{ - QByteArray signatureBuffer(olm_account_signature_length(m_account), '0'); - - const auto error = olm_account_sign(m_account, message.data(), message.length(), - signatureBuffer.data(), signatureBuffer.length()); - - if (error == olm_error()) { - throw lastError(m_account); - } - return signatureBuffer; -} - -QByteArray QOlmAccount::sign(const QJsonObject &message) const -{ - return sign(QJsonDocument(message).toJson(QJsonDocument::Compact)); -} - -QByteArray QOlmAccount::signIdentityKeys() const -{ - const auto keys = identityKeys(); - QJsonObject body - { - {"algorithms", QJsonArray{"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}}, - {"user_id", m_userId}, - {"device_id", m_deviceId}, - {"keys", - QJsonObject{ - {QStringLiteral("curve25519:") + m_deviceId, QString::fromUtf8(keys.curve25519)}, - {QStringLiteral("ed25519:") + m_deviceId, QString::fromUtf8(keys.ed25519)} - } - } - }; - return sign(QJsonDocument(body).toJson(QJsonDocument::Compact)); - -} - -size_t QOlmAccount::maxNumberOfOneTimeKeys() const -{ - return olm_account_max_number_of_one_time_keys(m_account); -} - -size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const -{ - const size_t randomLength = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); - QByteArray randomBuffer = getRandom(randomLength); - const auto error = olm_account_generate_one_time_keys(m_account, numberOfKeys, randomBuffer.data(), randomLength); - - if (error == olm_error()) { - throw lastError(m_account); - } - Q_EMIT needsSave(); - return error; -} - -OneTimeKeys QOlmAccount::oneTimeKeys() const -{ - const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); - QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0'); - - const auto error = olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength); - if (error == olm_error()) { - throw lastError(m_account); - } - const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); - OneTimeKeys oneTimeKeys; - - for (const QString& key1 : json.keys()) { - auto oneTimeKeyObject = json[key1].toObject(); - auto keyMap = QMap(); - for (const QString &key2 : oneTimeKeyObject.keys()) { - keyMap[key2] = oneTimeKeyObject[key2].toString(); - } - oneTimeKeys.keys[key1] = keyMap; - } - return oneTimeKeys; -} - -QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const -{ - QMap signedOneTimeKeys; - for (const auto &keyid : keys.curve25519().keys()) { - const auto oneTimeKey = keys.curve25519()[keyid]; - QByteArray sign = signOneTimeKey(oneTimeKey); - signedOneTimeKeys["signed_curve25519:" + keyid] = signedOneTimeKey(oneTimeKey.toUtf8(), sign); - } - return signedOneTimeKeys; -} - -SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray &key, const QString &signature) const -{ - SignedOneTimeKey sign{}; - sign.key = key; - sign.signatures = {{m_userId, {{"ed25519:" + m_deviceId, signature}}}}; - return sign; -} - -QByteArray QOlmAccount::signOneTimeKey(const QString &key) const -{ - QJsonDocument j(QJsonObject{{"key", key}}); - return sign(j.toJson(QJsonDocument::Compact)); -} - -std::optional QOlmAccount::removeOneTimeKeys(const QOlmSessionPtr &session) const -{ - const auto error = olm_remove_one_time_keys(m_account, session->raw()); - - if (error == olm_error()) { - return lastError(m_account); - } - Q_EMIT needsSave(); - return std::nullopt; -} - -OlmAccount *QOlmAccount::data() -{ - return m_account; -} - -DeviceKeys QOlmAccount::deviceKeys() const -{ - DeviceKeys deviceKeys; - deviceKeys.userId = m_userId; - deviceKeys.deviceId = m_deviceId; - deviceKeys.algorithms = QStringList {"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}; - - const auto idKeys = identityKeys(); - deviceKeys.keys["curve25519:" + m_deviceId] = idKeys.curve25519; - deviceKeys.keys["ed25519:" + m_deviceId] = idKeys.ed25519; - - const auto sign = signIdentityKeys(); - deviceKeys.signatures[m_userId]["ed25519:" + m_deviceId] = sign; - - return deviceKeys; -} - -UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKeys) -{ - auto keys = deviceKeys(); - - if (oneTimeKeys.curve25519().isEmpty()) { - return new UploadKeysJob(keys); - } - - // Sign & append the one time keys. - auto temp = signOneTimeKeys(oneTimeKeys); - QHash oneTimeKeysSigned; - for (const auto &[keyId, key] : asKeyValueRange(temp)) { - oneTimeKeysSigned[keyId] = QVariant::fromValue(toJson(key)); - } - - return new UploadKeysJob(keys, oneTimeKeysSigned); -} - -std::variant QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) -{ - Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); - return QOlmSession::createInboundSession(this, preKeyMessage); -} - -std::variant QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) -{ - Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); - return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); -} - -std::variant QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) -{ - return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); -} - -void QOlmAccount::markKeysAsPublished() -{ - olm_account_mark_keys_as_published(m_account); - Q_EMIT needsSave(); -} - -bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys, - const QString &deviceId, - const QString &userId) -{ - const auto signKeyId = "ed25519:" + deviceId; - const auto signingKey = deviceKeys.keys[signKeyId]; - const auto signature = deviceKeys.signatures[userId][signKeyId]; - - if (signature.isEmpty()) { - return false; - } - - return ed25519VerifySignature(signingKey, toJson(deviceKeys), signature); -} - -bool Quotient::ed25519VerifySignature(const QString &signingKey, - const QJsonObject &obj, - const QString &signature) -{ - if (signature.isEmpty()) { - return false; - } - QJsonObject obj1 = obj; - - obj1.remove("unsigned"); - obj1.remove("signatures"); - - auto canonicalJson = QJsonDocument(obj1).toJson(QJsonDocument::Compact); - - QByteArray signingKeyBuf = signingKey.toUtf8(); - QOlmUtility utility; - auto signatureBuf = signature.toUtf8(); - auto result = utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf); - if (std::holds_alternative(result)) { - return false; - } - - return std::get(result); -} diff --git a/lib/crypto/qolmaccount.h b/lib/crypto/qolmaccount.h deleted file mode 100644 index dd461e8b..00000000 --- a/lib/crypto/qolmaccount.h +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - - -#pragma once - -#include "csapi/keys.h" -#include "crypto/e2ee.h" -#include "crypto/qolmerrors.h" -#include "crypto/qolmmessage.h" -#include "crypto/qolmsession.h" -#include - -struct OlmAccount; - -namespace Quotient { - -class QOlmSession; -class Connection; - -using QOlmSessionPtr = std::unique_ptr; - -//! An olm account manages all cryptographic keys used on a device. -//! \code{.cpp} -//! const auto olmAccount = new QOlmAccount(this); -//! \endcode -class QOlmAccount : public QObject -{ - Q_OBJECT -public: - QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent = nullptr); - ~QOlmAccount(); - - //! Creates a new instance of OlmAccount. During the instantiation - //! the Ed25519 fingerprint key pair and the Curve25519 identity key - //! pair are generated. For more information see here. - //! This needs to be called before any other action or use unpickle() instead. - void createNewAccount(); - - //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmAccount`. - //! This needs to be called before any other action or use createNewAccount() instead. - void unpickle(QByteArray &pickled, const PicklingMode &mode); - - //! Serialises an OlmAccount to encrypted Base64. - std::variant pickle(const PicklingMode &mode); - - //! Returns the account's public identity keys already formatted as JSON - IdentityKeys identityKeys() const; - - //! Returns the signature of the supplied message. - QByteArray sign(const QByteArray &message) const; - QByteArray sign(const QJsonObject& message) const; - - //! Sign identity keys. - QByteArray signIdentityKeys() const; - - //! Maximum number of one time keys that this OlmAccount can - //! currently hold. - size_t maxNumberOfOneTimeKeys() const; - - //! Generates the supplied number of one time keys. - size_t generateOneTimeKeys(size_t numberOfKeys) const; - - //! Gets the OlmAccount's one time keys formatted as JSON. - OneTimeKeys oneTimeKeys() const; - - //! Sign all one time keys. - QMap signOneTimeKeys(const OneTimeKeys &keys) const; - - //! Sign one time key. - QByteArray signOneTimeKey(const QString &key) const; - - SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; - - UploadKeysJob *createUploadKeyRequest(const OneTimeKeys &oneTimeKeys); - - DeviceKeys deviceKeys() const; - - //! Remove the one time key used to create the supplied session. - [[nodiscard]] std::optional removeOneTimeKeys(const QOlmSessionPtr &session) const; - - //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. - //! - //! \param message An Olm pre-key message that was encrypted for this account. - std::variant createInboundSession(const QOlmMessage &preKeyMessage); - - //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. - //! - //! \param theirIdentityKey - The identity key of the Olm account that - //! encrypted this Olm message. - std::variant createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage); - - //! Creates an outbound session for sending messages to a specific - /// identity and one time key. - std::variant createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); - - void markKeysAsPublished(); - - // HACK do not use directly - QOlmAccount(OlmAccount *account); - OlmAccount *data(); - -Q_SIGNALS: - void needsSave() const; - -private: - OlmAccount *m_account = nullptr; // owning - QString m_userId; - QString m_deviceId; -}; - -bool verifyIdentitySignature(const DeviceKeys &deviceKeys, - const QString &deviceId, - const QString &userId); - -//! checks if the signature is signed by the signing_key -bool ed25519VerifySignature(const QString &signingKey, - const QJsonObject &obj, - const QString &signature); - -} // namespace Quotient diff --git a/lib/crypto/qolmerrors.cpp b/lib/crypto/qolmerrors.cpp deleted file mode 100644 index 6db1803c..00000000 --- a/lib/crypto/qolmerrors.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - - -#include "qolmerrors.h" - -Quotient::QOlmError Quotient::fromString(const std::string &error_raw) { - if (!error_raw.compare("BAD_ACCOUNT_KEY")) { - return QOlmError::BadAccountKey; - } else if (!error_raw.compare("BAD_MESSAGE_KEY_ID")) { - return QOlmError::BadMessageKeyId; - } else if (!error_raw.compare("INVALID_BASE64")) { - return QOlmError::InvalidBase64; - } else if (!error_raw.compare("NOT_ENOUGH_RANDOM")) { - return QOlmError::NotEnoughRandom; - } else if (!error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { - return QOlmError::OutputBufferTooSmall; - } else { - return QOlmError::Unknown; - } -} diff --git a/lib/crypto/qolmerrors.h b/lib/crypto/qolmerrors.h deleted file mode 100644 index f8390d2a..00000000 --- a/lib/crypto/qolmerrors.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include - -namespace Quotient { -//! All errors that could be caused by an operation regarding Olm -//! Errors are named exactly like the ones in libolm. -enum QOlmError -{ - BadAccountKey, - BadMessageFormat, - BadMessageKeyId, - BadMessageMac, - BadMessageVersion, - InvalidBase64, - NotEnoughRandom, - OutputBufferTooSmall, - UnknownMessageIndex, - Unknown, -}; - -QOlmError fromString(const std::string &error_raw); - -} //namespace Quotient diff --git a/lib/crypto/qolminboundsession.cpp b/lib/crypto/qolminboundsession.cpp deleted file mode 100644 index 31d699f1..00000000 --- a/lib/crypto/qolminboundsession.cpp +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "crypto/qolminboundsession.h" -#include -#include -using namespace Quotient; - -QOlmError lastError(OlmInboundGroupSession *session) { - const std::string error_raw = olm_inbound_group_session_last_error(session); - - return fromString(error_raw); -} - -QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session) - : m_groupSession(session) -{ -} - -QOlmInboundGroupSession::~QOlmInboundGroupSession() -{ - olm_clear_inbound_group_session(m_groupSession); - //delete[](reinterpret_cast(m_groupSession)); -} - -std::unique_ptr QOlmInboundGroupSession::create(const QByteArray &key) -{ - const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); - const auto temp = key; - const auto error = olm_init_inbound_group_session(olmInboundGroupSession, - reinterpret_cast(temp.data()), temp.size()); - - if (error == olm_error()) { - throw lastError(olmInboundGroupSession); - } - - return std::make_unique(olmInboundGroupSession); -} - -std::unique_ptr QOlmInboundGroupSession::import(const QByteArray &key) -{ - const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); - QByteArray keyBuf = key; - - const auto error = olm_import_inbound_group_session(olmInboundGroupSession, - reinterpret_cast(keyBuf.data()), keyBuf.size()); - if (error == olm_error()) { - throw lastError(olmInboundGroupSession); - } - - return std::make_unique(olmInboundGroupSession); -} - -QByteArray toKey(const PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return ""; - } - return std::get(mode).key; -} - -QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const -{ - QByteArray pickledBuf(olm_pickle_inbound_group_session_length(m_groupSession), '0'); - const QByteArray key = toKey(mode); - const auto error = olm_pickle_inbound_group_session(m_groupSession, key.data(), key.length(), pickledBuf.data(), - pickledBuf.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); - } - return pickledBuf; -} - -std::variant, QOlmError> QOlmInboundGroupSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) -{ - QByteArray pickledBuf = pickled; - const auto groupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); - QByteArray key = toKey(mode); - const auto error = olm_unpickle_inbound_group_session(groupSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.size()); - if (error == olm_error()) { - return lastError(groupSession); - } - key.clear(); - - return std::make_unique(groupSession); -} - -std::variant, QOlmError> QOlmInboundGroupSession::decrypt(const QByteArray &message) -{ - // This is for capturing the output of olm_group_decrypt - uint32_t messageIndex = 0; - - // We need to clone the message because - // olm_decrypt_max_plaintext_length destroys the input buffer - QByteArray messageBuf(message.length(), '0'); - std::copy(message.begin(), message.end(), messageBuf.begin()); - - QByteArray plaintextBuf(olm_group_decrypt_max_plaintext_length(m_groupSession, - reinterpret_cast(messageBuf.data()), messageBuf.length()), '0'); - - messageBuf = QByteArray(message.length(), '0'); - std::copy(message.begin(), message.end(), messageBuf.begin()); - - const auto plaintextLen = olm_group_decrypt(m_groupSession, reinterpret_cast(messageBuf.data()), - messageBuf.length(), reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), &messageIndex); - - // Error code or plaintext length is returned - const auto decryptError = plaintextLen; - - if (decryptError == olm_error()) { - return lastError(m_groupSession); - } - - QByteArray output(plaintextLen, '0'); - std::memcpy(output.data(), plaintextBuf.data(), plaintextLen); - - return std::make_pair(QString(output), messageIndex); -} - -std::variant QOlmInboundGroupSession::exportSession(uint32_t messageIndex) -{ - const auto keyLength = olm_export_inbound_group_session_length(m_groupSession); - QByteArray keyBuf(keyLength, '0'); - const auto error = olm_export_inbound_group_session(m_groupSession, reinterpret_cast(keyBuf.data()), keyLength, messageIndex); - - if (error == olm_error()) { - return lastError(m_groupSession); - } - return keyBuf; -} - -uint32_t QOlmInboundGroupSession::firstKnownIndex() const -{ - return olm_inbound_group_session_first_known_index(m_groupSession); -} - -QByteArray QOlmInboundGroupSession::sessionId() const -{ - QByteArray sessionIdBuf(olm_inbound_group_session_id_length(m_groupSession), '0'); - const auto error = olm_inbound_group_session_id(m_groupSession, reinterpret_cast(sessionIdBuf.data()), - sessionIdBuf.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); - } - return sessionIdBuf; -} - -bool QOlmInboundGroupSession::isVerified() const -{ - return olm_inbound_group_session_is_verified(m_groupSession) != 0; -} diff --git a/lib/crypto/qolminboundsession.h b/lib/crypto/qolminboundsession.h deleted file mode 100644 index 362e42ba..00000000 --- a/lib/crypto/qolminboundsession.h +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include -#include -#include -#include "olm/olm.h" -#include "crypto/qolmerrors.h" -#include "crypto/e2ee.h" - -namespace Quotient { - -//! An in-bound group session is responsible for decrypting incoming -//! communication in a Megolm session. -struct QOlmInboundGroupSession -{ -public: - ~QOlmInboundGroupSession(); - //! Creates a new instance of `OlmInboundGroupSession`. - static std::unique_ptr create(const QByteArray &key); - //! Import an inbound group session, from a previous export. - static std::unique_ptr import(const QByteArray &key); - //! Serialises an `OlmInboundGroupSession` to encrypted Base64. - QByteArray pickle(const PicklingMode &mode) const; - //! Deserialises from encrypted Base64 that was previously obtained by pickling - //! an `OlmInboundGroupSession`. - static std::variant, QOlmError> unpickle(const QByteArray &picked, const PicklingMode &mode); - //! Decrypts ciphertext received for this group session. - std::variant, QOlmError> decrypt(const QByteArray &message); - //! Export the base64-encoded ratchet key for this session, at the given index, - //! in a format which can be used by import. - std::variant exportSession(uint32_t messageIndex); - //! Get the first message index we know how to decrypt. - uint32_t firstKnownIndex() const; - //! Get a base64-encoded identifier for this session. - QByteArray sessionId() const; - bool isVerified() const; - QOlmInboundGroupSession(OlmInboundGroupSession *session); -private: - OlmInboundGroupSession *m_groupSession; -}; - -using QOlmInboundGroupSessionPtr = std::unique_ptr; -using OlmInboundGroupSessionPtr = std::unique_ptr; -} // namespace Quotient diff --git a/lib/crypto/qolmmessage.cpp b/lib/crypto/qolmmessage.cpp deleted file mode 100644 index 15008b75..00000000 --- a/lib/crypto/qolmmessage.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "qolmmessage.h" - -using namespace Quotient; - -QOlmMessage::QOlmMessage(const QByteArray &ciphertext, QOlmMessage::Type type) - : QByteArray(std::move(ciphertext)) - , m_messageType(type) -{ - Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); -} - -QOlmMessage::QOlmMessage(const QOlmMessage &message) - : QByteArray(message) - , m_messageType(message.type()) -{ -} - -QOlmMessage::Type QOlmMessage::type() const -{ - return m_messageType; -} - -QByteArray QOlmMessage::toCiphertext() const -{ - return QByteArray(*this); -} - -QOlmMessage QOlmMessage::fromCiphertext(const QByteArray &ciphertext) -{ - return QOlmMessage(ciphertext, QOlmMessage::General); -} diff --git a/lib/crypto/qolmmessage.h b/lib/crypto/qolmmessage.h deleted file mode 100644 index 52aba78c..00000000 --- a/lib/crypto/qolmmessage.h +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include -#include - -namespace Quotient { - -/*! \brief A wrapper around an olm encrypted message - * - * This class encapsulates a Matrix olm encrypted message, - * passed in either of 2 forms: a general message or a pre-key message. - * - * The class provides functions to get a type and the ciphertext. - */ -class QOlmMessage : public QByteArray { - Q_GADGET -public: - enum Type { - General, - PreKey, - }; - Q_ENUM(Type) - - QOlmMessage() = default; - explicit QOlmMessage(const QByteArray &ciphertext, Type type = General); - explicit QOlmMessage(const QOlmMessage &message); - - static QOlmMessage fromCiphertext(const QByteArray &ciphertext); - - Q_INVOKABLE Type type() const; - Q_INVOKABLE QByteArray toCiphertext() const; - -private: - Type m_messageType = General; -}; - -} //namespace Quotient diff --git a/lib/crypto/qolmoutboundsession.cpp b/lib/crypto/qolmoutboundsession.cpp deleted file mode 100644 index bc572ba5..00000000 --- a/lib/crypto/qolmoutboundsession.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "qolmoutboundsession.h" -#include "crypto/qolmutils.h" - -using namespace Quotient; - -QOlmError lastError(OlmOutboundGroupSession *session) { - const std::string error_raw = olm_outbound_group_session_last_error(session); - - return fromString(error_raw); -} - -QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session) - : m_groupSession(session) -{ -} - -QOlmOutboundGroupSession::~QOlmOutboundGroupSession() -{ - olm_clear_outbound_group_session(m_groupSession); - delete[](reinterpret_cast(m_groupSession)); -} - -std::unique_ptr QOlmOutboundGroupSession::create() -{ - auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); - const auto randomLength = olm_init_outbound_group_session_random_length(olmOutboundGroupSession); - QByteArray randomBuf = getRandom(randomLength); - - const auto error = olm_init_outbound_group_session(olmOutboundGroupSession, - reinterpret_cast(randomBuf.data()), randomBuf.length()); - - if (error == olm_error()) { - throw lastError(olmOutboundGroupSession); - } - - const auto keyMaxLength = olm_outbound_group_session_key_length(olmOutboundGroupSession); - QByteArray keyBuffer(keyMaxLength, '0'); - olm_outbound_group_session_key(olmOutboundGroupSession, reinterpret_cast(keyBuffer.data()), - keyMaxLength); - - randomBuf.clear(); - - return std::make_unique(olmOutboundGroupSession); -} - -std::variant QOlmOutboundGroupSession::pickle(const PicklingMode &mode) -{ - QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); - QByteArray key = toKey(mode); - const auto error = olm_pickle_outbound_group_session(m_groupSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()); - - if (error == olm_error()) { - return lastError(m_groupSession); - } - - key.clear(); - - return pickledBuf; -} - - -std::variant, QOlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) -{ - QByteArray pickledBuf = pickled; - auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); - QByteArray key = toKey(mode); - const auto error = olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), key.length(), - pickled.data(), pickled.length()); - if (error == olm_error()) { - return lastError(olmOutboundGroupSession); - } - const auto idMaxLength = olm_outbound_group_session_id_length(olmOutboundGroupSession); - QByteArray idBuffer(idMaxLength, '0'); - olm_outbound_group_session_id(olmOutboundGroupSession, reinterpret_cast(idBuffer.data()), - idBuffer.length()); - - key.clear(); - return std::make_unique(olmOutboundGroupSession); -} - -std::variant QOlmOutboundGroupSession::encrypt(const QString &plaintext) -{ - QByteArray plaintextBuf = plaintext.toUtf8(); - const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); - QByteArray messageBuf(messageMaxLength, '0'); - const auto error = olm_group_encrypt(m_groupSession, reinterpret_cast(plaintextBuf.data()), - plaintextBuf.length(), reinterpret_cast(messageBuf.data()), messageBuf.length()); - - if (error == olm_error()) { - return lastError(m_groupSession); - } - - return messageBuf; -} - -uint32_t QOlmOutboundGroupSession::sessionMessageIndex() const -{ - return olm_outbound_group_session_message_index(m_groupSession); -} - -QByteArray QOlmOutboundGroupSession::sessionId() const -{ - const auto idMaxLength = olm_outbound_group_session_id_length(m_groupSession); - QByteArray idBuffer(idMaxLength, '0'); - const auto error = olm_outbound_group_session_id(m_groupSession, reinterpret_cast(idBuffer.data()), - idBuffer.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); - } - return idBuffer; -} - -std::variant QOlmOutboundGroupSession::sessionKey() const -{ - const auto keyMaxLength = olm_outbound_group_session_key_length(m_groupSession); - QByteArray keyBuffer(keyMaxLength, '0'); - const auto error = olm_outbound_group_session_key(m_groupSession, reinterpret_cast(keyBuffer.data()), - keyMaxLength); - if (error == olm_error()) { - return lastError(m_groupSession); - } - return keyBuffer; -} diff --git a/lib/crypto/qolmoutboundsession.h b/lib/crypto/qolmoutboundsession.h deleted file mode 100644 index 4e06561e..00000000 --- a/lib/crypto/qolmoutboundsession.h +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - - -#pragma once - -#include "olm/olm.h" -#include "crypto/qolmerrors.h" -#include "crypto/e2ee.h" -#include - -namespace Quotient { - - -//! An out-bound group session is responsible for encrypting outgoing -//! communication in a Megolm session. -class QOlmOutboundGroupSession -{ -public: - ~QOlmOutboundGroupSession(); - //! Creates a new instance of `QOlmOutboundGroupSession`. - //! Throw OlmError on errors - static std::unique_ptr create(); - //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); - //! Deserialises from encrypted Base64 that was previously obtained by - //! pickling a `QOlmOutboundGroupSession`. - static std::variant, QOlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); - //! Encrypts a plaintext message using the session. - std::variant encrypt(const QString &plaintext); - - //! Get the current message index for this session. - //! - //! Each message is sent with an increasing index; this returns the - //! index for the next message. - uint32_t sessionMessageIndex() const; - - //! Get a base64-encoded identifier for this session. - QByteArray sessionId() const; - - //! Get the base64-encoded current ratchet key for this session. - //! - //! Each message is sent with a different ratchet key. This function returns the - //! ratchet key that will be used for the next message. - std::variant sessionKey() const; - QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); -private: - OlmOutboundGroupSession *m_groupSession; -}; - -using QOlmOutboundGroupSessionPtr = std::unique_ptr; -using OlmOutboundGroupSessionPtr = std::unique_ptr; -} diff --git a/lib/crypto/qolmsession.cpp b/lib/crypto/qolmsession.cpp deleted file mode 100644 index a0386613..00000000 --- a/lib/crypto/qolmsession.cpp +++ /dev/null @@ -1,253 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "qolmsession.h" -#include "crypto/qolmutils.h" -#include "logging.h" -#include -#include - -using namespace Quotient; - -QOlmError lastError(OlmSession* session) { - const std::string error_raw = olm_session_last_error(session); - - return fromString(error_raw); -} - -Quotient::QOlmSession::~QOlmSession() -{ - olm_clear_session(m_session); - delete[](reinterpret_cast(m_session)); -} - -OlmSession* QOlmSession::create() -{ - return olm_session(new uint8_t[olm_session_size()]); -} - -std::variant QOlmSession::createInbound(QOlmAccount *account, const QOlmMessage &preKeyMessage, bool from, const QString &theirIdentityKey) -{ - if (preKeyMessage.type() != QOlmMessage::PreKey) { - qCCritical(E2EE) << "The message is not a pre-key in when creating inbound session" << BadMessageFormat; - } - - const auto olmSession = create(); - - QByteArray oneTimeKeyMessageBuf = preKeyMessage.toCiphertext(); - QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); - size_t error = 0; - if (from) { - error = olm_create_inbound_session_from(olmSession, account->data(), theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); - } else { - error = olm_create_inbound_session(olmSession, account->data(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); - } - - if (error == olm_error()) { - const auto lastErr = lastError(olmSession); - qCWarning(E2EE) << "Error when creating inbound session" << lastErr; - return lastErr; - } - - return std::make_unique(olmSession); -} - -std::variant QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) -{ - return createInbound(account, preKeyMessage); -} - -std::variant QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) -{ - return createInbound(account, preKeyMessage, true, theirIdentityKey); -} - -std::variant QOlmSession::createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey) -{ - auto *olmOutboundSession = create(); - const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); - QByteArray randomBuf = getRandom(randomLen); - - QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); - QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8(); - const auto error = olm_create_outbound_session(olmOutboundSession, - account->data(), - reinterpret_cast(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), - reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), - reinterpret_cast(randomBuf.data()), randomBuf.length()); - - if (error == olm_error()) { - const auto lastErr = lastError(olmOutboundSession); - if (lastErr == QOlmError::NotEnoughRandom) { - throw lastErr; - } - return lastErr; - } - - randomBuf.clear(); - return std::make_unique(olmOutboundSession); -} - -std::variant QOlmSession::pickle(const PicklingMode &mode) -{ - QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); - QByteArray key = toKey(mode); - const auto error = olm_pickle_session(m_session, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()); - - if (error == olm_error()) { - return lastError(m_session); - } - - key.clear(); - - return pickledBuf; -} - -std::variant QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) -{ - QByteArray pickledBuf = pickled; - auto *olmSession = create(); - QByteArray key = toKey(mode); - const auto error = olm_unpickle_session(olmSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()); - if (error == olm_error()) { - return lastError(olmSession); - } - - key.clear(); - return std::make_unique(olmSession); -} - -QOlmMessage QOlmSession::encrypt(const QString &plaintext) -{ - QByteArray plaintextBuf = plaintext.toUtf8(); - const auto messageMaxLen = olm_encrypt_message_length(m_session, plaintextBuf.length()); - QByteArray messageBuf(messageMaxLen, '0'); - const auto messageType = encryptMessageType(); - const auto randomLen = olm_encrypt_random_length(m_session); - QByteArray randomBuf = getRandom(randomLen); - const auto error = olm_encrypt(m_session, - reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), - reinterpret_cast(randomBuf.data()), randomBuf.length(), - reinterpret_cast(messageBuf.data()), messageBuf.length()); - - if (error == olm_error()) { - throw lastError(m_session); - } - - return QOlmMessage(messageBuf, messageType); -} - -std::variant QOlmSession::decrypt(const QOlmMessage &message) const -{ - const auto messageType = message.type(); - const auto ciphertext = message.toCiphertext(); - const auto messageTypeValue = messageType == QOlmMessage::Type::General - ? OLM_MESSAGE_TYPE_MESSAGE : OLM_MESSAGE_TYPE_PRE_KEY; - - // We need to clone the message because - // olm_decrypt_max_plaintext_length destroys the input buffer - QByteArray messageBuf(ciphertext.length(), '0'); - std::copy(message.begin(), message.end(), messageBuf.begin()); - - const auto plaintextMaxLen = olm_decrypt_max_plaintext_length(m_session, messageTypeValue, - reinterpret_cast(messageBuf.data()), messageBuf.length()); - - if (plaintextMaxLen == olm_error()) { - return lastError(m_session); - } - - QByteArray plaintextBuf(plaintextMaxLen, '0'); - QByteArray messageBuf2(ciphertext.length(), '0'); - std::copy(message.begin(), message.end(), messageBuf2.begin()); - - const auto plaintextResultLen = olm_decrypt(m_session, messageTypeValue, - reinterpret_cast(messageBuf2.data()), messageBuf2.length(), - reinterpret_cast(plaintextBuf.data()), plaintextMaxLen); - - if (plaintextResultLen == olm_error()) { - const auto lastErr = lastError(m_session); - if (lastErr == QOlmError::OutputBufferTooSmall) { - throw lastErr; - } - return lastErr; - } - QByteArray output(plaintextResultLen, '0'); - std::memcpy(output.data(), plaintextBuf.data(), plaintextResultLen); - plaintextBuf.clear(); - return output; -} - -QOlmMessage::Type QOlmSession::encryptMessageType() -{ - const auto messageTypeResult = olm_encrypt_message_type(m_session); - if (messageTypeResult == olm_error()) { - throw lastError(m_session); - } - if (messageTypeResult == OLM_MESSAGE_TYPE_PRE_KEY) { - return QOlmMessage::PreKey; - } - return QOlmMessage::General; -} - -QByteArray QOlmSession::sessionId() const -{ - const auto idMaxLength = olm_session_id_length(m_session); - QByteArray idBuffer(idMaxLength, '0'); - const auto error = olm_session_id(m_session, reinterpret_cast(idBuffer.data()), - idBuffer.length()); - if (error == olm_error()) { - throw lastError(m_session); - } - return idBuffer; -} - -bool QOlmSession::hasReceivedMessage() const -{ - return olm_session_has_received_message(m_session); -} - -std::variant QOlmSession::matchesInboundSession(const QOlmMessage &preKeyMessage) const -{ - Q_ASSERT(preKeyMessage.type() == QOlmMessage::Type::PreKey); - QByteArray oneTimeKeyBuf(preKeyMessage.data()); - const auto matchesResult = olm_matches_inbound_session(m_session, oneTimeKeyBuf.data(), oneTimeKeyBuf.length()); - - if (matchesResult == olm_error()) { - return lastError(m_session); - } - switch (matchesResult) { - case 0: - return false; - case 1: - return true; - default: - return QOlmError::Unknown; - } -} -std::variant QOlmSession::matchesInboundSessionFrom(const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) const -{ - const auto theirIdentityKeyBuf = theirIdentityKey.toUtf8(); - auto oneTimeKeyMessageBuf = preKeyMessage.toCiphertext(); - const auto error = olm_matches_inbound_session_from(m_session, theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), - oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); - - if (error == olm_error()) { - return lastError(m_session); - } - switch (error) { - case 0: - return false; - case 1: - return true; - default: - return QOlmError::Unknown; - } -} - -QOlmSession::QOlmSession(OlmSession *session) - : m_session(session) -{ -} diff --git a/lib/crypto/qolmsession.h b/lib/crypto/qolmsession.h deleted file mode 100644 index 711ca66b..00000000 --- a/lib/crypto/qolmsession.h +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Alexey Andreyev -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include -#include // FIXME: OlmSession -#include "crypto/e2ee.h" -#include "crypto/qolmmessage.h" -#include "crypto/qolmerrors.h" -#include "crypto/qolmaccount.h" - -namespace Quotient { - -class QOlmAccount; -class QOlmSession; - - -//! Either an outbound or inbound session for secure communication. -class QOlmSession -{ -public: - ~QOlmSession(); - //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. - static std::variant, QOlmError> createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage); - static std::variant, QOlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage); - static std::variant, QOlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); - //! Serialises an `QOlmSession` to encrypted Base64. - std::variant pickle(const PicklingMode &mode); - //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static std::variant, QOlmError> unpickle(const QByteArray &pickled, const PicklingMode &mode); - //! Encrypts a plaintext message using the session. - QOlmMessage encrypt(const QString &plaintext); - - //! Decrypts a message using this session. Decoding is lossy, meaing if - //! the decrypted plaintext contains invalid UTF-8 symbols, they will - //! be returned as `U+FFFD` (�). - std::variant decrypt(const QOlmMessage &message) const; - - //! Get a base64-encoded identifier for this session. - QByteArray sessionId() const; - - //! The type of the next message that will be returned from encryption. - QOlmMessage::Type encryptMessageType(); - - //! Checker for any received messages for this session. - bool hasReceivedMessage() const; - - //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSession(const QOlmMessage &preKeyMessage) const; - - //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSessionFrom(const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) const; - - friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) - { - return lhs.sessionId() < rhs.sessionId(); - } - - friend bool operator<(const std::unique_ptr &lhs, const std::unique_ptr &rhs) { - return *lhs < *rhs; - } - - OlmSession *raw() const - { - return m_session; - } - QOlmSession(OlmSession* session); -private: - //! Helper function for creating new sessions and handling errors. - static OlmSession* create(); - static std::variant, QOlmError> createInbound(QOlmAccount *account, const QOlmMessage& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); - OlmSession* m_session; -}; -} //namespace Quotient diff --git a/lib/crypto/qolmutility.cpp b/lib/crypto/qolmutility.cpp deleted file mode 100644 index bb50b4d0..00000000 --- a/lib/crypto/qolmutility.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "crypto/qolmutility.h" -#include "olm/olm.h" -#include - -using namespace Quotient; - -// Convert olm error to enum -QOlmError lastError(OlmUtility *utility) { - const std::string error_raw = olm_utility_last_error(utility); - - return fromString(error_raw); -} - -QOlmUtility::QOlmUtility() -{ - auto utility = new uint8_t[olm_utility_size()]; - m_utility = olm_utility(utility); -} - -QOlmUtility::~QOlmUtility() -{ - olm_clear_utility(m_utility); - delete[](reinterpret_cast(m_utility)); -} - -QString QOlmUtility::sha256Bytes(const QByteArray &inputBuf) const -{ - const auto outputLen = olm_sha256_length(m_utility); - QByteArray outputBuf(outputLen, '0'); - olm_sha256(m_utility, inputBuf.data(), inputBuf.length(), - outputBuf.data(), outputBuf.length()); - - return QString::fromUtf8(outputBuf); -} - -QString QOlmUtility::sha256Utf8Msg(const QString &message) const -{ - return sha256Bytes(message.toUtf8()); -} - -std::variant QOlmUtility::ed25519Verify(const QByteArray &key, - const QByteArray &message, const QByteArray &signature) -{ - QByteArray signatureBuf(signature.length(), '0'); - std::copy(signature.begin(), signature.end(), signatureBuf.begin()); - - const auto ret = olm_ed25519_verify(m_utility, key.data(), key.size(), - message.data(), message.size(), (void *)signatureBuf.data(), signatureBuf.size()); - - const auto error = ret; - if (error == olm_error()) { - return lastError(m_utility); - } - - if (ret != 0) { - return false; - } - return true; -} diff --git a/lib/crypto/qolmutility.h b/lib/crypto/qolmutility.h deleted file mode 100644 index 5fd28dcc..00000000 --- a/lib/crypto/qolmutility.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include -#include -#include "crypto/qolmerrors.h" - -struct OlmUtility; - -namespace Quotient { - -class QOlmSession; -class Connection; - -//! Allows you to make use of crytographic hashing via SHA-2 and -//! verifying ed25519 signatures. -class QOlmUtility -{ -public: - QOlmUtility(); - ~QOlmUtility(); - - //! Returns a sha256 of the supplied byte slice. - QString sha256Bytes(const QByteArray &inputBuf) const; - - //! Convenience function that converts the UTF-8 message - //! to bytes and then calls `sha256Bytes()`, returning its output. - QString sha256Utf8Msg(const QString &message) const; - - //! Verify a ed25519 signature. - //! \param key QByteArray The public part of the ed25519 key that signed the message. - //! \param message QByteArray The message that was signed. - //! \param signature QByteArray The signature of the message. - std::variant ed25519Verify(const QByteArray &key, - const QByteArray &message, const QByteArray &signature); - - -private: - OlmUtility *m_utility; - -}; -} diff --git a/lib/crypto/qolmutils.cpp b/lib/crypto/qolmutils.cpp deleted file mode 100644 index cd5ac83c..00000000 --- a/lib/crypto/qolmutils.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "crypto/qolmutils.h" -#include -#include - -using namespace Quotient; - -QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return {}; - } - return std::get(mode).key; -} - -QByteArray Quotient::getRandom(size_t bufferSize) -{ - QByteArray buffer(bufferSize, '0'); - RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); - return buffer; -} diff --git a/lib/crypto/qolmutils.h b/lib/crypto/qolmutils.h deleted file mode 100644 index 8b1c01ce..00000000 --- a/lib/crypto/qolmutils.h +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include - -#include "crypto/e2ee.h" - -namespace Quotient { -// Convert PicklingMode to key -QByteArray toKey(const PicklingMode &mode); -QByteArray getRandom(size_t bufferSize); -} diff --git a/lib/database.cpp b/lib/database.cpp index 153aab31..ec285d22 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -10,9 +10,9 @@ #include #include -#include "crypto/e2ee.h" -#include "crypto/qolmsession.h" -#include "crypto/qolminboundsession.h" +#include "e2ee/e2ee.h" +#include "e2ee/qolmsession.h" +#include "e2ee/qolminboundsession.h" //TODO: delete room specific data when leaving room diff --git a/lib/database.h b/lib/database.h index ed356820..8f8cd6cd 100644 --- a/lib/database.h +++ b/lib/database.h @@ -7,7 +7,7 @@ #include #include -#include "crypto/e2ee.h" +#include "e2ee/e2ee.h" namespace Quotient { class Database : public QObject diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h new file mode 100644 index 00000000..41cd2878 --- /dev/null +++ b/lib/e2ee/e2ee.h @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: 2019 Alexey Andreyev +// SPDX-FileCopyrightText: 2019 Kitsune Ral +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include "converters.h" +#include + +#include +#include +#include +#include + +#include "util.h" + +namespace Quotient { + +inline const auto CiphertextKeyL = "ciphertext"_ls; +inline const auto SenderKeyKeyL = "sender_key"_ls; +inline const auto DeviceIdKeyL = "device_id"_ls; +inline const auto SessionIdKeyL = "session_id"_ls; + +inline const auto AlgorithmKeyL = "algorithm"_ls; +inline const auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; +inline const auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls; + +inline const auto AlgorithmKey = QStringLiteral("algorithm"); +inline const auto RotationPeriodMsKey = QStringLiteral("rotation_period_ms"); +inline const auto RotationPeriodMsgsKey = + QStringLiteral("rotation_period_msgs"); + +inline const auto Ed25519Key = QStringLiteral("ed25519"); +inline const auto Curve25519Key = QStringLiteral("curve25519"); +inline const auto SignedCurve25519Key = QStringLiteral("signed_curve25519"); +inline const auto OlmV1Curve25519AesSha2AlgoKey = + QStringLiteral("m.olm.v1.curve25519-aes-sha2"); +inline const auto MegolmV1AesSha2AlgoKey = + QStringLiteral("m.megolm.v1.aes-sha2"); +inline const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoKey, + MegolmV1AesSha2AlgoKey }; +struct Unencrypted {}; +struct Encrypted { + QByteArray key; +}; + +using PicklingMode = std::variant; + +class QOlmSession; +using QOlmSessionPtr = std::unique_ptr; + +class QOlmInboundGroupSession; +using QOlmInboundGroupSessionPtr = std::unique_ptr; + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + +struct IdentityKeys +{ + QByteArray curve25519; + QByteArray ed25519; +}; + +//! Struct representing the one-time keys. +struct OneTimeKeys +{ + QMap> keys; + + //! Get the HashMap containing the curve25519 one-time keys. + QMap curve25519() const; + + //! Get a reference to the hashmap corresponding to given key type. + std::optional> get(QString keyType) const; +}; + +//! Struct representing the signed one-time keys. +class SignedOneTimeKey +{ +public: + SignedOneTimeKey() = default; + SignedOneTimeKey(const SignedOneTimeKey &) = default; + SignedOneTimeKey &operator=(const SignedOneTimeKey &) = default; + //! Required. The unpadded Base64-encoded 32-byte Curve25519 public key. + QString key; + + //! Required. Signatures of the key object. + //! The signature is calculated using the process described at Signing JSON. + QHash> signatures; +}; + + +template <> +struct JsonObjectConverter { + static void fillFrom(const QJsonObject& jo, + SignedOneTimeKey& result) + { + fromJson(jo.value("key"_ls), result.key); + fromJson(jo.value("signatures"_ls), result.signatures); + } + + static void dumpTo(QJsonObject &jo, const SignedOneTimeKey &result) + { + addParam<>(jo, QStringLiteral("key"), result.key); + addParam<>(jo, QStringLiteral("signatures"), result.signatures); + } +}; + +bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); + +template +class asKeyValueRange +{ +public: + asKeyValueRange(T &data) + : m_data{data} + { + } + + auto begin() { return m_data.keyValueBegin(); } + + auto end() { return m_data.keyValueEnd(); } + +private: + T &m_data; +}; + +} // namespace Quotient + +Q_DECLARE_METATYPE(Quotient::SignedOneTimeKey) diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp new file mode 100644 index 00000000..aaf51946 --- /dev/null +++ b/lib/e2ee/qolmaccount.cpp @@ -0,0 +1,330 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "qolmaccount.h" +#include "connection.h" +#include "csapi/keys.h" +#include "e2ee/qolmutils.h" +#include "e2ee/qolmutility.h" +#include +#include +#include +#include + +using namespace Quotient; + +QMap OneTimeKeys::curve25519() const +{ + return keys[QStringLiteral("curve25519")]; +} + +std::optional> OneTimeKeys::get(QString keyType) const +{ + if (!keys.contains(keyType)) { + return std::nullopt; + } + return keys[keyType]; +} + +bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) +{ + return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519; +} + +// Convert olm error to enum +QOlmError lastError(OlmAccount *account) { + const std::string error_raw = olm_account_last_error(account); + + return fromString(error_raw); +} + +QByteArray getRandom(size_t bufferSize) +{ + QByteArray buffer(bufferSize, '0'); + std::generate(buffer.begin(), buffer.end(), std::rand); + return buffer; +} + +QOlmAccount::QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent) + : QObject(parent) + , m_userId(userId) + , m_deviceId(deviceId) +{ +} + +QOlmAccount::~QOlmAccount() +{ + olm_clear_account(m_account); + delete[](reinterpret_cast(m_account)); +} + +void QOlmAccount::createNewAccount() +{ + m_account = olm_account(new uint8_t[olm_account_size()]); + size_t randomSize = olm_create_account_random_length(m_account); + QByteArray randomData = getRandom(randomSize); + const auto error = olm_create_account(m_account, randomData.data(), randomSize); + if (error == olm_error()) { + throw lastError(m_account); + } + Q_EMIT needsSave(); +} + +void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) +{ + m_account = olm_account(new uint8_t[olm_account_size()]); + const QByteArray key = toKey(mode); + const auto error = olm_unpickle_account(m_account, key.data(), key.length(), pickled.data(), pickled.size()); + if (error == olm_error()) { + qCWarning(E2EE) << "Failed to unpickle olm account"; + //TODO: Do something that is not dying + // Probably log the user out since we have no way of getting to the keys + //throw lastError(m_account); + } +} + +std::variant QOlmAccount::pickle(const PicklingMode &mode) +{ + const QByteArray key = toKey(mode); + const size_t pickleLength = olm_pickle_account_length(m_account); + QByteArray pickleBuffer(pickleLength, '0'); + const auto error = olm_pickle_account(m_account, key.data(), + key.length(), pickleBuffer.data(), pickleLength); + if (error == olm_error()) { + return lastError(m_account); + } + return pickleBuffer; +} + +IdentityKeys QOlmAccount::identityKeys() const +{ + const size_t keyLength = olm_account_identity_keys_length(m_account); + QByteArray keyBuffer(keyLength, '0'); + const auto error = olm_account_identity_keys(m_account, keyBuffer.data(), keyLength); + if (error == olm_error()) { + throw lastError(m_account); + } + const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object(); + return IdentityKeys { + key.value(QStringLiteral("curve25519")).toString().toUtf8(), + key.value(QStringLiteral("ed25519")).toString().toUtf8() + }; +} + +QByteArray QOlmAccount::sign(const QByteArray &message) const +{ + QByteArray signatureBuffer(olm_account_signature_length(m_account), '0'); + + const auto error = olm_account_sign(m_account, message.data(), message.length(), + signatureBuffer.data(), signatureBuffer.length()); + + if (error == olm_error()) { + throw lastError(m_account); + } + return signatureBuffer; +} + +QByteArray QOlmAccount::sign(const QJsonObject &message) const +{ + return sign(QJsonDocument(message).toJson(QJsonDocument::Compact)); +} + +QByteArray QOlmAccount::signIdentityKeys() const +{ + const auto keys = identityKeys(); + QJsonObject body + { + {"algorithms", QJsonArray{"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}}, + {"user_id", m_userId}, + {"device_id", m_deviceId}, + {"keys", + QJsonObject{ + {QStringLiteral("curve25519:") + m_deviceId, QString::fromUtf8(keys.curve25519)}, + {QStringLiteral("ed25519:") + m_deviceId, QString::fromUtf8(keys.ed25519)} + } + } + }; + return sign(QJsonDocument(body).toJson(QJsonDocument::Compact)); + +} + +size_t QOlmAccount::maxNumberOfOneTimeKeys() const +{ + return olm_account_max_number_of_one_time_keys(m_account); +} + +size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const +{ + const size_t randomLength = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); + QByteArray randomBuffer = getRandom(randomLength); + const auto error = olm_account_generate_one_time_keys(m_account, numberOfKeys, randomBuffer.data(), randomLength); + + if (error == olm_error()) { + throw lastError(m_account); + } + Q_EMIT needsSave(); + return error; +} + +OneTimeKeys QOlmAccount::oneTimeKeys() const +{ + const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); + QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0'); + + const auto error = olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength); + if (error == olm_error()) { + throw lastError(m_account); + } + const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); + OneTimeKeys oneTimeKeys; + + for (const QString& key1 : json.keys()) { + auto oneTimeKeyObject = json[key1].toObject(); + auto keyMap = QMap(); + for (const QString &key2 : oneTimeKeyObject.keys()) { + keyMap[key2] = oneTimeKeyObject[key2].toString(); + } + oneTimeKeys.keys[key1] = keyMap; + } + return oneTimeKeys; +} + +QMap QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const +{ + QMap signedOneTimeKeys; + for (const auto &keyid : keys.curve25519().keys()) { + const auto oneTimeKey = keys.curve25519()[keyid]; + QByteArray sign = signOneTimeKey(oneTimeKey); + signedOneTimeKeys["signed_curve25519:" + keyid] = signedOneTimeKey(oneTimeKey.toUtf8(), sign); + } + return signedOneTimeKeys; +} + +SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray &key, const QString &signature) const +{ + SignedOneTimeKey sign{}; + sign.key = key; + sign.signatures = {{m_userId, {{"ed25519:" + m_deviceId, signature}}}}; + return sign; +} + +QByteArray QOlmAccount::signOneTimeKey(const QString &key) const +{ + QJsonDocument j(QJsonObject{{"key", key}}); + return sign(j.toJson(QJsonDocument::Compact)); +} + +std::optional QOlmAccount::removeOneTimeKeys(const QOlmSessionPtr &session) const +{ + const auto error = olm_remove_one_time_keys(m_account, session->raw()); + + if (error == olm_error()) { + return lastError(m_account); + } + Q_EMIT needsSave(); + return std::nullopt; +} + +OlmAccount *QOlmAccount::data() +{ + return m_account; +} + +DeviceKeys QOlmAccount::deviceKeys() const +{ + DeviceKeys deviceKeys; + deviceKeys.userId = m_userId; + deviceKeys.deviceId = m_deviceId; + deviceKeys.algorithms = QStringList {"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}; + + const auto idKeys = identityKeys(); + deviceKeys.keys["curve25519:" + m_deviceId] = idKeys.curve25519; + deviceKeys.keys["ed25519:" + m_deviceId] = idKeys.ed25519; + + const auto sign = signIdentityKeys(); + deviceKeys.signatures[m_userId]["ed25519:" + m_deviceId] = sign; + + return deviceKeys; +} + +UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKeys) +{ + auto keys = deviceKeys(); + + if (oneTimeKeys.curve25519().isEmpty()) { + return new UploadKeysJob(keys); + } + + // Sign & append the one time keys. + auto temp = signOneTimeKeys(oneTimeKeys); + QHash oneTimeKeysSigned; + for (const auto &[keyId, key] : asKeyValueRange(temp)) { + oneTimeKeysSigned[keyId] = QVariant::fromValue(toJson(key)); + } + + return new UploadKeysJob(keys, oneTimeKeysSigned); +} + +std::variant QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) +{ + Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); + return QOlmSession::createInboundSession(this, preKeyMessage); +} + +std::variant QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) +{ + Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); + return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); +} + +std::variant QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) +{ + return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); +} + +void QOlmAccount::markKeysAsPublished() +{ + olm_account_mark_keys_as_published(m_account); + Q_EMIT needsSave(); +} + +bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys, + const QString &deviceId, + const QString &userId) +{ + const auto signKeyId = "ed25519:" + deviceId; + const auto signingKey = deviceKeys.keys[signKeyId]; + const auto signature = deviceKeys.signatures[userId][signKeyId]; + + if (signature.isEmpty()) { + return false; + } + + return ed25519VerifySignature(signingKey, toJson(deviceKeys), signature); +} + +bool Quotient::ed25519VerifySignature(const QString &signingKey, + const QJsonObject &obj, + const QString &signature) +{ + if (signature.isEmpty()) { + return false; + } + QJsonObject obj1 = obj; + + obj1.remove("unsigned"); + obj1.remove("signatures"); + + auto canonicalJson = QJsonDocument(obj1).toJson(QJsonDocument::Compact); + + QByteArray signingKeyBuf = signingKey.toUtf8(); + QOlmUtility utility; + auto signatureBuf = signature.toUtf8(); + auto result = utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf); + if (std::holds_alternative(result)) { + return false; + } + + return std::get(result); +} diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h new file mode 100644 index 00000000..00afc0e6 --- /dev/null +++ b/lib/e2ee/qolmaccount.h @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + + +#pragma once + +#include "csapi/keys.h" +#include "e2ee/e2ee.h" +#include "e2ee/qolmerrors.h" +#include "e2ee/qolmmessage.h" +#include "e2ee/qolmsession.h" +#include + +struct OlmAccount; + +namespace Quotient { + +class QOlmSession; +class Connection; + +using QOlmSessionPtr = std::unique_ptr; + +//! An olm account manages all cryptographic keys used on a device. +//! \code{.cpp} +//! const auto olmAccount = new QOlmAccount(this); +//! \endcode +class QOlmAccount : public QObject +{ + Q_OBJECT +public: + QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent = nullptr); + ~QOlmAccount(); + + //! Creates a new instance of OlmAccount. During the instantiation + //! the Ed25519 fingerprint key pair and the Curve25519 identity key + //! pair are generated. For more information see here. + //! This needs to be called before any other action or use unpickle() instead. + void createNewAccount(); + + //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmAccount`. + //! This needs to be called before any other action or use createNewAccount() instead. + void unpickle(QByteArray &pickled, const PicklingMode &mode); + + //! Serialises an OlmAccount to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + + //! Returns the account's public identity keys already formatted as JSON + IdentityKeys identityKeys() const; + + //! Returns the signature of the supplied message. + QByteArray sign(const QByteArray &message) const; + QByteArray sign(const QJsonObject& message) const; + + //! Sign identity keys. + QByteArray signIdentityKeys() const; + + //! Maximum number of one time keys that this OlmAccount can + //! currently hold. + size_t maxNumberOfOneTimeKeys() const; + + //! Generates the supplied number of one time keys. + size_t generateOneTimeKeys(size_t numberOfKeys) const; + + //! Gets the OlmAccount's one time keys formatted as JSON. + OneTimeKeys oneTimeKeys() const; + + //! Sign all one time keys. + QMap signOneTimeKeys(const OneTimeKeys &keys) const; + + //! Sign one time key. + QByteArray signOneTimeKey(const QString &key) const; + + SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; + + UploadKeysJob *createUploadKeyRequest(const OneTimeKeys &oneTimeKeys); + + DeviceKeys deviceKeys() const; + + //! Remove the one time key used to create the supplied session. + [[nodiscard]] std::optional removeOneTimeKeys(const QOlmSessionPtr &session) const; + + //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. + //! + //! \param message An Olm pre-key message that was encrypted for this account. + std::variant createInboundSession(const QOlmMessage &preKeyMessage); + + //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. + //! + //! \param theirIdentityKey - The identity key of the Olm account that + //! encrypted this Olm message. + std::variant createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage); + + //! Creates an outbound session for sending messages to a specific + /// identity and one time key. + std::variant createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey); + + void markKeysAsPublished(); + + // HACK do not use directly + QOlmAccount(OlmAccount *account); + OlmAccount *data(); + +Q_SIGNALS: + void needsSave() const; + +private: + OlmAccount *m_account = nullptr; // owning + QString m_userId; + QString m_deviceId; +}; + +bool verifyIdentitySignature(const DeviceKeys &deviceKeys, + const QString &deviceId, + const QString &userId); + +//! checks if the signature is signed by the signing_key +bool ed25519VerifySignature(const QString &signingKey, + const QJsonObject &obj, + const QString &signature); + +} // namespace Quotient diff --git a/lib/e2ee/qolmerrors.cpp b/lib/e2ee/qolmerrors.cpp new file mode 100644 index 00000000..6db1803c --- /dev/null +++ b/lib/e2ee/qolmerrors.cpp @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + + +#include "qolmerrors.h" + +Quotient::QOlmError Quotient::fromString(const std::string &error_raw) { + if (!error_raw.compare("BAD_ACCOUNT_KEY")) { + return QOlmError::BadAccountKey; + } else if (!error_raw.compare("BAD_MESSAGE_KEY_ID")) { + return QOlmError::BadMessageKeyId; + } else if (!error_raw.compare("INVALID_BASE64")) { + return QOlmError::InvalidBase64; + } else if (!error_raw.compare("NOT_ENOUGH_RANDOM")) { + return QOlmError::NotEnoughRandom; + } else if (!error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) { + return QOlmError::OutputBufferTooSmall; + } else { + return QOlmError::Unknown; + } +} diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h new file mode 100644 index 00000000..f8390d2a --- /dev/null +++ b/lib/e2ee/qolmerrors.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include + +namespace Quotient { +//! All errors that could be caused by an operation regarding Olm +//! Errors are named exactly like the ones in libolm. +enum QOlmError +{ + BadAccountKey, + BadMessageFormat, + BadMessageKeyId, + BadMessageMac, + BadMessageVersion, + InvalidBase64, + NotEnoughRandom, + OutputBufferTooSmall, + UnknownMessageIndex, + Unknown, +}; + +QOlmError fromString(const std::string &error_raw); + +} //namespace Quotient diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp new file mode 100644 index 00000000..9bf56b6c --- /dev/null +++ b/lib/e2ee/qolminboundsession.cpp @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "e2ee/qolminboundsession.h" +#include +#include +using namespace Quotient; + +QOlmError lastError(OlmInboundGroupSession *session) { + const std::string error_raw = olm_inbound_group_session_last_error(session); + + return fromString(error_raw); +} + +QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session) + : m_groupSession(session) +{ +} + +QOlmInboundGroupSession::~QOlmInboundGroupSession() +{ + olm_clear_inbound_group_session(m_groupSession); + //delete[](reinterpret_cast(m_groupSession)); +} + +std::unique_ptr QOlmInboundGroupSession::create(const QByteArray &key) +{ + const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); + const auto temp = key; + const auto error = olm_init_inbound_group_session(olmInboundGroupSession, + reinterpret_cast(temp.data()), temp.size()); + + if (error == olm_error()) { + throw lastError(olmInboundGroupSession); + } + + return std::make_unique(olmInboundGroupSession); +} + +std::unique_ptr QOlmInboundGroupSession::import(const QByteArray &key) +{ + const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); + QByteArray keyBuf = key; + + const auto error = olm_import_inbound_group_session(olmInboundGroupSession, + reinterpret_cast(keyBuf.data()), keyBuf.size()); + if (error == olm_error()) { + throw lastError(olmInboundGroupSession); + } + + return std::make_unique(olmInboundGroupSession); +} + +QByteArray toKey(const PicklingMode &mode) +{ + if (std::holds_alternative(mode)) { + return ""; + } + return std::get(mode).key; +} + +QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const +{ + QByteArray pickledBuf(olm_pickle_inbound_group_session_length(m_groupSession), '0'); + const QByteArray key = toKey(mode); + const auto error = olm_pickle_inbound_group_session(m_groupSession, key.data(), key.length(), pickledBuf.data(), + pickledBuf.length()); + if (error == olm_error()) { + throw lastError(m_groupSession); + } + return pickledBuf; +} + +std::variant, QOlmError> QOlmInboundGroupSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +{ + QByteArray pickledBuf = pickled; + const auto groupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); + QByteArray key = toKey(mode); + const auto error = olm_unpickle_inbound_group_session(groupSession, key.data(), key.length(), + pickledBuf.data(), pickledBuf.size()); + if (error == olm_error()) { + return lastError(groupSession); + } + key.clear(); + + return std::make_unique(groupSession); +} + +std::variant, QOlmError> QOlmInboundGroupSession::decrypt(const QByteArray &message) +{ + // This is for capturing the output of olm_group_decrypt + uint32_t messageIndex = 0; + + // We need to clone the message because + // olm_decrypt_max_plaintext_length destroys the input buffer + QByteArray messageBuf(message.length(), '0'); + std::copy(message.begin(), message.end(), messageBuf.begin()); + + QByteArray plaintextBuf(olm_group_decrypt_max_plaintext_length(m_groupSession, + reinterpret_cast(messageBuf.data()), messageBuf.length()), '0'); + + messageBuf = QByteArray(message.length(), '0'); + std::copy(message.begin(), message.end(), messageBuf.begin()); + + const auto plaintextLen = olm_group_decrypt(m_groupSession, reinterpret_cast(messageBuf.data()), + messageBuf.length(), reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), &messageIndex); + + // Error code or plaintext length is returned + const auto decryptError = plaintextLen; + + if (decryptError == olm_error()) { + return lastError(m_groupSession); + } + + QByteArray output(plaintextLen, '0'); + std::memcpy(output.data(), plaintextBuf.data(), plaintextLen); + + return std::make_pair(QString(output), messageIndex); +} + +std::variant QOlmInboundGroupSession::exportSession(uint32_t messageIndex) +{ + const auto keyLength = olm_export_inbound_group_session_length(m_groupSession); + QByteArray keyBuf(keyLength, '0'); + const auto error = olm_export_inbound_group_session(m_groupSession, reinterpret_cast(keyBuf.data()), keyLength, messageIndex); + + if (error == olm_error()) { + return lastError(m_groupSession); + } + return keyBuf; +} + +uint32_t QOlmInboundGroupSession::firstKnownIndex() const +{ + return olm_inbound_group_session_first_known_index(m_groupSession); +} + +QByteArray QOlmInboundGroupSession::sessionId() const +{ + QByteArray sessionIdBuf(olm_inbound_group_session_id_length(m_groupSession), '0'); + const auto error = olm_inbound_group_session_id(m_groupSession, reinterpret_cast(sessionIdBuf.data()), + sessionIdBuf.length()); + if (error == olm_error()) { + throw lastError(m_groupSession); + } + return sessionIdBuf; +} + +bool QOlmInboundGroupSession::isVerified() const +{ + return olm_inbound_group_session_is_verified(m_groupSession) != 0; +} diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h new file mode 100644 index 00000000..7d52991c --- /dev/null +++ b/lib/e2ee/qolminboundsession.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include +#include "olm/olm.h" +#include "e2ee/qolmerrors.h" +#include "e2ee/e2ee.h" + +namespace Quotient { + +//! An in-bound group session is responsible for decrypting incoming +//! communication in a Megolm session. +struct QOlmInboundGroupSession +{ +public: + ~QOlmInboundGroupSession(); + //! Creates a new instance of `OlmInboundGroupSession`. + static std::unique_ptr create(const QByteArray &key); + //! Import an inbound group session, from a previous export. + static std::unique_ptr import(const QByteArray &key); + //! Serialises an `OlmInboundGroupSession` to encrypted Base64. + QByteArray pickle(const PicklingMode &mode) const; + //! Deserialises from encrypted Base64 that was previously obtained by pickling + //! an `OlmInboundGroupSession`. + static std::variant, QOlmError> unpickle(const QByteArray &picked, const PicklingMode &mode); + //! Decrypts ciphertext received for this group session. + std::variant, QOlmError> decrypt(const QByteArray &message); + //! Export the base64-encoded ratchet key for this session, at the given index, + //! in a format which can be used by import. + std::variant exportSession(uint32_t messageIndex); + //! Get the first message index we know how to decrypt. + uint32_t firstKnownIndex() const; + //! Get a base64-encoded identifier for this session. + QByteArray sessionId() const; + bool isVerified() const; + QOlmInboundGroupSession(OlmInboundGroupSession *session); +private: + OlmInboundGroupSession *m_groupSession; +}; + +using QOlmInboundGroupSessionPtr = std::unique_ptr; +using OlmInboundGroupSessionPtr = std::unique_ptr; +} // namespace Quotient diff --git a/lib/e2ee/qolmmessage.cpp b/lib/e2ee/qolmmessage.cpp new file mode 100644 index 00000000..15008b75 --- /dev/null +++ b/lib/e2ee/qolmmessage.cpp @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "qolmmessage.h" + +using namespace Quotient; + +QOlmMessage::QOlmMessage(const QByteArray &ciphertext, QOlmMessage::Type type) + : QByteArray(std::move(ciphertext)) + , m_messageType(type) +{ + Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); +} + +QOlmMessage::QOlmMessage(const QOlmMessage &message) + : QByteArray(message) + , m_messageType(message.type()) +{ +} + +QOlmMessage::Type QOlmMessage::type() const +{ + return m_messageType; +} + +QByteArray QOlmMessage::toCiphertext() const +{ + return QByteArray(*this); +} + +QOlmMessage QOlmMessage::fromCiphertext(const QByteArray &ciphertext) +{ + return QOlmMessage(ciphertext, QOlmMessage::General); +} diff --git a/lib/e2ee/qolmmessage.h b/lib/e2ee/qolmmessage.h new file mode 100644 index 00000000..52aba78c --- /dev/null +++ b/lib/e2ee/qolmmessage.h @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include + +namespace Quotient { + +/*! \brief A wrapper around an olm encrypted message + * + * This class encapsulates a Matrix olm encrypted message, + * passed in either of 2 forms: a general message or a pre-key message. + * + * The class provides functions to get a type and the ciphertext. + */ +class QOlmMessage : public QByteArray { + Q_GADGET +public: + enum Type { + General, + PreKey, + }; + Q_ENUM(Type) + + QOlmMessage() = default; + explicit QOlmMessage(const QByteArray &ciphertext, Type type = General); + explicit QOlmMessage(const QOlmMessage &message); + + static QOlmMessage fromCiphertext(const QByteArray &ciphertext); + + Q_INVOKABLE Type type() const; + Q_INVOKABLE QByteArray toCiphertext() const; + +private: + Type m_messageType = General; +}; + +} //namespace Quotient diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp new file mode 100644 index 00000000..88e6b2e1 --- /dev/null +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "e2ee/qolmoutboundsession.h" +#include "e2ee/qolmutils.h" + +using namespace Quotient; + +QOlmError lastError(OlmOutboundGroupSession *session) { + const std::string error_raw = olm_outbound_group_session_last_error(session); + + return fromString(error_raw); +} + +QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session) + : m_groupSession(session) +{ +} + +QOlmOutboundGroupSession::~QOlmOutboundGroupSession() +{ + olm_clear_outbound_group_session(m_groupSession); + delete[](reinterpret_cast(m_groupSession)); +} + +std::unique_ptr QOlmOutboundGroupSession::create() +{ + auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); + const auto randomLength = olm_init_outbound_group_session_random_length(olmOutboundGroupSession); + QByteArray randomBuf = getRandom(randomLength); + + const auto error = olm_init_outbound_group_session(olmOutboundGroupSession, + reinterpret_cast(randomBuf.data()), randomBuf.length()); + + if (error == olm_error()) { + throw lastError(olmOutboundGroupSession); + } + + const auto keyMaxLength = olm_outbound_group_session_key_length(olmOutboundGroupSession); + QByteArray keyBuffer(keyMaxLength, '0'); + olm_outbound_group_session_key(olmOutboundGroupSession, reinterpret_cast(keyBuffer.data()), + keyMaxLength); + + randomBuf.clear(); + + return std::make_unique(olmOutboundGroupSession); +} + +std::variant QOlmOutboundGroupSession::pickle(const PicklingMode &mode) +{ + QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); + QByteArray key = toKey(mode); + const auto error = olm_pickle_outbound_group_session(m_groupSession, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()); + + if (error == olm_error()) { + return lastError(m_groupSession); + } + + key.clear(); + + return pickledBuf; +} + + +std::variant, QOlmError> QOlmOutboundGroupSession::unpickle(QByteArray &pickled, const PicklingMode &mode) +{ + QByteArray pickledBuf = pickled; + auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); + QByteArray key = toKey(mode); + const auto error = olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), key.length(), + pickled.data(), pickled.length()); + if (error == olm_error()) { + return lastError(olmOutboundGroupSession); + } + const auto idMaxLength = olm_outbound_group_session_id_length(olmOutboundGroupSession); + QByteArray idBuffer(idMaxLength, '0'); + olm_outbound_group_session_id(olmOutboundGroupSession, reinterpret_cast(idBuffer.data()), + idBuffer.length()); + + key.clear(); + return std::make_unique(olmOutboundGroupSession); +} + +std::variant QOlmOutboundGroupSession::encrypt(const QString &plaintext) +{ + QByteArray plaintextBuf = plaintext.toUtf8(); + const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); + QByteArray messageBuf(messageMaxLength, '0'); + const auto error = olm_group_encrypt(m_groupSession, reinterpret_cast(plaintextBuf.data()), + plaintextBuf.length(), reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (error == olm_error()) { + return lastError(m_groupSession); + } + + return messageBuf; +} + +uint32_t QOlmOutboundGroupSession::sessionMessageIndex() const +{ + return olm_outbound_group_session_message_index(m_groupSession); +} + +QByteArray QOlmOutboundGroupSession::sessionId() const +{ + const auto idMaxLength = olm_outbound_group_session_id_length(m_groupSession); + QByteArray idBuffer(idMaxLength, '0'); + const auto error = olm_outbound_group_session_id(m_groupSession, reinterpret_cast(idBuffer.data()), + idBuffer.length()); + if (error == olm_error()) { + throw lastError(m_groupSession); + } + return idBuffer; +} + +std::variant QOlmOutboundGroupSession::sessionKey() const +{ + const auto keyMaxLength = olm_outbound_group_session_key_length(m_groupSession); + QByteArray keyBuffer(keyMaxLength, '0'); + const auto error = olm_outbound_group_session_key(m_groupSession, reinterpret_cast(keyBuffer.data()), + keyMaxLength); + if (error == olm_error()) { + return lastError(m_groupSession); + } + return keyBuffer; +} diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h new file mode 100644 index 00000000..967f563f --- /dev/null +++ b/lib/e2ee/qolmoutboundsession.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + + +#pragma once + +#include "olm/olm.h" +#include "e2ee/qolmerrors.h" +#include "e2ee/e2ee.h" +#include + +namespace Quotient { + + +//! An out-bound group session is responsible for encrypting outgoing +//! communication in a Megolm session. +class QOlmOutboundGroupSession +{ +public: + ~QOlmOutboundGroupSession(); + //! Creates a new instance of `QOlmOutboundGroupSession`. + //! Throw OlmError on errors + static std::unique_ptr create(); + //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by + //! pickling a `QOlmOutboundGroupSession`. + static std::variant, QOlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + std::variant encrypt(const QString &plaintext); + + //! Get the current message index for this session. + //! + //! Each message is sent with an increasing index; this returns the + //! index for the next message. + uint32_t sessionMessageIndex() const; + + //! Get a base64-encoded identifier for this session. + QByteArray sessionId() const; + + //! Get the base64-encoded current ratchet key for this session. + //! + //! Each message is sent with a different ratchet key. This function returns the + //! ratchet key that will be used for the next message. + std::variant sessionKey() const; + QOlmOutboundGroupSession(OlmOutboundGroupSession *groupSession); +private: + OlmOutboundGroupSession *m_groupSession; +}; + +using QOlmOutboundGroupSessionPtr = std::unique_ptr; +using OlmOutboundGroupSessionPtr = std::unique_ptr; +} diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp new file mode 100644 index 00000000..69d8b431 --- /dev/null +++ b/lib/e2ee/qolmsession.cpp @@ -0,0 +1,253 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "qolmsession.h" +#include "e2ee/qolmutils.h" +#include "logging.h" +#include +#include + +using namespace Quotient; + +QOlmError lastError(OlmSession* session) { + const std::string error_raw = olm_session_last_error(session); + + return fromString(error_raw); +} + +Quotient::QOlmSession::~QOlmSession() +{ + olm_clear_session(m_session); + delete[](reinterpret_cast(m_session)); +} + +OlmSession* QOlmSession::create() +{ + return olm_session(new uint8_t[olm_session_size()]); +} + +std::variant QOlmSession::createInbound(QOlmAccount *account, const QOlmMessage &preKeyMessage, bool from, const QString &theirIdentityKey) +{ + if (preKeyMessage.type() != QOlmMessage::PreKey) { + qCCritical(E2EE) << "The message is not a pre-key in when creating inbound session" << BadMessageFormat; + } + + const auto olmSession = create(); + + QByteArray oneTimeKeyMessageBuf = preKeyMessage.toCiphertext(); + QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); + size_t error = 0; + if (from) { + error = olm_create_inbound_session_from(olmSession, account->data(), theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); + } else { + error = olm_create_inbound_session(olmSession, account->data(), oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); + } + + if (error == olm_error()) { + const auto lastErr = lastError(olmSession); + qCWarning(E2EE) << "Error when creating inbound session" << lastErr; + return lastErr; + } + + return std::make_unique(olmSession); +} + +std::variant QOlmSession::createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage) +{ + return createInbound(account, preKeyMessage); +} + +std::variant QOlmSession::createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) +{ + return createInbound(account, preKeyMessage, true, theirIdentityKey); +} + +std::variant QOlmSession::createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey) +{ + auto *olmOutboundSession = create(); + const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); + QByteArray randomBuf = getRandom(randomLen); + + QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); + QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8(); + const auto error = olm_create_outbound_session(olmOutboundSession, + account->data(), + reinterpret_cast(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), + reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length()); + + if (error == olm_error()) { + const auto lastErr = lastError(olmOutboundSession); + if (lastErr == QOlmError::NotEnoughRandom) { + throw lastErr; + } + return lastErr; + } + + randomBuf.clear(); + return std::make_unique(olmOutboundSession); +} + +std::variant QOlmSession::pickle(const PicklingMode &mode) +{ + QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); + QByteArray key = toKey(mode); + const auto error = olm_pickle_session(m_session, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()); + + if (error == olm_error()) { + return lastError(m_session); + } + + key.clear(); + + return pickledBuf; +} + +std::variant QOlmSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +{ + QByteArray pickledBuf = pickled; + auto *olmSession = create(); + QByteArray key = toKey(mode); + const auto error = olm_unpickle_session(olmSession, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()); + if (error == olm_error()) { + return lastError(olmSession); + } + + key.clear(); + return std::make_unique(olmSession); +} + +QOlmMessage QOlmSession::encrypt(const QString &plaintext) +{ + QByteArray plaintextBuf = plaintext.toUtf8(); + const auto messageMaxLen = olm_encrypt_message_length(m_session, plaintextBuf.length()); + QByteArray messageBuf(messageMaxLen, '0'); + const auto messageType = encryptMessageType(); + const auto randomLen = olm_encrypt_random_length(m_session); + QByteArray randomBuf = getRandom(randomLen); + const auto error = olm_encrypt(m_session, + reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length(), + reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (error == olm_error()) { + throw lastError(m_session); + } + + return QOlmMessage(messageBuf, messageType); +} + +std::variant QOlmSession::decrypt(const QOlmMessage &message) const +{ + const auto messageType = message.type(); + const auto ciphertext = message.toCiphertext(); + const auto messageTypeValue = messageType == QOlmMessage::Type::General + ? OLM_MESSAGE_TYPE_MESSAGE : OLM_MESSAGE_TYPE_PRE_KEY; + + // We need to clone the message because + // olm_decrypt_max_plaintext_length destroys the input buffer + QByteArray messageBuf(ciphertext.length(), '0'); + std::copy(message.begin(), message.end(), messageBuf.begin()); + + const auto plaintextMaxLen = olm_decrypt_max_plaintext_length(m_session, messageTypeValue, + reinterpret_cast(messageBuf.data()), messageBuf.length()); + + if (plaintextMaxLen == olm_error()) { + return lastError(m_session); + } + + QByteArray plaintextBuf(plaintextMaxLen, '0'); + QByteArray messageBuf2(ciphertext.length(), '0'); + std::copy(message.begin(), message.end(), messageBuf2.begin()); + + const auto plaintextResultLen = olm_decrypt(m_session, messageTypeValue, + reinterpret_cast(messageBuf2.data()), messageBuf2.length(), + reinterpret_cast(plaintextBuf.data()), plaintextMaxLen); + + if (plaintextResultLen == olm_error()) { + const auto lastErr = lastError(m_session); + if (lastErr == QOlmError::OutputBufferTooSmall) { + throw lastErr; + } + return lastErr; + } + QByteArray output(plaintextResultLen, '0'); + std::memcpy(output.data(), plaintextBuf.data(), plaintextResultLen); + plaintextBuf.clear(); + return output; +} + +QOlmMessage::Type QOlmSession::encryptMessageType() +{ + const auto messageTypeResult = olm_encrypt_message_type(m_session); + if (messageTypeResult == olm_error()) { + throw lastError(m_session); + } + if (messageTypeResult == OLM_MESSAGE_TYPE_PRE_KEY) { + return QOlmMessage::PreKey; + } + return QOlmMessage::General; +} + +QByteArray QOlmSession::sessionId() const +{ + const auto idMaxLength = olm_session_id_length(m_session); + QByteArray idBuffer(idMaxLength, '0'); + const auto error = olm_session_id(m_session, reinterpret_cast(idBuffer.data()), + idBuffer.length()); + if (error == olm_error()) { + throw lastError(m_session); + } + return idBuffer; +} + +bool QOlmSession::hasReceivedMessage() const +{ + return olm_session_has_received_message(m_session); +} + +std::variant QOlmSession::matchesInboundSession(const QOlmMessage &preKeyMessage) const +{ + Q_ASSERT(preKeyMessage.type() == QOlmMessage::Type::PreKey); + QByteArray oneTimeKeyBuf(preKeyMessage.data()); + const auto matchesResult = olm_matches_inbound_session(m_session, oneTimeKeyBuf.data(), oneTimeKeyBuf.length()); + + if (matchesResult == olm_error()) { + return lastError(m_session); + } + switch (matchesResult) { + case 0: + return false; + case 1: + return true; + default: + return QOlmError::Unknown; + } +} +std::variant QOlmSession::matchesInboundSessionFrom(const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) const +{ + const auto theirIdentityKeyBuf = theirIdentityKey.toUtf8(); + auto oneTimeKeyMessageBuf = preKeyMessage.toCiphertext(); + const auto error = olm_matches_inbound_session_from(m_session, theirIdentityKeyBuf.data(), theirIdentityKeyBuf.length(), + oneTimeKeyMessageBuf.data(), oneTimeKeyMessageBuf.length()); + + if (error == olm_error()) { + return lastError(m_session); + } + switch (error) { + case 0: + return false; + case 1: + return true; + default: + return QOlmError::Unknown; + } +} + +QOlmSession::QOlmSession(OlmSession *session) + : m_session(session) +{ +} diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h new file mode 100644 index 00000000..1febfa0f --- /dev/null +++ b/lib/e2ee/qolmsession.h @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2021 Alexey Andreyev +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include // FIXME: OlmSession +#include "e2ee/e2ee.h" +#include "e2ee/qolmmessage.h" +#include "e2ee/qolmerrors.h" +#include "e2ee/qolmaccount.h" + +namespace Quotient { + +class QOlmAccount; +class QOlmSession; + + +//! Either an outbound or inbound session for secure communication. +class QOlmSession +{ +public: + ~QOlmSession(); + //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. + static std::variant, QOlmError> createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage); + static std::variant, QOlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage); + static std::variant, QOlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); + //! Serialises an `QOlmSession` to encrypted Base64. + std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. + static std::variant, QOlmError> unpickle(const QByteArray &pickled, const PicklingMode &mode); + //! Encrypts a plaintext message using the session. + QOlmMessage encrypt(const QString &plaintext); + + //! Decrypts a message using this session. Decoding is lossy, meaing if + //! the decrypted plaintext contains invalid UTF-8 symbols, they will + //! be returned as `U+FFFD` (�). + std::variant decrypt(const QOlmMessage &message) const; + + //! Get a base64-encoded identifier for this session. + QByteArray sessionId() const; + + //! The type of the next message that will be returned from encryption. + QOlmMessage::Type encryptMessageType(); + + //! Checker for any received messages for this session. + bool hasReceivedMessage() const; + + //! Checks if the 'prekey' message is for this in-bound session. + std::variant matchesInboundSession(const QOlmMessage &preKeyMessage) const; + + //! Checks if the 'prekey' message is for this in-bound session. + std::variant matchesInboundSessionFrom(const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) const; + + friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) + { + return lhs.sessionId() < rhs.sessionId(); + } + + friend bool operator<(const std::unique_ptr &lhs, const std::unique_ptr &rhs) { + return *lhs < *rhs; + } + + OlmSession *raw() const + { + return m_session; + } + QOlmSession(OlmSession* session); +private: + //! Helper function for creating new sessions and handling errors. + static OlmSession* create(); + static std::variant, QOlmError> createInbound(QOlmAccount *account, const QOlmMessage& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); + OlmSession* m_session; +}; +} //namespace Quotient diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp new file mode 100644 index 00000000..d0684055 --- /dev/null +++ b/lib/e2ee/qolmutility.cpp @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "e2ee/qolmutility.h" +#include "olm/olm.h" +#include + +using namespace Quotient; + +// Convert olm error to enum +QOlmError lastError(OlmUtility *utility) { + const std::string error_raw = olm_utility_last_error(utility); + + return fromString(error_raw); +} + +QOlmUtility::QOlmUtility() +{ + auto utility = new uint8_t[olm_utility_size()]; + m_utility = olm_utility(utility); +} + +QOlmUtility::~QOlmUtility() +{ + olm_clear_utility(m_utility); + delete[](reinterpret_cast(m_utility)); +} + +QString QOlmUtility::sha256Bytes(const QByteArray &inputBuf) const +{ + const auto outputLen = olm_sha256_length(m_utility); + QByteArray outputBuf(outputLen, '0'); + olm_sha256(m_utility, inputBuf.data(), inputBuf.length(), + outputBuf.data(), outputBuf.length()); + + return QString::fromUtf8(outputBuf); +} + +QString QOlmUtility::sha256Utf8Msg(const QString &message) const +{ + return sha256Bytes(message.toUtf8()); +} + +std::variant QOlmUtility::ed25519Verify(const QByteArray &key, + const QByteArray &message, const QByteArray &signature) +{ + QByteArray signatureBuf(signature.length(), '0'); + std::copy(signature.begin(), signature.end(), signatureBuf.begin()); + + const auto ret = olm_ed25519_verify(m_utility, key.data(), key.size(), + message.data(), message.size(), (void *)signatureBuf.data(), signatureBuf.size()); + + const auto error = ret; + if (error == olm_error()) { + return lastError(m_utility); + } + + if (ret != 0) { + return false; + } + return true; +} diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h new file mode 100644 index 00000000..b360d625 --- /dev/null +++ b/lib/e2ee/qolmutility.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include "e2ee/qolmerrors.h" + +struct OlmUtility; + +namespace Quotient { + +class QOlmSession; +class Connection; + +//! Allows you to make use of crytographic hashing via SHA-2 and +//! verifying ed25519 signatures. +class QOlmUtility +{ +public: + QOlmUtility(); + ~QOlmUtility(); + + //! Returns a sha256 of the supplied byte slice. + QString sha256Bytes(const QByteArray &inputBuf) const; + + //! Convenience function that converts the UTF-8 message + //! to bytes and then calls `sha256Bytes()`, returning its output. + QString sha256Utf8Msg(const QString &message) const; + + //! Verify a ed25519 signature. + //! \param key QByteArray The public part of the ed25519 key that signed the message. + //! \param message QByteArray The message that was signed. + //! \param signature QByteArray The signature of the message. + std::variant ed25519Verify(const QByteArray &key, + const QByteArray &message, const QByteArray &signature); + + +private: + OlmUtility *m_utility; + +}; +} diff --git a/lib/e2ee/qolmutils.cpp b/lib/e2ee/qolmutils.cpp new file mode 100644 index 00000000..ce27710d --- /dev/null +++ b/lib/e2ee/qolmutils.cpp @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "e2ee/qolmutils.h" +#include +#include + +using namespace Quotient; + +QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) +{ + if (std::holds_alternative(mode)) { + return {}; + } + return std::get(mode).key; +} + +QByteArray Quotient::getRandom(size_t bufferSize) +{ + QByteArray buffer(bufferSize, '0'); + RAND_bytes(reinterpret_cast(buffer.data()), buffer.size()); + return buffer; +} diff --git a/lib/e2ee/qolmutils.h b/lib/e2ee/qolmutils.h new file mode 100644 index 00000000..bbd71332 --- /dev/null +++ b/lib/e2ee/qolmutils.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include + +#include "e2ee/e2ee.h" + +namespace Quotient { +// Convert PicklingMode to key +QByteArray toKey(const PicklingMode &mode); +QByteArray getRandom(size_t bufferSize); +} diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index e5fa978f..3d616965 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -6,7 +6,6 @@ #include "encryptionmanager.h" #include "connection.h" -#include "crypto/e2ee.h" #include "events/encryptedfile.h" #include "database.h" @@ -16,11 +15,12 @@ #include #include -#include "crypto/qolmaccount.h" -#include "crypto/qolmsession.h" -#include "crypto/qolmmessage.h" -#include "crypto/qolmerrors.h" -#include "crypto/qolmutils.h" +#include "e2ee/e2ee.h" +#include "e2ee/qolmaccount.h" +#include "e2ee/qolmsession.h" +#include "e2ee/qolmmessage.h" +#include "e2ee/qolmerrors.h" +#include "e2ee/qolmutils.h" #include #include diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 28398827..4cc3bf8e 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -3,7 +3,7 @@ #pragma once -#include "crypto/e2ee.h" +#include "e2ee/e2ee.h" #include "roomevent.h" namespace Quotient { diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index d7bb953a..6272c668 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -4,7 +4,7 @@ #include "encryptionevent.h" -#include "crypto/e2ee.h" +#include "e2ee/e2ee.h" #include diff --git a/lib/room.cpp b/lib/room.cpp index 8181f16a..755f677a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -12,7 +12,6 @@ #include "avatar.h" #include "connection.h" #include "converters.h" -#include "crypto/e2ee.h" #include "syncdata.h" #include "user.h" #include "eventstats.h" @@ -65,9 +64,10 @@ #include #ifdef Quotient_E2EE_ENABLED -#include "crypto/qolmaccount.h" -#include "crypto/qolmerrors.h" -#include "crypto/qolminboundsession.h" +#include "e2ee/e2ee.h" +#include "e2ee/qolmaccount.h" +#include "e2ee/qolmerrors.h" +#include "e2ee/qolminboundsession.h" #endif // Quotient_E2EE_ENABLED #include "database.h" -- cgit v1.2.3 From a0ce17dfe793c924205b449c026f2f776b032ff3 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 8 Dec 2021 23:07:14 +0100 Subject: Store encryptedevent in decrypted roomevents --- lib/events/roomevent.cpp | 13 +++++++++++++ lib/events/roomevent.h | 4 ++++ lib/room.cpp | 7 +++++-- 3 files changed, 22 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index fb921af6..b99d1381 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -126,3 +126,16 @@ CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) if (callId().isEmpty()) qCWarning(EVENTS) << id() << "is a call event with an empty call id"; } + +void RoomEvent::setOriginalEvent(event_ptr_tt originalEvent) +{ + _originalEvent = std::move(originalEvent); +} + +const QJsonObject RoomEvent::encryptedJson() const +{ + if(!_originalEvent) { + return {}; + } + return _originalEvent->fullJson(); +} diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 7f13f6f2..35527a62 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -60,11 +60,15 @@ public: //! callback for that in RoomEvent. void addId(const QString& newId); + void setOriginalEvent(event_ptr_tt originalEvent); + const QJsonObject encryptedJson() const; + protected: void dumpTo(QDebug dbg) const override; private: event_ptr_tt _redactedBecause; + event_ptr_tt _originalEvent; }; using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; diff --git a/lib/room.cpp b/lib/room.cpp index 8e348089..b3a092f3 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1517,6 +1517,7 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, auto decrypted = decryptMessage(*encryptedEvent); if(decrypted) { auto oldEvent = event->replaceEvent(std::move(decrypted)); + decrypted->setOriginalEvent(std::move(oldEvent)); emit replacedEvent(event->event(), rawPtr(oldEvent)); d->undecryptedEvents[roomKeyEvent.sessionId()] -= eventId; } @@ -2596,7 +2597,8 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) if(auto* encrypted = eventCast(events[i])) { auto decrypted = q->decryptMessage(*encrypted); if(decrypted) { - events[i] = std::move(decrypted); + auto oldEvent = std::exchange(events[i], std::move(decrypted)); + events[i]->setOriginalEvent(std::move(oldEvent)); } else { undecryptedEvents[encrypted->sessionId()] += encrypted->id(); } @@ -2760,7 +2762,8 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) if(auto* encrypted = eventCast(events[i])) { auto decrypted = q->decryptMessage(*encrypted); if(decrypted) { - events[i] = std::move(decrypted); + auto oldEvent = std::exchange(events[i], std::move(decrypted)); + events[i]->setOriginalEvent(std::move(oldEvent)); } else { undecryptedEvents[encrypted->sessionId()] += encrypted->id(); } -- 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 --- CMakeLists.txt | 13 ++++++++----- lib/connection.cpp | 12 ++++-------- lib/events/roomevent.cpp | 2 ++ lib/events/roomevent.h | 5 +++++ lib/room.cpp | 6 +++++- 5 files changed, 24 insertions(+), 14 deletions(-) (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index a84a70fb..43fed3e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,11 +83,12 @@ else() set(QtExtraModules "Multimedia") # See #483 endif() string(REGEX REPLACE "^(.).*" "Qt\\1" Qt ${QtMinVersion}) # makes "Qt5" or "Qt6" -find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test Sql ${QtExtraModules}) +find_package(${Qt} ${QtMinVersion} REQUIRED Core Network Gui Test ${QtExtraModules}) get_filename_component(Qt_Prefix "${${Qt}_DIR}/../../../.." ABSOLUTE) message(STATUS "Using Qt ${${Qt}_VERSION} at ${Qt_Prefix}") if (${PROJECT_NAME}_ENABLE_E2EE) + find_package(${Qt} ${QtMinVersion} REQUIRED Sql) find_package(Olm 3.2.1 REQUIRED) set_package_properties(Olm PROPERTIES DESCRIPTION "Implementation of the Olm and Megolm cryptographic ratchets" @@ -107,9 +108,9 @@ if (${PROJECT_NAME}_ENABLE_E2EE) if (OpenSSL_FOUND) message(STATUS "Using OpenSSL ${OpenSSL_VERSION} at ${OpenSSL_DIR}") endif() + find_package(${Qt}Keychain REQUIRED) endif() -find_package(${Qt}Keychain REQUIRED) # Set up source files list(APPEND lib_SRCS @@ -133,7 +134,6 @@ list(APPEND lib_SRCS lib/eventitem.cpp lib/accountregistry.cpp lib/mxcreply.cpp - lib/database.cpp lib/events/event.cpp lib/events/roomevent.cpp lib/events/stateevent.cpp @@ -164,6 +164,7 @@ list(APPEND lib_SRCS ) if (${PROJECT_NAME}_ENABLE_E2EE) list(APPEND lib_SRCS + lib/database.cpp lib/e2ee/qolmaccount.cpp lib/e2ee/qolmsession.cpp lib/e2ee/qolminboundsession.cpp @@ -323,12 +324,14 @@ target_include_directories(${PROJECT_NAME} PUBLIC if (${PROJECT_NAME}_ENABLE_E2EE) target_link_libraries(${PROJECT_NAME} Olm::Olm OpenSSL::Crypto - OpenSSL::SSL) + OpenSSL::SSL + ${Qt}::Sql + ${QTKEYCHAIN_LIBRARIES}) set(FIND_DEPS "find_dependency(Olm) find_dependency(OpenSSL)") # For QuotientConfig.cmake.in endif() -target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui ${Qt}::Sql ${QTKEYCHAIN_LIBRARIES}) +target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui) if (Qt STREQUAL Qt5) # See #483 target_link_libraries(${PROJECT_NAME} ${Qt}::Multimedia) 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] { diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index b99d1381..dbce2255 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -127,6 +127,7 @@ CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) qCWarning(EVENTS) << id() << "is a call event with an empty call id"; } +#ifdef Quotient_E2EE_ENABLED void RoomEvent::setOriginalEvent(event_ptr_tt originalEvent) { _originalEvent = std::move(originalEvent); @@ -139,3 +140,4 @@ const QJsonObject RoomEvent::encryptedJson() const } return _originalEvent->fullJson(); } +#endif diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 35527a62..36b45f09 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -60,15 +60,20 @@ public: //! callback for that in RoomEvent. void addId(const QString& newId); +#ifdef Quotient_E2EE_ENABLED void setOriginalEvent(event_ptr_tt originalEvent); const QJsonObject encryptedJson() const; +#endif protected: void dumpTo(QDebug dbg) const override; private: event_ptr_tt _redactedBecause; + +#ifdef Quotient_E2EE_ENABLED event_ptr_tt _originalEvent; +#endif }; using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; diff --git a/lib/room.cpp b/lib/room.cpp index b3a092f3..7d608520 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -68,9 +68,9 @@ #include "e2ee/qolmaccount.h" #include "e2ee/qolmerrors.h" #include "e2ee/qolminboundsession.h" +#include "database.h" #endif // Quotient_E2EE_ENABLED -#include "database.h" using namespace Quotient; using namespace std::placeholders; @@ -2593,6 +2593,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) QElapsedTimer et; et.start(); +#ifdef Quotient_E2EE_ENABLED for(long unsigned int i = 0; i < events.size(); i++) { if(auto* encrypted = eventCast(events[i])) { auto decrypted = q->decryptMessage(*encrypted); @@ -2604,6 +2605,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) } } } +#endif { // Pre-process redactions and edits so that events that get @@ -2758,6 +2760,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) Changes changes {}; +#ifdef Quotient_E2EE_ENABLED for(long unsigned int i = 0; i < events.size(); i++) { if(auto* encrypted = eventCast(events[i])) { auto decrypted = q->decryptMessage(*encrypted); @@ -2769,6 +2772,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) } } } +#endif // In case of lazy-loading new members may be loaded with historical // messages. Also, the cache doesn't store events with empty content; -- cgit v1.2.3 From b4a6070d44140a3cbc931b18530721e31f069455 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Fri, 10 Dec 2021 16:15:50 +0100 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- lib/events/roomevent.cpp | 2 +- lib/events/roomevent.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index dbce2255..eb5d0485 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -128,7 +128,7 @@ CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) } #ifdef Quotient_E2EE_ENABLED -void RoomEvent::setOriginalEvent(event_ptr_tt originalEvent) +void RoomEvent::setOriginalEvent(event_ptr_tt&& originalEvent) { _originalEvent = std::move(originalEvent); } diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 36b45f09..3d46bf9b 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -61,7 +61,8 @@ public: void addId(const QString& newId); #ifdef Quotient_E2EE_ENABLED - void setOriginalEvent(event_ptr_tt originalEvent); + void setOriginalEvent(event_ptr_tt&& originalEvent); + const RoomEvent* originalEvent() { return _originalEvent.get(); } const QJsonObject encryptedJson() const; #endif -- cgit v1.2.3 From e5256e0b1e4c43ce96d99d1b82ca5d98a1baded6 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 17 Dec 2021 08:07:07 +0100 Subject: RoomMemberEvent: fix an off-by-one error Also: extended quotest to cover member renames, not just user profile renames. --- lib/events/roommemberevent.cpp | 8 +++----- quotest/quotest.cpp | 35 +++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 13 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index b0bc7bcb..3141f6b5 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -48,11 +48,9 @@ void MemberEventContent::fillJson(QJsonObject* o) const { Q_ASSERT(o); if (membership != Membership::Invalid) - o->insert( - QStringLiteral("membership"), - MembershipStrings[qCountTrailingZeroBits( - std::underlying_type_t(membership)) - + 1]); + o->insert(QStringLiteral("membership"), + MembershipStrings[qCountTrailingZeroBits( + std::underlying_type_t(membership))]); if (displayName) o->insert(QStringLiteral("displayname"), *displayName); if (avatarUrl && avatarUrl->isValid()) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 764d5dfd..8703efb2 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -214,6 +214,7 @@ TestManager::TestManager(int& argc, char** argv) // Big countdown watchdog QTimer::singleShot(180000, this, [this] { + clog << "Time is up, stopping the session"; if (testSuite) conclude(); else @@ -537,14 +538,32 @@ TEST_IMPL(setTopic) TEST_IMPL(changeName) { - auto* const localUser = connection()->user(); - const auto& newName = connection()->generateTxnId(); // See setTopic() - clog << "Renaming the user to " << newName.toStdString() << endl; - localUser->rename(newName); - connectUntil(localUser, &User::defaultNameChanged, this, - [this, thisTest, localUser, newName] { - FINISH_TEST(localUser->name() == newName); - }); + connectSingleShot(targetRoom, &Room::allMembersLoaded, this, [this, thisTest] { + auto* const localUser = connection()->user(); + const auto& newName = connection()->generateTxnId(); // See setTopic() + clog << "Renaming the user to " << newName.toStdString() + << " in the target room" << endl; + localUser->rename(newName, targetRoom); + connectUntil(targetRoom, &Room::memberRenamed, this, + [this, thisTest, localUser, newName](const User* u) { + if (localUser != u) + return false; + if (localUser->name(targetRoom) != newName) + FAIL_TEST(); + + clog + << "Member rename successful, renaming the account" + << endl; + const auto newN = newName.mid(0, 5); + localUser->rename(newN); + connectUntil(localUser, &User::defaultNameChanged, + this, [this, thisTest, localUser, newN] { + targetRoom->localUser()->rename({}); + FINISH_TEST(localUser->name() == newN); + }); + return true; + }); + }); return false; } -- cgit v1.2.3 From b989383165b648269a231d1febadc8150676d5cf Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 21 Dec 2021 18:40:47 +0100 Subject: Don't chain RoomEvent to Event factory any more Objects derived from Event are not room events (in the spec sense) and never occur in the same arrays as room events; therefore this chaining has always been superfluous. --- lib/events/roomevent.cpp | 3 --- 1 file changed, 3 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index fb921af6..b728e0bf 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -9,9 +9,6 @@ using namespace Quotient; -[[maybe_unused]] static auto roomEventTypeInitialised = - Event::factory_t::chainFactory(); - RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, const QJsonObject& contentJson) : Event(type, matrixType, contentJson) -- cgit v1.2.3 From 231c4d723eb53f3ea5f641b743d198584840a963 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Dec 2021 13:44:39 +0100 Subject: Simplify the code around EventFactory<> The former code assumed that EventFactory<> is just a class-level shell for a bunch of functions and a static data member that only exists to allow specialisations to occur for the whole group together. On top of that, setupFactory() and registerEventType() strived to protect this group from double registration coming from static variables in an anonymous namespace produced by REGISTER_EVENT_TYPE. The whole thing is now de-static-ed: resolving the factory now relies on class-static Event/RoomEvent/StateEventBase::factory variables instead of factory_t type aliases; and REGISTER_EVENT_TYPE produces non-static inline variables instead, obviating the need of registerEventType/setupFactory kludge. --- lib/events/event.h | 166 +++++++++++++++++++++++-------------------- lib/events/eventloader.h | 18 ++--- lib/events/roomevent.h | 2 +- lib/events/roommemberevent.h | 32 ++++----- lib/events/stateevent.cpp | 15 ---- lib/events/stateevent.h | 18 ++++- 6 files changed, 125 insertions(+), 126 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 4d4bb16b..e786fb30 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -110,94 +110,90 @@ inline event_type_t typeId() inline event_type_t unknownEventTypeId() { return typeId(); } -// === EventFactory === +// === Event creation facilities === -/** Create an event of arbitrary type from its arguments */ +//! Create an event of arbitrary type from its arguments template inline event_ptr_tt makeEvent(ArgTs&&... args) { return std::make_unique(std::forward(args)...); } -template -class EventFactory { -public: - template - static auto addMethod(FnT&& method) - { - factories().emplace_back(std::forward(method)); - return 0; - } - - /** Chain two type factories - * Adds the factory class of EventT2 (EventT2::factory_t) to - * the list in factory class of EventT1 (EventT1::factory_t) so - * that when EventT1::factory_t::make() is invoked, types of - * EventT2 factory are looked through as well. This is used - * to include RoomEvent types into the more general Event factory, - * and state event types into the RoomEvent factory. - */ - template - static auto chainFactory() - { - return addMethod(&EventT::factory_t::make); - } - - static event_ptr_tt make(const QJsonObject& json, - const QString& matrixType) - { - for (const auto& f : factories()) - if (auto e = f(json, matrixType)) - return e; - return nullptr; - } - -private: - static auto& factories() +namespace _impl { + template + event_ptr_tt makeIfMatches(const QJsonObject& json, + const QString& matrixType) { - using inner_factory_tt = std::function( - const QJsonObject&, const QString&)>; - static std::vector _factories {}; - return _factories; + return QLatin1String(EventT::matrixTypeId()) == matrixType + ? makeEvent(json) + : nullptr; } -}; - -/** Add a type to its default factory - * Adds a standard factory method (via makeEvent<>) for a given - * type to EventT::factory_t factory class so that it can be - * created dynamically from loadEvent<>(). - * - * \tparam EventT the type to enable dynamic creation of - * \return the registered type id - * \sa loadEvent, Event::type - */ -template -inline auto setupFactory() -{ - qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId(); - return EventT::factory_t::addMethod([](const QJsonObject& json, - const QString& jsonMatrixType) { - return EventT::matrixTypeId() == jsonMatrixType ? makeEvent(json) - : nullptr; - }); -} -template -inline auto registerEventType() -{ - // Initialise exactly once, even if this function is called twice for - // the same type (for whatever reason - you never know the ways of - // static initialisation is done). - static const auto _ = setupFactory(); - return _; // Only to facilitate usage in static initialisation -} + //! \brief A family of event factories to create events from CS API responses + //! + //! Each of these factories, as instantiated by event base types (Event, + //! RoomEvent etc.) is capable of producing an event object derived from + //! \p BaseEventT, using the JSON payload and the event type passed to its + //! make() method. Don't use these directly to make events; use loadEvent() + //! overloads as the frontend for these. Never instantiate new factories + //! outside of base event classes. + //! \sa loadEvent, setupFactory, Event::factory, RoomEvent::factory, + //! StateEventBase::factory + template + class EventFactory + : private std::vector (*)(const QJsonObject&, + const QString&)> { + // Actual makeIfMatches specialisations will differ in the first + // template parameter but that doesn't affect the function type + public: + explicit EventFactory(const char* name = "") + : name(name) + { + static auto yetToBeConstructed = true; + Q_ASSERT(yetToBeConstructed); + if (!yetToBeConstructed) // For Release builds that pass Q_ASSERT + qCritical(EVENTS) + << "Another EventFactory for the same base type is being " + "created - event creation logic will be splintered"; + yetToBeConstructed = false; + } + EventFactory(const EventFactory&) = delete; + + //! \brief Add a method to create events of a given type + //! + //! Adds a standard factory method (makeIfMatches) for \p EventT so that + //! event objects of this type can be created dynamically by loadEvent. + //! The caller is responsible for ensuring this method is called only + //! once per type. + //! \sa makeIfMatches, loadEvent, Quotient::loadEvent + template + bool addMethod() + { + this->emplace_back(&makeIfMatches); + qDebug(EVENTS) << "Added factory method for" + << EventT::matrixTypeId() << "events;" << this->size() + << "methods in the" << name << "chain by now"; + return true; + } + + auto loadEvent(const QJsonObject& json, const QString& matrixType) + { + for (const auto& f : *this) + if (auto e = f(json, matrixType)) + return e; + return makeEvent(unknownEventTypeId(), json); + } + + const char* const name; + }; +} // namespace _impl // === Event === class Event { public: using Type = event_type_t; - using factory_t = EventFactory; + static inline _impl::EventFactory factory { "Event" }; explicit Event(Type type, const QJsonObject& json); explicit Event(Type type, event_mtype_t matrixType, @@ -272,7 +268,7 @@ template using EventsArray = std::vector>; using Events = EventsArray; -// === Macros used with event class definitions === +// === Facilities for event class definitions === // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). @@ -284,13 +280,27 @@ using Events = EventsArray; // This macro should be put after an event class definition (in .h or .cpp) // to enable its deserialisation from a /sync and other // polymorphic event arrays -#define REGISTER_EVENT_TYPE(_Type) \ - namespace { \ - [[maybe_unused]] static const auto _factoryAdded##_Type = \ - registerEventType<_Type>(); \ - } \ +#define REGISTER_EVENT_TYPE(_Type) \ + [[maybe_unused]] inline const auto _factoryAdded##_Type = \ + _Type::factory.addMethod<_Type>(); \ // End of macro +// === Event loading === +// (see also event_loader.h) + +//! \brief Point of customisation to dynamically load events +//! +//! The default specialisation of this calls BaseEventT::factory and if that +//! fails (i.e. returns nullptr) creates an unknown event of BaseEventT. +//! Other specialisations may reuse other factories, add validations common to +//! BaseEventT, and so on +template +event_ptr_tt doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + return BaseEventT::factory.loadEvent(json, matrixType); +} + // === is<>(), eventCast<>() and switchOnType<>() === template diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index 978668f2..fe624d70 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -6,16 +6,6 @@ #include "stateevent.h" namespace Quotient { -namespace _impl { - template - static inline auto loadEvent(const QJsonObject& json, - const QString& matrixType) - { - if (auto e = EventFactory::make(json, matrixType)) - return e; - return makeEvent(unknownEventTypeId(), json); - } -} // namespace _impl /*! Create an event with proper type from a JSON object * @@ -26,7 +16,7 @@ namespace _impl { template inline event_ptr_tt loadEvent(const QJsonObject& fullJson) { - return _impl::loadEvent(fullJson, fullJson[TypeKeyL].toString()); + return doLoadEvent(fullJson, fullJson[TypeKeyL].toString()); } /*! Create an event from a type string and content JSON @@ -39,8 +29,8 @@ template inline event_ptr_tt loadEvent(const QString& matrixType, const QJsonObject& content) { - return _impl::loadEvent(basicEventJson(matrixType, content), - matrixType); + return doLoadEvent(basicEventJson(matrixType, content), + matrixType); } /*! Create a state event from a type string, content JSON and state key @@ -53,7 +43,7 @@ inline StateEventPtr loadStateEvent(const QString& matrixType, const QJsonObject& content, const QString& stateKey = {}) { - return _impl::loadEvent( + return doLoadEvent( basicStateEventJson(matrixType, content, stateKey), matrixType); } diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 7f13f6f2..8be58481 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -13,7 +13,7 @@ class RedactionEvent; /** This class corresponds to m.room.* events */ class RoomEvent : public Event { public: - using factory_t = EventFactory; + static inline _impl::EventFactory factory { "RoomEvent" }; // RedactionEvent is an incomplete type here so we cannot inline // constructors and destructors and we cannot use 'using'. diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index f3047159..0fb464d4 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -49,16 +49,15 @@ public: std::forward(contentArgs)...) {} - /// A special constructor to create unknown RoomMemberEvents - /** - * This is needed in order to use RoomMemberEvent as a "base event - * class" in cases like GetMembersByRoomJob when RoomMemberEvents - * (rather than RoomEvents or StateEvents) are resolved from JSON. - * For such cases loadEvent<> requires an underlying class to be - * constructible with unknownTypeId() instead of its genuine id. - * Don't use it directly. - * \sa GetMembersByRoomJob, loadEvent, unknownTypeId - */ + //! \brief A special constructor to create unknown RoomMemberEvents + //! + //! This is needed in order to use RoomMemberEvent as a "base event class" + //! in cases like GetMembersByRoomJob when RoomMemberEvents (rather than + //! RoomEvents or StateEvents) are resolved from JSON. For such cases + //! loadEvent\<> requires an underlying class to have a specialisation of + //! EventFactory\<> and be constructible with unknownTypeId() instead of + //! its genuine id. Don't use directly. + //! \sa EventFactory, loadEvent, GetMembersByRoomJob RoomMemberEvent(Type type, const QJsonObject& fullJson) : StateEvent(type, fullJson) {} @@ -89,14 +88,13 @@ public: }; template <> -class EventFactory { -public: - static event_ptr_tt make(const QJsonObject& json, - const QString&) - { +inline event_ptr_tt +doLoadEvent(const QJsonObject& json, const QString& matrixType) +{ + if (matrixType == QLatin1String(RoomMemberEvent::matrixTypeId())) return makeEvent(json); - } -}; + return makeEvent(unknownEventTypeId(), json); +} REGISTER_EVENT_TYPE(RoomMemberEvent) } // namespace Quotient diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index efe011a0..9535af56 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -5,21 +5,6 @@ using namespace Quotient; -// Aside from the normal factory to instantiate StateEventBase inheritors -// StateEventBase itself can be instantiated if there's a state_key JSON key -// but the event type is unknown. -[[maybe_unused]] static auto stateEventTypeInitialised = - RoomEvent::factory_t::addMethod( - [](const QJsonObject& json, const QString& matrixType) -> StateEventPtr { - if (!json.contains(StateKeyKeyL)) - return nullptr; - - if (auto e = StateEventBase::factory_t::make(json, matrixType)) - return e; - - return makeEvent(unknownEventTypeId(), json); - }); - StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, const QString& stateKey, const QJsonObject& contentJson) diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index b0aa9907..919e8f86 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -19,7 +19,7 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, class StateEventBase : public RoomEvent { public: - using factory_t = EventFactory; + static inline _impl::EventFactory factory { "StateEvent" }; StateEventBase(Type type, const QJsonObject& json) : RoomEvent(type, json) {} @@ -37,6 +37,22 @@ public: using StateEventPtr = event_ptr_tt; using StateEvents = EventsArray; +//! \brief Override RoomEvent factory with that from StateEventBase if JSON has +//! stateKey +//! +//! This means in particular that an event with a type known to RoomEvent but +//! having stateKey set (even to an empty value) will be treated as a state +//! event and most likely end up as unknown (consider, e.g., m.room.message +//! that has stateKey set). +template <> +inline RoomEventPtr doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + if (json.contains(StateKeyKeyL)) + return StateEventBase::factory.loadEvent(json, matrixType); + return RoomEvent::factory.loadEvent(json, matrixType); +} + template <> inline bool is(const Event& e) { -- cgit v1.2.3 From 060af9334049c58767b6457da2d7c07fdb0d171e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Dec 2021 16:13:02 +0100 Subject: StateEventBase: force type to unknown if stateKey is not in JSON --- lib/events/stateevent.cpp | 8 ++++++++ lib/events/stateevent.h | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 9535af56..e53d47d4 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -5,6 +5,14 @@ using namespace Quotient; +StateEventBase::StateEventBase(Type type, const QJsonObject& json) + : RoomEvent(json.contains(StateKeyKeyL) ? type : unknownEventTypeId(), json) +{ + if (Event::type() == unknownEventTypeId() && !json.contains(StateKeyKeyL)) + qWarning(EVENTS) << "Attempt to create a state event with no stateKey -" + "forcing the event type to unknown to avoid damage"; +} + StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, const QString& stateKey, const QJsonObject& contentJson) diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 919e8f86..c37965aa 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -21,8 +21,7 @@ class StateEventBase : public RoomEvent { public: static inline _impl::EventFactory factory { "StateEvent" }; - StateEventBase(Type type, const QJsonObject& json) : RoomEvent(type, json) - {} + StateEventBase(Type type, const QJsonObject& json); StateEventBase(Type type, event_mtype_t matrixType, const QString& stateKey = {}, const QJsonObject& contentJson = {}); -- cgit v1.2.3 From ae0180acc50de443e98bc1c59dee94f0096df2e0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 15:40:13 +0100 Subject: Prune empty/ish call*event.cpp files --- CMakeLists.txt | 6 +++--- lib/events/callcandidatesevent.cpp | 27 --------------------------- lib/events/callhangupevent.cpp | 36 ------------------------------------ lib/events/callhangupevent.h | 8 ++++++-- 4 files changed, 9 insertions(+), 68 deletions(-) delete mode 100644 lib/events/callcandidatesevent.cpp delete mode 100644 lib/events/callhangupevent.cpp (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 7675daad..adb5be7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,10 +162,10 @@ list(APPEND lib_SRCS lib/events/accountdataevents.h lib/events/receiptevent.h lib/events/receiptevent.cpp lib/events/reactionevent.h lib/events/reactionevent.cpp - lib/events/callanswerevent.h lib/events/callanswerevent.cpp - lib/events/callcandidatesevent.h lib/events/callcandidatesevent.cpp - lib/events/callhangupevent.h lib/events/callhangupevent.cpp lib/events/callinviteevent.h lib/events/callinviteevent.cpp + lib/events/callcandidatesevent.h + lib/events/callanswerevent.h lib/events/callanswerevent.cpp + lib/events/callhangupevent.h lib/events/directchatevent.h lib/events/directchatevent.cpp lib/events/encryptionevent.h lib/events/encryptionevent.cpp lib/events/encryptedevent.h lib/events/encryptedevent.cpp diff --git a/lib/events/callcandidatesevent.cpp b/lib/events/callcandidatesevent.cpp deleted file mode 100644 index b87c8e9b..00000000 --- a/lib/events/callcandidatesevent.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Marius Gripsgard -// SPDX-FileCopyrightText: 2018 Josip Delic -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "callcandidatesevent.h" - -/* -m.call.candidates -{ - "age": 242352, - "content": { - "call_id": "12345", - "candidates": [ - { - "candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156 -43670 typ host generation 0", "sdpMLineIndex": 0, "sdpMid": "audio" - } - ], - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.candidates" -} -*/ diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp deleted file mode 100644 index 43bc4db0..00000000 --- a/lib/events/callhangupevent.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Marius Gripsgard - * SPDX-FileCopyrightText: 2018 Josip Delic - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -#include "callhangupevent.h" - -/* -m.call.hangup -{ - "age": 242352, - "content": { - "call_id": "12345", - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.hangup" -} -*/ - -using namespace Quotient; - -CallHangupEvent::CallHangupEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) -{ - qCDebug(EVENTS) << "Call Hangup event"; -} - -CallHangupEvent::CallHangupEvent(const QString& callId) - : CallEventBase(typeId(), matrixTypeId(), callId, 0) -{} diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index 24382ac2..f3f82833 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -11,8 +11,12 @@ class CallHangupEvent : public CallEventBase { public: DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent) - explicit CallHangupEvent(const QJsonObject& obj); - explicit CallHangupEvent(const QString& callId); + explicit CallHangupEvent(const QJsonObject& obj) + : CallEventBase(typeId(), obj) + {} + explicit CallHangupEvent(const QString& callId) + : CallEventBase(typeId(), matrixTypeId(), callId, 0) + {} }; REGISTER_EVENT_TYPE(CallHangupEvent) -- cgit v1.2.3 From 674e984e459375974f619d0e778d43a2cc928dc3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 26 Dec 2021 09:33:14 +0100 Subject: Key* strings: drop 'static'; add 'constexpr' where ok --- lib/events/event.h | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index e786fb30..8f62872d 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -29,24 +29,24 @@ inline TargetEventT* weakPtrCast(const event_ptr_tt& ptr) // === Standard Matrix key names and basicEventJson() === -static const auto TypeKey = QStringLiteral("type"); -static const auto BodyKey = QStringLiteral("body"); -static const auto ContentKey = QStringLiteral("content"); -static const auto EventIdKey = QStringLiteral("event_id"); -static const auto SenderKey = QStringLiteral("sender"); -static const auto RoomIdKey = QStringLiteral("room_id"); -static const auto UnsignedKey = QStringLiteral("unsigned"); -static const auto StateKeyKey = QStringLiteral("state_key"); -static const auto TypeKeyL = "type"_ls; -static const auto BodyKeyL = "body"_ls; -static const auto ContentKeyL = "content"_ls; -static const auto EventIdKeyL = "event_id"_ls; -static const auto SenderKeyL = "sender"_ls; -static const auto RoomIdKeyL = "room_id"_ls; -static const auto UnsignedKeyL = "unsigned"_ls; -static const auto RedactedCauseKeyL = "redacted_because"_ls; -static const auto PrevContentKeyL = "prev_content"_ls; -static const auto StateKeyKeyL = "state_key"_ls; +constexpr auto TypeKeyL = "type"_ls; +constexpr auto BodyKeyL = "body"_ls; +constexpr auto ContentKeyL = "content"_ls; +constexpr auto EventIdKeyL = "event_id"_ls; +constexpr auto SenderKeyL = "sender"_ls; +constexpr auto RoomIdKeyL = "room_id"_ls; +constexpr auto UnsignedKeyL = "unsigned"_ls; +constexpr auto RedactedCauseKeyL = "redacted_because"_ls; +constexpr auto PrevContentKeyL = "prev_content"_ls; +constexpr auto StateKeyKeyL = "state_key"_ls; +const QString TypeKey { TypeKeyL }; +const QString BodyKey { BodyKeyL }; +const QString ContentKey { ContentKeyL }; +const QString EventIdKey { EventIdKeyL }; +const QString SenderKey { SenderKeyL }; +const QString RoomIdKey { RoomIdKeyL }; +const QString UnsignedKey { UnsignedKeyL }; +const QString StateKeyKey { StateKeyKeyL }; /// Make a minimal correct Matrix event JSON inline QJsonObject basicEventJson(const QString& matrixType, -- cgit v1.2.3 From 5f4d9ef6df492bfd91fa7c48fb4d5f8c2afc40b2 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 27 Dec 2021 20:14:07 +0100 Subject: EventFactory: remove default constructor This is a leftover from deferred `name` initialisation that wasn't needed in the end. --- lib/events/event.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 8f62872d..8347bb4f 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -146,7 +146,7 @@ namespace _impl { // Actual makeIfMatches specialisations will differ in the first // template parameter but that doesn't affect the function type public: - explicit EventFactory(const char* name = "") + explicit EventFactory(const char* name) : name(name) { static auto yetToBeConstructed = true; -- cgit v1.2.3 From 418b85c285fa0a0c196a26eef5cc0c9c3dbe20fe Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 28 Dec 2021 09:07:37 +0100 Subject: EventContent::FileInfo: default payloadSize to 0 Fixes a clang-tidy warning. --- lib/events/eventcontent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index f609a603..f6dbd4bf 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -105,7 +105,7 @@ namespace EventContent { QJsonObject originalInfoJson; QMimeType mimeType; QUrl url; - qint64 payloadSize; + qint64 payloadSize = 0; QString originalName; Omittable file = none; }; -- cgit v1.2.3 From 7350fe82953cf6274b8845a890eafb21a09b9931 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 29 Dec 2021 15:59:58 +0100 Subject: Add QUOTIENT_API throughout non-generated code This include all (hopefully) classes/structures and functions that have non-inline definitions, as well as namespaces with Q_NAMESPACE since those have non-inline (as of Qt 5.15) QMetaObject - for that a new macro, QUO_NAMESPACE, has been devised to accommodate the lack of Q_NAMESPACE_EXPORT in Qt before 5.14. --- lib/accountregistry.h | 4 +++- lib/avatar.h | 4 +++- lib/connection.h | 2 +- lib/converters.h | 6 +++--- lib/eventitem.cpp | 4 ++++ lib/eventitem.h | 9 +++++---- lib/events/accountdataevents.h | 2 +- lib/events/callanswerevent.h | 3 +-- lib/events/callhangupevent.h | 2 +- lib/events/callinviteevent.h | 2 +- lib/events/directchatevent.h | 2 +- lib/events/encryptedevent.h | 2 +- lib/events/encryptionevent.h | 5 ++--- lib/events/event.h | 23 +++++++++++++---------- lib/events/eventcontent.h | 19 ++++++++++--------- lib/events/reactionevent.h | 6 +++--- lib/events/receiptevent.h | 2 +- lib/events/roomavatarevent.h | 3 ++- lib/events/roomcreateevent.h | 2 +- lib/events/roomevent.h | 4 ++-- lib/events/roomkeyevent.h | 2 +- lib/events/roommemberevent.h | 5 ++--- lib/events/roommessageevent.h | 8 ++++---- lib/events/roompowerlevelsevent.h | 8 +++----- lib/events/roomtombstoneevent.h | 2 +- lib/events/simplestateevents.h | 3 ++- lib/events/stateevent.h | 2 +- lib/events/stickerevent.h | 2 +- lib/events/typingevent.h | 2 +- lib/eventstats.h | 4 ++-- lib/jobs/basejob.h | 4 ++-- lib/jobs/downloadfilejob.h | 2 +- lib/jobs/mediathumbnailjob.h | 2 +- lib/jobs/requestdata.h | 4 +++- lib/mxcreply.h | 4 +++- lib/networkaccessmanager.h | 4 +++- lib/networksettings.h | 2 +- lib/quotient_common.h | 17 ++++++++++++++++- lib/room.h | 8 ++++---- lib/settings.h | 8 +++++--- lib/ssosession.h | 4 +++- lib/syncdata.h | 2 +- lib/uri.h | 2 +- lib/uriresolver.h | 6 +++--- lib/user.h | 3 ++- lib/util.h | 22 ++++++++++++---------- 46 files changed, 138 insertions(+), 100 deletions(-) (limited to 'lib/events') diff --git a/lib/accountregistry.h b/lib/accountregistry.h index 5efda459..f7a864df 100644 --- a/lib/accountregistry.h +++ b/lib/accountregistry.h @@ -4,6 +4,8 @@ #pragma once +#include "quotient_export.h" + #include #include #include @@ -11,7 +13,7 @@ namespace Quotient { class Connection; -class AccountRegistry : public QAbstractListModel { +class QUOTIENT_API AccountRegistry : public QAbstractListModel { Q_OBJECT public: enum EventRoles { diff --git a/lib/avatar.h b/lib/avatar.h index d4634aea..93f43948 100644 --- a/lib/avatar.h +++ b/lib/avatar.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -12,7 +14,7 @@ namespace Quotient { class Connection; -class Avatar { +class QUOTIENT_API Avatar { public: explicit Avatar(); explicit Avatar(QUrl url); diff --git a/lib/connection.h b/lib/connection.h index 0713af16..d669462e 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -105,7 +105,7 @@ using DirectChatsMap = QMultiHash; using DirectChatUsersMap = QMultiHash; using IgnoredUsersList = IgnoredUsersEvent::content_type; -class Connection : public QObject { +class QUOTIENT_API Connection : public QObject { Q_OBJECT Q_PROPERTY(User* localUser READ user NOTIFY stateChanged) diff --git a/lib/converters.h b/lib/converters.h index 9c3d5749..a6028f1b 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -183,7 +183,7 @@ struct JsonConverter { }; template <> -struct JsonConverter { +struct QUOTIENT_API JsonConverter { static QJsonValue dump(const QVariant& v); static QVariant load(const QJsonValue& jv); }; @@ -281,9 +281,9 @@ template struct JsonObjectConverter> : public HashMapFromJson> {}; -QJsonObject toJson(const QVariantHash& vh); +QJsonObject QUOTIENT_API toJson(const QVariantHash& vh); template <> -QVariantHash fromJson(const QJsonValue& jv); +QVariantHash QUOTIENT_API fromJson(const QJsonValue& jv); // Conditional insertion into a QJsonObject diff --git a/lib/eventitem.cpp b/lib/eventitem.cpp index 4f1595bc..a2d65d8d 100644 --- a/lib/eventitem.cpp +++ b/lib/eventitem.cpp @@ -25,3 +25,7 @@ void PendingEventItem::setFileUploaded(const QUrl& remoteUrl) } setStatus(EventStatus::FileUploaded); } + +// Not exactly sure why but this helps with the linker not finding +// Quotient::EventStatus::staticMetaObject when building Quaternion +#include "moc_eventitem.cpp" diff --git a/lib/eventitem.h b/lib/eventitem.h index 0ab1a01d..2f1e72cc 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -4,6 +4,7 @@ #pragma once #include "events/stateevent.h" +#include "quotient_common.h" #include #include @@ -11,7 +12,7 @@ namespace Quotient { namespace EventStatus { - Q_NAMESPACE + QUO_NAMESPACE /** Special marks an event can assume * @@ -33,7 +34,7 @@ namespace EventStatus { Q_ENUM_NS(Code) } // namespace EventStatus -class EventItemBase { +class QUOTIENT_API EventItemBase { public: explicit EventItemBase(RoomEventPtr&& e) : evt(std::move(e)) { @@ -74,7 +75,7 @@ private: std::any data; }; -class TimelineItem : public EventItemBase { +class QUOTIENT_API TimelineItem : public EventItemBase { public: // For compatibility with Qt containers, even though we use // a std:: container now for the room timeline @@ -103,7 +104,7 @@ inline const CallEventBase* EventItemBase::viewAs() const return evt->isCallEvent() ? weakPtrCast(evt) : nullptr; } -class PendingEventItem : public EventItemBase { +class QUOTIENT_API PendingEventItem : public EventItemBase { public: using EventItemBase::EventItemBase; diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 9cf77be3..c0f2202d 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -50,7 +50,7 @@ struct JsonObjectConverter { using TagsMap = QHash; #define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _ContentType, _ContentKey) \ - class _Name : public Event { \ + class QUOTIENT_API _Name : public Event { \ public: \ using content_type = _ContentType; \ DEFINE_EVENT_TYPEID(_TypeId, _Name) \ diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index 4c01c941..8ffe60f2 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -7,7 +7,7 @@ #include "roomevent.h" namespace Quotient { -class CallAnswerEvent : public CallEventBase { +class QUOTIENT_API CallAnswerEvent : public CallEventBase { public: DEFINE_EVENT_TYPEID("m.call.answer", CallAnswerEvent) @@ -26,6 +26,5 @@ public: return contentPart("answer"_ls).value("sdp"_ls).toString(); } }; - REGISTER_EVENT_TYPE(CallAnswerEvent) } // namespace Quotient diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index f3f82833..b0017c59 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -7,7 +7,7 @@ #include "roomevent.h" namespace Quotient { -class CallHangupEvent : public CallEventBase { +class QUOTIENT_API CallHangupEvent : public CallEventBase { public: DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent) diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index 80b7d651..47362b5c 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -7,7 +7,7 @@ #include "roomevent.h" namespace Quotient { -class CallInviteEvent : public CallEventBase { +class QUOTIENT_API CallInviteEvent : public CallEventBase { public: DEFINE_EVENT_TYPEID("m.call.invite", CallInviteEvent) diff --git a/lib/events/directchatevent.h b/lib/events/directchatevent.h index e2143779..2018d3d6 100644 --- a/lib/events/directchatevent.h +++ b/lib/events/directchatevent.h @@ -6,7 +6,7 @@ #include "event.h" namespace Quotient { -class DirectChatEvent : public Event { +class QUOTIENT_API DirectChatEvent : public Event { public: DEFINE_EVENT_TYPEID("m.direct", DirectChatEvent) diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index de89a7c6..81343a29 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -25,7 +25,7 @@ namespace Quotient { * in general. It's possible, because RoomEvent interface is similar to Event's * one and doesn't add new restrictions, just provides additional features. */ -class EncryptedEvent : public RoomEvent { +class QUOTIENT_API EncryptedEvent : public RoomEvent { public: DEFINE_EVENT_TYPEID("m.room.encrypted", EncryptedEvent) diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 14439fcc..dfb28b2f 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -8,7 +8,7 @@ #include "stateevent.h" namespace Quotient { -class EncryptionEventContent : public EventContent::Base { +class QUOTIENT_API EncryptionEventContent : public EventContent::Base { public: enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined }; @@ -26,7 +26,7 @@ protected: using EncryptionType = EncryptionEventContent::EncryptionType; -class EncryptionEvent : public StateEvent { +class QUOTIENT_API EncryptionEvent : public StateEvent { Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent) @@ -51,6 +51,5 @@ public: int rotationPeriodMs() const { return content().rotationPeriodMs; } int rotationPeriodMsgs() const { return content().rotationPeriodMsgs; } }; - REGISTER_EVENT_TYPE(EncryptionEvent) } // namespace Quotient diff --git a/lib/events/event.h b/lib/events/event.h index 8f62872d..8a0076d0 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -60,14 +60,14 @@ inline QJsonObject basicEventJson(const QString& matrixType, using event_type_t = size_t; using event_mtype_t = const char*; -class EventTypeRegistry { +class QUOTIENT_API EventTypeRegistry { public: ~EventTypeRegistry() = default; static event_type_t initializeTypeId(event_mtype_t matrixTypeId); template - static inline event_type_t initializeTypeId() + static event_type_t initializeTypeId() { return initializeTypeId(EventT::matrixTypeId()); } @@ -190,7 +190,7 @@ namespace _impl { // === Event === -class Event { +class QUOTIENT_API Event { public: using Type = event_type_t; static inline _impl::EventFactory factory { "Event" }; @@ -243,7 +243,7 @@ public: return fromJson(unsignedJson()[std::forward(key)]); } - friend QDebug operator<<(QDebug dbg, const Event& e) + friend QUOTIENT_API QDebug operator<<(QDebug dbg, const Event& e) { QDebugStateSaver _dss { dbg }; dbg.noquote().nospace() << e.matrixType() << '(' << e.type() << "): "; @@ -272,17 +272,20 @@ using Events = EventsArray; // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). -#define DEFINE_EVENT_TYPEID(_Id, _Type) \ - static constexpr event_mtype_t matrixTypeId() { return _Id; } \ - static auto typeId() { return Quotient::typeId<_Type>(); } \ +#define DEFINE_EVENT_TYPEID(_Id, _Type) \ + static QUOTIENT_EXPORT constexpr event_mtype_t matrixTypeId() \ + { \ + return _Id; \ + } \ + static QUOTIENT_EXPORT auto typeId() { return Quotient::typeId<_Type>(); } \ // End of macro // This macro should be put after an event class definition (in .h or .cpp) // to enable its deserialisation from a /sync and other // polymorphic event arrays -#define REGISTER_EVENT_TYPE(_Type) \ - [[maybe_unused]] inline const auto _factoryAdded##_Type = \ - _Type::factory.addMethod<_Type>(); \ +#define REGISTER_EVENT_TYPE(_Type) \ + [[maybe_unused]] QUOTIENT_API inline const auto _factoryAdded##_Type = \ + _Type::factory.addMethod<_Type>(); \ // End of macro // === Event loading === diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index f609a603..bfa7d926 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -6,14 +6,15 @@ // This file contains generic event content definitions, applicable to room // message events as well as other events (e.g., avatars). +#include "encryptedfile.h" +#include "quotient_export.h" + #include #include #include #include #include -#include "encryptedfile.h" - class QFileInfo; namespace Quotient { @@ -28,7 +29,7 @@ namespace EventContent { * assumed but not required that a content object can also be created * from plain data. */ - class Base { + class QUOTIENT_API Base { public: explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {} virtual ~Base() = default; @@ -76,7 +77,7 @@ namespace EventContent { * * This class is not polymorphic. */ - class FileInfo { + class QUOTIENT_API FileInfo { public: FileInfo() = default; explicit FileInfo(const QFileInfo& fi); @@ -121,7 +122,7 @@ namespace EventContent { /** * A content info class for image content types: image, thumbnail, video */ - class ImageInfo : public FileInfo { + class QUOTIENT_API ImageInfo : public FileInfo { public: ImageInfo() = default; explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); @@ -146,7 +147,7 @@ namespace EventContent { * the JSON representation of event content; namely, * "info/thumbnail_url" and "info/thumbnail_info" fields are used. */ - class Thumbnail : public ImageInfo { + class QUOTIENT_API Thumbnail : public ImageInfo { public: Thumbnail() = default; // Allow empty thumbnails Thumbnail(const QJsonObject& infoJson, const Omittable &file = none); @@ -160,7 +161,7 @@ namespace EventContent { void fillInfoJson(QJsonObject* infoJson) const; }; - class TypedBase : public Base { + class QUOTIENT_API TypedBase : public Base { public: virtual QMimeType type() const = 0; virtual const FileInfo* fileInfo() const { return nullptr; } @@ -183,7 +184,7 @@ namespace EventContent { * \tparam InfoT base info class */ template - class UrlBasedContent : public TypedBase, public InfoT { + class QUOTIENT_API UrlBasedContent : public TypedBase, public InfoT { public: using InfoT::InfoT; explicit UrlBasedContent(const QJsonObject& json) @@ -215,7 +216,7 @@ namespace EventContent { }; template - class UrlWithThumbnailContent : public UrlBasedContent { + class QUOTIENT_API UrlWithThumbnailContent : public UrlBasedContent { public: // NB: when using inherited constructors, thumbnail has to be // initialised separately diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index 5a2b98c4..ce11eaed 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -7,7 +7,7 @@ namespace Quotient { -struct EventRelation { +struct QUOTIENT_API EventRelation { using reltypeid_t = const char*; static constexpr reltypeid_t Reply() { return "m.in_reply_to"; } static constexpr reltypeid_t Annotation() { return "m.annotation"; } @@ -31,12 +31,12 @@ struct EventRelation { } }; template <> -struct JsonObjectConverter { +struct QUOTIENT_API JsonObjectConverter { static void dumpTo(QJsonObject& jo, const EventRelation& pod); static void fillFrom(const QJsonObject& jo, EventRelation& pod); }; -class ReactionEvent : public RoomEvent { +class QUOTIENT_API ReactionEvent : public RoomEvent { public: DEFINE_EVENT_TYPEID("m.reaction", ReactionEvent) diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index 9683deef..5e077e47 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -19,7 +19,7 @@ struct ReceiptsForEvent { }; using EventsWithReceipts = QVector; -class ReceiptEvent : public Event { +class QUOTIENT_API ReceiptEvent : public Event { public: DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent) explicit ReceiptEvent(const EventsWithReceipts& ewrs); diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index 8618ba31..c54b5801 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -7,7 +7,8 @@ #include "stateevent.h" namespace Quotient { -class RoomAvatarEvent : public StateEvent { +class QUOTIENT_API RoomAvatarEvent + : public StateEvent { // It's a bit of an overkill to use a full-fledged ImageContent // because in reality m.room.avatar usually only has a single URL, // without a thumbnail. But The Spec says there be thumbnails, and diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index b3ad287c..016855b9 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -7,7 +7,7 @@ #include "quotient_common.h" namespace Quotient { -class RoomCreateEvent : public StateEventBase { +class QUOTIENT_API RoomCreateEvent : public StateEventBase { public: DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent) diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 8be58481..3fbb247e 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -11,7 +11,7 @@ namespace Quotient { class RedactionEvent; /** This class corresponds to m.room.* events */ -class RoomEvent : public Event { +class QUOTIENT_API RoomEvent : public Event { public: static inline _impl::EventFactory factory { "RoomEvent" }; @@ -70,7 +70,7 @@ using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; using RoomEventsRange = Range; -class CallEventBase : public RoomEvent { +class QUOTIENT_API CallEventBase : public RoomEvent { public: CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, int version, const QJsonObject& contentJson = {}); diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index d021fbec..c4df7936 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -6,7 +6,7 @@ #include "event.h" namespace Quotient { -class RoomKeyEvent : public Event +class QUOTIENT_API RoomKeyEvent : public Event { public: DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent) diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index 0fb464d4..5e446dbe 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -10,7 +10,7 @@ #include "quotient_common.h" namespace Quotient { -class MemberEventContent : public EventContent::Base { +class QUOTIENT_API MemberEventContent : public EventContent::Base { public: using MembershipType [[deprecated("Use Quotient::Membership instead")]] = Membership; @@ -33,7 +33,7 @@ protected: using MembershipType [[deprecated("Use Membership instead")]] = Membership; -class RoomMemberEvent : public StateEvent { +class QUOTIENT_API RoomMemberEvent : public StateEvent { Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent) @@ -95,6 +95,5 @@ doLoadEvent(const QJsonObject& json, const QString& matrixType) return makeEvent(json); return makeEvent(unknownEventTypeId(), json); } - REGISTER_EVENT_TYPE(RoomMemberEvent) } // namespace Quotient diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 56597ddc..0c901b7a 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -16,7 +16,7 @@ namespace MessageEventContent = EventContent; // Back-compatibility /** * The event class corresponding to m.room.message events */ -class RoomMessageEvent : public RoomEvent { +class QUOTIENT_API RoomMessageEvent : public RoomEvent { Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.message", RoomMessageEvent) @@ -120,7 +120,7 @@ namespace EventContent { * Available fields: mimeType, body. The body can be either rich text * or plain text, depending on what mimeType specifies. */ - class TextContent : public TypedBase { + class QUOTIENT_API TextContent : public TypedBase { public: TextContent(QString text, const QString& contentType, Omittable relatesTo = none); @@ -149,7 +149,7 @@ namespace EventContent { * - thumbnail.mimeType * - thumbnail.imageSize */ - class LocationContent : public TypedBase { + class QUOTIENT_API LocationContent : public TypedBase { public: LocationContent(const QString& geoUri, const Thumbnail& thumbnail = {}); explicit LocationContent(const QJsonObject& json); @@ -168,7 +168,7 @@ namespace EventContent { * A base class for info types that include duration: audio and video */ template - class PlayableContent : public ContentT { + class QUOTIENT_API PlayableContent : public ContentT { public: using ContentT::ContentT; PlayableContent(const QJsonObject& json) diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h index 0346fc0d..80e27048 100644 --- a/lib/events/roompowerlevelsevent.h +++ b/lib/events/roompowerlevelsevent.h @@ -7,7 +7,7 @@ #include "stateevent.h" namespace Quotient { -class PowerLevelsEventContent : public EventContent::Base { +class QUOTIENT_API PowerLevelsEventContent : public EventContent::Base { public: struct Notifications { int room; @@ -34,7 +34,8 @@ protected: void fillJson(QJsonObject* o) const override; }; -class RoomPowerLevelsEvent : public StateEvent { +class QUOTIENT_API RoomPowerLevelsEvent + : public StateEvent { Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.power_levels", RoomPowerLevelsEvent) @@ -61,9 +62,6 @@ public: int powerLevelForEvent(const QString& eventId) const; int powerLevelForState(const QString& eventId) const; int powerLevelForUser(const QString& userId) const; - -private: }; - REGISTER_EVENT_TYPE(RoomPowerLevelsEvent) } // namespace Quotient diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h index 30e53738..e336c448 100644 --- a/lib/events/roomtombstoneevent.h +++ b/lib/events/roomtombstoneevent.h @@ -6,7 +6,7 @@ #include "stateevent.h" namespace Quotient { -class RoomTombstoneEvent : public StateEventBase { +class QUOTIENT_API RoomTombstoneEvent : public StateEventBase { public: DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent) diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 9ce78609..d6557012 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -30,7 +30,8 @@ namespace EventContent { } // namespace EventContent #define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \ - class _Name : public StateEvent> { \ + class QUOTIENT_API _Name \ + : public StateEvent> { \ public: \ using value_type = content_type::value_type; \ DEFINE_EVENT_TYPEID(_TypeId, _Name) \ diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index c37965aa..6095d628 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -17,7 +17,7 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, { ContentKey, content } }; } -class StateEventBase : public RoomEvent { +class QUOTIENT_API StateEventBase : public RoomEvent { public: static inline _impl::EventFactory factory { "StateEvent" }; diff --git a/lib/events/stickerevent.h b/lib/events/stickerevent.h index 93671086..0957dca3 100644 --- a/lib/events/stickerevent.h +++ b/lib/events/stickerevent.h @@ -11,7 +11,7 @@ namespace Quotient { /// Sticker messages are specialised image messages that are displayed without /// controls (e.g. no "download" link, or light-box view on click, as would be /// displayed for for m.image events). -class StickerEvent : public RoomEvent +class QUOTIENT_API StickerEvent : public RoomEvent { public: DEFINE_EVENT_TYPEID("m.sticker", StickerEvent) diff --git a/lib/events/typingevent.h b/lib/events/typingevent.h index 7456100a..522f7e42 100644 --- a/lib/events/typingevent.h +++ b/lib/events/typingevent.h @@ -6,7 +6,7 @@ #include "event.h" namespace Quotient { -class TypingEvent : public Event { +class QUOTIENT_API TypingEvent : public Event { public: DEFINE_EVENT_TYPEID("m.typing", TypingEvent) diff --git a/lib/eventstats.h b/lib/eventstats.h index 77c661a7..a10c81fb 100644 --- a/lib/eventstats.h +++ b/lib/eventstats.h @@ -16,7 +16,7 @@ namespace Quotient { //! \note It's just a simple grouping of counters and is not automatically //! updated from the room as subsequent syncs arrive. //! \sa Room::unreadStats, Room::partiallyReadStats, Room::isEventNotable -struct EventStats { +struct QUOTIENT_API EventStats { Q_GADGET Q_PROPERTY(qsizetype notableCount MEMBER notableCount CONSTANT) Q_PROPERTY(qsizetype highlightCount MEMBER highlightCount CONSTANT) @@ -109,6 +109,6 @@ public: bool isValidFor(const Room* room, const marker_t& marker) const; }; -QDebug operator<<(QDebug dbg, const EventStats& es); +QUOTIENT_API QDebug operator<<(QDebug dbg, const EventStats& es); } diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index ddf243ed..f41fc63c 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -20,7 +20,7 @@ class ConnectionData; enum class HttpVerb { Get, Put, Post, Delete }; -class BaseJob : public QObject { +class QUOTIENT_API BaseJob : public QObject { Q_OBJECT Q_PROPERTY(QUrl requestUrl READ requestUrl CONSTANT) Q_PROPERTY(int maxRetries READ maxRetries WRITE setMaxRetries) @@ -470,7 +470,7 @@ private: QScopedPointer d; }; -inline bool isJobPending(BaseJob* job) +inline bool QUOTIENT_API isJobPending(BaseJob* job) { return job && job->error() == BaseJob::Pending; } diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index 0752af89..d9f3b686 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -6,7 +6,7 @@ #include "csapi/content-repo.h" namespace Quotient { -class DownloadFileJob : public GetContentJob { +class QUOTIENT_API DownloadFileJob : public GetContentJob { public: using GetContentJob::makeRequestUrl; static QUrl makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri); diff --git a/lib/jobs/mediathumbnailjob.h b/lib/jobs/mediathumbnailjob.h index 3183feb1..c9f6da35 100644 --- a/lib/jobs/mediathumbnailjob.h +++ b/lib/jobs/mediathumbnailjob.h @@ -8,7 +8,7 @@ #include namespace Quotient { -class MediaThumbnailJob : public GetContentThumbnailJob { +class QUOTIENT_API MediaThumbnailJob : public GetContentThumbnailJob { public: using GetContentThumbnailJob::makeRequestUrl; static QUrl makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri, diff --git a/lib/jobs/requestdata.h b/lib/jobs/requestdata.h index 4f05e5ff..41ad833a 100644 --- a/lib/jobs/requestdata.h +++ b/lib/jobs/requestdata.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -19,7 +21,7 @@ namespace Quotient { * as well as JSON (and possibly other structures in the future) to * a QByteArray consumed by QNetworkAccessManager request methods. */ -class RequestData { +class QUOTIENT_API RequestData { public: RequestData(const QByteArray& a = {}); RequestData(const QJsonObject& jo); diff --git a/lib/mxcreply.h b/lib/mxcreply.h index efaf01c6..23049b7d 100644 --- a/lib/mxcreply.h +++ b/lib/mxcreply.h @@ -3,13 +3,15 @@ #pragma once +#include "quotient_export.h" + #include #include namespace Quotient { class Room; -class MxcReply : public QNetworkReply +class QUOTIENT_API MxcReply : public QNetworkReply { public: explicit MxcReply(); diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index 87bc12a1..d06f9736 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -10,7 +12,7 @@ namespace Quotient { class Room; class Connection; -class NetworkAccessManager : public QNetworkAccessManager { +class QUOTIENT_API NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: NetworkAccessManager(QObject* parent = nullptr); diff --git a/lib/networksettings.h b/lib/networksettings.h index df11a9c8..c1446355 100644 --- a/lib/networksettings.h +++ b/lib/networksettings.h @@ -10,7 +10,7 @@ Q_DECLARE_METATYPE(QNetworkProxy::ProxyType) namespace Quotient { -class NetworkSettings : public SettingsGroup { +class QUOTIENT_API NetworkSettings : public SettingsGroup { Q_OBJECT QTNT_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType) QTNT_DECLARE_SETTING(QString, proxyHostName, setProxyHostName) diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 0e3e2a40..a5926e8c 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -26,8 +28,21 @@ #define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \ Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) +// The first line is a usual way to indicate a namespace to moc; +// the second line redeclares the namespace static metaobject with +// QUOTIENT_API so that dynamically linked clients could serialise +// flag/enum values from the namespace. +#define QUO_NAMESPACE \ +Q_NAMESPACE \ +extern QUOTIENT_API const QMetaObject staticMetaObject; +#else +// Since Qt 5.14.0, it's all packed in a single macro +#define QUO_NAMESPACE Q_NAMESPACE_EXPORT(QUOTIENT_API) +#endif + namespace Quotient { -Q_NAMESPACE +QUO_NAMESPACE // std::array {} needs explicit template parameters on macOS because // Apple stdlib doesn't have deduction guides for std::array. C++20 has diff --git a/lib/room.h b/lib/room.h index 85c51a87..63a4aaea 100644 --- a/lib/room.h +++ b/lib/room.h @@ -45,7 +45,7 @@ class RedactEventJob; * This is specifically tuned to work with QML exposing all traits as * Q_PROPERTY values. */ -class FileTransferInfo { +class QUOTIENT_API FileTransferInfo { Q_GADGET Q_PROPERTY(bool isUpload MEMBER isUpload CONSTANT) Q_PROPERTY(bool active READ active CONSTANT) @@ -73,7 +73,7 @@ public: //! \brief Data structure for a room member's read receipt //! \sa Room::lastReadReceipt -class ReadReceipt { +class QUOTIENT_API ReadReceipt { Q_GADGET Q_PROPERTY(QString eventId MEMBER eventId CONSTANT) Q_PROPERTY(QDateTime timestamp MEMBER timestamp CONSTANT) @@ -110,7 +110,7 @@ private: Q_PROPERTY(Type type MEMBER type CONSTANT) }; -class Room : public QObject { +class QUOTIENT_API Room : public QObject { Q_OBJECT Q_PROPERTY(Connection* connection READ connection CONSTANT) Q_PROPERTY(User* localUser READ localUser CONSTANT) @@ -1037,7 +1037,7 @@ private: void setJoinState(JoinState state); }; -class MemberSorter { +class QUOTIENT_API MemberSorter { public: explicit MemberSorter(const Room* r) : room(r) {} diff --git a/lib/settings.h b/lib/settings.h index efd0d714..b66879c5 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include #include @@ -11,7 +13,7 @@ class QVariant; namespace Quotient { -class Settings : public QSettings { +class QUOTIENT_API Settings : public QSettings { Q_OBJECT public: /// Add a legacy organisation/application name to migrate settings from @@ -76,7 +78,7 @@ protected: QSettings legacySettings { legacyOrganizationName, legacyApplicationName }; }; -class SettingsGroup : public Settings { +class QUOTIENT_API SettingsGroup : public Settings { public: explicit SettingsGroup(QString path, QObject* parent = nullptr) : Settings(parent) @@ -124,7 +126,7 @@ private: setValue(QStringLiteral(qsettingname), std::move(newValue)); \ } -class AccountSettings : public SettingsGroup { +class QUOTIENT_API AccountSettings : public SettingsGroup { Q_OBJECT Q_PROPERTY(QString userId READ userId CONSTANT) QTNT_DECLARE_SETTING(QString, deviceId, setDeviceId) diff --git a/lib/ssosession.h b/lib/ssosession.h index 72dd60c4..a658c043 100644 --- a/lib/ssosession.h +++ b/lib/ssosession.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -29,7 +31,7 @@ class Connection; * connection->prepareForSso(initialDeviceName)->ssoUrl()); * \endcode */ -class SsoSession : public QObject { +class QUOTIENT_API SsoSession : public QObject { Q_OBJECT Q_PROPERTY(QUrl ssoUrl READ ssoUrl CONSTANT) Q_PROPERTY(QUrl callbackUrl READ callbackUrl CONSTANT) diff --git a/lib/syncdata.h b/lib/syncdata.h index 36d2e0bf..e29540c2 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -22,7 +22,7 @@ constexpr auto HighlightCountKey = "highlight_count"_ls; * means that nothing has come from the server; heroes.value().isEmpty() * means a peculiar case of a room with the only member - the current user. */ -struct RoomSummary { +struct QUOTIENT_API RoomSummary { Omittable joinedMemberCount; Omittable invitedMemberCount; Omittable heroes; //< mxids of users to take part in the room diff --git a/lib/uri.h b/lib/uri.h index d8b892b6..78cd27c8 100644 --- a/lib/uri.h +++ b/lib/uri.h @@ -23,7 +23,7 @@ namespace Quotient { * its type, and obtain components, also in either unencoded (for displaying) * or encoded (for APIs) form. */ -class Uri : private QUrl { +class QUOTIENT_API Uri : private QUrl { Q_GADGET public: enum Type : char { diff --git a/lib/uriresolver.h b/lib/uriresolver.h index ff97324a..9140046c 100644 --- a/lib/uriresolver.h +++ b/lib/uriresolver.h @@ -25,7 +25,7 @@ class User; * gradual implementation. Derived classes are encouraged to override as many * of them as possible. */ -class UriResolverBase { +class QUOTIENT_API UriResolverBase { public: /*! \brief Resolve the resource and dispatch an action depending on its type * @@ -105,7 +105,7 @@ protected: * * \sa UriResolverBase, UriDispatcher */ -UriResolveResult +QUOTIENT_API UriResolveResult visitResource(Connection* account, const Uri& uri, std::function userHandler, std::function roomEventHandler, @@ -141,7 +141,7 @@ inline UriResolveResult checkResource(Connection* account, const Uri& uri) * synchronously - the returned value is the result of resolving the URI, * not acting on it. */ -class UriDispatcher : public QObject, public UriResolverBase { +class QUOTIENT_API UriDispatcher : public QObject, public UriResolverBase { Q_OBJECT public: explicit UriDispatcher(QObject* parent = nullptr) : QObject(parent) {} diff --git a/lib/user.h b/lib/user.h index 78b72bf2..435304ce 100644 --- a/lib/user.h +++ b/lib/user.h @@ -5,6 +5,7 @@ #pragma once #include "avatar.h" +#include "quotient_export.h" #include @@ -13,7 +14,7 @@ class Connection; class Room; class RoomMemberEvent; -class User : public QObject { +class QUOTIENT_API User : public QObject { Q_OBJECT Q_PROPERTY(QString id READ id CONSTANT) Q_PROPERTY(bool isGuest READ isGuest CONSTANT) diff --git a/lib/util.h b/lib/util.h index 97f0ecbc..399f93c2 100644 --- a/lib/util.h +++ b/lib/util.h @@ -4,6 +4,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -244,26 +246,26 @@ inline std::pair findFirstOf(InputIt first, InputIt last, } /** Convert what looks like a URL or a Matrix ID to an HTML hyperlink */ -void linkifyUrls(QString& htmlEscapedText); +QUOTIENT_API void linkifyUrls(QString& htmlEscapedText); /** Sanitize the text before showing in HTML * * This does toHtmlEscaped() and removes Unicode BiDi marks. */ -QString sanitized(const QString& plainText); +QUOTIENT_API QString sanitized(const QString& plainText); /** Pretty-print plain text into HTML * * This includes HTML escaping of <,>,",& and calling linkifyUrls() */ -QString prettyPrint(const QString& plainText); +QUOTIENT_API QString prettyPrint(const QString& plainText); /** Return a path to cache directory after making sure that it exists * * The returned path has a trailing slash, clients don't need to append it. * \param dir path to cache directory relative to the standard cache path */ -QString cacheLocation(const QString& dirName); +QUOTIENT_API QString cacheLocation(const QString& dirName); /** Hue color component of based of the hash of the string. * @@ -272,13 +274,13 @@ QString cacheLocation(const QString& dirName); * Naming and range are the same as QColor's hueF method: * https://doc.qt.io/qt-5/qcolor.html#integer-vs-floating-point-precision */ -qreal stringToHueF(const QString& s); +QUOTIENT_API qreal stringToHueF(const QString& s); /** Extract the serverpart from MXID */ -QString serverPart(const QString& mxId); +QUOTIENT_API QString serverPart(const QString& mxId); -QString versionString(); -int majorVersion(); -int minorVersion(); -int patchVersion(); +QUOTIENT_API QString versionString(); +QUOTIENT_API int majorVersion(); +QUOTIENT_API int minorVersion(); +QUOTIENT_API int patchVersion(); } // namespace Quotient -- cgit v1.2.3 From 7eda212753057c07f429dfdfb0cf3a18312de054 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 28 Dec 2021 21:46:57 +0100 Subject: Refactor EventFactory and move it out of _impl:: Strictly speaking, EventFactory can be further instantiated if any client application figures they need a whole new base class for events and respectively a separate EventFactory specialisation for it. Where this whole commit started though was a linkage error because I did not plan to expose Quotient-specific logging categories for linkage (effectively, usage) from the client code - meanwhile the inline code of EventFactory uses qDebug(EVENTS), meaning I had to either add QUOTIENT_API to EVENTS or hide those invocations. This in turn led to trimming the EventFactory constructor back to trivial implementation and dropping the guard variable that was supposed to trace duplicate EventFactory objects for the same BaseEventT - with the reasoning that such situation is not really dangerous (unlike EventTypeRegistry double-initialisation fiasco, see #413), and at the same time it can be easily detected in the logs by duplicated factory method registration messages. And while I was at it, I replaced the meaningless bool in the return type of EventFactory<>::addMethod with the slightly more (but still barely) useful reference to the inserted factory method. One can (in theory) use it now if they need to turn some event JSON into an object of some specific event type or nullptr if the event type in the JSON payload doesn't match - but at the same rate (for now at least) one can call makeIfMatches() directly. With this commit, both Quotest and Quaternion build and link using either Clang or GCC even under -fvisibility=hidden. However, running quotest now reproduces #413, which is a matter of event typeId infrastructure refactoring, coming in further commits. --- lib/events/event.cpp | 8 +++ lib/events/event.h | 159 ++++++++++++++++++++++++------------------------ lib/events/roomevent.h | 2 +- lib/events/stateevent.h | 2 +- 4 files changed, 88 insertions(+), 83 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 96be717c..715e7da2 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -27,6 +27,14 @@ QString EventTypeRegistry::getMatrixType(event_type_t typeId) : QString(); } +void _impl::EventFactoryBase::logAddingMethod(event_mtype_t matrixType, + size_t newSize) +{ + qDebug(EVENTS) << "Adding factory method for" << matrixType << "events;" + << newSize << "methods will be in the" << name + << "chain"; +} + Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json) { if (!json.contains(ContentKeyL) diff --git a/lib/events/event.h b/lib/events/event.h index 8a0076d0..0aef49f7 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -120,80 +120,93 @@ inline event_ptr_tt makeEvent(ArgTs&&... args) } namespace _impl { - template - event_ptr_tt makeIfMatches(const QJsonObject& json, - const QString& matrixType) + class QUOTIENT_API EventFactoryBase { + public: + EventFactoryBase(const EventFactoryBase&) = delete; + + protected: // This class is only to inherit from + explicit EventFactoryBase(const char* name) + : name(name) + {} + void logAddingMethod(event_mtype_t mtypeId, size_t newSize); + + private: + const char* const name; + }; +} // namespace _impl + +//! \brief A family of event factories to create events from CS API responses +//! +//! Each of these factories, as instantiated by event base types (Event, +//! RoomEvent etc.) is capable of producing an event object derived from +//! \p BaseEventT, using the JSON payload and the event type passed to its +//! make() method. Don't use these directly to make events; use loadEvent() +//! overloads as the frontend for these. Never instantiate new factories +//! outside of base event classes. +//! \sa loadEvent, setupFactory, Event::factory, RoomEvent::factory, +//! StateEventBase::factory +template +class EventFactory : public _impl::EventFactoryBase { +private: + std::vector (*)(const QJsonObject&, const QString&)> + methods {}; + + template + static event_ptr_tt makeIfMatches(const QJsonObject& json, + const QString& matrixType) { return QLatin1String(EventT::matrixTypeId()) == matrixType ? makeEvent(json) : nullptr; } - //! \brief A family of event factories to create events from CS API responses +public: + explicit EventFactory(const char* fName) + : EventFactoryBase { fName } + {} + + //! \brief Add a method to create events of a given type //! - //! Each of these factories, as instantiated by event base types (Event, - //! RoomEvent etc.) is capable of producing an event object derived from - //! \p BaseEventT, using the JSON payload and the event type passed to its - //! make() method. Don't use these directly to make events; use loadEvent() - //! overloads as the frontend for these. Never instantiate new factories - //! outside of base event classes. - //! \sa loadEvent, setupFactory, Event::factory, RoomEvent::factory, - //! StateEventBase::factory - template - class EventFactory - : private std::vector (*)(const QJsonObject&, - const QString&)> { - // Actual makeIfMatches specialisations will differ in the first - // template parameter but that doesn't affect the function type - public: - explicit EventFactory(const char* name = "") - : name(name) - { - static auto yetToBeConstructed = true; - Q_ASSERT(yetToBeConstructed); - if (!yetToBeConstructed) // For Release builds that pass Q_ASSERT - qCritical(EVENTS) - << "Another EventFactory for the same base type is being " - "created - event creation logic will be splintered"; - yetToBeConstructed = false; - } - EventFactory(const EventFactory&) = delete; - - //! \brief Add a method to create events of a given type - //! - //! Adds a standard factory method (makeIfMatches) for \p EventT so that - //! event objects of this type can be created dynamically by loadEvent. - //! The caller is responsible for ensuring this method is called only - //! once per type. - //! \sa makeIfMatches, loadEvent, Quotient::loadEvent - template - bool addMethod() - { - this->emplace_back(&makeIfMatches); - qDebug(EVENTS) << "Added factory method for" - << EventT::matrixTypeId() << "events;" << this->size() - << "methods in the" << name << "chain by now"; - return true; - } - - auto loadEvent(const QJsonObject& json, const QString& matrixType) - { - for (const auto& f : *this) - if (auto e = f(json, matrixType)) - return e; - return makeEvent(unknownEventTypeId(), json); - } + //! Adds a standard factory method (makeIfMatches) for \p EventT so that + //! event objects of this type can be created dynamically by loadEvent. + //! The caller is responsible for ensuring this method is called only + //! once per type. + //! \sa loadEvent, Quotient::loadEvent + template + const auto& addMethod() + { + logAddingMethod(EventT::matrixTypeId(), methods.size() + 1); + return methods.emplace_back(&makeIfMatches); + } - const char* const name; - }; -} // namespace _impl + auto loadEvent(const QJsonObject& json, const QString& matrixType) + { + for (const auto& f : methods) + if (auto e = f(json, matrixType)) + return e; + return makeEvent(unknownEventTypeId(), json); + } +}; + +//! \brief Point of customisation to dynamically load events +//! +//! The default specialisation of this calls BaseEventT::factory.loadEvent() +//! and if that fails (i.e. returns nullptr) creates an unknown event of +//! BaseEventT. Other specialisations may reuse other factories, add validations +//! common to BaseEventT events, and so on. +template +event_ptr_tt doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + return BaseEventT::factory.loadEvent(json, matrixType); +} // === Event === class QUOTIENT_API Event { public: using Type = event_type_t; - static inline _impl::EventFactory factory { "Event" }; + static inline EventFactory factory { "Event" }; explicit Event(Type type, const QJsonObject& json); explicit Event(Type type, event_mtype_t matrixType, @@ -273,37 +286,21 @@ using Events = EventsArray; // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). #define DEFINE_EVENT_TYPEID(_Id, _Type) \ - static QUOTIENT_EXPORT constexpr event_mtype_t matrixTypeId() \ + static QUOTIENT_API constexpr event_mtype_t matrixTypeId() \ { \ return _Id; \ } \ - static QUOTIENT_EXPORT auto typeId() { return Quotient::typeId<_Type>(); } \ + static QUOTIENT_API auto typeId() { return Quotient::typeId<_Type>(); } \ // End of macro // This macro should be put after an event class definition (in .h or .cpp) // to enable its deserialisation from a /sync and other // polymorphic event arrays -#define REGISTER_EVENT_TYPE(_Type) \ - [[maybe_unused]] QUOTIENT_API inline const auto _factoryAdded##_Type = \ - _Type::factory.addMethod<_Type>(); \ +#define REGISTER_EVENT_TYPE(Type_) \ + [[maybe_unused]] QUOTIENT_API inline const auto& factoryMethodFor##Type_ = \ + Type_::factory.addMethod(); \ // End of macro -// === Event loading === -// (see also event_loader.h) - -//! \brief Point of customisation to dynamically load events -//! -//! The default specialisation of this calls BaseEventT::factory and if that -//! fails (i.e. returns nullptr) creates an unknown event of BaseEventT. -//! Other specialisations may reuse other factories, add validations common to -//! BaseEventT, and so on -template -event_ptr_tt doLoadEvent(const QJsonObject& json, - const QString& matrixType) -{ - return BaseEventT::factory.loadEvent(json, matrixType); -} - // === is<>(), eventCast<>() and switchOnType<>() === template diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 3fbb247e..dcee1170 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -13,7 +13,7 @@ class RedactionEvent; /** This class corresponds to m.room.* events */ class QUOTIENT_API RoomEvent : public Event { public: - static inline _impl::EventFactory factory { "RoomEvent" }; + static inline EventFactory factory { "RoomEvent" }; // RedactionEvent is an incomplete type here so we cannot inline // constructors and destructors and we cannot use 'using'. diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 6095d628..88da68f8 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -19,7 +19,7 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, class QUOTIENT_API StateEventBase : public RoomEvent { public: - static inline _impl::EventFactory factory { "StateEvent" }; + static inline EventFactory factory { "StateEvent" }; StateEventBase(Type type, const QJsonObject& json); StateEventBase(Type type, event_mtype_t matrixType, -- cgit v1.2.3 From 27bb7ba696ae803c6a6903f85fe14074b23b7bcc Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 28 Dec 2021 21:34:03 +0100 Subject: Use QLatin1String for event typeId's Before all, this fixes the problem with double-initialising of type ids; it could have been fixed with a smaller change but EventTypeRegistry is fairly superfluous now when inline variables are a thing and it's possible to have an extensible registry system using literally pointers to the memory that are guaranteed to be unique. That being said, event_type_t is still QLatin1String and not a bare const char* (or void*), mostly to stay on the safe side when it comes to type identities: unlike const char*, QLatin1String's are deep-compared, meaning that matching for switchOnType (former visit) occurs a bit slower now. This may change in the future; but this is the first step in getting rid of EventTypeRegistry. This change means that initializeTypeId is no more needed; also, two static member functions, typeId() and matrixTypeId(), are being replaced with a single inline static member variable, TypeId. This commit doesn't apply that transition across the event types, meaning that you'll get a pile of warnings when compiling the library. These warnings will be tackled in further commits within this branch. --- lib/events/event.cpp | 22 +++-------------- lib/events/event.h | 68 ++++++++++++++++------------------------------------ 2 files changed, 23 insertions(+), 67 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 715e7da2..4c304a3c 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -9,28 +9,12 @@ using namespace Quotient; -event_type_t EventTypeRegistry::initializeTypeId(event_mtype_t matrixTypeId) -{ - const auto id = get().eventTypes.size(); - get().eventTypes.push_back(matrixTypeId); - if (strncmp(matrixTypeId, "", 1) == 0) - qDebug(EVENTS) << "Initialized unknown event type with id" << id; - else - qDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id" - << id; - return id; -} - -QString EventTypeRegistry::getMatrixType(event_type_t typeId) -{ - return typeId < get().eventTypes.size() ? get().eventTypes[typeId] - : QString(); -} +QString EventTypeRegistry::getMatrixType(event_type_t typeId) { return typeId; } -void _impl::EventFactoryBase::logAddingMethod(event_mtype_t matrixType, +void _impl::EventFactoryBase::logAddingMethod(event_type_t TypeId, size_t newSize) { - qDebug(EVENTS) << "Adding factory method for" << matrixType << "events;" + qDebug(EVENTS) << "Adding factory method for" << TypeId << "events;" << newSize << "methods will be in the" << name << "chain"; } diff --git a/lib/events/event.h b/lib/events/event.h index 0aef49f7..47f07c1d 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -55,60 +55,32 @@ inline QJsonObject basicEventJson(const QString& matrixType, return { { TypeKey, matrixType }, { ContentKey, content } }; } -// === Event types and event types registry === +// === Event types === -using event_type_t = size_t; +using event_type_t = QLatin1String; using event_mtype_t = const char*; class QUOTIENT_API EventTypeRegistry { public: ~EventTypeRegistry() = default; - static event_type_t initializeTypeId(event_mtype_t matrixTypeId); - - template - static event_type_t initializeTypeId() - { - return initializeTypeId(EventT::matrixTypeId()); - } - + [[deprecated("event_type_t is a string now, use it directly instead")]] static QString getMatrixType(event_type_t typeId); private: EventTypeRegistry() = default; Q_DISABLE_COPY_MOVE(EventTypeRegistry) - - static EventTypeRegistry& get() - { - static EventTypeRegistry etr; - return etr; - } - - std::vector eventTypes; -}; - -template <> -inline event_type_t EventTypeRegistry::initializeTypeId() -{ - return initializeTypeId(""); -} - -template -struct EventTypeTraits { - static event_type_t id() - { - static const auto id = EventTypeRegistry::initializeTypeId(); - return id; - } }; template inline event_type_t typeId() { - return EventTypeTraits>::id(); + return std::decay_t::TypeId; } -inline event_type_t unknownEventTypeId() { return typeId(); } +constexpr inline event_type_t UnknownEventTypeId = "?"_ls; +[[deprecated("Use UnknownEventTypeId")]] +constexpr inline event_type_t unknownEventTypeId() { return UnknownEventTypeId; } // === Event creation facilities === @@ -128,7 +100,7 @@ namespace _impl { explicit EventFactoryBase(const char* name) : name(name) {} - void logAddingMethod(event_mtype_t mtypeId, size_t newSize); + void logAddingMethod(event_type_t TypeId, size_t newSize); private: const char* const name; @@ -155,9 +127,9 @@ private: static event_ptr_tt makeIfMatches(const QJsonObject& json, const QString& matrixType) { - return QLatin1String(EventT::matrixTypeId()) == matrixType - ? makeEvent(json) - : nullptr; + // If your matrix event type is not all ASCII, it's your problem + // (see https://github.com/matrix-org/matrix-doc/pull/2758) + return EventT::TypeId == matrixType ? makeEvent(json) : nullptr; } public: @@ -175,7 +147,7 @@ public: template const auto& addMethod() { - logAddingMethod(EventT::matrixTypeId(), methods.size() + 1); + logAddingMethod(EventT::TypeId, methods.size() + 1); return methods.emplace_back(&makeIfMatches); } @@ -184,7 +156,7 @@ public: for (const auto& f : methods) if (auto e = f(json, matrixType)) return e; - return makeEvent(unknownEventTypeId(), json); + return makeEvent(UnknownEventTypeId, json); } }; @@ -285,12 +257,12 @@ using Events = EventsArray; // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). -#define DEFINE_EVENT_TYPEID(_Id, _Type) \ - static QUOTIENT_API constexpr event_mtype_t matrixTypeId() \ - { \ - return _Id; \ - } \ - static QUOTIENT_API auto typeId() { return Quotient::typeId<_Type>(); } \ +#define DEFINE_EVENT_TYPEID(Id_, Type_) \ + static inline constexpr event_type_t TypeId = Id_##_ls; \ + [[deprecated("Use _Type::TypeId directly instead")]] \ + static constexpr event_mtype_t matrixTypeId() { return Id_; } \ + [[deprecated("Use _Type::TypeId directly instead")]] \ + static event_type_t typeId() { return TypeId; } \ // End of macro // This macro should be put after an event class definition (in .h or .cpp) @@ -311,7 +283,7 @@ inline bool is(const Event& e) inline bool isUnknown(const Event& e) { - return e.type() == unknownEventTypeId(); + return e.type() == UnknownEventTypeId; } template -- cgit v1.2.3 From 8f03628ee0e4d1d1cb4e2f237e8fa695bc2cde42 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 30 Dec 2021 15:24:58 +0100 Subject: Drop inline next to constexpr Thanks to Sonar for reminding that constexpr implies inline. --- lib/events/event.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 47f07c1d..692e88e7 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -78,9 +78,9 @@ inline event_type_t typeId() return std::decay_t::TypeId; } -constexpr inline event_type_t UnknownEventTypeId = "?"_ls; +constexpr event_type_t UnknownEventTypeId = "?"_ls; [[deprecated("Use UnknownEventTypeId")]] -constexpr inline event_type_t unknownEventTypeId() { return UnknownEventTypeId; } +constexpr event_type_t unknownEventTypeId() { return UnknownEventTypeId; } // === Event creation facilities === @@ -258,7 +258,7 @@ using Events = EventsArray; // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). #define DEFINE_EVENT_TYPEID(Id_, Type_) \ - static inline constexpr event_type_t TypeId = Id_##_ls; \ + static constexpr event_type_t TypeId = Id_##_ls; \ [[deprecated("Use _Type::TypeId directly instead")]] \ static constexpr event_mtype_t matrixTypeId() { return Id_; } \ [[deprecated("Use _Type::TypeId directly instead")]] \ -- cgit v1.2.3 From 22ac47b275c2bcad5b5ff3c0cc3e10f3caaeb65b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 30 Dec 2021 15:46:11 +0100 Subject: Don't use QUOTIENT_API inside REGISTER_EVENT_TYPE On Windows QUOTIENT_API expands to different things depending on whether the library is built or used. This results in confusing statements (and MSVC erroring out on them, in some cases - see below - quite legitimately) not only when the application includes Quotient headers but also when the application defines custom events and uses REGISTER_EVENT_TYPE to make them creatable from /sync responses. To avoid repeated registration when dynamic linking is involved, EventFactory<>::addMethod() now bluntly looks up the method for this type in the vector of already registered methods. It would surely be quicker to use a static variable instead; but since the refreshed API for addMethod returns a reference to the factory method it's necessary to do this lookup anyway. Once the primary goal of this branch is achieved across platforms I might experiment with lighter ways to register factory methods; for now here's a minimal change to make the code build on Windows. --- lib/events/event.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 692e88e7..4024c6f8 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -73,7 +73,7 @@ private: }; template -inline event_type_t typeId() +constexpr event_type_t typeId() { return std::decay_t::TypeId; } @@ -147,8 +147,12 @@ public: template const auto& addMethod() { + const auto m = &makeIfMatches; + const auto it = std::find(methods.cbegin(), methods.cend(), m); + if (it != methods.cend()) + return *it; logAddingMethod(EventT::TypeId, methods.size() + 1); - return methods.emplace_back(&makeIfMatches); + return methods.emplace_back(m); } auto loadEvent(const QJsonObject& json, const QString& matrixType) @@ -268,9 +272,9 @@ using Events = EventsArray; // This macro should be put after an event class definition (in .h or .cpp) // to enable its deserialisation from a /sync and other // polymorphic event arrays -#define REGISTER_EVENT_TYPE(Type_) \ - [[maybe_unused]] QUOTIENT_API inline const auto& factoryMethodFor##Type_ = \ - Type_::factory.addMethod(); \ +#define REGISTER_EVENT_TYPE(Type_) \ + [[maybe_unused]] inline const auto& factoryMethodFor##Type_ = \ + Type_::factory.addMethod(); \ // End of macro // === is<>(), eventCast<>() and switchOnType<>() === -- cgit v1.2.3 From 874ea3fae21d6b5cab12c8e524e8b25442e4cdd5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 30 Dec 2021 17:55:23 +0100 Subject: Fix more Sonar warnings --- lib/events/event.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 4024c6f8..f12e525e 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -120,8 +120,9 @@ namespace _impl { template class EventFactory : public _impl::EventFactoryBase { private: - std::vector (*)(const QJsonObject&, const QString&)> - methods {}; + using method_t = event_ptr_tt (*)(const QJsonObject&, + const QString&); + std::vector methods {}; template static event_ptr_tt makeIfMatches(const QJsonObject& json, -- cgit v1.2.3 From 9e3752b8333813b9f00970a1af6e7ca9087ca424 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 14:37:07 +0100 Subject: Thumbnail: drop unneeded constructors Those are already inherited with 'using'. --- lib/events/eventcontent.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 87ea3672..de9a792b 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -149,10 +149,8 @@ namespace EventContent { */ class QUOTIENT_API Thumbnail : public ImageInfo { public: - Thumbnail() = default; // Allow empty thumbnails - Thumbnail(const QJsonObject& infoJson, const Omittable &file = none); - Thumbnail(const ImageInfo& info) : ImageInfo(info) {} using ImageInfo::ImageInfo; + Thumbnail(const QJsonObject& infoJson, const Omittable &file = none); /** * Writes thumbnail information to "thumbnail_info" subobject -- cgit v1.2.3 From 3986de7f8f1c98f952911c2f93891ea8643df62c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 15:08:57 +0100 Subject: Make TagRecord generally better It doesn't need all those things inside - order_type alias is no more in use; operator<() is better outside; QLatin1String is better to compare against than const char* (because const char* is assumed to be UTF-8); and TagRecord is really small so it doesn't need const& for parameters. --- lib/events/accountdataevents.h | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) (limited to 'lib/events') diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index c0f2202d..12f1f00b 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -4,27 +4,24 @@ #pragma once #include "event.h" +#include "util.h" namespace Quotient { -constexpr const char* FavouriteTag = "m.favourite"; -constexpr const char* LowPriorityTag = "m.lowpriority"; -constexpr const char* ServerNoticeTag = "m.server_notice"; +constexpr auto FavouriteTag [[maybe_unused]] = "m.favourite"_ls; +constexpr auto LowPriorityTag [[maybe_unused]] = "m.lowpriority"_ls; +constexpr auto ServerNoticeTag [[maybe_unused]] = "m.server_notice"_ls; struct TagRecord { - using order_type = Omittable; - - order_type order; - - TagRecord(order_type order = none) : order(order) {} - - bool operator<(const TagRecord& other) const - { - // Per The Spec, rooms with no order should be after those with order, - // against std::optional<>::operator<() convention. - return order && (!other.order || *order < *other.order); - } + Omittable order = none; }; +inline bool operator<(TagRecord lhs, TagRecord rhs) +{ + // Per The Spec, rooms with no order should be after those with order, + // against std::optional<>::operator<() convention. + return lhs.order && (!rhs.order || *lhs.order < *rhs.order); +} + template <> struct JsonObjectConverter { static void fillFrom(const QJsonObject& jo, TagRecord& rec) @@ -41,7 +38,7 @@ struct JsonObjectConverter { rec.order = none; } } - static void dumpTo(QJsonObject& jo, const TagRecord& rec) + static void dumpTo(QJsonObject& jo, TagRecord rec) { addParam(jo, QStringLiteral("order"), rec.order); } -- cgit v1.2.3 From 9351d9afcbaae0bdc8aa26f7361be1f84cac7467 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 15:13:24 +0100 Subject: DEFINE_EVENT_TYPEID: fix up deprecation warnings --- lib/events/event.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index f12e525e..858972da 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -264,9 +264,9 @@ using Events = EventsArray; // provide matrixTypeId() and typeId(). #define DEFINE_EVENT_TYPEID(Id_, Type_) \ static constexpr event_type_t TypeId = Id_##_ls; \ - [[deprecated("Use _Type::TypeId directly instead")]] \ + [[deprecated("Use " #Type_ "::TypeId directly instead")]] \ static constexpr event_mtype_t matrixTypeId() { return Id_; } \ - [[deprecated("Use _Type::TypeId directly instead")]] \ + [[deprecated("Use " #Type_ "::TypeId directly instead")]] \ static event_type_t typeId() { return TypeId; } \ // End of macro -- cgit v1.2.3 From bd280a087ecab30f94f7937513ee298c233fcba1 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 18 Jan 2022 08:54:52 +0100 Subject: Revise inline keyword usage - Templates and constexpr imply inline - A function called from a single site better be inlined. --- lib/events/event.h | 2 +- lib/jobs/basejob.h | 2 +- lib/quotient_common.h | 8 +++----- lib/uri.cpp | 2 +- lib/util.h | 8 ++++---- 5 files changed, 10 insertions(+), 12 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 858972da..f10f6a8d 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -311,7 +311,7 @@ inline auto switchOnType(const BaseEventT& event, FnT&& fn) namespace _impl { // Using bool instead of auto below because auto apparently upsets MSVC template - inline constexpr bool needs_downcast = + constexpr bool needs_downcast = std::is_base_of_v>> && !std::is_same_v>>; } diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 1567e635..9ed58ba8 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -28,7 +28,7 @@ class QUOTIENT_API BaseJob : public QObject { static QByteArray encodeIfParam(const QString& paramPart); template - static inline auto encodeIfParam(const char (&constPart)[N]) + static auto encodeIfParam(const char (&constPart)[N]) { return constPart; } diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 3d38ce1f..af3e2730 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -77,7 +77,7 @@ enum class Membership : unsigned int { }; QUO_DECLARE_FLAGS_NS(MembershipMask, Membership) -constexpr inline auto MembershipStrings = make_array( +constexpr auto MembershipStrings = make_array( // The order MUST be the same as the order in the original enum "join", "leave", "invite", "knock", "ban"); @@ -95,7 +95,7 @@ enum class JoinState : std::underlying_type_t { }; QUO_DECLARE_FLAGS_NS(JoinStates, JoinState) -constexpr inline auto JoinStateStrings = make_array( +constexpr auto JoinStateStrings = make_array( MembershipStrings[0], MembershipStrings[1], MembershipStrings[2], MembershipStrings[3] /* same as MembershipStrings, sans "ban" */ ); @@ -125,9 +125,7 @@ enum RoomType { }; Q_ENUM_NS(RoomType) -constexpr inline auto RoomTypeStrings = make_array( - "m.space" -); +constexpr auto RoomTypeStrings = make_array("m.space"); } // namespace Quotient Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) diff --git a/lib/uri.cpp b/lib/uri.cpp index c8843dda..3ce81a21 100644 --- a/lib/uri.cpp +++ b/lib/uri.cpp @@ -75,7 +75,7 @@ static auto decodeFragmentPart(QStringView part) return QUrl::fromPercentEncoding(part.toLatin1()).toUtf8(); } -static auto matrixToUrlRegexInit() +static inline auto matrixToUrlRegexInit() { // See https://matrix.org/docs/spec/appendices#matrix-to-navigation const QRegularExpression MatrixToUrlRE { diff --git a/lib/util.h b/lib/util.h index 66db0ece..3505b62f 100644 --- a/lib/util.h +++ b/lib/util.h @@ -54,9 +54,9 @@ using UnorderedMap = std::unordered_map>; namespace _impl { template - constexpr inline auto IsOmittableValue = false; + constexpr auto IsOmittableValue = false; template - constexpr inline auto IsOmittable = IsOmittableValue>; + constexpr auto IsOmittable = IsOmittableValue>; } constexpr auto none = std::nullopt; @@ -165,7 +165,7 @@ Omittable(T&&) -> Omittable; namespace _impl { template - constexpr inline auto IsOmittableValue> = true; + constexpr auto IsOmittableValue> = true; } template @@ -191,7 +191,7 @@ inline auto merge(T1& lhs, const Omittable& rhs) return true; } -inline constexpr auto operator"" _ls(const char* s, std::size_t size) +constexpr auto operator"" _ls(const char* s, std::size_t size) { return QLatin1String(s, int(size)); } -- cgit v1.2.3 From c7907084282c7957d085acb329574ab6a7d593c8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 18 Jan 2022 11:43:32 +0100 Subject: Move over non-interface code to QLatin1String It's better than const char* because any interaction between const char* and QString assumes that const char* contains UTF-8, which is pessimistic and therefore inefficient; at the same time: - construction of QString from QLatin1String is extremely fast (boiling down to padding null bytes) - "something"_ls is much shorter than QStringLiteral("something") - "something"_ls produces a direct pointer to the literal at compile time, using the benefits of raw string literals (deduplication, e.g.) The library API will also transition to QLatin1String where applicable, likely in 0.8. --- lib/events/roommessageevent.cpp | 64 ++++++++++++++++++++--------------------- lib/settings.cpp | 9 +++--- lib/uri.cpp | 19 ++++++------ 3 files changed, 47 insertions(+), 45 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 2b7b4166..0f58d8a6 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -19,15 +19,13 @@ using namespace EventContent; using MsgType = RoomMessageEvent::MsgType; -static const auto RelatesToKeyL = "m.relates_to"_ls; -static const auto MsgTypeKeyL = "msgtype"_ls; -static const auto FormattedBodyKeyL = "formatted_body"_ls; - -static const auto TextTypeKey = "m.text"; -static const auto EmoteTypeKey = "m.emote"; -static const auto NoticeTypeKey = "m.notice"; - -static const auto HtmlContentTypeId = QStringLiteral("org.matrix.custom.html"); +static constexpr auto RelatesToKey = "m.relates_to"_ls; +static constexpr auto MsgTypeKey = "msgtype"_ls; +static constexpr auto FormattedBodyKey = "formatted_body"_ls; +static constexpr auto TextTypeKey = "m.text"_ls; +static constexpr auto EmoteTypeKey = "m.emote"_ls; +static constexpr auto NoticeTypeKey = "m.notice"_ls; +static constexpr auto HtmlContentTypeId = "org.matrix.custom.html"_ls; template TypedBase* make(const QJsonObject& json) @@ -38,13 +36,13 @@ TypedBase* make(const QJsonObject& json) template <> TypedBase* make(const QJsonObject& json) { - return json.contains(FormattedBodyKeyL) || json.contains(RelatesToKeyL) + return json.contains(FormattedBodyKey) || json.contains(RelatesToKey) ? new TextContent(json) : nullptr; } struct MsgTypeDesc { - QString matrixType; + QLatin1String matrixType; MsgType enumType; TypedBase* (*maker)(const QJsonObject&); }; @@ -53,11 +51,11 @@ const std::vector msgTypes = { { TextTypeKey, MsgType::Text, make }, { EmoteTypeKey, MsgType::Emote, make }, { NoticeTypeKey, MsgType::Notice, make }, - { QStringLiteral("m.image"), MsgType::Image, make }, - { QStringLiteral("m.file"), MsgType::File, make }, - { QStringLiteral("m.location"), MsgType::Location, make }, - { QStringLiteral("m.video"), MsgType::Video, make }, - { QStringLiteral("m.audio"), MsgType::Audio, make } + { "m.image"_ls, MsgType::Image, make }, + { "m.file"_ls, MsgType::File, make }, + { "m.location"_ls, MsgType::Location, make }, + { "m.video"_ls, MsgType::Video, make }, + { "m.audio"_ls, MsgType::Audio, make } }; QString msgTypeToJson(MsgType enumType) @@ -94,12 +92,12 @@ QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, TypedBase* content) { auto json = content ? content->toJson() : QJsonObject(); - if (json.contains(RelatesToKeyL)) { + if (json.contains(RelatesToKey)) { if (jsonMsgType != TextTypeKey && jsonMsgType != NoticeTypeKey && jsonMsgType != EmoteTypeKey) { - json.remove(RelatesToKeyL); + json.remove(RelatesToKey); qCWarning(EVENTS) - << RelatesToKeyL << "cannot be used in" << jsonMsgType + << RelatesToKey << "cannot be used in" << jsonMsgType << "messages; the relation has been stripped off"; } else { // After the above, we know for sure that the content is TextContent @@ -109,9 +107,9 @@ QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, if (textContent->relatesTo->type == RelatesTo::ReplacementTypeId()) { auto newContentJson = json.take("m.new_content"_ls).toObject(); newContentJson.insert(BodyKey, plainBody); - newContentJson.insert(MsgTypeKeyL, jsonMsgType); + newContentJson.insert(MsgTypeKey, jsonMsgType); json.insert(QStringLiteral("m.new_content"), newContentJson); - json[MsgTypeKeyL] = jsonMsgType; + json[MsgTypeKey] = jsonMsgType; json[BodyKeyL] = "* " + plainBody; return json; } @@ -177,8 +175,8 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) if (isRedacted()) return; const QJsonObject content = contentJson(); - if (content.contains(MsgTypeKeyL) && content.contains(BodyKeyL)) { - auto msgtype = content[MsgTypeKeyL].toString(); + if (content.contains(MsgTypeKey) && content.contains(BodyKeyL)) { + auto msgtype = content[MsgTypeKey].toString(); bool msgTypeFound = false; for (const auto& mt : msgTypes) if (mt.matrixType == msgtype) { @@ -204,7 +202,7 @@ RoomMessageEvent::MsgType RoomMessageEvent::msgtype() const QString RoomMessageEvent::rawMsgtype() const { - return contentPart(MsgTypeKeyL); + return contentPart(MsgTypeKey); } QString RoomMessageEvent::plainBody() const @@ -295,7 +293,7 @@ Omittable fromJson(const QJsonValue& jv) } // namespace Quotient TextContent::TextContent(const QJsonObject& json) - : relatesTo(fromJson>(json[RelatesToKeyL])) + : relatesTo(fromJson>(json[RelatesToKey])) { QMimeDatabase db; static const auto PlainTextMimeType = db.mimeTypeForName("text/plain"); @@ -308,7 +306,7 @@ TextContent::TextContent(const QJsonObject& json) // of sending HTML messages. if (actualJson["format"_ls].toString() == HtmlContentTypeId) { mimeType = HtmlMimeType; - body = actualJson[FormattedBodyKeyL].toString(); + body = actualJson[FormattedBodyKey].toString(); } else { // Falling back to plain text, as there's no standard way to describe // rich text in messages. @@ -320,7 +318,6 @@ TextContent::TextContent(const QJsonObject& json) void TextContent::fillJson(QJsonObject* json) const { static const auto FormatKey = QStringLiteral("format"); - static const auto FormattedBodyKey = QStringLiteral("formatted_body"); Q_ASSERT(json); if (mimeType.inherits("text/html")) { @@ -328,11 +325,14 @@ void TextContent::fillJson(QJsonObject* json) const json->insert(FormattedBodyKey, body); } if (relatesTo) { - json->insert(QStringLiteral("m.relates_to"), - relatesTo->type == RelatesTo::ReplyTypeId() ? - QJsonObject { { relatesTo->type, QJsonObject{ { EventIdKey, relatesTo->eventId } } } } : - QJsonObject { { "rel_type", relatesTo->type }, { EventIdKey, relatesTo->eventId } } - ); + json->insert( + QStringLiteral("m.relates_to"), + relatesTo->type == RelatesTo::ReplyTypeId() + ? QJsonObject { { relatesTo->type, + QJsonObject { + { EventIdKey, relatesTo->eventId } } } } + : QJsonObject { { "rel_type", relatesTo->type }, + { EventIdKey, relatesTo->eventId } }); if (relatesTo->type == RelatesTo::ReplacementTypeId()) { QJsonObject newContentJson; if (mimeType.inherits("text/html")) { diff --git a/lib/settings.cpp b/lib/settings.cpp index 5549e4de..20734a7e 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -3,6 +3,7 @@ #include "settings.h" +#include "util.h" #include "logging.h" #include @@ -109,10 +110,10 @@ QUO_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {}, QUO_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false, setKeepLoggedIn) -static const auto HomeserverKey = QStringLiteral("homeserver"); -static const auto AccessTokenKey = QStringLiteral("access_token"); -static const auto EncryptionAccountPickleKey = - QStringLiteral("encryption_account_pickle"); +static constexpr auto HomeserverKey = "homeserver"_ls; +static constexpr auto AccessTokenKey = "access_token"_ls; +static constexpr auto EncryptionAccountPickleKey = + "encryption_account_pickle"_ls; QUrl AccountSettings::homeserver() const { diff --git a/lib/uri.cpp b/lib/uri.cpp index 3ce81a21..11c59b69 100644 --- a/lib/uri.cpp +++ b/lib/uri.cpp @@ -3,27 +3,28 @@ #include "uri.h" +#include "util.h" #include "logging.h" #include using namespace Quotient; -struct ReplacePair { QByteArray uriString; char sigil; }; +struct ReplacePair { QLatin1String uriString; char sigil; }; /// \brief Defines bi-directional mapping of path prefixes and sigils /// /// When there are two prefixes for the same sigil, the first matching /// entry for a given sigil is used. -static const auto replacePairs = { - ReplacePair { "u/", '@' }, - { "user/", '@' }, - { "roomid/", '!' }, - { "r/", '#' }, - { "room/", '#' }, +static const ReplacePair replacePairs[] = { + { "u/"_ls, '@' }, + { "user/"_ls, '@' }, + { "roomid/"_ls, '!' }, + { "r/"_ls, '#' }, + { "room/"_ls, '#' }, // The notation for bare event ids is not proposed in MSC2312 but there's // https://github.com/matrix-org/matrix-doc/pull/2644 - { "e/", '$' }, - { "event/", '$' } + { "e/"_ls, '$' }, + { "event/"_ls, '$' } }; Uri::Uri(QByteArray primaryId, QByteArray secondaryId, QString query) -- cgit v1.2.3 From bf82aeea369cacfc93a0e6d6d9feb01f1f2afdb2 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 18 Jan 2022 11:43:21 +0100 Subject: Don't use 'static' on top-level/namespace scope When internal linkage is necessary, anonymous namespaces fulfil the same purpose in a better way. See also: https://stackoverflow.com/questions/4422507/superiority-of-unnamed-namespace-over-static --- lib/converters.h | 2 +- lib/events/roommessageevent.cpp | 18 +++++++++++------- lib/settings.cpp | 9 +++++---- lib/uri.cpp | 6 +++++- 4 files changed, 22 insertions(+), 13 deletions(-) (limited to 'lib/events') diff --git a/lib/converters.h b/lib/converters.h index a6028f1b..8eea1cd3 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -355,7 +355,7 @@ namespace _impl { }; } // namespace _impl -static constexpr bool IfNotEmpty = false; +constexpr bool IfNotEmpty = false; /*! Add a key-value pair to QJsonObject or QUrlQuery * diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 0f58d8a6..5ab0f845 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -19,13 +19,15 @@ using namespace EventContent; using MsgType = RoomMessageEvent::MsgType; -static constexpr auto RelatesToKey = "m.relates_to"_ls; -static constexpr auto MsgTypeKey = "msgtype"_ls; -static constexpr auto FormattedBodyKey = "formatted_body"_ls; -static constexpr auto TextTypeKey = "m.text"_ls; -static constexpr auto EmoteTypeKey = "m.emote"_ls; -static constexpr auto NoticeTypeKey = "m.notice"_ls; -static constexpr auto HtmlContentTypeId = "org.matrix.custom.html"_ls; +namespace { // Supporting internal definitions + +constexpr auto RelatesToKey = "m.relates_to"_ls; +constexpr auto MsgTypeKey = "msgtype"_ls; +constexpr auto FormattedBodyKey = "formatted_body"_ls; +constexpr auto TextTypeKey = "m.text"_ls; +constexpr auto EmoteTypeKey = "m.emote"_ls; +constexpr auto NoticeTypeKey = "m.notice"_ls; +constexpr auto HtmlContentTypeId = "org.matrix.custom.html"_ls; template TypedBase* make(const QJsonObject& json) @@ -87,6 +89,8 @@ inline bool isReplacement(const Omittable& rel) return rel && rel->type == RelatesTo::ReplacementTypeId(); } +} // anonymous namespace + QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, const QString& jsonMsgType, TypedBase* content) diff --git a/lib/settings.cpp b/lib/settings.cpp index 20734a7e..2491d89d 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -110,10 +110,11 @@ QUO_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {}, QUO_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false, setKeepLoggedIn) -static constexpr auto HomeserverKey = "homeserver"_ls; -static constexpr auto AccessTokenKey = "access_token"_ls; -static constexpr auto EncryptionAccountPickleKey = - "encryption_account_pickle"_ls; +namespace { +constexpr auto HomeserverKey = "homeserver"_ls; +constexpr auto AccessTokenKey = "access_token"_ls; +constexpr auto EncryptionAccountPickleKey = "encryption_account_pickle"_ls; +} QUrl AccountSettings::homeserver() const { diff --git a/lib/uri.cpp b/lib/uri.cpp index 11c59b69..6b7d1d20 100644 --- a/lib/uri.cpp +++ b/lib/uri.cpp @@ -10,12 +10,14 @@ using namespace Quotient; +namespace { + struct ReplacePair { QLatin1String uriString; char sigil; }; /// \brief Defines bi-directional mapping of path prefixes and sigils /// /// When there are two prefixes for the same sigil, the first matching /// entry for a given sigil is used. -static const ReplacePair replacePairs[] = { +const ReplacePair replacePairs[] = { { "u/"_ls, '@' }, { "user/"_ls, '@' }, { "roomid/"_ls, '!' }, @@ -27,6 +29,8 @@ static const ReplacePair replacePairs[] = { { "event/"_ls, '$' } }; +} + Uri::Uri(QByteArray primaryId, QByteArray secondaryId, QString query) { if (primaryId.isEmpty()) -- cgit v1.2.3 From fdff209744ac4c422f63fe2549aa0132df7e6292 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 21 Jan 2022 02:04:10 +0100 Subject: Redo EventRelation; deprecate RelatesTo RelatesTo and EventRelation have been two means to the same end in two different contexts. (Modernised) EventRelation is the one used now both for ReactionEvent and EventContent::TextContent. The modernisation mostly boils down to using inline variables instead of functions to return relation types and switching to QLatin1String from const char* (because we know exactly that those constants are Latin-1 and QLatin1String is more efficient than const char* to compare/convert to QString). --- CMakeLists.txt | 3 ++- lib/events/eventrelation.cpp | 38 ++++++++++++++++++++++++++++++ lib/events/eventrelation.h | 52 +++++++++++++++++++++++++++++++++++++++++ lib/events/reactionevent.cpp | 29 ----------------------- lib/events/reactionevent.h | 32 ++----------------------- lib/events/roommessageevent.cpp | 38 ++++++++---------------------- lib/events/roommessageevent.h | 27 +++++++++++---------- lib/room.cpp | 11 ++++----- lib/room.h | 5 ++-- quotest/quotest.cpp | 2 +- 10 files changed, 128 insertions(+), 109 deletions(-) create mode 100644 lib/events/eventrelation.cpp create mode 100644 lib/events/eventrelation.h delete mode 100644 lib/events/reactionevent.cpp (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index fd5f1dca..df193b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,7 @@ list(APPEND lib_SRCS lib/events/stateevent.h lib/events/stateevent.cpp lib/events/simplestateevents.h lib/events/eventcontent.h lib/events/eventcontent.cpp + lib/events/eventrelation.h lib/events/eventrelation.cpp lib/events/roomcreateevent.h lib/events/roomcreateevent.cpp lib/events/roomtombstoneevent.h lib/events/roomtombstoneevent.cpp lib/events/roommessageevent.h lib/events/roommessageevent.cpp @@ -162,7 +163,7 @@ list(APPEND lib_SRCS lib/events/typingevent.h lib/events/typingevent.cpp lib/events/accountdataevents.h lib/events/receiptevent.h lib/events/receiptevent.cpp - lib/events/reactionevent.h lib/events/reactionevent.cpp + lib/events/reactionevent.h lib/events/callinviteevent.h lib/events/callinviteevent.cpp lib/events/callcandidatesevent.h lib/events/callanswerevent.h lib/events/callanswerevent.cpp diff --git a/lib/events/eventrelation.cpp b/lib/events/eventrelation.cpp new file mode 100644 index 00000000..04972f45 --- /dev/null +++ b/lib/events/eventrelation.cpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2022 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "eventrelation.h" + +#include "../logging.h" +#include "event.h" + +using namespace Quotient; + +void JsonObjectConverter::dumpTo(QJsonObject& jo, + const EventRelation& pod) +{ + if (pod.type.isEmpty()) { + qCWarning(MAIN) << "Empty relation type; won't dump to JSON"; + return; + } + jo.insert(RelTypeKey, pod.type); + jo.insert(EventIdKey, pod.eventId); + if (pod.type == EventRelation::AnnotationType) + jo.insert(QStringLiteral("key"), pod.key); +} + +void JsonObjectConverter::fillFrom(const QJsonObject& jo, + EventRelation& pod) +{ + if (const auto replyJson = jo.value(EventRelation::ReplyType).toObject(); + !replyJson.isEmpty()) { + pod.type = EventRelation::ReplyType; + fromJson(replyJson[EventIdKeyL], pod.eventId); + } else { + // The experimental logic for generic relationships (MSC1849) + fromJson(jo[RelTypeKey], pod.type); + fromJson(jo[EventIdKeyL], pod.eventId); + if (pod.type == EventRelation::AnnotationType) + fromJson(jo["key"_ls], pod.key); + } +} diff --git a/lib/events/eventrelation.h b/lib/events/eventrelation.h new file mode 100644 index 00000000..e445ee42 --- /dev/null +++ b/lib/events/eventrelation.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2022 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "converters.h" + +namespace Quotient { + +[[maybe_unused]] constexpr auto RelatesToKey = "m.relates_to"_ls; +constexpr auto RelTypeKey = "rel_type"_ls; + +struct QUOTIENT_API EventRelation { + using reltypeid_t = QLatin1String; + + QString type; + QString eventId; + QString key = {}; // Only used for m.annotation for now + + static constexpr auto ReplyType = "m.in_reply_to"_ls; + static constexpr auto AnnotationType = "m.annotation"_ls; + static constexpr auto ReplacementType = "m.replace"_ls; + + static EventRelation replyTo(QString eventId) + { + return { ReplyType, std::move(eventId) }; + } + static EventRelation annotate(QString eventId, QString key) + { + return { AnnotationType, std::move(eventId), std::move(key) }; + } + static EventRelation replace(QString eventId) + { + return { ReplacementType, std::move(eventId) }; + } + + [[deprecated("Use ReplyRelation variable instead")]] + static constexpr auto Reply() { return ReplyType; } + [[deprecated("Use AnnotationRelation variable instead")]] // + static constexpr auto Annotation() { return AnnotationType; } + [[deprecated("Use ReplacementRelation variable instead")]] // + static constexpr auto Replacement() { return ReplacementType; } +}; + +template <> +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const EventRelation& pod); + static void fillFrom(const QJsonObject& jo, EventRelation& pod); +}; + +} + diff --git a/lib/events/reactionevent.cpp b/lib/events/reactionevent.cpp deleted file mode 100644 index b53fffd6..00000000 --- a/lib/events/reactionevent.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "reactionevent.h" - -using namespace Quotient; - -void JsonObjectConverter::dumpTo( - QJsonObject& jo, const EventRelation& pod) -{ - if (pod.type.isEmpty()) { - qCWarning(MAIN) << "Empty relation type; won't dump to JSON"; - return; - } - jo.insert(QStringLiteral("rel_type"), pod.type); - jo.insert(EventIdKey, pod.eventId); - if (pod.type == EventRelation::Annotation()) - jo.insert(QStringLiteral("key"), pod.key); -} - -void JsonObjectConverter::fillFrom( - const QJsonObject& jo, EventRelation& pod) -{ - // The experimental logic for generic relationships (MSC1849) - fromJson(jo["rel_type"_ls], pod.type); - fromJson(jo[EventIdKeyL], pod.eventId); - if (pod.type == EventRelation::Annotation()) - fromJson(jo["key"_ls], pod.key); -} diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index ce11eaed..b3cb3ca7 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -4,38 +4,10 @@ #pragma once #include "roomevent.h" +#include "eventrelation.h" namespace Quotient { -struct QUOTIENT_API EventRelation { - using reltypeid_t = const char*; - static constexpr reltypeid_t Reply() { return "m.in_reply_to"; } - static constexpr reltypeid_t Annotation() { return "m.annotation"; } - static constexpr reltypeid_t Replacement() { return "m.replace"; } - - QString type; - QString eventId; - QString key = {}; // Only used for m.annotation for now - - static EventRelation replyTo(QString eventId) - { - return { Reply(), std::move(eventId) }; - } - static EventRelation annotate(QString eventId, QString key) - { - return { Annotation(), std::move(eventId), std::move(key) }; - } - static EventRelation replace(QString eventId) - { - return { Replacement(), std::move(eventId) }; - } -}; -template <> -struct QUOTIENT_API JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const EventRelation& pod); - static void fillFrom(const QJsonObject& jo, EventRelation& pod); -}; - class QUOTIENT_API ReactionEvent : public RoomEvent { public: DEFINE_EVENT_TYPEID("m.reaction", ReactionEvent) @@ -47,7 +19,7 @@ public: explicit ReactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) {} EventRelation relation() const { - return contentPart("m.relates_to"_ls); + return contentPart(RelatesToKey); } }; REGISTER_EVENT_TYPE(ReactionEvent) diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 5ab0f845..c07a4f3c 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -6,6 +6,7 @@ #include "roommessageevent.h" #include "logging.h" +#include "events/eventrelation.h" #include #include @@ -20,7 +21,6 @@ using namespace EventContent; using MsgType = RoomMessageEvent::MsgType; namespace { // Supporting internal definitions - constexpr auto RelatesToKey = "m.relates_to"_ls; constexpr auto MsgTypeKey = "msgtype"_ls; constexpr auto FormattedBodyKey = "formatted_body"_ls; @@ -84,9 +84,9 @@ MsgType jsonToMsgType(const QString& matrixType) return MsgType::Unknown; } -inline bool isReplacement(const Omittable& rel) +inline bool isReplacement(const Omittable& rel) { - return rel && rel->type == RelatesTo::ReplacementTypeId(); + return rel && rel->type == EventRelation::ReplacementType; } } // anonymous namespace @@ -105,10 +105,10 @@ QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, << "messages; the relation has been stripped off"; } else { // After the above, we know for sure that the content is TextContent - // and that its RelatesTo structure is not omitted + // and that its EventRelation structure is not omitted auto* textContent = static_cast(content); Q_ASSERT(textContent && textContent->relatesTo.has_value()); - if (textContent->relatesTo->type == RelatesTo::ReplacementTypeId()) { + if (textContent->relatesTo->type == EventRelation::ReplacementType) { auto newContentJson = json.take("m.new_content"_ls).toObject(); newContentJson.insert(BodyKey, plainBody); newContentJson.insert(MsgTypeKey, jsonMsgType); @@ -269,7 +269,7 @@ QString RoomMessageEvent::rawMsgTypeForFile(const QFileInfo& fi) } TextContent::TextContent(QString text, const QString& contentType, - Omittable relatesTo) + Omittable relatesTo) : mimeType(QMimeDatabase().mimeTypeForName(contentType)) , body(std::move(text)) , relatesTo(std::move(relatesTo)) @@ -278,26 +278,8 @@ TextContent::TextContent(QString text, const QString& contentType, mimeType = QMimeDatabase().mimeTypeForName("text/html"); } -namespace Quotient { -// Overload the default fromJson<> logic that defined in converters.h -// as we want -template <> -Omittable fromJson(const QJsonValue& jv) -{ - const auto jo = jv.toObject(); - if (jo.isEmpty()) - return none; - const auto replyJson = jo.value(RelatesTo::ReplyTypeId()).toObject(); - if (!replyJson.isEmpty()) - return replyTo(fromJson(replyJson[EventIdKeyL])); - - return RelatesTo { jo.value("rel_type"_ls).toString(), - jo.value(EventIdKeyL).toString() }; -} -} // namespace Quotient - TextContent::TextContent(const QJsonObject& json) - : relatesTo(fromJson>(json[RelatesToKey])) + : relatesTo(fromJson>(json[RelatesToKey])) { QMimeDatabase db; static const auto PlainTextMimeType = db.mimeTypeForName("text/plain"); @@ -331,13 +313,13 @@ void TextContent::fillJson(QJsonObject* json) const if (relatesTo) { json->insert( QStringLiteral("m.relates_to"), - relatesTo->type == RelatesTo::ReplyTypeId() + relatesTo->type == EventRelation::ReplyType ? QJsonObject { { relatesTo->type, QJsonObject { { EventIdKey, relatesTo->eventId } } } } - : QJsonObject { { "rel_type", relatesTo->type }, + : QJsonObject { { RelTypeKey, relatesTo->type }, { EventIdKey, relatesTo->eventId } }); - if (relatesTo->type == RelatesTo::ReplacementTypeId()) { + if (relatesTo->type == EventRelation::ReplacementType) { QJsonObject newContentJson; if (mimeType.inherits("text/html")) { newContentJson.insert(FormatKey, HtmlContentTypeId); diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 0c901b7a..44ef05fb 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -6,6 +6,7 @@ #pragma once #include "eventcontent.h" +#include "eventrelation.h" #include "roomevent.h" class QFileInfo; @@ -97,23 +98,25 @@ REGISTER_EVENT_TYPE(RoomMessageEvent) using MessageEventType = RoomMessageEvent::MsgType; namespace EventContent { - // Additional event content types - struct RelatesTo { - static constexpr const char* ReplyTypeId() { return "m.in_reply_to"; } - static constexpr const char* ReplacementTypeId() { return "m.replace"; } - QString type; // The only supported relation so far - QString eventId; + struct [[deprecated("Use Quotient::EventRelation instead")]] RelatesTo + : EventRelation { + static constexpr auto ReplyTypeId() { return Reply(); } + static constexpr auto ReplacementTypeId() { return Replacement(); } }; - inline RelatesTo replyTo(QString eventId) + [[deprecated("Use EventRelation::replyTo() instead")]] + inline auto replyTo(QString eventId) { - return { RelatesTo::ReplyTypeId(), std::move(eventId) }; + return EventRelation::replyTo(std::move(eventId)); } - inline RelatesTo replacementOf(QString eventId) + [[deprecated("Use EventRelation::replace() instead")]] + inline auto replacementOf(QString eventId) { - return { RelatesTo::ReplacementTypeId(), std::move(eventId) }; + return EventRelation::replace(std::move(eventId)); } + // Additional event content types + /** * Rich text content for m.text, m.emote, m.notice * @@ -123,14 +126,14 @@ namespace EventContent { class QUOTIENT_API TextContent : public TypedBase { public: TextContent(QString text, const QString& contentType, - Omittable relatesTo = none); + Omittable relatesTo = none); explicit TextContent(const QJsonObject& json); QMimeType type() const override { return mimeType; } QMimeType mimeType; QString body; - Omittable relatesTo; + Omittable relatesTo; protected: void fillJson(QJsonObject* json) const override; diff --git a/lib/room.cpp b/lib/room.cpp index 55efb5b9..ba63f50d 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -996,14 +996,14 @@ Room::findPendingEvent(const QString& txnId) const }); } -const Room::RelatedEvents Room::relatedEvents(const QString& evtId, - const char* relType) const +const Room::RelatedEvents Room::relatedEvents( + const QString& evtId, EventRelation::reltypeid_t relType) const { return d->relations.value({ evtId, relType }); } -const Room::RelatedEvents Room::relatedEvents(const RoomEvent& evt, - const char* relType) const +const Room::RelatedEvents Room::relatedEvents( + const RoomEvent& evt, EventRelation::reltypeid_t relType) const { return relatedEvents(evt.id(), relType); } @@ -2506,8 +2506,7 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) } if (const auto* reaction = eventCast(oldEvent)) { const auto& targetEvtId = reaction->relation().eventId; - const auto lookupKey = - qMakePair(targetEvtId, EventRelation::Annotation()); + const QPair lookupKey { targetEvtId, EventRelation::AnnotationType }; if (relations.contains(lookupKey)) { relations[lookupKey].removeOne(reaction); emit q->updatedEvent(targetEvtId); diff --git a/lib/room.h b/lib/room.h index 61f475e8..15bc7648 100644 --- a/lib/room.h +++ b/lib/room.h @@ -21,6 +21,7 @@ #include "events/roommessageevent.h" #include "events/roomcreateevent.h" #include "events/roomtombstoneevent.h" +#include "events/eventrelation.h" #include #include @@ -394,9 +395,9 @@ public: PendingEvents::const_iterator findPendingEvent(const QString& txnId) const; const RelatedEvents relatedEvents(const QString& evtId, - const char* relType) const; + EventRelation::reltypeid_t relType) const; const RelatedEvents relatedEvents(const RoomEvent& evt, - const char* relType) const; + EventRelation::reltypeid_t relType) const; const RoomCreateEvent* creation() const { diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 5a5642fe..40bc456b 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -381,7 +381,7 @@ TEST_IMPL(sendReaction) if (actualTargetEvtId != targetEvtId) return false; const auto reactions = targetRoom->relatedEvents( - targetEvtId, EventRelation::Annotation()); + targetEvtId, EventRelation::AnnotationType); // It's a test room, assuming no interference there should // be exactly one reaction if (reactions.size() != 1) -- cgit v1.2.3 From 0689028f4a0db403a55c6158e750fee3ba6c7098 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 20 Jan 2022 17:35:28 +0100 Subject: Refactor assembleContentJson() Get rid of that Q_ASSERT() in the middle that only worked in Debug builds anyway. --- lib/events/roommessageevent.cpp | 43 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 21 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index c07a4f3c..d63352cb 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -95,32 +95,33 @@ QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, const QString& jsonMsgType, TypedBase* content) { - auto json = content ? content->toJson() : QJsonObject(); - if (json.contains(RelatesToKey)) { + QJsonObject json; + if (content) { + // TODO: replace with content->fillJson(json) when it starts working + json = content->toJson(); if (jsonMsgType != TextTypeKey && jsonMsgType != NoticeTypeKey && jsonMsgType != EmoteTypeKey) { - json.remove(RelatesToKey); - qCWarning(EVENTS) - << RelatesToKey << "cannot be used in" << jsonMsgType - << "messages; the relation has been stripped off"; - } else { - // After the above, we know for sure that the content is TextContent - // and that its EventRelation structure is not omitted - auto* textContent = static_cast(content); - Q_ASSERT(textContent && textContent->relatesTo.has_value()); - if (textContent->relatesTo->type == EventRelation::ReplacementType) { - auto newContentJson = json.take("m.new_content"_ls).toObject(); - newContentJson.insert(BodyKey, plainBody); - newContentJson.insert(MsgTypeKey, jsonMsgType); - json.insert(QStringLiteral("m.new_content"), newContentJson); - json[MsgTypeKey] = jsonMsgType; - json[BodyKeyL] = "* " + plainBody; - return json; + if (json.contains(RelatesToKey)) { + json.remove(RelatesToKey); + qCWarning(EVENTS) + << RelatesToKey << "cannot be used in" << jsonMsgType + << "messages; the relation has been stripped off"; } + } else if (auto* textContent = static_cast(content); + textContent->relatesTo + && textContent->relatesTo->type + == EventRelation::ReplacementType) { + auto newContentJson = json.take("m.new_content"_ls).toObject(); + newContentJson.insert(BodyKey, plainBody); + newContentJson.insert(MsgTypeKey, jsonMsgType); + json.insert(QStringLiteral("m.new_content"), newContentJson); + json[MsgTypeKey] = jsonMsgType; + json[BodyKeyL] = "* " + plainBody; + return json; } } - json.insert(QStringLiteral("msgtype"), jsonMsgType); - json.insert(QStringLiteral("body"), plainBody); + json.insert(MsgTypeKey, jsonMsgType); + json.insert(BodyKey, plainBody); return json; } -- cgit v1.2.3 From cc9908e5159ed93a18eda9f9794a7c9fc7f67f27 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 23 Jan 2022 10:34:38 +0100 Subject: Fix visit() return type It's too restrictive compared to switchOnType() overloads and doesn't map to the case with a default value. --- lib/events/event.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index f10f6a8d..113fa3fa 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -354,8 +354,7 @@ switchOnType(const BaseT& event, FnT1&& fn1, FnT2&& fn2, FnTs&&... fns) template [[deprecated("The new name for visit() is switchOnType()")]] // -inline std::common_type_t...> -visit(const BaseT& event, FnTs&&... fns) +inline auto visit(const BaseT& event, FnTs&&... fns) { return switchOnType(event, std::forward(fns)...); } -- cgit v1.2.3 From 29775218e0c8b6c176015dd3128a0d545906ae6f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Dec 2021 20:54:39 +0100 Subject: Cleanup some #includes --- lib/events/eventcontent.cpp | 1 - lib/events/receiptevent.cpp | 1 - lib/events/roomevent.cpp | 1 - lib/events/roommemberevent.cpp | 1 - lib/jobs/basejob.cpp | 2 -- lib/jobs/basejob.h | 6 +++--- 6 files changed, 3 insertions(+), 9 deletions(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 22878d4c..4ce130a6 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -4,7 +4,6 @@ #include "eventcontent.h" #include "converters.h" -#include "util.h" #include "logging.h" #include diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp index 72dbf2e3..7f06d99f 100644 --- a/lib/events/receiptevent.cpp +++ b/lib/events/receiptevent.cpp @@ -20,7 +20,6 @@ Example of a Receipt Event: #include "receiptevent.h" -#include "converters.h" #include "logging.h" using namespace Quotient; diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index b728e0bf..3502e3f7 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -3,7 +3,6 @@ #include "roomevent.h" -#include "converters.h" #include "logging.h" #include "redactionevent.h" diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 3141f6b5..b4770224 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -4,7 +4,6 @@ #include "roommemberevent.h" -#include "converters.h" #include "logging.h" #include diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index f518a1b0..b6858b5a 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -5,11 +5,9 @@ #include "basejob.h" #include "connectiondata.h" -#include "quotient_common.h" #include #include -#include #include #include #include diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 9ed58ba8..555c602b 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -5,9 +5,9 @@ #pragma once #include "requestdata.h" -#include "../logging.h" -#include "../converters.h" -#include "../quotient_common.h" +#include "logging.h" +#include "converters.h" // Common for csapi/ headers even though not used here +#include "quotient_common.h" // For DECL_DEPRECATED_ENUMERATOR #include #include -- cgit v1.2.3 From 08612cb253417fe70ef45a1ad08663a0745d748a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 10 Dec 2021 19:26:23 +0100 Subject: No more default construction of events Default construction was only done to support stubbed state in Room and even that did not really use those, opting to construct an event from an empty QJsonObject instead. Now that Room doesn't have stubbed state, default constructors are even less needed. --- lib/events/encryptionevent.h | 20 ++++++++++++-------- lib/events/roomcreateevent.h | 1 - lib/events/roommemberevent.h | 10 +++------- lib/events/roompowerlevelsevent.h | 4 +++- lib/events/roomtombstoneevent.h | 1 - lib/events/simplestateevents.h | 1 - 6 files changed, 18 insertions(+), 19 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index dfb28b2f..56913393 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -12,7 +12,11 @@ class QUOTIENT_API EncryptionEventContent : public EventContent::Base { public: enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined }; - explicit EncryptionEventContent(EncryptionType et = Undefined); + explicit(false) EncryptionEventContent(EncryptionType et); + [[deprecated("This constructor will require explicit EncryptionType soon")]] // + explicit EncryptionEventContent() + : EncryptionEventContent(Undefined) + {} explicit EncryptionEventContent(const QJsonObject& json); EncryptionType encryption; @@ -34,15 +38,15 @@ public: using EncryptionType = EncryptionEventContent::EncryptionType; Q_ENUM(EncryptionType) - explicit EncryptionEvent(const QJsonObject& obj = {}) // TODO: apropriate - // default value + explicit EncryptionEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} - EncryptionEvent(EncryptionEvent&&) = delete; - template - EncryptionEvent(ArgTs&&... contentArgs) - : StateEvent(typeId(), matrixTypeId(), QString(), - std::forward(contentArgs)...) + [[deprecated("This constructor will require an explicit parameter soon")]] // +// explicit EncryptionEvent() +// : EncryptionEvent(QJsonObject()) +// {} + explicit EncryptionEvent(EncryptionEventContent&& content) + : StateEvent(typeId(), matrixTypeId(), QString(), std::move(content)) {} EncryptionType encryption() const { return content().encryption; } diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index 016855b9..989030ac 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -11,7 +11,6 @@ class QUOTIENT_API RoomCreateEvent : public StateEventBase { public: DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent) - explicit RoomCreateEvent() : StateEventBase(typeId(), matrixTypeId()) {} explicit RoomCreateEvent(const QJsonObject& obj) : StateEventBase(typeId(), obj) {} diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index 5e446dbe..3296ae22 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -15,9 +15,7 @@ public: using MembershipType [[deprecated("Use Quotient::Membership instead")]] = Membership; - explicit MemberEventContent(Membership ms = Membership::Join) - : membership(ms) - {} + explicit(false) MemberEventContent(Membership ms) : membership(ms) {} explicit MemberEventContent(const QJsonObject& json); Membership membership; @@ -43,10 +41,8 @@ public: explicit RoomMemberEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} - template - RoomMemberEvent(const QString& userId, ArgTs&&... contentArgs) - : StateEvent(typeId(), matrixTypeId(), userId, - std::forward(contentArgs)...) + RoomMemberEvent(const QString& userId, MemberEventContent&& content) + : StateEvent(typeId(), matrixTypeId(), userId, std::move(content)) {} //! \brief A special constructor to create unknown RoomMemberEvents diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h index 80e27048..415cc814 100644 --- a/lib/events/roompowerlevelsevent.h +++ b/lib/events/roompowerlevelsevent.h @@ -36,10 +36,12 @@ protected: class QUOTIENT_API RoomPowerLevelsEvent : public StateEvent { - Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.power_levels", RoomPowerLevelsEvent) + explicit RoomPowerLevelsEvent(PowerLevelsEventContent&& content) + : StateEvent(typeId(), matrixTypeId(), QString(), std::move(content)) + {} explicit RoomPowerLevelsEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h index e336c448..15d26923 100644 --- a/lib/events/roomtombstoneevent.h +++ b/lib/events/roomtombstoneevent.h @@ -10,7 +10,6 @@ class QUOTIENT_API RoomTombstoneEvent : public StateEventBase { public: DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent) - explicit RoomTombstoneEvent() : StateEventBase(typeId(), matrixTypeId()) {} explicit RoomTombstoneEvent(const QJsonObject& obj) : StateEventBase(typeId(), obj) {} diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index e6c05880..9610574b 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -35,7 +35,6 @@ namespace EventContent { public: \ using value_type = content_type::value_type; \ DEFINE_EVENT_TYPEID(_TypeId, _Name) \ - explicit _Name() : _Name(value_type()) {} \ template \ explicit _Name(T&& value) \ : StateEvent(typeId(), matrixTypeId(), QString(), \ -- cgit v1.2.3 From 1747575321cda4fc11f90c2ffb2148a69d0d9946 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 28 Jan 2022 17:08:10 +0100 Subject: QUO_IMPLICIT Because Apple Clang choked on `explicit(false)`. --- lib/events/encryptionevent.h | 3 ++- lib/events/roommemberevent.h | 2 +- lib/quotient_common.h | 7 +++++++ 3 files changed, 10 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 56913393..124ced33 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -6,13 +6,14 @@ #include "eventcontent.h" #include "stateevent.h" +#include "quotient_common.h" namespace Quotient { class QUOTIENT_API EncryptionEventContent : public EventContent::Base { public: enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined }; - explicit(false) EncryptionEventContent(EncryptionType et); + QUO_IMPLICIT EncryptionEventContent(EncryptionType et); [[deprecated("This constructor will require explicit EncryptionType soon")]] // explicit EncryptionEventContent() : EncryptionEventContent(Undefined) diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index 3296ae22..ceb7826b 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -15,7 +15,7 @@ public: using MembershipType [[deprecated("Use Quotient::Membership instead")]] = Membership; - explicit(false) MemberEventContent(Membership ms) : membership(ms) {} + QUO_IMPLICIT MemberEventContent(Membership ms) : membership(ms) {} explicit MemberEventContent(const QJsonObject& json); Membership membership; diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 02a9f0cd..b3fb3efa 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -25,6 +25,13 @@ Q_ENUM_NS_IMPL(Enum) \ Q_FLAG_NS(Flags) +// Apple Clang hasn't caught up with explicit(bool) yet +#if __cpp_conditional_explicit >= 201806L +#define QUO_IMPLICIT explicit(false) +#else +#define QUO_IMPLICIT +#endif + #define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \ Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended -- 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 --- CMakeLists.txt | 2 +- autotests/testolmaccount.cpp | 2 +- lib/connection.cpp | 108 ++++++++++++++++++++++++----- lib/connection.h | 2 - lib/e2ee/qolminboundsession.cpp | 2 + lib/encryptionmanager.cpp | 149 ---------------------------------------- lib/encryptionmanager.h | 33 --------- lib/events/encryptedfile.cpp | 27 ++++++++ lib/events/encryptedfile.h | 6 ++ lib/jobs/downloadfilejob.cpp | 5 +- lib/mxcreply.cpp | 3 +- 11 files changed, 130 insertions(+), 209 deletions(-) delete mode 100644 lib/encryptionmanager.cpp delete mode 100644 lib/encryptionmanager.h create mode 100644 lib/events/encryptedfile.cpp (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ef3477e..69ac7e20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,7 @@ list(APPEND lib_SRCS lib/events/roomkeyevent.cpp lib/events/stickerevent.cpp lib/events/keyverificationevent.cpp + lib/events/encryptedfile.cpp lib/jobs/requestdata.cpp lib/jobs/basejob.cpp lib/jobs/syncjob.cpp @@ -174,7 +175,6 @@ if (${PROJECT_NAME}_ENABLE_E2EE) lib/e2ee/qolmerrors.cpp lib/e2ee/qolmsession.cpp lib/e2ee/qolmmessage.cpp - lib/encryptionmanager.cpp ) endif() diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 45d158eb..62b786d0 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -537,7 +537,7 @@ void TestOlmAccount::enableEncryption() QString joinedRoom; auto job = alice->createRoom(Connection::PublishRoom, QString(), QString(), QString(), {"@bob:localhost"}); - connect(alice.get(), &Connection::newRoom, this, [alice, bob, joinedRoom, this] (Quotient::Room *room) { + connect(alice.get(), &Connection::newRoom, this, [alice, bob, &joinedRoom, this] (Quotient::Room *room) { room->activateEncryption(); QSignalSpy spy(room, &Room::encryption); 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) { diff --git a/lib/connection.h b/lib/connection.h index 93ee496e..8dec2a0c 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -401,8 +401,6 @@ public: bool lazyLoading() const; void setLazyLoading(bool newValue); - QString e2eeDataDir() const; - /*! Start a pre-created job object on this connection */ Q_INVOKABLE BaseJob* run(BaseJob* job, RunningPolicy runningPolicy = ForegroundRequest); diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 9bc80eef..2e9cc716 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -4,6 +4,8 @@ #include "e2ee/qolminboundsession.h" #include +#include + using namespace Quotient; QOlmError lastError(OlmInboundGroupSession *session) { diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp deleted file mode 100644 index abdcdcee..00000000 --- a/lib/encryptionmanager.cpp +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#include "encryptionmanager.h" - -#include "connection.h" -#include "events/encryptedfile.h" -#include "database.h" - -#include "csapi/keys.h" - -#include -#include -#include - -#include "e2ee/e2ee.h" -#include "e2ee/qolmaccount.h" -#include "e2ee/qolmsession.h" -#include "e2ee/qolmmessage.h" -#include "e2ee/qolmerrors.h" -#include "e2ee/qolmutils.h" -#include -#include - -#include - -using namespace Quotient; -using std::move; - -class EncryptionManager::Private { -public: - EncryptionManager* q; - - Connection* connection; - - // A map from SenderKey to vector of InboundSession - UnorderedMap> sessions; - - void loadSessions() { - sessions = connection->database()->loadOlmSessions(connection->picklingMode()); - } - void saveSession(QOlmSessionPtr& session, const QString &senderKey) { - auto pickleResult = session->pickle(connection->picklingMode()); - if (std::holds_alternative(pickleResult)) { - qCWarning(E2EE) << "Failed to pickle olm session. Error" << std::get(pickleResult); - return; - } - connection->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 : sessions[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); - sessions[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 : sessions[senderKey]) { - const auto result = session->decrypt(message); - if(std::holds_alternative(result)) { - return std::get(result); - } - } - qCWarning(E2EE) << "Failed to decrypt message"; - return {}; - } -}; - -EncryptionManager::EncryptionManager(QObject* parent) - : QObject(parent) - , d(std::make_unique()) -{ - d->q = this; - d->connection = static_cast(parent); - d->loadSessions(); -} - -EncryptionManager::~EncryptionManager() = default; - -QString EncryptionManager::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 = d->sessionDecryptPrekey(preKeyMessage, senderKey, account); - } else if (type == 1) { - QOlmMessage message(body, QOlmMessage::General); - decrypted = d->sessionDecryptGeneral(message, senderKey); - } - return decrypted; -} - -QByteArray EncryptionManager::decryptFile(const QByteArray &ciphertext, EncryptedFile* file) -{ - const auto key = QByteArray::fromBase64(file->key.k.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); - const auto iv = QByteArray::fromBase64(file->iv.toLatin1()); - const auto sha256 = QByteArray::fromBase64(file->hashes["sha256"].toLatin1()); - if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return QByteArray(); - } - QByteArray plaintext(ciphertext.size(), 0); - EVP_CIPHER_CTX *ctx; - int length; - ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)key.data(), (const unsigned char *)iv.data()); - EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); - EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext; -} -#endif // Quotient_E2EE_ENABLED diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h deleted file mode 100644 index 96569980..00000000 --- a/lib/encryptionmanager.h +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifdef Quotient_E2EE_ENABLED -#pragma once - -#include - -#include -#include - -namespace Quotient { -class Connection; -class QOlmAccount; -struct EncryptedFile; - -class EncryptionManager : public QObject { - Q_OBJECT - -public: - explicit EncryptionManager(QObject* parent = nullptr); - ~EncryptionManager(); - QString sessionDecryptMessage(const QJsonObject& personalCipherObject, - const QByteArray& senderKey, std::unique_ptr& account); - static QByteArray decryptFile(const QByteArray &ciphertext, EncryptedFile* encryptedFile); - -private: - class Private; - std::unique_ptr d; -}; - -} // namespace Quotient -#endif // Quotient_E2EE_ENABLED diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp new file mode 100644 index 00000000..5ec344bb --- /dev/null +++ b/lib/events/encryptedfile.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "encryptedfile.h" + +using namespace Quotient; + +QByteArray EncryptedFile::decryptFile(const QByteArray &ciphertext) const +{ + QString _key = key.k; + _key = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); + const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); + if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return QByteArray(); + } + QByteArray plaintext(ciphertext.size(), 0); + EVP_CIPHER_CTX *ctx; + int length; + ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)_key.data(), (const unsigned char *)iv.toLatin1().data()); + EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); + EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext; +} diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 24ac9de1..f271d345 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -5,6 +5,10 @@ #pragma once #include "converters.h" +#include "logging.h" + +#include +#include namespace Quotient { /** @@ -44,6 +48,8 @@ public: QString iv; QHash hashes; QString v; + + QByteArray decryptFile(const QByteArray &ciphertext) const; }; template <> diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 2eea9d59..c5280770 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -9,7 +9,6 @@ #ifdef Quotient_E2EE_ENABLED # include -# include "encryptionmanager.h" # include "events/encryptedfile.h" #endif @@ -126,7 +125,7 @@ BaseJob::Status DownloadFileJob::prepareResult() QByteArray encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = EncryptionManager::decryptFile(encrypted, &file); + auto decrypted = file.decryptFile(encrypted); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -151,7 +150,7 @@ BaseJob::Status DownloadFileJob::prepareResult() auto encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = EncryptionManager::decryptFile(encrypted, &file); + auto decrypted = file.decryptFile(encrypted); d->tempFile->write(decrypted); } else { #endif diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index c7f27b0c..c666cce3 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -9,7 +9,6 @@ #include "room.h" #ifdef Quotient_E2EE_ENABLED -#include "encryptionmanager.h" #include "events/encryptedfile.h" #endif @@ -51,7 +50,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) } else { EncryptedFile file = *d->m_encryptedFile; auto buffer = new QBuffer(this); - buffer->setData(EncryptionManager::decryptFile(d->m_reply->readAll(), &file)); + buffer->setData(file.decryptFile(d->m_reply->readAll())); buffer->open(ReadOnly); d->m_device = buffer; } -- cgit v1.2.3 From 840aead6e77a7ab8605bd2f70820ddd2219bdad5 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 7 Feb 2022 22:04:38 +0100 Subject: Refactor KeyVerificationEvents --- lib/events/keyverificationevent.cpp | 83 ++++++++++++------------------------- 1 file changed, 27 insertions(+), 56 deletions(-) (limited to 'lib/events') diff --git a/lib/events/keyverificationevent.cpp b/lib/events/keyverificationevent.cpp index 938b3bde..4803955d 100644 --- a/lib/events/keyverificationevent.cpp +++ b/lib/events/keyverificationevent.cpp @@ -11,26 +11,22 @@ KeyVerificationRequestEvent::KeyVerificationRequestEvent(const QJsonObject &obj) QString KeyVerificationRequestEvent::fromDevice() const { - return contentJson()["from_device"_ls].toString(); + return contentPart("from_device"_ls); } QString KeyVerificationRequestEvent::transactionId() const { - return contentJson()["transaction_id"_ls].toString(); + return contentPart("transaction_id"_ls); } QStringList KeyVerificationRequestEvent::methods() const { - QStringList methods; - for (const auto &method : contentJson()["methods"].toArray()) { - methods.append(method.toString()); - } - return methods; + return contentPart("methods"_ls); } uint64_t KeyVerificationRequestEvent::timestamp() const { - return contentJson()["timestamp"_ls].toDouble(); + return contentPart("timestamp"_ls); } KeyVerificationStartEvent::KeyVerificationStartEvent(const QJsonObject &obj) @@ -39,62 +35,46 @@ KeyVerificationStartEvent::KeyVerificationStartEvent(const QJsonObject &obj) QString KeyVerificationStartEvent::fromDevice() const { - return contentJson()["from_device"_ls].toString(); + return contentPart("from_device"_ls); } QString KeyVerificationStartEvent::transactionId() const { - return contentJson()["transaction_id"_ls].toString(); + return contentPart("transaction_id"_ls); } QString KeyVerificationStartEvent::method() const { - return contentJson()["method"_ls].toString(); + return contentPart("method"_ls); } Omittable KeyVerificationStartEvent::nextMethod() const { - auto next = contentJson()["method"_ls]; - if (next.isUndefined()) { - return std::nullopt; - } - return next.toString(); + return contentPart>("method_ls"); } QStringList KeyVerificationStartEvent::keyAgreementProtocols() const { Q_ASSERT(method() == QStringLiteral("m.sas.v1")); - QStringList protocols; - for (const auto &proto : contentJson()["key_agreement_protocols"_ls].toArray()) { - protocols.append(proto.toString()); - } - return protocols; + return contentPart("key_agreement_protocols"_ls); } QStringList KeyVerificationStartEvent::hashes() const { Q_ASSERT(method() == QStringLiteral("m.sas.v1")); - QStringList hashes; - for (const auto &hashItem : contentJson()["hashes"_ls].toArray()) { - hashes.append(hashItem.toString()); - } - return hashes; + return contentPart("hashes"_ls); + } QStringList KeyVerificationStartEvent::messageAuthenticationCodes() const { Q_ASSERT(method() == QStringLiteral("m.sas.v1")); - - QStringList codes; - for (const auto &code : contentJson()["message_authentication_codes"_ls].toArray()) { - codes.append(code.toString()); - } - return codes; + return contentPart("message_authentication_codes"_ls); } QString KeyVerificationStartEvent::shortAuthenticationString() const { - return contentJson()["short_authentification_string"_ls].toString(); + return contentPart("short_authentification_string"_ls); } KeyVerificationAcceptEvent::KeyVerificationAcceptEvent(const QJsonObject &obj) @@ -103,36 +83,32 @@ KeyVerificationAcceptEvent::KeyVerificationAcceptEvent(const QJsonObject &obj) QString KeyVerificationAcceptEvent::transactionId() const { - return contentJson()["transaction_id"_ls].toString(); + return contentPart("transaction_id"_ls); } QString KeyVerificationAcceptEvent::method() const { - return contentJson()["method"_ls].toString(); + return contentPart("method"_ls); } QString KeyVerificationAcceptEvent::keyAgreementProtocol() const { - return contentJson()["key_agreement_protocol"_ls].toString(); + return contentPart("key_agreement_protocol"_ls); } QString KeyVerificationAcceptEvent::hashData() const { - return contentJson()["hash"_ls].toString(); + return contentPart("hash"_ls); } QStringList KeyVerificationAcceptEvent::shortAuthenticationString() const { - QStringList strings; - for (const auto &authenticationString : contentJson()["short_authentification_string"].toArray()) { - strings.append(authenticationString.toString()); - } - return strings; + return contentPart("short_authentification_string"_ls); } QString KeyVerificationAcceptEvent::commitement() const { - return contentJson()["commitement"].toString(); + return contentPart("commitment"_ls); } KeyVerificationCancelEvent::KeyVerificationCancelEvent(const QJsonObject &obj) @@ -141,17 +117,17 @@ KeyVerificationCancelEvent::KeyVerificationCancelEvent(const QJsonObject &obj) QString KeyVerificationCancelEvent::transactionId() const { - return contentJson()["transaction_id"_ls].toString(); + return contentPart("transaction_id"_ls); } QString KeyVerificationCancelEvent::reason() const { - return contentJson()["reason"_ls].toString(); + return contentPart("reason"_ls); } QString KeyVerificationCancelEvent::code() const { - return contentJson()["code"_ls].toString(); + return contentPart("code"_ls); } KeyVerificationKeyEvent::KeyVerificationKeyEvent(const QJsonObject &obj) @@ -160,12 +136,12 @@ KeyVerificationKeyEvent::KeyVerificationKeyEvent(const QJsonObject &obj) QString KeyVerificationKeyEvent::transactionId() const { - return contentJson()["transaction_id"_ls].toString(); + return contentPart("transaction_id"_ls); } QString KeyVerificationKeyEvent::key() const { - return contentJson()["key"_ls].toString(); + return contentPart("key"_ls); } KeyVerificationMacEvent::KeyVerificationMacEvent(const QJsonObject &obj) @@ -174,20 +150,15 @@ KeyVerificationMacEvent::KeyVerificationMacEvent(const QJsonObject &obj) QString KeyVerificationMacEvent::transactionId() const { - return contentJson()["transaction_id"].toString(); + return contentPart("transaction_id"_ls); } QString KeyVerificationMacEvent::keys() const { - return contentJson()["keys"].toString(); + return contentPart("keys"_ls); } QHash KeyVerificationMacEvent::mac() const { - QHash macs; - const auto macObj = contentJson()["mac"_ls].toObject(); - for (auto mac = macObj.constBegin(); mac != macObj.constEnd(); mac++) { - macs.insert(mac.key(), mac.value().toString()); - } - return macs; + return contentPart>("mac"_ls); } -- cgit v1.2.3 From 94f34099b8a4c8a40cc99496ceaf9ad5b285c08f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Wed, 9 Feb 2022 22:18:42 +0100 Subject: Move includes to .cpp file --- lib/events/encryptedfile.cpp | 4 ++++ lib/events/encryptedfile.h | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index 5ec344bb..74119127 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -3,6 +3,10 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedfile.h" +#include "logging.h" + +#include +#include using namespace Quotient; diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index f271d345..6199be8e 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -5,10 +5,6 @@ #pragma once #include "converters.h" -#include "logging.h" - -#include -#include namespace Quotient { /** -- cgit v1.2.3 From 73623b69477352f901aa0d02a667bd2438a91491 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 11 Feb 2022 14:22:57 +0100 Subject: EventRelation: defer to non-deprecated symbols --- lib/events/roommessageevent.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 44ef05fb..03a51328 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -101,8 +101,8 @@ namespace EventContent { struct [[deprecated("Use Quotient::EventRelation instead")]] RelatesTo : EventRelation { - static constexpr auto ReplyTypeId() { return Reply(); } - static constexpr auto ReplacementTypeId() { return Replacement(); } + static constexpr auto ReplyTypeId() { return ReplyType; } + static constexpr auto ReplacementTypeId() { return ReplacementType; } }; [[deprecated("Use EventRelation::replyTo() instead")]] inline auto replyTo(QString eventId) -- cgit v1.2.3 From 2cf44607cf0f057e147c2c4fe6dded6c13c58a8a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 14 Feb 2022 14:45:38 +0100 Subject: Don't #include "logging.h" from headers Logging categories used by Quotient are not supposed to be exposed externally, which basically forbids usage of logging in header files. A more flexible solution would involve moving logging.h to private headers but Quotient doesn't have that thing yet. --- lib/events/event.h | 1 - lib/syncdata.cpp | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 113fa3fa..b6f36306 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -4,7 +4,6 @@ #pragma once #include "converters.h" -#include "logging.h" #include "function_traits.h" namespace Quotient { diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index b0cd8e4d..b896d710 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -3,6 +3,8 @@ #include "syncdata.h" +#include "logging.h" + #include "events/eventloader.h" #include -- cgit v1.2.3 From 7221c10118a8895ec39ba6a78768574a4b00dfed Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 14 Feb 2022 15:41:36 +0100 Subject: Revert "Don't #include "logging.h" from headers" This reverts commit 2cf44607cf0f057e147c2c4fe6dded6c13c58a8a (that was stupid, honestly). --- lib/events/event.h | 1 + lib/syncdata.cpp | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index b6f36306..113fa3fa 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -4,6 +4,7 @@ #pragma once #include "converters.h" +#include "logging.h" #include "function_traits.h" namespace Quotient { diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index b896d710..b0cd8e4d 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -3,8 +3,6 @@ #include "syncdata.h" -#include "logging.h" - #include "events/eventloader.h" #include -- cgit v1.2.3 From 34cd6a6c6dfe2981001341f39e4e1e9aaa9c8898 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 14 Feb 2022 21:46:27 +0100 Subject: Don't set lifetime as version in call invites --- lib/events/callinviteevent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index 5ea54662..11d50768 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -36,7 +36,7 @@ CallInviteEvent::CallInviteEvent(const QJsonObject& obj) CallInviteEvent::CallInviteEvent(const QString& callId, const int lifetime, const QString& sdp) : CallEventBase( - typeId(), matrixTypeId(), callId, lifetime, + typeId(), matrixTypeId(), callId, 0, { { QStringLiteral("lifetime"), lifetime }, { QStringLiteral("offer"), QJsonObject { { QStringLiteral("type"), QStringLiteral("offer") }, -- cgit v1.2.3 From 52a787eefb3fb3d147648d08fc439a4b8a966fd3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 16 Feb 2022 17:57:30 +0100 Subject: Add a few missing QUOTIENT_API stanzas Also, removed Q_GADGET macros from key verification events as those don't seem to do anything (no Q_ENUM/Q_FLAG things, namely). --- lib/database.h | 3 +-- lib/e2ee/e2ee.h | 2 +- lib/e2ee/qolmaccount.h | 14 +++++++------- lib/e2ee/qolmerrors.h | 4 +++- lib/e2ee/qolminboundsession.h | 2 +- lib/e2ee/qolmmessage.h | 4 +++- lib/e2ee/qolmoutboundsession.h | 2 +- lib/e2ee/qolmsession.h | 2 +- lib/e2ee/qolmutility.h | 2 +- lib/e2ee/qolmutils.h | 4 ++-- lib/events/encryptedfile.h | 2 +- lib/events/keyverificationevent.h | 16 +++++----------- 12 files changed, 27 insertions(+), 30 deletions(-) (limited to 'lib/events') diff --git a/lib/database.h b/lib/database.h index 96256a55..d244dc0b 100644 --- a/lib/database.h +++ b/lib/database.h @@ -10,10 +10,9 @@ #include "e2ee/e2ee.h" namespace Quotient { -class Database : public QObject +class QUOTIENT_API Database : public QObject { Q_OBJECT - public: Database(const QString& matrixId, QObject* parent); diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 41cd2878..4c825376 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -65,7 +65,7 @@ struct IdentityKeys }; //! Struct representing the one-time keys. -struct OneTimeKeys +struct QUOTIENT_API OneTimeKeys { QMap> keys; diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 00afc0e6..17aca8aa 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -25,7 +25,7 @@ using QOlmSessionPtr = std::unique_ptr; //! \code{.cpp} //! const auto olmAccount = new QOlmAccount(this); //! \endcode -class QOlmAccount : public QObject +class QUOTIENT_API QOlmAccount : public QObject { Q_OBJECT public: @@ -111,13 +111,13 @@ private: QString m_deviceId; }; -bool verifyIdentitySignature(const DeviceKeys &deviceKeys, - const QString &deviceId, - const QString &userId); +QUOTIENT_API bool verifyIdentitySignature(const DeviceKeys& deviceKeys, + const QString& deviceId, + const QString& userId); //! checks if the signature is signed by the signing_key -bool ed25519VerifySignature(const QString &signingKey, - const QJsonObject &obj, - const QString &signature); +QUOTIENT_API bool ed25519VerifySignature(const QString& signingKey, + const QJsonObject& obj, + const QString& signature); } // namespace Quotient diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h index 24e87d95..20e61c12 100644 --- a/lib/e2ee/qolmerrors.h +++ b/lib/e2ee/qolmerrors.h @@ -4,6 +4,8 @@ #pragma once +#include "quotient_export.h" + namespace Quotient { //! All errors that could be caused by an operation regarding Olm //! Errors are named exactly like the ones in libolm. @@ -21,6 +23,6 @@ enum QOlmError Unknown, }; -QOlmError fromString(const char* error_raw); +QUOTIENT_API QOlmError fromString(const char* error_raw); } //namespace Quotient diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 7d52991c..1f5dadd3 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -15,7 +15,7 @@ namespace Quotient { //! An in-bound group session is responsible for decrypting incoming //! communication in a Megolm session. -struct QOlmInboundGroupSession +class QUOTIENT_API QOlmInboundGroupSession { public: ~QOlmInboundGroupSession(); diff --git a/lib/e2ee/qolmmessage.h b/lib/e2ee/qolmmessage.h index 52aba78c..557c02b1 100644 --- a/lib/e2ee/qolmmessage.h +++ b/lib/e2ee/qolmmessage.h @@ -4,6 +4,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -16,7 +18,7 @@ namespace Quotient { * * The class provides functions to get a type and the ciphertext. */ -class QOlmMessage : public QByteArray { +class QUOTIENT_API QOlmMessage : public QByteArray { Q_GADGET public: enum Type { diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 39263c77..0122bbfd 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -13,7 +13,7 @@ namespace Quotient { //! An out-bound group session is responsible for encrypting outgoing //! communication in a Megolm session. -class QOlmOutboundGroupSession +class QUOTIENT_API QOlmOutboundGroupSession { public: ~QOlmOutboundGroupSession(); diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index 1febfa0f..889a606d 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -18,7 +18,7 @@ class QOlmSession; //! Either an outbound or inbound session for secure communication. -class QOlmSession +class QUOTIENT_API QOlmSession { public: ~QOlmSession(); diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h index b360d625..b2e79e29 100644 --- a/lib/e2ee/qolmutility.h +++ b/lib/e2ee/qolmutility.h @@ -17,7 +17,7 @@ class Connection; //! Allows you to make use of crytographic hashing via SHA-2 and //! verifying ed25519 signatures. -class QOlmUtility +class QUOTIENT_API QOlmUtility { public: QOlmUtility(); diff --git a/lib/e2ee/qolmutils.h b/lib/e2ee/qolmutils.h index bbd71332..f218e628 100644 --- a/lib/e2ee/qolmutils.h +++ b/lib/e2ee/qolmutils.h @@ -10,6 +10,6 @@ namespace Quotient { // Convert PicklingMode to key -QByteArray toKey(const PicklingMode &mode); -QByteArray getRandom(size_t bufferSize); +QUOTIENT_API QByteArray toKey(const PicklingMode &mode); +QUOTIENT_API QByteArray getRandom(size_t bufferSize); } diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 6199be8e..43bafc49 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -29,7 +29,7 @@ public: bool ext; }; -struct EncryptedFile +struct QUOTIENT_API EncryptedFile { Q_GADGET Q_PROPERTY(QUrl url MEMBER url CONSTANT) diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index 13e7dcdd..497e56a2 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -7,8 +7,7 @@ namespace Quotient { /// Requests a key verification with another user's devices. /// Typically sent as a to-device event. -class KeyVerificationRequestEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationRequestEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.request", KeyVerificationRequestEvent) @@ -33,8 +32,7 @@ public: REGISTER_EVENT_TYPE(KeyVerificationRequestEvent) /// Begins a key verification process. -class KeyVerificationStartEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationStartEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent) @@ -76,8 +74,7 @@ REGISTER_EVENT_TYPE(KeyVerificationStartEvent) /// Accepts a previously sent m.key.verification.start message. /// Typically sent as a to-device event. -class KeyVerificationAcceptEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationAcceptEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.accept", KeyVerificationAcceptEvent) @@ -111,8 +108,7 @@ public: }; REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent) -class KeyVerificationCancelEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationCancelEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent) @@ -133,7 +129,6 @@ REGISTER_EVENT_TYPE(KeyVerificationCancelEvent) /// Sends the ephemeral public key for a device to the partner device. /// Typically sent as a to-device event. class KeyVerificationKeyEvent : public Event { - Q_GADGET public: DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent) @@ -148,8 +143,7 @@ public: REGISTER_EVENT_TYPE(KeyVerificationKeyEvent) /// Sends the MAC of a device's key to the partner device. -class KeyVerificationMacEvent : public Event { - Q_GADGET +class QUOTIENT_API KeyVerificationMacEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent) -- 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 +++++++----- lib/e2ee/e2ee.h | 76 ++++++++++++++++------------------------ lib/e2ee/qolmaccount.cpp | 79 ++++++++++++++++-------------------------- lib/e2ee/qolminboundsession.h | 26 +++++++------- lib/e2ee/qolmmessage.cpp | 4 +-- lib/e2ee/qolmmessage.h | 3 +- lib/e2ee/qolmoutboundsession.h | 4 +-- lib/e2ee/qolmsession.h | 41 +++++++++++++++------- lib/e2ee/qolmutility.h | 3 -- lib/events/encryptedevent.cpp | 13 +++++++ lib/events/encryptedevent.h | 14 +++----- lib/syncdata.h | 2 +- 12 files changed, 138 insertions(+), 149 deletions(-) (limited to 'lib/events') 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")) { diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 4c825376..e21aa87b 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -5,43 +5,34 @@ #pragma once -#include -#include #include "converters.h" +#include "quotient_common.h" + +#include #include -#include -#include -#include -#include +namespace Quotient { -#include "util.h" +constexpr auto CiphertextKeyL = "ciphertext"_ls; +constexpr auto SenderKeyKeyL = "sender_key"_ls; +constexpr auto DeviceIdKeyL = "device_id"_ls; +constexpr auto SessionIdKeyL = "session_id"_ls; -namespace Quotient { +constexpr auto AlgorithmKeyL = "algorithm"_ls; +constexpr auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; +constexpr auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls; + +constexpr auto AlgorithmKey = "algorithm"_ls; +constexpr auto RotationPeriodMsKey = "rotation_period_ms"_ls; +constexpr auto RotationPeriodMsgsKey = "rotation_period_msgs"_ls; + +constexpr auto Ed25519Key = "ed25519"_ls; +constexpr auto Curve25519Key = "curve25519"_ls; +constexpr auto SignedCurve25519Key = "signed_curve25519"_ls; + +constexpr auto OlmV1Curve25519AesSha2AlgoKey = "m.olm.v1.curve25519-aes-sha2"_ls; +constexpr auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls; -inline const auto CiphertextKeyL = "ciphertext"_ls; -inline const auto SenderKeyKeyL = "sender_key"_ls; -inline const auto DeviceIdKeyL = "device_id"_ls; -inline const auto SessionIdKeyL = "session_id"_ls; - -inline const auto AlgorithmKeyL = "algorithm"_ls; -inline const auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; -inline const auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls; - -inline const auto AlgorithmKey = QStringLiteral("algorithm"); -inline const auto RotationPeriodMsKey = QStringLiteral("rotation_period_ms"); -inline const auto RotationPeriodMsgsKey = - QStringLiteral("rotation_period_msgs"); - -inline const auto Ed25519Key = QStringLiteral("ed25519"); -inline const auto Curve25519Key = QStringLiteral("curve25519"); -inline const auto SignedCurve25519Key = QStringLiteral("signed_curve25519"); -inline const auto OlmV1Curve25519AesSha2AlgoKey = - QStringLiteral("m.olm.v1.curve25519-aes-sha2"); -inline const auto MegolmV1AesSha2AlgoKey = - QStringLiteral("m.megolm.v1.aes-sha2"); -inline const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoKey, - MegolmV1AesSha2AlgoKey }; struct Unencrypted {}; struct Encrypted { QByteArray key; @@ -55,9 +46,6 @@ using QOlmSessionPtr = std::unique_ptr; class QOlmInboundGroupSession; using QOlmInboundGroupSessionPtr = std::unique_ptr; -template struct overloaded : Ts... { using Ts::operator()...; }; -template overloaded(Ts...) -> overloaded; - struct IdentityKeys { QByteArray curve25519; @@ -73,16 +61,13 @@ struct QUOTIENT_API OneTimeKeys QMap curve25519() const; //! Get a reference to the hashmap corresponding to given key type. - std::optional> get(QString keyType) const; +// std::optional> get(QString keyType) const; }; //! Struct representing the signed one-time keys. class SignedOneTimeKey { public: - SignedOneTimeKey() = default; - SignedOneTimeKey(const SignedOneTimeKey &) = default; - SignedOneTimeKey &operator=(const SignedOneTimeKey &) = default; //! Required. The unpadded Base64-encoded 32-byte Curve25519 public key. QString key; @@ -94,8 +79,7 @@ public: template <> struct JsonObjectConverter { - static void fillFrom(const QJsonObject& jo, - SignedOneTimeKey& result) + static void fillFrom(const QJsonObject& jo, SignedOneTimeKey& result) { fromJson(jo.value("key"_ls), result.key); fromJson(jo.value("signatures"_ls), result.signatures); @@ -108,24 +92,22 @@ struct JsonObjectConverter { } }; -bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs); - template class asKeyValueRange { public: - asKeyValueRange(T &data) - : m_data{data} - { - } + asKeyValueRange(T& data) + : m_data { data } + {} auto begin() { return m_data.keyValueBegin(); } - auto end() { return m_data.keyValueEnd(); } private: T &m_data; }; +template +asKeyValueRange(T&) -> asKeyValueRange; } // namespace Quotient diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 34ee7ea0..9cbb14f5 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -3,53 +3,41 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "qolmaccount.h" + #include "connection.h" -#include "csapi/keys.h" -#include "e2ee/qolmutils.h" #include "e2ee/qolmutility.h" -#include -#include -#include -#include +#include "e2ee/qolmutils.h" + +#include "csapi/keys.h" + +#include using namespace Quotient; QMap OneTimeKeys::curve25519() const { - return keys[QStringLiteral("curve25519")]; + return keys[Curve25519Key]; } -std::optional> OneTimeKeys::get(QString keyType) const -{ - if (!keys.contains(keyType)) { - return std::nullopt; - } - return keys[keyType]; -} - -bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs) -{ - return lhs.curve25519 == rhs.curve25519 && lhs.ed25519 == rhs.ed25519; -} +//std::optional> OneTimeKeys::get(QString keyType) const +//{ +// if (!keys.contains(keyType)) { +// return std::nullopt; +// } +// return keys[keyType]; +//} // Convert olm error to enum QOlmError lastError(OlmAccount *account) { return fromString(olm_account_last_error(account)); } -QByteArray getRandom(size_t bufferSize) -{ - QByteArray buffer(bufferSize, '0'); - std::generate(buffer.begin(), buffer.end(), std::rand); - return buffer; -} - -QOlmAccount::QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent) +QOlmAccount::QOlmAccount(const QString& userId, const QString& deviceId, + QObject* parent) : QObject(parent) , m_userId(userId) , m_deviceId(deviceId) -{ -} +{} QOlmAccount::~QOlmAccount() { @@ -66,7 +54,7 @@ void QOlmAccount::createNewAccount() if (error == olm_error()) { throw lastError(m_account); } - Q_EMIT needsSave(); + emit needsSave(); } void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) @@ -161,7 +149,7 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const if (error == olm_error()) { throw lastError(m_account); } - Q_EMIT needsSave(); + emit needsSave(); return error; } @@ -220,14 +208,11 @@ std::optional QOlmAccount::removeOneTimeKeys(const QOlmSessionPtr &se if (error == olm_error()) { return lastError(m_account); } - Q_EMIT needsSave(); + emit needsSave(); return std::nullopt; } -OlmAccount *QOlmAccount::data() -{ - return m_account; -} +OlmAccount* QOlmAccount::data() { return m_account; } DeviceKeys QOlmAccount::deviceKeys() const { @@ -284,31 +269,27 @@ std::variant QOlmAccount::createOutboundSession(const void QOlmAccount::markKeysAsPublished() { olm_account_mark_keys_as_published(m_account); - Q_EMIT needsSave(); + emit needsSave(); } -bool Quotient::verifyIdentitySignature(const DeviceKeys &deviceKeys, - const QString &deviceId, - const QString &userId) +bool Quotient::verifyIdentitySignature(const DeviceKeys& deviceKeys, + const QString& deviceId, + const QString& userId) { const auto signKeyId = "ed25519:" + deviceId; const auto signingKey = deviceKeys.keys[signKeyId]; const auto signature = deviceKeys.signatures[userId][signKeyId]; - if (signature.isEmpty()) { - return false; - } - return ed25519VerifySignature(signingKey, toJson(deviceKeys), signature); } -bool Quotient::ed25519VerifySignature(const QString &signingKey, - const QJsonObject &obj, - const QString &signature) +bool Quotient::ed25519VerifySignature(const QString& signingKey, + const QJsonObject& obj, + const QString& signature) { - if (signature.isEmpty()) { + if (signature.isEmpty()) return false; - } + QJsonObject obj1 = obj; obj1.remove("unsigned"); diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 1f5dadd3..437f753d 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -4,12 +4,12 @@ #pragma once -#include -#include -#include -#include "olm/olm.h" -#include "e2ee/qolmerrors.h" #include "e2ee/e2ee.h" +#include "e2ee/qolmerrors.h" +#include "olm/olm.h" + +#include +#include namespace Quotient { @@ -20,16 +20,18 @@ class QUOTIENT_API QOlmInboundGroupSession public: ~QOlmInboundGroupSession(); //! Creates a new instance of `OlmInboundGroupSession`. - static std::unique_ptr create(const QByteArray &key); + static std::unique_ptr create(const QByteArray& key); //! Import an inbound group session, from a previous export. - static std::unique_ptr import(const QByteArray &key); + static std::unique_ptr import(const QByteArray& key); //! Serialises an `OlmInboundGroupSession` to encrypted Base64. QByteArray pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. - static std::variant, QOlmError> unpickle(const QByteArray &picked, const PicklingMode &mode); + static std::variant, QOlmError> + unpickle(const QByteArray& picked, const PicklingMode& mode); //! Decrypts ciphertext received for this group session. - std::variant, QOlmError> decrypt(const QByteArray &message); + std::variant, QOlmError> decrypt( + const QByteArray& message); //! Export the base64-encoded ratchet key for this session, at the given index, //! in a format which can be used by import. std::variant exportSession(uint32_t messageIndex); @@ -38,11 +40,11 @@ public: //! Get a base64-encoded identifier for this session. QByteArray sessionId() const; bool isVerified() const; - QOlmInboundGroupSession(OlmInboundGroupSession *session); + + QOlmInboundGroupSession(OlmInboundGroupSession* session); private: - OlmInboundGroupSession *m_groupSession; + OlmInboundGroupSession* m_groupSession; }; using QOlmInboundGroupSessionPtr = std::unique_ptr; -using OlmInboundGroupSessionPtr = std::unique_ptr; } // namespace Quotient diff --git a/lib/e2ee/qolmmessage.cpp b/lib/e2ee/qolmmessage.cpp index 15008b75..81b166b0 100644 --- a/lib/e2ee/qolmmessage.cpp +++ b/lib/e2ee/qolmmessage.cpp @@ -6,11 +6,11 @@ using namespace Quotient; -QOlmMessage::QOlmMessage(const QByteArray &ciphertext, QOlmMessage::Type type) +QOlmMessage::QOlmMessage(QByteArray ciphertext, QOlmMessage::Type type) : QByteArray(std::move(ciphertext)) , m_messageType(type) { - Q_ASSERT_X(!ciphertext.isEmpty(), "olm message", "Ciphertext is empty"); + Q_ASSERT_X(!isEmpty(), "olm message", "Ciphertext is empty"); } QOlmMessage::QOlmMessage(const QOlmMessage &message) diff --git a/lib/e2ee/qolmmessage.h b/lib/e2ee/qolmmessage.h index 557c02b1..5d5db636 100644 --- a/lib/e2ee/qolmmessage.h +++ b/lib/e2ee/qolmmessage.h @@ -28,8 +28,9 @@ public: Q_ENUM(Type) QOlmMessage() = default; - explicit QOlmMessage(const QByteArray &ciphertext, Type type = General); + explicit QOlmMessage(QByteArray ciphertext, Type type = General); explicit QOlmMessage(const QOlmMessage &message); + ~QOlmMessage() = default; static QOlmMessage fromCiphertext(const QByteArray &ciphertext); diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 0122bbfd..32ba2b3b 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -24,7 +24,8 @@ public: std::variant pickle(const PicklingMode &mode); //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. - static std::variant, QOlmError> unpickle(QByteArray &pickled, const PicklingMode &mode); + static std::variant, QOlmError> + unpickle(QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. std::variant encrypt(const QString &plaintext); @@ -48,5 +49,4 @@ private: }; using QOlmOutboundGroupSessionPtr = std::unique_ptr; -using OlmOutboundGroupSessionPtr = std::unique_ptr; } diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index 889a606d..f20c9837 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -16,20 +16,31 @@ namespace Quotient { class QOlmAccount; class QOlmSession; - //! Either an outbound or inbound session for secure communication. class QUOTIENT_API QOlmSession { public: ~QOlmSession(); //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. - static std::variant, QOlmError> createInboundSession(QOlmAccount *account, const QOlmMessage &preKeyMessage); - static std::variant, QOlmError> createInboundSessionFrom(QOlmAccount *account, const QString &theirIdentityKey, const QOlmMessage &preKeyMessage); - static std::variant, QOlmError> createOutboundSession(QOlmAccount *account, const QString &theirIdentityKey, const QString &theirOneTimeKey); + static std::variant, QOlmError> + createInboundSession(QOlmAccount* account, const QOlmMessage& preKeyMessage); + + static std::variant, QOlmError> + createInboundSessionFrom(QOlmAccount* account, + const QString& theirIdentityKey, + const QOlmMessage& preKeyMessage); + + static std::variant, QOlmError> + createOutboundSession(QOlmAccount* account, const QString& theirIdentityKey, + const QString& theirOneTimeKey); + //! Serialises an `QOlmSession` to encrypted Base64. std::variant pickle(const PicklingMode &mode); + //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static std::variant, QOlmError> unpickle(const QByteArray &pickled, const PicklingMode &mode); + static std::variant, QOlmError> unpickle( + const QByteArray& pickled, const PicklingMode& mode); + //! Encrypts a plaintext message using the session. QOlmMessage encrypt(const QString &plaintext); @@ -48,29 +59,33 @@ public: bool hasReceivedMessage() const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSession(const QOlmMessage &preKeyMessage) const; + std::variant matchesInboundSession( + const QOlmMessage& preKeyMessage) const; //! Checks if the 'prekey' message is for this in-bound session. - std::variant matchesInboundSessionFrom(const QString &theirIdentityKey, const QOlmMessage &preKeyMessage) const; + std::variant matchesInboundSessionFrom( + const QString& theirIdentityKey, const QOlmMessage& preKeyMessage) const; friend bool operator<(const QOlmSession& lhs, const QOlmSession& rhs) { return lhs.sessionId() < rhs.sessionId(); } - friend bool operator<(const std::unique_ptr &lhs, const std::unique_ptr &rhs) { + friend bool operator<(const std::unique_ptr& lhs, + const std::unique_ptr& rhs) + { return *lhs < *rhs; } - OlmSession *raw() const - { - return m_session; - } + OlmSession* raw() const { return m_session; } + QOlmSession(OlmSession* session); private: //! Helper function for creating new sessions and handling errors. static OlmSession* create(); - static std::variant, QOlmError> createInbound(QOlmAccount *account, const QOlmMessage& preKeyMessage, bool from = false, const QString& theirIdentityKey = ""); + static std::variant, QOlmError> createInbound( + QOlmAccount* account, const QOlmMessage& preKeyMessage, + bool from = false, const QString& theirIdentityKey = ""); OlmSession* m_session; }; } //namespace Quotient diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h index b2e79e29..a12af49a 100644 --- a/lib/e2ee/qolmutility.h +++ b/lib/e2ee/qolmutility.h @@ -4,7 +4,6 @@ #pragma once -#include #include #include "e2ee/qolmerrors.h" @@ -13,7 +12,6 @@ struct OlmUtility; namespace Quotient { class QOlmSession; -class Connection; //! Allows you to make use of crytographic hashing via SHA-2 and //! verifying ed25519 signatures. @@ -37,7 +35,6 @@ public: std::variant ed25519Verify(const QByteArray &key, const QByteArray &message, const QByteArray &signature); - private: OlmUtility *m_utility; diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 1b5e4441..ba4dd154 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -33,6 +33,19 @@ EncryptedEvent::EncryptedEvent(const QJsonObject& obj) qCDebug(E2EE) << "Encrypted event from" << senderId(); } +QString EncryptedEvent::algorithm() const +{ + auto algo = contentPart(AlgorithmKeyL); + static constexpr auto SupportedAlgorithms = + make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); + if (std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), + algo) == SupportedAlgorithms.cend()) { + qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo + << "is not supported"; + } + return algo; +} + RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const { auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index c838bbd8..72efffd4 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -39,22 +39,16 @@ public: const QString& deviceId, const QString& sessionId); explicit EncryptedEvent(const QJsonObject& obj); - QString algorithm() const - { - QString algo = contentPart(AlgorithmKeyL); - if (!SupportedAlgorithms.contains(algo)) { - qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo - << "is not supported"; - } - return algo; - } + QString algorithm() const; QByteArray ciphertext() const { return contentPart(CiphertextKeyL).toLatin1(); } QJsonObject ciphertext(const QString& identityKey) const { - return contentPart(CiphertextKeyL).value(identityKey).toObject(); + return contentPart(CiphertextKeyL) + .value(identityKey) + .toObject(); } QString senderKey() const { return contentPart(SenderKeyKeyL); } diff --git a/lib/syncdata.h b/lib/syncdata.h index 633f4b52..6b70140d 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -54,7 +54,7 @@ struct DevicesList { QStringList left; }; -QDebug operator<<(QDebug dhg, const DevicesList &devicesList); +QDebug operator<<(QDebug dhg, const DevicesList& devicesList); template <> struct JsonObjectConverter { -- cgit v1.2.3 From 445e34f26450cf8262a65b74e1294579d9cd56be Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 13 Feb 2022 22:11:52 +0100 Subject: Fix file decryption --- lib/events/encryptedfile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index 74119127..dbb72af8 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -13,7 +13,7 @@ using namespace Quotient; QByteArray EncryptedFile::decryptFile(const QByteArray &ciphertext) const { QString _key = key.k; - _key = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); + auto keyBytes = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { qCWarning(E2EE) << "Hash verification failed for file"; @@ -23,7 +23,7 @@ QByteArray EncryptedFile::decryptFile(const QByteArray &ciphertext) const EVP_CIPHER_CTX *ctx; int length; ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)_key.data(), (const unsigned char *)iv.toLatin1().data()); + EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)keyBytes.data(), (const unsigned char *)QByteArray::fromBase64(iv.toLatin1()).data()); EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); EVP_CIPHER_CTX_free(ctx); -- 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 +++++-- lib/e2ee/e2ee.h | 9 +++++++++ lib/events/encryptedevent.cpp | 9 +++------ 3 files changed, 17 insertions(+), 8 deletions(-) (limited to 'lib/events') 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)) { diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 361c48ff..268cb525 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -33,6 +33,15 @@ constexpr auto SignedCurve25519Key = "signed_curve25519"_ls; constexpr auto OlmV1Curve25519AesSha2AlgoKey = "m.olm.v1.curve25519-aes-sha2"_ls; constexpr auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls; +inline bool isSupportedAlgorithm(const QString& algorithm) +{ + static constexpr auto SupportedAlgorithms = + make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); + return std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), + algorithm) + != SupportedAlgorithms.cend(); +} + struct Unencrypted {}; struct Encrypted { QByteArray key; diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index ba4dd154..9d07a35f 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -35,14 +35,11 @@ EncryptedEvent::EncryptedEvent(const QJsonObject& obj) QString EncryptedEvent::algorithm() const { - auto algo = contentPart(AlgorithmKeyL); - static constexpr auto SupportedAlgorithms = - make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); - if (std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), - algo) == SupportedAlgorithms.cend()) { + const auto algo = contentPart(AlgorithmKeyL); + if (!isSupportedAlgorithm(algo)) qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo << "is not supported"; - } + return algo; } -- 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 ++++++++--- lib/events/encryptedfile.cpp | 89 +++++++++++++++++++++++++++++++++++++------- lib/events/encryptedfile.h | 40 +++----------------- lib/jobs/downloadfilejob.cpp | 6 +-- 4 files changed, 99 insertions(+), 57 deletions(-) (limited to 'lib/events') 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(); diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index dbb72af8..d4a517bd 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -5,27 +5,88 @@ #include "encryptedfile.h" #include "logging.h" +#ifdef Quotient_E2EE_ENABLED #include #include +#endif using namespace Quotient; -QByteArray EncryptedFile::decryptFile(const QByteArray &ciphertext) const +QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const { - QString _key = key.k; - auto keyBytes = QByteArray::fromBase64(_key.replace(QLatin1Char('_'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('+')).toLatin1()); +#ifdef Quotient_E2EE_ENABLED + auto _key = key.k; + const auto keyBytes = QByteArray::fromBase64( + _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); - if(sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { + if (sha256 + != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { qCWarning(E2EE) << "Hash verification failed for file"; - return QByteArray(); + return {}; } - QByteArray plaintext(ciphertext.size(), 0); - EVP_CIPHER_CTX *ctx; - int length; - ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, (const unsigned char *)keyBytes.data(), (const unsigned char *)QByteArray::fromBase64(iv.toLatin1()).data()); - EVP_DecryptUpdate(ctx, (unsigned char *)plaintext.data(), &length, (const unsigned char *)ciphertext.data(), ciphertext.size()); - EVP_DecryptFinal_ex(ctx, (unsigned char *)plaintext.data() + length, &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext; + { + int length; + auto* ctx = EVP_CIPHER_CTX_new(); + QByteArray plaintext(ciphertext.size() + EVP_CIPHER_CTX_block_size(ctx) + - 1, + '\0'); + EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, + reinterpret_cast( + keyBytes.data()), + reinterpret_cast( + QByteArray::fromBase64(iv.toLatin1()).data())); + EVP_DecryptUpdate( + ctx, reinterpret_cast(plaintext.data()), &length, + reinterpret_cast(ciphertext.data()), + ciphertext.size()); + EVP_DecryptFinal_ex(ctx, + reinterpret_cast(plaintext.data()) + + length, + &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext; + } +#else + qWarning(MAIN) << "This build of libQuotient doesn't support E2EE, " + "cannot decrypt the file"; + return ciphertext; +#endif +} + +void JsonObjectConverter::dumpTo(QJsonObject& jo, + const EncryptedFile& pod) +{ + addParam<>(jo, QStringLiteral("url"), pod.url); + addParam<>(jo, QStringLiteral("key"), pod.key); + addParam<>(jo, QStringLiteral("iv"), pod.iv); + addParam<>(jo, QStringLiteral("hashes"), pod.hashes); + addParam<>(jo, QStringLiteral("v"), pod.v); +} + +void JsonObjectConverter::fillFrom(const QJsonObject& jo, + EncryptedFile& pod) +{ + fromJson(jo.value("url"_ls), pod.url); + fromJson(jo.value("key"_ls), pod.key); + fromJson(jo.value("iv"_ls), pod.iv); + fromJson(jo.value("hashes"_ls), pod.hashes); + fromJson(jo.value("v"_ls), pod.v); +} + +void JsonObjectConverter::dumpTo(QJsonObject &jo, const JWK &pod) +{ + addParam<>(jo, QStringLiteral("kty"), pod.kty); + addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); + addParam<>(jo, QStringLiteral("alg"), pod.alg); + addParam<>(jo, QStringLiteral("k"), pod.k); + addParam<>(jo, QStringLiteral("ext"), pod.ext); +} + +void JsonObjectConverter::fillFrom(const QJsonObject &jo, JWK &pod) +{ + fromJson(jo.value("kty"_ls), pod.kty); + fromJson(jo.value("key_ops"_ls), pod.keyOps); + fromJson(jo.value("alg"_ls), pod.alg); + fromJson(jo.value("k"_ls), pod.k); + fromJson(jo.value("ext"_ls), pod.ext); } diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 43bafc49..0558563f 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -49,42 +49,14 @@ public: }; template <> -struct JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const EncryptedFile& pod) - { - addParam<>(jo, QStringLiteral("url"), pod.url); - addParam<>(jo, QStringLiteral("key"), pod.key); - addParam<>(jo, QStringLiteral("iv"), pod.iv); - addParam<>(jo, QStringLiteral("hashes"), pod.hashes); - addParam<>(jo, QStringLiteral("v"), pod.v); - } - static void fillFrom(const QJsonObject& jo, EncryptedFile& pod) - { - fromJson(jo.value("url"_ls), pod.url); - fromJson(jo.value("key"_ls), pod.key); - fromJson(jo.value("iv"_ls), pod.iv); - fromJson(jo.value("hashes"_ls), pod.hashes); - fromJson(jo.value("v"_ls), pod.v); - } +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const EncryptedFile& pod); + static void fillFrom(const QJsonObject& jo, EncryptedFile& pod); }; template <> -struct JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const JWK& pod) - { - addParam<>(jo, QStringLiteral("kty"), pod.kty); - addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); - addParam<>(jo, QStringLiteral("alg"), pod.alg); - addParam<>(jo, QStringLiteral("k"), pod.k); - addParam<>(jo, QStringLiteral("ext"), pod.ext); - } - static void fillFrom(const QJsonObject& jo, JWK& pod) - { - fromJson(jo.value("kty"_ls), pod.kty); - fromJson(jo.value("key_ops"_ls), pod.keyOps); - fromJson(jo.value("alg"_ls), pod.alg); - fromJson(jo.value("k"_ls), pod.k); - fromJson(jo.value("ext"_ls), pod.ext); - } +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const JWK& pod); + static void fillFrom(const QJsonObject& jo, JWK& pod); }; } // namespace Quotient diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 634e5fb9..d00fc5f4 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -127,7 +127,7 @@ BaseJob::Status DownloadFileJob::prepareResult() QByteArray encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = file.decryptFile(encrypted); + const auto decrypted = file.decryptFile(encrypted); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -149,10 +149,10 @@ BaseJob::Status DownloadFileJob::prepareResult() #ifdef Quotient_E2EE_ENABLED if (d->encryptedFile.has_value()) { d->tempFile->seek(0); - auto encrypted = d->tempFile->readAll(); + const auto encrypted = d->tempFile->readAll(); EncryptedFile file = *d->encryptedFile; - auto decrypted = file.decryptFile(encrypted); + const auto decrypted = file.decryptFile(encrypted); d->tempFile->write(decrypted); } else { #endif -- cgit v1.2.3 From 4f137493f026afba2299ae5b65ef6eb17939aece Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 27 Feb 2022 19:15:16 +0100 Subject: Add constructor for creating roomkeyevents --- lib/events/roomkeyevent.cpp | 13 +++++++++++++ lib/events/roomkeyevent.h | 1 + 2 files changed, 14 insertions(+) (limited to 'lib/events') diff --git a/lib/events/roomkeyevent.cpp b/lib/events/roomkeyevent.cpp index 332be3f7..68962950 100644 --- a/lib/events/roomkeyevent.cpp +++ b/lib/events/roomkeyevent.cpp @@ -10,3 +10,16 @@ RoomKeyEvent::RoomKeyEvent(const QJsonObject &obj) : Event(typeId(), obj) if (roomId().isEmpty()) qCWarning(E2EE) << "Room key event has empty room id"; } + +RoomKeyEvent::RoomKeyEvent(const QString& algorithm, const QString& roomId, const QString& sessionId, const QString& sessionKey, const QString& senderId) + : Event(typeId(), { + {"content", QJsonObject{ + {"algorithm", algorithm}, + {"room_id", roomId}, + {"session_id", sessionId}, + {"session_key", sessionKey}, + }}, + {"sender", senderId}, + {"type", "m.room_key"}, + }) +{} diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index c4df7936..cb3fe7e7 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -12,6 +12,7 @@ public: DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent) explicit RoomKeyEvent(const QJsonObject& obj); + explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, const QString &sessionId, const QString& sessionKey, const QString& senderId); QString algorithm() const { return contentPart("algorithm"_ls); } QString roomId() const { return contentPart(RoomIdKeyL); } -- cgit v1.2.3 From 2071f5020975bc3f5ecbb9e2444acaad8f13060a Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 8 Mar 2022 00:06:36 +0100 Subject: Implement sending encrypted files --- autotests/CMakeLists.txt | 1 + autotests/testfilecrypto.cpp | 17 +++++++++++ autotests/testfilecrypto.h | 12 ++++++++ lib/eventitem.cpp | 10 +++++++ lib/eventitem.h | 3 ++ lib/events/encryptedfile.cpp | 26 +++++++++++++++-- lib/events/encryptedfile.h | 1 + lib/room.cpp | 67 +++++++++++++++++++++++++++++--------------- lib/room.h | 2 +- 9 files changed, 113 insertions(+), 26 deletions(-) create mode 100644 autotests/testfilecrypto.cpp create mode 100644 autotests/testfilecrypto.h (limited to 'lib/events') diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 671d6c08..c11901bf 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -18,4 +18,5 @@ if(${PROJECT_NAME}_ENABLE_E2EE) quotient_add_test(NAME testgroupsession) quotient_add_test(NAME testolmsession) quotient_add_test(NAME testolmutility) + quotient_add_test(NAME testfilecrypto) endif() diff --git a/autotests/testfilecrypto.cpp b/autotests/testfilecrypto.cpp new file mode 100644 index 00000000..e6bec1fe --- /dev/null +++ b/autotests/testfilecrypto.cpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2022 Tobias Fella +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "testfilecrypto.h" +#include "events/encryptedfile.h" +#include + +using namespace Quotient; +void TestFileCrypto::encryptDecryptData() +{ + QByteArray data = "ABCDEF"; + auto [file, cipherText] = EncryptedFile::encryptFile(data); + auto decrypted = file.decryptFile(cipherText); + QCOMPARE(data, decrypted); +} +QTEST_APPLESS_MAIN(TestFileCrypto) diff --git a/autotests/testfilecrypto.h b/autotests/testfilecrypto.h new file mode 100644 index 00000000..9096a8c7 --- /dev/null +++ b/autotests/testfilecrypto.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2022 Tobias Fella +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include + +class TestFileCrypto : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void encryptDecryptData(); +}; diff --git a/lib/eventitem.cpp b/lib/eventitem.cpp index a2d65d8d..302ae053 100644 --- a/lib/eventitem.cpp +++ b/lib/eventitem.cpp @@ -26,6 +26,16 @@ void PendingEventItem::setFileUploaded(const QUrl& remoteUrl) setStatus(EventStatus::FileUploaded); } +void PendingEventItem::setEncryptedFile(const EncryptedFile& encryptedFile) +{ + if (auto* rme = getAs()) { + Q_ASSERT(rme->hasFileContent()); + rme->editContent([encryptedFile](EventContent::TypedBase& ec) { + ec.fileInfo()->file = encryptedFile; + }); + } +} + // Not exactly sure why but this helps with the linker not finding // Quotient::EventStatus::staticMetaObject when building Quaternion #include "moc_eventitem.cpp" diff --git a/lib/eventitem.h b/lib/eventitem.h index f04ef323..d8313736 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -9,6 +9,8 @@ #include #include +#include "events/encryptedfile.h" + namespace Quotient { namespace EventStatus { @@ -114,6 +116,7 @@ public: void setDeparted() { setStatus(EventStatus::Departed); } void setFileUploaded(const QUrl& remoteUrl); + void setEncryptedFile(const EncryptedFile& encryptedFile); void setReachedServer(const QString& eventId) { setStatus(EventStatus::ReachedServer); diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index d4a517bd..e90be428 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -8,6 +8,7 @@ #ifdef Quotient_E2EE_ENABLED #include #include +#include "e2ee/qolmutils.h" #endif using namespace Quotient; @@ -27,7 +28,7 @@ QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const { int length; auto* ctx = EVP_CIPHER_CTX_new(); - QByteArray plaintext(ciphertext.size() + EVP_CIPHER_CTX_block_size(ctx) + QByteArray plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH - 1, '\0'); EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, @@ -44,7 +45,7 @@ QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const + length, &length); EVP_CIPHER_CTX_free(ctx); - return plaintext; + return plaintext.left(ciphertext.size()); } #else qWarning(MAIN) << "This build of libQuotient doesn't support E2EE, " @@ -53,6 +54,27 @@ QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const #endif } +std::pair EncryptedFile::encryptFile(const QByteArray &plainText) +{ + QByteArray k = getRandom(32); + auto kBase64 = k.toBase64(); + QByteArray iv = getRandom(16); + JWK key = {"oct"_ls, {"encrypt"_ls, "decrypt"_ls}, "A256CTR"_ls, QString(k.toBase64()).replace(u'/', u'_').replace(u'+', u'-').left(kBase64.indexOf('=')), true}; + + int length; + auto* ctx = EVP_CIPHER_CTX_new(); + QByteArray cipherText(plainText.size(), plainText.size() + EVP_MAX_BLOCK_LENGTH - 1); + EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast(k.data()),reinterpret_cast(iv.data())); + EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), &length, reinterpret_cast(plainText.data()), plainText.size()); + EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + length, &length); + EVP_CIPHER_CTX_free(ctx); + + auto hash = QCryptographicHash::hash(cipherText, QCryptographicHash::Sha256).toBase64(); + auto ivBase64 = iv.toBase64(); + EncryptedFile file = {{}, key, ivBase64.left(ivBase64.indexOf('=')), {{QStringLiteral("sha256"), hash.left(hash.indexOf('='))}}, "v2"_ls}; + return {file, cipherText}; +} + void JsonObjectConverter::dumpTo(QJsonObject& jo, const EncryptedFile& pod) { diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 0558563f..76aff837 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -46,6 +46,7 @@ public: QString v; QByteArray decryptFile(const QByteArray &ciphertext) const; + static std::pair encryptFile(const QByteArray &plainText); }; template <> diff --git a/lib/room.cpp b/lib/room.cpp index b21bb863..cbbd9e0e 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -299,8 +299,7 @@ public: RoomEvent* addAsPending(RoomEventPtr&& event); - //TODO deleteWhenFinishedis ugly, find out if there's something nicer - QString doSendEvent(const RoomEvent* pEvent, bool deleteWhenFinished = false); + QString doSendEvent(const RoomEvent* pEvent); void onEventSendingFailure(const QString& txnId, BaseJob* call = nullptr); SetRoomStateWithKeyJob* requestSetState(const QString& evtType, @@ -2060,6 +2059,16 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) qCWarning(MAIN) << q << "has been upgraded, event won't be sent"; return {}; } + + return doSendEvent(addAsPending(std::move(event))); +} + +QString Room::Private::doSendEvent(const RoomEvent* pEvent) +{ + const auto txnId = pEvent->transactionId(); + // TODO, #133: Enqueue the job rather than immediately trigger it. + const RoomEvent* _event = pEvent; + if (q->usesEncryption()) { if (!hasValidMegolmSession() || shouldRotateMegolmSession()) { createMegolmSession(); @@ -2067,10 +2076,8 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) const auto devicesWithoutKey = getDevicesWithoutKey(); sendMegolmSession(devicesWithoutKey); - //TODO check if this is necessary //TODO check if we increment the sent message count - event->setRoomId(id); - const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(event->fullJson()).toJson()); + const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson()); currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); if(std::holds_alternative(encrypted)) { @@ -2082,23 +2089,14 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) encryptedEvent->setTransactionId(connection->generateTxnId()); encryptedEvent->setRoomId(id); encryptedEvent->setSender(connection->userId()); - event->setTransactionId(encryptedEvent->transactionId()); // We show the unencrypted event locally while pending. The echo check will throw the encrypted version out - addAsPending(std::move(event)); - return doSendEvent(encryptedEvent, true); + _event = encryptedEvent; } - return doSendEvent(addAsPending(std::move(event))); -} - -QString Room::Private::doSendEvent(const RoomEvent* pEvent, bool deleteWhenFinished) -{ - const auto txnId = pEvent->transactionId(); - // TODO, #133: Enqueue the job rather than immediately trigger it. if (auto call = connection->callApi(BackgroundRequest, id, - pEvent->matrixType(), txnId, - pEvent->contentJson())) { + _event->matrixType(), txnId, + _event->contentJson())) { Room::connect(call, &BaseJob::sentRequest, q, [this, txnId] { auto it = q->findPendingEvent(txnId); if (it == unsyncedEvents.end()) { @@ -2112,7 +2110,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent, bool deleteWhenFinis Room::connect(call, &BaseJob::failure, q, std::bind(&Room::Private::onEventSendingFailure, this, txnId, call)); - Room::connect(call, &BaseJob::success, q, [this, call, txnId, deleteWhenFinished, pEvent] { + Room::connect(call, &BaseJob::success, q, [this, call, txnId, _event] { auto it = q->findPendingEvent(txnId); if (it != unsyncedEvents.end()) { if (it->deliveryStatus() != EventStatus::ReachedServer) { @@ -2124,8 +2122,8 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent, bool deleteWhenFinis << "already merged"; emit q->messageSent(txnId, call->eventId()); - if (deleteWhenFinished){ - delete pEvent; + if (q->usesEncryption()){ + delete _event; } }); } else @@ -2250,13 +2248,16 @@ QString Room::Private::doPostFile(RoomEventPtr&& msgEvent, const QUrl& localUrl) // Below, the upload job is used as a context object to clean up connections const auto& transferJob = fileTransfers.value(txnId).job; connect(q, &Room::fileTransferCompleted, transferJob, - [this, txnId](const QString& tId, const QUrl&, const QUrl& mxcUri) { + [this, txnId](const QString& tId, const QUrl&, const QUrl& mxcUri, Omittable encryptedFile) { if (tId != txnId) return; const auto it = q->findPendingEvent(txnId); if (it != unsyncedEvents.end()) { it->setFileUploaded(mxcUri); + if (encryptedFile) { + it->setEncryptedFile(*encryptedFile); + } emit q->pendingEventChanged( int(it - unsyncedEvents.begin())); doSendEvent(it->get()); @@ -2491,6 +2492,20 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, Q_ASSERT_X(localFilename.isLocalFile(), __FUNCTION__, "localFilename should point at a local file"); auto fileName = localFilename.toLocalFile(); + Omittable encryptedFile = std::nullopt; +#ifdef Quotient_E2EE_ENABLED + QTemporaryFile tempFile; + if (usesEncryption()) { + tempFile.open(); + QFile file(localFilename.toLocalFile()); + file.open(QFile::ReadOnly); + auto [e, data] = EncryptedFile::encryptFile(file.readAll()); + tempFile.write(data); + tempFile.close(); + fileName = QFileInfo(tempFile).absoluteFilePath(); + encryptedFile = e; + } +#endif auto job = connection()->uploadFile(fileName, overrideContentType); if (isJobPending(job)) { d->fileTransfers[id] = { job, fileName, true }; @@ -2499,9 +2514,15 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, d->fileTransfers[id].update(sent, total); emit fileTransferProgress(id, sent, total); }); - connect(job, &BaseJob::success, this, [this, id, localFilename, job] { + connect(job, &BaseJob::success, this, [this, id, localFilename, job, encryptedFile] { d->fileTransfers[id].status = FileTransferInfo::Completed; - emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri())); + if (encryptedFile) { + auto file = *encryptedFile; + file.url = QUrl(job->contentUri()); + emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri()), file); + } else { + emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri())); + } }); connect(job, &BaseJob::failure, this, std::bind(&Private::failedTransfer, d, id, job->errorString())); diff --git a/lib/room.h b/lib/room.h index 9f70d77a..1a1d839f 100644 --- a/lib/room.h +++ b/lib/room.h @@ -997,7 +997,7 @@ Q_SIGNALS: void newFileTransfer(QString id, QUrl localFile); void fileTransferProgress(QString id, qint64 progress, qint64 total); - void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl); + void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl, Omittable encryptedFile = std::nullopt); void fileTransferFailed(QString id, QString errorMessage = {}); // fileTransferCancelled() is no more here; use fileTransferFailed() and // check the transfer status instead -- cgit v1.2.3 From 7ba17e2b9b6d76aeea250f37c833ad7eed2f61da Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 8 Mar 2022 21:44:10 +0100 Subject: Properly create encrypted edits --- lib/events/encryptedevent.cpp | 7 +++++++ lib/events/encryptedevent.h | 2 ++ lib/room.cpp | 3 +++ 3 files changed, 12 insertions(+) (limited to 'lib/events') diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 9d07a35f..3af3d6ff 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -61,3 +61,10 @@ RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const } return loadEvent(eventObject); } + +void EncryptedEvent::setRelation(const QJsonObject& relation) +{ + auto content = editJson()["content"_ls].toObject(); + content["m.relates_to"] = relation; + editJson()["content"] = content; +} diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 72efffd4..ddd5e415 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -56,6 +56,8 @@ public: QString deviceId() const { return contentPart(DeviceIdKeyL); } QString sessionId() const { return contentPart(SessionIdKeyL); } RoomEventPtr createDecrypted(const QString &decrypted) const; + + void setRelation(const QJsonObject& relation); }; REGISTER_EVENT_TYPE(EncryptedEvent) diff --git a/lib/room.cpp b/lib/room.cpp index cbbd9e0e..1762fd5a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2089,6 +2089,9 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) encryptedEvent->setTransactionId(connection->generateTxnId()); encryptedEvent->setRoomId(id); encryptedEvent->setSender(connection->userId()); + if(pEvent->contentJson().contains("m.relates_to"_ls)) { + encryptedEvent->setRelation(pEvent->contentJson()["m.relates_to"_ls].toObject()); + } // We show the unencrypted event locally while pending. The echo check will throw the encrypted version out _event = encryptedEvent; } -- 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 +++++++++++++++++++++++++++++++------- lib/connection.h | 3 +-- lib/database.cpp | 18 +++++++++++++++--- lib/database.h | 3 ++- lib/events/encryptedfile.cpp | 4 ++++ lib/events/roomevent.h | 8 ++++++++ lib/room.cpp | 27 ++++++++++++++++----------- 7 files changed, 77 insertions(+), 24 deletions(-) (limited to 'lib/events') 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))); diff --git a/lib/connection.h b/lib/connection.h index 6f75b068..52e1700c 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -327,8 +327,7 @@ public: void saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data); - //This currently assumes that an olm session with (user, device) exists - //TODO make this return an event? + //This assumes that an olm session with (user, device) exists QPair olmEncryptMessage(User* user, const QString& device, const QByteArray& message); void createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey); #endif // Quotient_E2EE_ENABLED diff --git a/lib/database.cpp b/lib/database.cpp index 863cbf0d..d9dce517 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "e2ee/e2ee.h" #include "e2ee/qolmsession.h" @@ -166,7 +167,7 @@ void Database::saveOlmSession(const QString& senderKey, const QString& sessionId UnorderedMap> Database::loadOlmSessions(const PicklingMode& picklingMode) { - auto query = prepareQuery(QStringLiteral("SELECT * FROM olm_sessions;")); + auto query = prepareQuery(QStringLiteral("SELECT * FROM olm_sessions ORDER BY lastReceived DESC;")); transaction(); execute(query); commit(); @@ -317,7 +318,6 @@ QOlmOutboundGroupSessionPtr Database::loadCurrentOutboundMegolmSession(const QSt void Database::setDevicesReceivedKey(const QString& roomId, QHash devices, const QString& sessionId, int index) { - //TODO this better auto connection = dynamic_cast(parent()); transaction(); for (const auto& user : devices.keys()) { @@ -339,7 +339,7 @@ QHash Database::devicesWithoutKey(Room* room, const QStrin { auto connection = dynamic_cast(parent()); QHash devices; - for (const auto& user : room->users()) {//TODO does this include invited & left? + for (const auto& user : room->users()) { devices[user->id()] = connection->devicesForUser(user); } @@ -354,3 +354,15 @@ QHash Database::devicesWithoutKey(Room* room, const QStrin } return devices; } + +void Database::updateOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle) +{ + auto query = prepareQuery(QStringLiteral("UPDATE olm_sessions SET pickle=:pickle WHERE senderKey=:senderKey AND sessionId=:sessionId;")); + query.bindValue(":pickle", pickle); + query.bindValue(":senderKey", senderKey); + query.bindValue(":sessionId", sessionId); + transaction(); + execute(query); + commit(); +} + diff --git a/lib/database.h b/lib/database.h index ed567a86..76d86f12 100644 --- a/lib/database.h +++ b/lib/database.h @@ -33,7 +33,7 @@ public: QByteArray accountPickle(); void setAccountPickle(const QByteArray &pickle); void clear(); - void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray &pickle, const QDateTime& timestamp); + void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle, const QDateTime& timestamp); UnorderedMap> loadOlmSessions(const PicklingMode& picklingMode); UnorderedMap, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode); void saveMegolmSession(const QString& roomId, const QString& senderKey, const QString& sessionKey, const QString& ed25519Key, const QByteArray& pickle); @@ -43,6 +43,7 @@ public: void setOlmSessionLastReceived(const QString& sessionId, const QDateTime& timestamp); QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode); void saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& data); + void updateOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle); // Returns a map User -> [Device] that have not received key yet QHash devicesWithoutKey(Room* room, const QString &sessionId); diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index e90be428..bb4e26c7 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -56,6 +56,7 @@ QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const std::pair EncryptedFile::encryptFile(const QByteArray &plainText) { +#ifdef Quotient_E2EE_ENABLED QByteArray k = getRandom(32); auto kBase64 = k.toBase64(); QByteArray iv = getRandom(16); @@ -73,6 +74,9 @@ std::pair EncryptedFile::encryptFile(const QByteArray auto ivBase64 = iv.toBase64(); EncryptedFile file = {{}, key, ivBase64.left(ivBase64.indexOf('=')), {{QStringLiteral("sha256"), hash.left(hash.indexOf('='))}}, "v2"_ls}; return {file, cipherText}; +#else + return {{}, {}}; +#endif } void JsonObjectConverter::dumpTo(QJsonObject& jo, diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index c4b0131a..a7d6c428 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -80,6 +80,14 @@ using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; using RoomEventsRange = Range; +template <> +inline EventPtr doLoadEvent(const QJsonObject& json, const QString& matrixType) +{ + if (matrixType == "m.room.encrypted") + return RoomEvent::factory.loadEvent(json, matrixType); + return Event::factory.loadEvent(json, matrixType); +} + class QUOTIENT_API CallEventBase : public RoomEvent { public: CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, diff --git a/lib/room.cpp b/lib/room.cpp index 1762fd5a..6d938d4a 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -438,8 +438,8 @@ public: const auto sessionKey = currentOutboundMegolmSession->sessionKey(); if(std::holds_alternative(sessionKey)) { - qCWarning(E2EE) << "Session error"; - //TODO something + qCWarning(E2EE) << "Failed to load key for new megolm session"; + return; } addInboundGroupSession(q->connection()->olmAccount()->identityKeys().curve25519, currentOutboundMegolmSession->sessionId(), std::get(sessionKey), QString(connection->olmAccount()->identityKeys().ed25519)); } @@ -448,7 +448,6 @@ public: { // Noisy but nice for debugging //qCDebug(E2EE) << "Creating the payload for" << user->id() << device << sessionId << sessionKey.toHex(); - //TODO: store {user->id(), device, sessionId, theirIdentityKey}; required for key requests const auto event = makeEvent("m.megolm.v1.aes-sha2", q->id(), sessionId, sessionKey, q->localUser()->id()); QJsonObject payloadJson = event->fullJson(); payloadJson["recipient"] = user->id(); @@ -493,6 +492,9 @@ public: hash[user->id()] = u; } } + if (hash.isEmpty()) { + return; + } auto job = connection->callApi(hash); connect(job, &BaseJob::success, q, [job, this, sessionId, sessionKey, devices, index](){ Connection::UsersToDevicesToEvents usersToDevicesToEvents; @@ -514,7 +516,6 @@ public: signedData.remove("signatures"); auto signatureMatch = QOlmUtility().ed25519Verify(connection->edKeyForUserDevice(user->id(), device).toLatin1(), QJsonDocument(signedData).toJson(QJsonDocument::Compact), signature); if (std::holds_alternative(signatureMatch)) { - //TODO i think there are more failed signature checks than expected. Investigate qCWarning(E2EE) << "Failed to verify one-time-key signature for" << user->id() << device << ". Skipping this device."; continue; } else { @@ -524,8 +525,10 @@ public: usersToDevicesToEvents[user->id()][device] = payloadForUserDevice(user, device, sessionId, sessionKey); } } - connection->sendToDevices("m.room.encrypted", usersToDevicesToEvents); - connection->database()->setDevicesReceivedKey(q->id(), devices, sessionId, index); + if (!usersToDevicesToEvents.empty()) { + connection->sendToDevices("m.room.encrypted", usersToDevicesToEvents); + connection->database()->setDevicesReceivedKey(q->id(), devices, sessionId, index); + } }); } @@ -534,8 +537,8 @@ public: const auto sessionId = currentOutboundMegolmSession->sessionId(); const auto _sessionKey = currentOutboundMegolmSession->sessionKey(); if(std::holds_alternative(_sessionKey)) { - qCWarning(E2EE) << "Session error"; - //TODO something + qCWarning(E2EE) << "Error loading session key"; + return; } const auto sessionKey = std::get(_sessionKey); const auto senderKey = q->connection()->olmAccount()->identityKeys().curve25519; @@ -575,7 +578,6 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) connect(this, &Room::userAdded, this, [this, connection](){ if(usesEncryption()) { connection->encryptionUpdate(this); - //TODO key at currentIndex to all user devices } }); d->groupSessions = connection->loadRoomMegolmSessions(this); @@ -2070,18 +2072,20 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) const RoomEvent* _event = pEvent; if (q->usesEncryption()) { +#ifndef Quotient_E2EE_ENABLED + qWarning() << "This build of libQuotient does not support E2EE."; + return {}; +#else if (!hasValidMegolmSession() || shouldRotateMegolmSession()) { createMegolmSession(); } const auto devicesWithoutKey = getDevicesWithoutKey(); sendMegolmSession(devicesWithoutKey); - //TODO check if we increment the sent message count const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson()); currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); if(std::holds_alternative(encrypted)) { - //TODO something qWarning(E2EE) << "Error encrypting message" << std::get(encrypted); return {}; } @@ -2094,6 +2098,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) } // We show the unencrypted event locally while pending. The echo check will throw the encrypted version out _event = encryptedEvent; +#endif } if (auto call = -- cgit v1.2.3 From 80251f1db42268be0e678543b0b054f925fbfe88 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Thu, 10 Mar 2022 21:47:51 +0100 Subject: Update lib/events/encryptedfile.h --- lib/events/encryptedfile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 76aff837..b2808395 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -46,7 +46,7 @@ public: QString v; QByteArray decryptFile(const QByteArray &ciphertext) const; - static std::pair encryptFile(const QByteArray &plainText); + static std::pair encryptFile(const QByteArray& plainText); }; template <> -- cgit v1.2.3 From fc3ad90a054e3c674127a0cdd385ddbb98cf2010 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 8 Apr 2022 23:23:30 +0200 Subject: Correctly load EncryptedEvents --- lib/events/roomevent.h | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'lib/events') diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index c4b0131a..a7d6c428 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -80,6 +80,14 @@ using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; using RoomEventsRange = Range; +template <> +inline EventPtr doLoadEvent(const QJsonObject& json, const QString& matrixType) +{ + if (matrixType == "m.room.encrypted") + return RoomEvent::factory.loadEvent(json, matrixType); + return Event::factory.loadEvent(json, matrixType); +} + class QUOTIENT_API CallEventBase : public RoomEvent { public: CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, -- 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 --- CMakeLists.txt | 3 + lib/connection.cpp | 104 +- lib/connection.h | 20 + lib/database.cpp | 30 +- lib/database.h | 4 + lib/events/encryptedevent.h | 2 + lib/events/keyverificationevent.cpp | 32 +- lib/events/keyverificationevent.h | 36 +- lib/events/roomevent.h | 2 +- lib/keyverificationsession.cpp | 522 +++++++++ lib/keyverificationsession.h | 140 +++ res.qrc | 5 + sas-emoji.json | 2178 +++++++++++++++++++++++++++++++++++ 13 files changed, 3066 insertions(+), 12 deletions(-) create mode 100644 lib/keyverificationsession.cpp create mode 100644 lib/keyverificationsession.h create mode 100644 res.qrc create mode 100644 sas-emoji.json (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 15726240..1030e12d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,7 @@ message(STATUS " Header files will be installed to ${CMAKE_INSTALL_PREFIX}/${${ # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) option(BUILD_WITH_QT6 "Build Quotient with Qt 6 (EXPERIMENTAL)" OFF) @@ -138,6 +139,7 @@ list(APPEND lib_SRCS lib/eventitem.h lib/eventitem.cpp lib/accountregistry.h lib/accountregistry.cpp lib/mxcreply.h lib/mxcreply.cpp + lib/keyverificationsession.h lib/keyverificationsession.cpp lib/events/event.h lib/events/event.cpp lib/events/eventloader.h lib/events/roomevent.h lib/events/roomevent.cpp @@ -171,6 +173,7 @@ list(APPEND lib_SRCS lib/jobs/syncjob.h lib/jobs/syncjob.cpp lib/jobs/mediathumbnailjob.h lib/jobs/mediathumbnailjob.cpp lib/jobs/downloadfilejob.h lib/jobs/downloadfilejob.cpp + res.qrc ) if (${PROJECT_NAME}_ENABLE_E2EE) list(APPEND lib_SRCS 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(); +} diff --git a/lib/connection.h b/lib/connection.h index 12db2e30..fc189ac4 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -26,6 +26,8 @@ #include "e2ee/e2ee.h" #include "e2ee/qolmmessage.h" #include "e2ee/qolmoutboundsession.h" +#include "events/keyverificationevent.h" +#include "keyverificationsession.h" #endif Q_DECLARE_METATYPE(Quotient::GetLoginFlowsJob::LoginFlow) @@ -324,6 +326,8 @@ public: QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(Room* room); void saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data); + /// Returns true if this megolm session comes from a verified device + bool isVerifiedSession(const QString& megolmSessionId); //This assumes that an olm session with (user, device) exists QPair olmEncryptMessage(User* user, const QString& device, const QByteArray& message); @@ -512,6 +516,9 @@ public: /// Saves the olm account data to disk. Usually doesn't need to be called manually. void saveOlmAccount(); + // This assumes that an olm session already exists. If it doesn't, no message is sent. + void sendToDevice(const QString& userId, const QString& deviceId, event_ptr_tt event, bool encrypted); + public Q_SLOTS: /// \brief Set the homeserver base URL and retrieve its login flows /// @@ -688,6 +695,8 @@ public Q_SLOTS: /** \deprecated Do not use this directly, use Room::leaveRoom() instead */ virtual LeaveRoomJob* leaveRoom(Room* room); + void startKeyVerificationSession(const QString& deviceId); + #ifdef Quotient_E2EE_ENABLED void encryptionUpdate(Room *room); PicklingMode picklingMode() const; @@ -698,6 +707,7 @@ public Q_SLOTS: QString edKeyForUserDevice(const QString& user, const QString& device) const; bool isKnownCurveKey(const QString& user, const QString& curveKey); #endif + Q_SIGNALS: /// \brief Initial server resolution has failed /// @@ -855,6 +865,16 @@ Q_SIGNALS: void lazyLoadingChanged(); void turnServersChanged(const QJsonObject& servers); void devicesListLoaded(); + void incomingKeyVerificationReady(const KeyVerificationReadyEvent& event); + void incomingKeyVerificationStart(const KeyVerificationStartEvent& event); + void incomingKeyVerificationAccept(const KeyVerificationAcceptEvent& event); + void incomingKeyVerificationKey(const KeyVerificationKeyEvent& event); + void incomingKeyVerificationMac(const KeyVerificationMacEvent& event); + void incomingKeyVerificationDone(const KeyVerificationDoneEvent& event); + void incomingKeyVerificationCancel(const KeyVerificationCancelEvent& event); + + void newKeyVerificationSession(KeyVerificationSession* session); + void sessionVerified(const QString& userId, const QString& deviceId); protected: /** diff --git a/lib/database.cpp b/lib/database.cpp index 902d0487..a85d96bb 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -35,6 +35,7 @@ Database::Database(const QString& matrixId, const QString& deviceId, QObject* pa case 1: migrateTo2(); case 2: migrateTo3(); case 3: migrateTo4(); + case 4: migrateTo5(); } } @@ -105,6 +106,7 @@ void Database::migrateTo2() { qCDebug(DATABASE) << "Migrating database to version 2"; transaction(); + execute(QStringLiteral("ALTER TABLE inbound_megolm_sessions ADD ed25519Key TEXT")); execute(QStringLiteral("ALTER TABLE olm_sessions ADD lastReceived TEXT")); @@ -133,7 +135,7 @@ void Database::migrateTo3() void Database::migrateTo4() { - qCDebug(DATABASE) << "Migrating database to ersion 4"; + qCDebug(DATABASE) << "Migrating database to version 4"; transaction(); execute(QStringLiteral("CREATE TABLE sent_megolm_sessions (roomId TEXT, userId TEXT, deviceId TEXT, identityKey TEXT, sessionId TEXT, i INTEGER);")); @@ -143,6 +145,16 @@ void Database::migrateTo4() commit(); } +void Database::migrateTo5() +{ + qCDebug(DATABASE) << "Migrating database to version 5"; + transaction(); + + execute(QStringLiteral("ALTER TABLE tracked_devices ADD verified BOOL;")); + execute(QStringLiteral("PRAGMA user_version = 5")); + commit(); +} + QByteArray Database::accountPickle() { auto query = prepareQuery(QStringLiteral("SELECT pickle FROM accounts;")); @@ -396,3 +408,19 @@ void Database::updateOlmSession(const QString& senderKey, const QString& session commit(); } +void Database::setSessionVerified(const QString& edKeyId) +{ + auto query = prepareQuery(QStringLiteral("UPDATE tracked_devices SET verified=true WHERE edKeyId=:edKeyId;")); + query.bindValue(":edKeyId", edKeyId); + transaction(); + execute(query); + commit(); +} + +bool Database::isSessionVerified(const QString& edKey) +{ + auto query = prepareQuery(QStringLiteral("SELECT verified FROM tracked_devices WHERE edKey=:edKey")); + query.bindValue(":edKey", edKey); + execute(query); + return query.next() && query.value("verified").toBool(); +} diff --git a/lib/database.h b/lib/database.h index 3eb26b0a..afc41e42 100644 --- a/lib/database.h +++ b/lib/database.h @@ -50,11 +50,15 @@ public: QHash devicesWithoutKey(Room* room, const QString &sessionId); void setDevicesReceivedKey(const QString& roomId, QHash devices, const QString& sessionId, int index); + bool isSessionVerified(const QString& edKey); + void setSessionVerified(const QString& edKeyId); + private: void migrateTo1(); void migrateTo2(); void migrateTo3(); void migrateTo4(); + void migrateTo5(); QString m_matrixId; }; diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index ddd5e415..bfacdec9 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -58,6 +58,8 @@ public: RoomEventPtr createDecrypted(const QString &decrypted) const; void setRelation(const QJsonObject& relation); + + bool isVerified(); }; REGISTER_EVENT_TYPE(EncryptedEvent) diff --git a/lib/events/keyverificationevent.cpp b/lib/events/keyverificationevent.cpp index 4803955d..e7f5b019 100644 --- a/lib/events/keyverificationevent.cpp +++ b/lib/events/keyverificationevent.cpp @@ -106,7 +106,7 @@ QStringList KeyVerificationAcceptEvent::shortAuthenticationString() const return contentPart("short_authentification_string"_ls); } -QString KeyVerificationAcceptEvent::commitement() const +QString KeyVerificationAcceptEvent::commitment() const { return contentPart("commitment"_ls); } @@ -162,3 +162,33 @@ QHash KeyVerificationMacEvent::mac() const { return contentPart>("mac"_ls); } + +KeyVerificationDoneEvent::KeyVerificationDoneEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{ +} + +QString KeyVerificationDoneEvent::transactionId() const +{ + return contentPart("transaction_id"_ls); +} + + +KeyVerificationReadyEvent::KeyVerificationReadyEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationReadyEvent::fromDevice() const +{ + return contentPart("from_device"_ls); +} + +QString KeyVerificationReadyEvent::transactionId() const +{ + return contentPart("transaction_id"_ls); +} + +QStringList KeyVerificationReadyEvent::methods() const +{ + return contentPart("methods"_ls); +} diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index 497e56a2..a9f63968 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2021 Carl Schwan // SPDX-License-Identifier: LGPL-2.1-or-later +#pragma once + #include "event.h" namespace Quotient { @@ -31,6 +33,24 @@ public: }; REGISTER_EVENT_TYPE(KeyVerificationRequestEvent) +class QUOTIENT_API KeyVerificationReadyEvent : public Event { +public: + DEFINE_EVENT_TYPEID("m.key.verification.ready", KeyVerificationReadyEvent) + + explicit KeyVerificationReadyEvent(const QJsonObject& obj); + + /// The device ID which is accepting the request. + QString fromDevice() const; + + /// The transaction id of the verification request + QString transactionId() const; + + /// The verification methods supported by the sender. + QStringList methods() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationReadyEvent) + + /// Begins a key verification process. class QUOTIENT_API KeyVerificationStartEvent : public Event { public: @@ -104,7 +124,7 @@ public: /// The hash (encoded as unpadded base64) of the concatenation of the /// device's ephemeral public key (encoded as unpadded base64) and the /// canonical JSON representation of the m.key.verification.start message. - QString commitement() const; + QString commitment() const; }; REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent) @@ -128,7 +148,7 @@ REGISTER_EVENT_TYPE(KeyVerificationCancelEvent) /// Sends the ephemeral public key for a device to the partner device. /// Typically sent as a to-device event. -class KeyVerificationKeyEvent : public Event { +class QUOTIENT_API KeyVerificationKeyEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent) @@ -158,4 +178,16 @@ public: QHash mac() const; }; REGISTER_EVENT_TYPE(KeyVerificationMacEvent) + +class QUOTIENT_API KeyVerificationDoneEvent : public Event { +public: + DEFINE_EVENT_TYPEID("m.key.verification.done", KeyVerificationRequestEvent) + + explicit KeyVerificationDoneEvent(const QJsonObject& obj); + + /// The same transactionId as before + QString transactionId() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationDoneEvent) + } // namespace Quotient diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index a7d6c428..5670f55f 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -62,7 +62,7 @@ public: #ifdef Quotient_E2EE_ENABLED void setOriginalEvent(event_ptr_tt&& originalEvent); - const RoomEvent* originalEvent() { return _originalEvent.get(); } + const RoomEvent* originalEvent() const { return _originalEvent.get(); } const QJsonObject encryptedJson() const; #endif diff --git a/lib/keyverificationsession.cpp b/lib/keyverificationsession.cpp new file mode 100644 index 00000000..3b3b7627 --- /dev/null +++ b/lib/keyverificationsession.cpp @@ -0,0 +1,522 @@ +// SPDX-FileCopyrightText: 2022 Tobias Fella +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "connection.h" +#include "keyverificationsession.h" +#include "olm/sas.h" +#include "e2ee/qolmaccount.h" +#include "e2ee/qolmutils.h" +#include "events/event.h" +#include +#include +#include +#include "database.h" + +using namespace Quotient; + +KeyVerificationSession::KeyVerificationSession(const QString& remoteUserId, const KeyVerificationRequestEvent& event, Connection *connection, bool encrypted, QObject* parent) + : QObject(parent) + , m_remoteUserId(remoteUserId) + , m_remoteDeviceId(event.fromDevice()) + , m_transactionId(event.transactionId()) + , m_connection(connection) + , m_encrypted(encrypted) + , m_remoteSupportedMethods(event.methods()) +{ + auto timeoutTime = std::min(event.timestamp() + 600000, QDateTime::currentDateTime().addSecs(120).toMSecsSinceEpoch()); + m_timeout = timeoutTime - QDateTime::currentMSecsSinceEpoch(); + if (m_timeout <= 5000) { + return; + } + init(); + setState(INCOMING); +} + +KeyVerificationSession::KeyVerificationSession(const QString& userId, const QString& deviceId, Connection* connection, QObject* parent) + : QObject(parent) + , m_remoteUserId(userId) + , m_remoteDeviceId(deviceId) + , m_transactionId(QUuid::createUuid().toString()) + , m_connection(connection) + , m_encrypted(false) +{ + m_timeout = 600000; + init(); + QMetaObject::invokeMethod(this, &KeyVerificationSession::sendRequest); +} + +void KeyVerificationSession::init() +{ + connect(m_connection, &Connection::incomingKeyVerificationReady, this, [this](const KeyVerificationReadyEvent& event) { + if (event.transactionId() == m_transactionId && event.fromDevice() == m_remoteDeviceId) { + handleReady(event); + } + }); + connect(m_connection, &Connection::incomingKeyVerificationStart, this, [this](const KeyVerificationStartEvent& event) { + if (event.transactionId() == m_transactionId && event.fromDevice() == m_remoteDeviceId) { + handleStart(event); + } + }); + connect(m_connection, &Connection::incomingKeyVerificationAccept, this, [this](const KeyVerificationAcceptEvent& event) { + if (event.transactionId() == m_transactionId) { + handleAccept(event); + } + }); + connect(m_connection, &Connection::incomingKeyVerificationKey, this, [this](const KeyVerificationKeyEvent& event) { + if (event.transactionId() == m_transactionId) { + handleKey(event); + } + }); + connect(m_connection, &Connection::incomingKeyVerificationMac, this, [this](const KeyVerificationMacEvent& event) { + if (event.transactionId() == m_transactionId) { + handleMac(event); + } + }); + connect(m_connection, &Connection::incomingKeyVerificationDone, this, [this](const KeyVerificationDoneEvent& event) { + if (event.transactionId() == m_transactionId) { + handleDone(event); + } + }); + connect(m_connection, &Connection::incomingKeyVerificationCancel, this, [this](const KeyVerificationCancelEvent& event) { + if (event.transactionId() == m_transactionId) { + handleCancel(event); + } + }); + + QTimer::singleShot(m_timeout, this, [this] { + cancelVerification(TIMEOUT); + }); + + + m_sas = olm_sas(new uint8_t[olm_sas_size()]); + auto randomSize = olm_create_sas_random_length(m_sas); + auto random = getRandom(randomSize); + olm_create_sas(m_sas, random.data(), randomSize); + + m_language = QLocale::system().uiLanguages()[0]; + m_language = m_language.left(m_language.indexOf('-')); +} + +KeyVerificationSession::~KeyVerificationSession() +{ + delete[] reinterpret_cast(m_sas); +} + +void KeyVerificationSession::handleKey(const KeyVerificationKeyEvent& event) +{ + if (state() != WAITINGFORKEY && state() != WAITINGFORVERIFICATION) { + cancelVerification(UNEXPECTED_MESSAGE); + return; + } + olm_sas_set_their_key(m_sas, event.key().toLatin1().data(), event.key().toLatin1().size()); + + if (startSentByUs) { + auto commitment = QString(QCryptographicHash::hash((event.key() % m_startEvent).toLatin1(), QCryptographicHash::Sha256).toBase64()); + commitment = commitment.left(commitment.indexOf('=')); + if (commitment != m_commitment) { + qCWarning(E2EE) << "Commitment mismatch; aborting verification"; + cancelVerification(MISMATCHED_COMMITMENT); + return; + } + } else { + sendKey(); + } + setState(WAITINGFORVERIFICATION); + + QByteArray keyBytes(olm_sas_pubkey_length(m_sas), '\0'); + olm_sas_get_pubkey(m_sas, keyBytes.data(), keyBytes.size()); + QString key = QString(keyBytes); + + QByteArray output(6, '\0'); + QString infoTemplate = startSentByUs ? "MATRIX_KEY_VERIFICATION_SAS|%1|%2|%3|%4|%5|%6|%7"_ls : "MATRIX_KEY_VERIFICATION_SAS|%4|%5|%6|%1|%2|%3|%7"_ls; + + auto info = infoTemplate.arg(m_connection->userId()).arg(m_connection->deviceId()).arg(key).arg(m_remoteUserId).arg(m_remoteDeviceId).arg(event.key()).arg(m_transactionId); + olm_sas_generate_bytes(m_sas, info.toLatin1().data(), info.toLatin1().size(), output.data(), output.size()); + + QVector code(7, 0); + const auto& data = (uint8_t *) output.data(); + + code[0] = data[0] >> 2; + code[1] = (data[0] << 4 & 0x3f) | data[1] >> 4; + code[2] = (data[1] << 2 & 0x3f) | data[2] >> 6; + code[3] = data[2] & 0x3f; + code[4] = data[3] >> 2; + code[5] = (data[3] << 4 & 0x3f) | data[4] >> 4; + code[6] = (data[4] << 2 & 0x3f) | data[5] >> 6; + + for (const auto& c : code) { + auto [emoji, description] = emojiForCode(c); + QVariantMap map; + map["emoji"] = emoji; + map["description"] = description; + m_sasEmojis += map; + } + emit sasEmojisChanged(); + emit keyReceived(); +} + +QByteArray KeyVerificationSession::macInfo(bool verifying, const QString& key) +{ + return (verifying ? "MATRIX_KEY_VERIFICATION_MAC%3%4%1%2%5%6"_ls : "MATRIX_KEY_VERIFICATION_MAC%1%2%3%4%5%6"_ls).arg(m_connection->userId()).arg(m_connection->deviceId()).arg(m_remoteUserId).arg(m_remoteDeviceId).arg(m_transactionId).arg(key).toLatin1(); +} + +QString KeyVerificationSession::calculateMac(const QString& input, bool verifying, const QString& keyId) +{ + QByteArray inputBytes = input.toLatin1(); + QByteArray outputBytes(olm_sas_mac_length(m_sas), '\0'); + olm_sas_calculate_mac(m_sas, inputBytes.data(), inputBytes.size(), macInfo(verifying, keyId).data(), macInfo(verifying, keyId).size(), outputBytes.data(), outputBytes.size()); + auto output = QString(outputBytes); + return output.left(output.indexOf('=')); +} + +void KeyVerificationSession::sendMac() +{ + QString edKeyId = "ed25519:" % m_connection->deviceId(); + + auto keys = calculateMac(edKeyId, false); + + QJsonObject mac; + auto key = m_connection->olmAccount()->deviceKeys().keys[edKeyId]; + mac[edKeyId] = calculateMac(key, false, edKeyId); + + auto event = makeEvent(QJsonObject { + {"type", "m.key.verification.mac"}, + {"content", QJsonObject{ + {"transaction_id", m_transactionId}, + {"keys", keys}, + {"mac", mac}, + }} + }); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + setState (macReceived ? DONE : WAITINGFORMAC); +} + +void KeyVerificationSession::sendDone() +{ + auto event = makeEvent(QJsonObject { + {"type", "m.key.verification.done"}, + {"content", QJsonObject{ + {"transaction_id", m_transactionId}, + }} + }); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); +} + +void KeyVerificationSession::sendKey() +{ + QByteArray keyBytes(olm_sas_pubkey_length(m_sas), '\0'); + olm_sas_get_pubkey(m_sas, keyBytes.data(), keyBytes.size()); + QString key = QString(keyBytes); + auto event = makeEvent(QJsonObject { + {"type", "m.key.verification.key"}, + {"content", QJsonObject{ + {"transaction_id", m_transactionId}, + {"key", key}, + }} + }); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); +} + + +void KeyVerificationSession::cancelVerification(Error error) +{ + auto event = makeEvent(QJsonObject { + {"type", "m.key.verification.cancel"}, + {"content", QJsonObject{ + {"code", errorToString(error)}, + {"reason", errorToString(error)}, + {"transaction_id", m_transactionId} + }} + }); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + setState(CANCELED); + setError(error); + emit finished(); + deleteLater(); +} + +void KeyVerificationSession::sendReady() +{ + auto methods = commonSupportedMethods(m_remoteSupportedMethods); + + if (methods.isEmpty()) { + cancelVerification(UNKNOWN_METHOD); + return; + } + + auto event = makeEvent(QJsonObject { + {"type", "m.key.verification.ready"}, + {"content", QJsonObject { + {"from_device", m_connection->deviceId()}, + {"methods", toJson(methods)}, + {"transaction_id", m_transactionId}, + }} + }); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + setState(READY); + + if (methods.size() == 1) { + sendStartSas(); + } +} + +void KeyVerificationSession::sendStartSas() +{ + startSentByUs = true; + auto event = makeEvent(QJsonObject { + {"type", "m.key.verification.start"}, + {"content", QJsonObject { + {"from_device", m_connection->deviceId()}, + {"hashes", QJsonArray {"sha256"}}, + {"key_agreement_protocols", QJsonArray { "curve25519-hkdf-sha256" }}, + {"message_authentication_codes", QJsonArray { "hkdf-hmac-sha256" }}, + {"method", "m.sas.v1"}, + {"short_authentication_string", QJsonArray { "decimal", "emoji" }}, + {"transaction_id", m_transactionId}, + }} + }); + m_startEvent = QJsonDocument(event->contentJson()).toJson(QJsonDocument::Compact); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + setState(WAITINGFORACCEPT); +} + +void KeyVerificationSession::handleReady(const KeyVerificationReadyEvent& event) +{ + if (state() != WAITINGFORREADY) { + cancelVerification(UNEXPECTED_MESSAGE); + return; + } + setState(READY); + m_remoteSupportedMethods = event.methods(); + auto methods = commonSupportedMethods(m_remoteSupportedMethods); + + if (methods.isEmpty()) { + cancelVerification(UNKNOWN_METHOD); + return; + } + + if (methods.size() == 1) { + sendStartSas(); + } +} + +void KeyVerificationSession::handleStart(const KeyVerificationStartEvent& event) +{ + if (state() != READY) { + cancelVerification(UNEXPECTED_MESSAGE); + return; + } + if (startSentByUs) { + if (m_remoteUserId > m_connection->userId() || (m_remoteUserId == m_connection->userId() && m_remoteDeviceId > m_connection->deviceId())) { + return; + } else { + startSentByUs = false; + } + } + QByteArray publicKey(olm_sas_pubkey_length(m_sas), '\0'); + olm_sas_get_pubkey(m_sas, publicKey.data(), publicKey.size()); + const auto canonicalEvent = QString(QJsonDocument(event.contentJson()).toJson(QJsonDocument::Compact)); + auto commitment = QString(QCryptographicHash::hash((QString(publicKey) % canonicalEvent).toLatin1(), QCryptographicHash::Sha256).toBase64()); + commitment = commitment.left(commitment.indexOf('=')); + + auto acceptEvent = makeEvent(QJsonObject { + {"type", "m.key.verification.accept"}, + {"content", QJsonObject { + {"commitment", commitment}, + {"hash", "sha256"}, + {"key_agreement_protocol", "curve25519-hkdf-sha256"}, + {"message_authentication_code", "hkdf-hmac-sha256"}, + {"method", "m.sas.v1"}, + {"short_authentication_string", QJsonArray { + "decimal", + "emoji", + }}, + {"transaction_id", m_transactionId}, + }} + }); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(acceptEvent), m_encrypted); + setState(ACCEPTED); +} + +void KeyVerificationSession::handleAccept(const KeyVerificationAcceptEvent& event) +{ + if(state() != WAITINGFORACCEPT) { + cancelVerification(UNEXPECTED_MESSAGE); + return; + } + m_commitment = event.commitment(); + sendKey(); + setState(WAITINGFORKEY); +} + +void KeyVerificationSession::handleMac(const KeyVerificationMacEvent& event) +{ + QStringList keys = event.mac().keys(); + keys.sort(); + const auto& key = keys.join(","); + const QString edKeyId = "ed25519:"_ls % m_remoteDeviceId; + + if (calculateMac(m_connection->edKeyForUserDevice(m_remoteUserId, m_remoteDeviceId), true, edKeyId) != event.mac()[edKeyId]) { + cancelVerification(KEY_MISMATCH); + return; + } + + if (calculateMac(key, true) != event.keys()) { + cancelVerification(KEY_MISMATCH); + return; + } + + m_connection->database()->setSessionVerified(edKeyId); + emit m_connection->sessionVerified(m_remoteUserId, m_remoteDeviceId); + macReceived = true; + + if (state() == WAITINGFORMAC) { + setState(DONE); + sendDone(); + emit finished(); + deleteLater(); + } +} + +void KeyVerificationSession::handleDone(const KeyVerificationDoneEvent& event) +{ + if (state() != DONE) { + cancelVerification(UNEXPECTED_MESSAGE); + } +} + +void KeyVerificationSession::handleCancel(const KeyVerificationCancelEvent& event) +{ + setError(stringToError(event.code())); + setState(CANCELED); +} + +std::pair KeyVerificationSession::emojiForCode(int code) +{ + static QJsonArray data; + if (data.isEmpty()) { + QFile dataFile(":/sas-emoji.json"); + dataFile.open(QFile::ReadOnly); + data = QJsonDocument::fromJson(dataFile.readAll()).array(); + } + if (data[code].toObject()["translated_descriptions"].toObject().contains(m_language)) { + return {data[code].toObject()["emoji"].toString(), data[code].toObject()["translated_descriptions"].toObject()[m_language].toString()}; + } + return {data[code].toObject()["emoji"].toString(), data[code].toObject()["description"].toString()}; +} + +QList KeyVerificationSession::sasEmojis() const +{ + return m_sasEmojis; +} + +void KeyVerificationSession::sendRequest() +{ + QJsonArray methods = toJson(m_supportedMethods); + auto event = makeEvent(QJsonObject { + {"type", "m.key.verification.request"}, + {"content", QJsonObject { + {"from_device", m_connection->deviceId()}, + {"transaction_id", m_transactionId}, + {"methods", methods}, + {"timestamp", QDateTime::currentMSecsSinceEpoch()}, + }}, + }); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + setState(WAITINGFORREADY); +} + +KeyVerificationSession::State KeyVerificationSession::state() const +{ + return m_state; +} + +void KeyVerificationSession::setState(KeyVerificationSession::State state) +{ + m_state = state; + emit stateChanged(); +} + +KeyVerificationSession::Error KeyVerificationSession::error() const +{ + return m_error; +} + +void KeyVerificationSession::setError(Error error) +{ + m_error = error; + emit errorChanged(); +} + +QString KeyVerificationSession::errorToString(Error error) const +{ + switch(error) { + case NONE: + return "none"_ls; + case TIMEOUT: + return "m.timeout"_ls; + case USER: + return "m.user"_ls; + case UNEXPECTED_MESSAGE: + return "m.unexpected_message"_ls; + case UNKNOWN_TRANSACTION: + return "m.unknown_transaction"_ls; + case UNKNOWN_METHOD: + return "m.unknown_method"_ls; + case KEY_MISMATCH: + return "m.key_mismatch"_ls; + case USER_MISMATCH: + return "m.user_mismatch"_ls; + case INVALID_MESSAGE: + return "m.invalid_message"_ls; + case SESSION_ACCEPTED: + return "m.accepted"_ls; + case MISMATCHED_COMMITMENT: + return "m.mismatched_commitment"_ls; + case MISMATCHED_SAS: + return "m.mismatched_sas"_ls; + default: + return "m.user"_ls; + } +} + +KeyVerificationSession::Error KeyVerificationSession::stringToError(const QString& error) const +{ + if (error == "m.timeout"_ls) { + return REMOTE_TIMEOUT; + } else if (error == "m.user"_ls) { + return REMOTE_USER; + } else if (error == "m.unexpected_message"_ls) { + return REMOTE_UNEXPECTED_MESSAGE; + } else if (error == "m.unknown_message"_ls) { + return REMOTE_UNEXPECTED_MESSAGE; + } else if (error == "m.unknown_transaction"_ls) { + return REMOTE_UNKNOWN_TRANSACTION; + } else if (error == "m.unknown_method"_ls) { + return REMOTE_UNKNOWN_METHOD; + } else if (error == "m.key_mismatch"_ls) { + return REMOTE_KEY_MISMATCH; + } else if (error == "m.user_mismatch"_ls) { + return REMOTE_USER_MISMATCH; + } else if (error == "m.invalid_message"_ls) { + return REMOTE_INVALID_MESSAGE; + } else if (error == "m.accepted"_ls) { + return REMOTE_SESSION_ACCEPTED; + } else if (error == "m.mismatched_commitment"_ls) { + return REMOTE_MISMATCHED_COMMITMENT; + } else if (error == "m.mismatched_sas"_ls) { + return REMOTE_MISMATCHED_SAS; + } + return NONE; +} + +QStringList KeyVerificationSession::commonSupportedMethods(const QStringList& remoteMethods) const +{ + QStringList result; + for (const auto& method : remoteMethods) { + if (m_supportedMethods.contains(method)) { + result += method; + } + } + return result; +} diff --git a/lib/keyverificationsession.h b/lib/keyverificationsession.h new file mode 100644 index 00000000..cb7a99e9 --- /dev/null +++ b/lib/keyverificationsession.h @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2022 Tobias Fella +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "events/keyverificationevent.h" +#include +#include + +class OlmSAS; + +namespace Quotient { +class Connection; + +/** A key verification session. Listen for incoming sessions by connecting to Connection::newKeyVerificationSession. + Start a new session using Connection::startKeyVerificationSession. + The object is delete after finished is emitted. +*/ +class QUOTIENT_API KeyVerificationSession : public QObject +{ + Q_OBJECT + +public: + enum State { + INCOMING, // There is a request for verification incoming + WAITINGFORREADY, // We sent a request for verification and are waiting for ready + READY, // Either party sent a ready as a response to a request; The user selects a method + WAITINGFORACCEPT, // We sent a start and are waiting for an accept + ACCEPTED, // The other party sent an accept and is waiting for a key + WAITINGFORKEY, // We're waiting for a key + WAITINGFORVERIFICATION, // We're waiting for the *user* to verify the emojis + WAITINGFORMAC, // We're waiting for the mac + CANCELED, // The session has been canceled + DONE, // The verification is done + }; + Q_ENUM(State) + + enum Error { + NONE, + TIMEOUT, + REMOTE_TIMEOUT, + USER, + REMOTE_USER, + UNEXPECTED_MESSAGE, + REMOTE_UNEXPECTED_MESSAGE, + UNKNOWN_TRANSACTION, + REMOTE_UNKNOWN_TRANSACTION, + UNKNOWN_METHOD, + REMOTE_UNKNOWN_METHOD, + KEY_MISMATCH, + REMOTE_KEY_MISMATCH, + USER_MISMATCH, + REMOTE_USER_MISMATCH, + INVALID_MESSAGE, + REMOTE_INVALID_MESSAGE, + SESSION_ACCEPTED, + REMOTE_SESSION_ACCEPTED, + MISMATCHED_COMMITMENT, + REMOTE_MISMATCHED_COMMITMENT, + MISMATCHED_SAS, + REMOTE_MISMATCHED_SAS, + }; + Q_ENUM(Error); + + //Q_PROPERTY(int timeLeft READ timeLeft NOTIFY timeLeftChanged) + Q_PROPERTY(QString remoteDeviceId MEMBER m_remoteDeviceId CONSTANT) + Q_PROPERTY(QList sasEmojis READ sasEmojis NOTIFY sasEmojisChanged) + Q_PROPERTY(State state READ state NOTIFY stateChanged) + Q_PROPERTY(Error error READ error NOTIFY errorChanged) + + KeyVerificationSession(const QString& remoteUserId, const KeyVerificationRequestEvent& event, Connection* connection, bool encrypted, QObject* parent = nullptr); + KeyVerificationSession(const QString& userId, const QString& deviceId, Connection* connection, QObject* parent = nullptr); + ~KeyVerificationSession(); + + int timeLeft() const; + QList sasEmojis() const; + State state() const; + + Error error() const; + +public Q_SLOTS: + void sendRequest(); + void sendReady(); + void sendMac(); + void sendStartSas(); + void sendKey(); + void sendDone(); + void cancelVerification(Error error); + +Q_SIGNALS: + + void timeLeftChanged(); + void startReceived(); + void keyReceived(); + void sasEmojisChanged(); + void stateChanged(); + void errorChanged(); + void finished(); + +private: + QString m_remoteUserId; + QString m_remoteDeviceId; + QString m_transactionId; + Connection* m_connection; + OlmSAS* m_sas = nullptr; + int timeleft = 0; + QList m_sasEmojis; + bool startSentByUs = false; + State m_state; + Error m_error; + QString m_startEvent; + QString m_commitment; + QString m_language; + int m_timeout; + bool macReceived = false; + bool m_encrypted; + QStringList m_remoteSupportedMethods; + + void handleReady(const KeyVerificationReadyEvent& event); + void handleStart(const KeyVerificationStartEvent& event); + void handleAccept(const KeyVerificationAcceptEvent& event); + void handleKey(const KeyVerificationKeyEvent& event); + void handleMac(const KeyVerificationMacEvent& event); + void handleDone(const KeyVerificationDoneEvent& event); + void handleCancel(const KeyVerificationCancelEvent& event); + void init(); + void setState(State state); + void setError(Error error); + QStringList commonSupportedMethods(const QStringList& remoteSupportedMethods) const; + QString errorToString(Error error) const; + Error stringToError(const QString& error) const; + QStringList m_supportedMethods = { "m.sas.v1"_ls }; + + QByteArray macInfo(bool verifying, const QString& key = "KEY_IDS"_ls); + QString calculateMac(const QString& input, bool verifying, const QString& keyId= "KEY_IDS"_ls); + + std::pair emojiForCode(int code); +}; + +} diff --git a/res.qrc b/res.qrc new file mode 100644 index 00000000..f6769103 --- /dev/null +++ b/res.qrc @@ -0,0 +1,5 @@ + + + sas-emoji.json + + diff --git a/sas-emoji.json b/sas-emoji.json new file mode 100644 index 00000000..06e1e4b3 --- /dev/null +++ b/sas-emoji.json @@ -0,0 +1,2178 @@ +[ + { + "number": 0, + "emoji": "🐶", + "description": "Dog", + "unicode": "U+1F436", + "translated_descriptions": { + "ar": "كَلب", + "bg": "Куче", + "ca": "Gos", + "cs": "Pes", + "de": "Hund", + "eo": "Hundo", + "es": "Perro", + "et": "Koer", + "fi": "Koira", + "fr": "Chien", + "hr": "pas", + "hu": "Kutya", + "it": "Cane", + "ja": "犬", + "nb_NO": "Hund", + "nl": "Hond", + "pt_BR": "Cachorro", + "ru": "Собака", + "si": "බල්ලා", + "sk": "Hlava psa", + "sr": "пас", + "sv": "Hund", + "szl": null, + "tzm": "Aydi", + "uk": "Пес", + "zh_Hans": "狗" + } + }, + { + "number": 1, + "emoji": "🐱", + "description": "Cat", + "unicode": "U+1F431", + "translated_descriptions": { + "ar": "هِرَّة", + "bg": "Котка", + "ca": "Gat", + "cs": "Kočka", + "de": "Katze", + "eo": "Kato", + "es": "Gato", + "et": "Kass", + "fi": "Kissa", + "fr": "Chat", + "hr": "mačka", + "hu": "Macska", + "it": "Gatto", + "ja": "猫", + "nb_NO": "Katt", + "nl": "Kat", + "pt_BR": "Gato", + "ru": "Кошка", + "si": "පූසා", + "sk": "Hlava mačky", + "sr": "мачка", + "sv": "Katt", + "szl": null, + "tzm": "Amuc", + "uk": "Кіт", + "zh_Hans": "猫" + } + }, + { + "number": 2, + "emoji": "🦁", + "description": "Lion", + "unicode": "U+1F981", + "translated_descriptions": { + "ar": "أَسَد", + "bg": "Лъв", + "ca": "Lleó", + "cs": "Lev", + "de": "Löwe", + "eo": "Leono", + "es": "León", + "et": "Lõvi", + "fi": "Leijona", + "fr": "Lion", + "hr": "lav", + "hu": "Oroszlán", + "it": "Leone", + "ja": "ライオン", + "nb_NO": "Løve", + "nl": "Leeuw", + "pt_BR": "Leão", + "ru": "Лев", + "si": "සිංහයා", + "sk": "Hlava leva", + "sr": "лав", + "sv": "Lejon", + "szl": null, + "tzm": "Izem", + "uk": "Лев", + "zh_Hans": "狮子" + } + }, + { + "number": 3, + "emoji": "🐎", + "description": "Horse", + "unicode": "U+1F40E", + "translated_descriptions": { + "ar": "حِصَان", + "bg": "Кон", + "ca": "Cavall", + "cs": "Kůň", + "de": "Pferd", + "eo": "Ĉevalo", + "es": "Caballo", + "et": "Hobune", + "fi": "Hevonen", + "fr": "Cheval", + "hr": "konj", + "hu": "Ló", + "it": "Cavallo", + "ja": "馬", + "nb_NO": "Hest", + "nl": "Paard", + "pt_BR": "Cavalo", + "ru": "Лошадь", + "si": "අශ්වයා", + "sk": "Kôň", + "sr": "коњ", + "sv": "Häst", + "szl": null, + "tzm": "Ayyis", + "uk": "Кінь", + "zh_Hans": "马" + } + }, + { + "number": 4, + "emoji": "🦄", + "description": "Unicorn", + "unicode": "U+1F984", + "translated_descriptions": { + "ar": "حِصَانٌ بِقَرن", + "bg": "Еднорог", + "ca": "Unicorn", + "cs": "Jednorožec", + "de": "Einhorn", + "eo": "Unukorno", + "es": "Unicornio", + "et": "Ükssarvik", + "fi": "Yksisarvinen", + "fr": "Licorne", + "hr": "jednorog", + "hu": "Egyszarvú", + "it": "Unicorno", + "ja": "ユニコーン", + "nb_NO": "Enhjørning", + "nl": "Eenhoorn", + "pt_BR": "Unicórnio", + "ru": "Единорог", + "si": null, + "sk": "Hlava jednorožca", + "sr": "једнорог", + "sv": "Enhörning", + "szl": null, + "tzm": null, + "uk": "Єдиноріг", + "zh_Hans": "独角兽" + } + }, + { + "number": 5, + "emoji": "🐷", + "description": "Pig", + "unicode": "U+1F437", + "translated_descriptions": { + "ar": "خِنزِير", + "bg": "Прасе", + "ca": "Porc", + "cs": "Prase", + "de": "Schwein", + "eo": "Porko", + "es": "Cerdo", + "et": "Siga", + "fi": "Sika", + "fr": "Cochon", + "hr": "svinja", + "hu": "Malac", + "it": "Maiale", + "ja": "ブタ", + "nb_NO": "Gris", + "nl": "Varken", + "pt_BR": "Porco", + "ru": "Свинья", + "si": null, + "sk": "Hlava prasaťa", + "sr": "прасе", + "sv": "Gris", + "szl": null, + "tzm": "Ilef", + "uk": "Свиня", + "zh_Hans": "猪" + } + }, + { + "number": 6, + "emoji": "🐘", + "description": "Elephant", + "unicode": "U+1F418", + "translated_descriptions": { + "ar": "فِيل", + "bg": "Слон", + "ca": "Elefant", + "cs": "Slon", + "de": "Elefant", + "eo": "Elefanto", + "es": "Elefante", + "et": "Elevant", + "fi": "Norsu", + "fr": "Éléphant", + "hr": "slon", + "hu": "Elefánt", + "it": "Elefante", + "ja": "ゾウ", + "nb_NO": "Elefant", + "nl": "Olifant", + "pt_BR": "Elefante", + "ru": "Слон", + "si": null, + "sk": "Slon", + "sr": "слон", + "sv": "Elefant", + "szl": null, + "tzm": "Ilu", + "uk": "Слон", + "zh_Hans": "大象" + } + }, + { + "number": 7, + "emoji": "🐰", + "description": "Rabbit", + "unicode": "U+1F430", + "translated_descriptions": { + "ar": "أَرنَب", + "bg": "Заек", + "ca": "Conill", + "cs": "Králík", + "de": "Hase", + "eo": "Kuniklo", + "es": "Conejo", + "et": "Jänes", + "fi": "Kani", + "fr": "Lapin", + "hr": "zec", + "hu": "Nyúl", + "it": "Coniglio", + "ja": "うさぎ", + "nb_NO": "Kanin", + "nl": "Konijn", + "pt_BR": "Coelho", + "ru": "Кролик", + "si": null, + "sk": "Hlava zajaca", + "sr": "зец", + "sv": "Kanin", + "szl": null, + "tzm": "Agnin", + "uk": "Кріль", + "zh_Hans": "兔子" + } + }, + { + "number": 8, + "emoji": "🐼", + "description": "Panda", + "unicode": "U+1F43C", + "translated_descriptions": { + "ar": "باندَا", + "bg": "Панда", + "ca": "Panda", + "cs": "Panda", + "de": "Panda", + "eo": "Pando", + "es": "Panda", + "et": "Panda", + "fi": "Panda", + "fr": "Panda", + "hr": "panda", + "hu": "Panda", + "it": "Panda", + "ja": "パンダ", + "nb_NO": "Panda", + "nl": "Panda", + "pt_BR": "Panda", + "ru": "Панда", + "si": null, + "sk": "Hlava pandy", + "sr": "панда", + "sv": "Panda", + "szl": null, + "tzm": null, + "uk": "Панда", + "zh_Hans": "熊猫" + } + }, + { + "number": 9, + "emoji": "🐓", + "description": "Rooster", + "unicode": "U+1F413", + "translated_descriptions": { + "ar": "دِيك", + "bg": "Петел", + "ca": "Gall", + "cs": "Kohout", + "de": "Hahn", + "eo": "Virkoko", + "es": "Gallo", + "et": "Kukk", + "fi": "Kukko", + "fr": "Coq", + "hr": "kokot", + "hu": "Kakas", + "it": "Gallo", + "ja": "ニワトリ", + "nb_NO": "Hane", + "nl": "Haan", + "pt_BR": "Galo", + "ru": "Петух", + "si": null, + "sk": "Kohút", + "sr": "петао", + "sv": "Tupp", + "szl": null, + "tzm": "Ayaẓiḍ", + "uk": "Когут", + "zh_Hans": "公鸡" + } + }, + { + "number": 10, + "emoji": "🐧", + "description": "Penguin", + "unicode": "U+1F427", + "translated_descriptions": { + "ar": "بِطريق", + "bg": "Пингвин", + "ca": "Pingüí", + "cs": "Tučňák", + "de": "Pinguin", + "eo": "Pingveno", + "es": "Pingüino", + "et": "Pingviin", + "fi": "Pingviini", + "fr": "Manchot", + "hr": "pingvin", + "hu": "Pingvin", + "it": "Pinguino", + "ja": "ペンギン", + "nb_NO": "Pingvin", + "nl": "Pinguïn", + "pt_BR": "Pinguim", + "ru": "Пингвин", + "si": null, + "sk": "Tučniak", + "sr": "пингвин", + "sv": "Pingvin", + "szl": null, + "tzm": null, + "uk": "Пінгвін", + "zh_Hans": "企鹅" + } + }, + { + "number": 11, + "emoji": "🐢", + "description": "Turtle", + "unicode": "U+1F422", + "translated_descriptions": { + "ar": "سُلحفاة", + "bg": "Костенурка", + "ca": "Tortuga", + "cs": "Želva", + "de": "Schildkröte", + "eo": "Testudo", + "es": "Tortuga", + "et": "Kilpkonn", + "fi": "Kilpikonna", + "fr": "Tortue", + "hr": "kornjača", + "hu": "Teknős", + "it": "Tartaruga", + "ja": "亀", + "nb_NO": "Skilpadde", + "nl": "Schildpad", + "pt_BR": "Tartaruga", + "ru": "Черепаха", + "si": null, + "sk": "Korytnačka", + "sr": "корњача", + "sv": "Sköldpadda", + "szl": null, + "tzm": "Ifker", + "uk": "Черепаха", + "zh_Hans": "乌龟" + } + }, + { + "number": 12, + "emoji": "🐟", + "description": "Fish", + "unicode": "U+1F41F", + "translated_descriptions": { + "ar": "سَمَكَة", + "bg": "Риба", + "ca": "Peix", + "cs": "Ryba", + "de": "Fisch", + "eo": "Fiŝo", + "es": "Pez", + "et": "Kala", + "fi": "Kala", + "fr": "Poisson", + "hr": "riba", + "hu": "Hal", + "it": "Pesce", + "ja": "魚", + "nb_NO": "Fisk", + "nl": "Vis", + "pt_BR": "Peixe", + "ru": "Рыба", + "si": null, + "sk": "Ryba", + "sr": "риба", + "sv": "Fisk", + "szl": null, + "tzm": "Aselm", + "uk": "Риба", + "zh_Hans": "鱼" + } + }, + { + "number": 13, + "emoji": "🐙", + "description": "Octopus", + "unicode": "U+1F419", + "translated_descriptions": { + "ar": "أُخطُبُوط", + "bg": "Октопод", + "ca": "Pop", + "cs": "Chobotnice", + "de": "Oktopus", + "eo": "Polpo", + "es": "Pulpo", + "et": "Kaheksajalg", + "fi": "Tursas", + "fr": "Poulpe", + "hr": "hobotnica", + "hu": "Polip", + "it": "Polpo", + "ja": "たこ", + "nb_NO": "Blekksprut", + "nl": "Octopus", + "pt_BR": "Polvo", + "ru": "Осьминог", + "si": null, + "sk": "Chobotnica", + "sr": "октопод", + "sv": "Bläckfisk", + "szl": null, + "tzm": null, + "uk": "Восьминіг", + "zh_Hans": "章鱼" + } + }, + { + "number": 14, + "emoji": "🦋", + "description": "Butterfly", + "unicode": "U+1F98B", + "translated_descriptions": { + "ar": "فَرَاشَة", + "bg": "Пеперуда", + "ca": "Papallona", + "cs": "Motýl", + "de": "Schmetterling", + "eo": "Papilio", + "es": "Mariposa", + "et": "Liblikas", + "fi": "Perhonen", + "fr": "Papillon", + "hr": "leptir", + "hu": "Pillangó", + "it": "Farfalla", + "ja": "ちょうちょ", + "nb_NO": "Sommerfugl", + "nl": "Vlinder", + "pt_BR": "Borboleta", + "ru": "Бабочка", + "si": null, + "sk": "Motýľ", + "sr": "лептир", + "sv": "Fjäril", + "szl": null, + "tzm": null, + "uk": "Метелик", + "zh_Hans": "蝴蝶" + } + }, + { + "number": 15, + "emoji": "🌷", + "description": "Flower", + "unicode": "U+1F337", + "translated_descriptions": { + "ar": "زَهرَة", + "bg": "Цвете", + "ca": "Flor", + "cs": "Květina", + "de": "Blume", + "eo": "Floro", + "es": "Flor", + "et": "Lill", + "fi": "Kukka", + "fr": "Fleur", + "hr": "svijet", + "hu": "Virág", + "it": "Fiore", + "ja": "花", + "nb_NO": "Blomst", + "nl": "Bloem", + "pt_BR": "Flor", + "ru": "Цветок", + "si": null, + "sk": "Tulipán", + "sr": "цвет", + "sv": "Blomma", + "szl": null, + "tzm": null, + "uk": "Квітка", + "zh_Hans": "花" + } + }, + { + "number": 16, + "emoji": "🌳", + "description": "Tree", + "unicode": "U+1F333", + "translated_descriptions": { + "ar": "شَجَرَة", + "bg": "Дърво", + "ca": "Arbre", + "cs": "Strom", + "de": "Baum", + "eo": "Arbo", + "es": "Árbol", + "et": "Puu", + "fi": "Puu", + "fr": "Arbre", + "hr": "drvo", + "hu": "Fa", + "it": "Albero", + "ja": "木", + "nb_NO": "Tre", + "nl": "Boom", + "pt_BR": "Árvore", + "ru": "Дерево", + "si": null, + "sk": "Listnatý strom", + "sr": "дрво", + "sv": "Träd", + "szl": null, + "tzm": "Aseklu", + "uk": "Дерево", + "zh_Hans": "树" + } + }, + { + "number": 17, + "emoji": "🌵", + "description": "Cactus", + "unicode": "U+1F335", + "translated_descriptions": { + "ar": "صبار", + "bg": "Кактус", + "ca": "Cactus", + "cs": "Kaktus", + "de": "Kaktus", + "eo": "Kakto", + "es": "Cactus", + "et": "Kaktus", + "fi": "Kaktus", + "fr": "Cactus", + "hr": "kaktus", + "hu": "Kaktusz", + "it": "Cactus", + "ja": "サボテン", + "nb_NO": "Kaktus", + "nl": "Cactus", + "pt_BR": "Cacto", + "ru": "Кактус", + "si": null, + "sk": "Kaktus", + "sr": "кактус", + "sv": "Kaktus", + "szl": null, + "tzm": null, + "uk": "Кактус", + "zh_Hans": "仙人掌" + } + }, + { + "number": 18, + "emoji": "🍄", + "description": "Mushroom", + "unicode": "U+1F344", + "translated_descriptions": { + "ar": "فُطر", + "bg": "Гъба", + "ca": "Bolet", + "cs": "Houba", + "de": "Pilz", + "eo": "Fungo", + "es": "Seta", + "et": "Seen", + "fi": "Sieni", + "fr": "Champignon", + "hr": "gljiva", + "hu": "Gomba", + "it": "Fungo", + "ja": "きのこ", + "nb_NO": "Sopp", + "nl": "Paddenstoel", + "pt_BR": "Cogumelo", + "ru": "Гриб", + "si": null, + "sk": "Huba", + "sr": "печурка", + "sv": "Svamp", + "szl": null, + "tzm": "Agursel", + "uk": "Гриб", + "zh_Hans": "蘑菇" + } + }, + { + "number": 19, + "emoji": "🌏", + "description": "Globe", + "unicode": "U+1F30F", + "translated_descriptions": { + "ar": "كُرَةٌ أرضِيَّة", + "bg": "Глобус", + "ca": "Globus terraqüi", + "cs": "Zeměkoule", + "de": "Globus", + "eo": "Globo", + "es": "Globo", + "et": "Maakera", + "fi": "Maapallo", + "fr": "Globe", + "hr": "Globus", + "hu": "Földgömb", + "it": "Globo", + "ja": "地球", + "nb_NO": "Globus", + "nl": "Wereldbol", + "pt_BR": "Globo", + "ru": "Глобус", + "si": null, + "sk": "Zemeguľa", + "sr": "глобус", + "sv": "Jordklot", + "szl": null, + "tzm": null, + "uk": "Глобус", + "zh_Hans": "地球" + } + }, + { + "number": 20, + "emoji": "🌙", + "description": "Moon", + "unicode": "U+1F319", + "translated_descriptions": { + "ar": "قَمَر", + "bg": "Луна", + "ca": "Lluna", + "cs": "Měsíc", + "de": "Mond", + "eo": "Luno", + "es": "Luna", + "et": "Kuu", + "fi": "Kuu", + "fr": "Lune", + "hr": "mjesec", + "hu": "Hold", + "it": "Luna", + "ja": "月", + "nb_NO": "Måne", + "nl": "Maan", + "pt_BR": "Lua", + "ru": "Луна", + "si": null, + "sk": "Polmesiac", + "sr": "месец", + "sv": "Måne", + "szl": null, + "tzm": "Ayyur", + "uk": "Місяць", + "zh_Hans": "月亮" + } + }, + { + "number": 21, + "emoji": "☁️", + "description": "Cloud", + "unicode": "U+2601U+FE0F", + "translated_descriptions": { + "ar": "سَحابَة", + "bg": "Облак", + "ca": "Núvol", + "cs": "Mrak", + "de": "Wolke", + "eo": "Nubo", + "es": "Nube", + "et": "Pilv", + "fi": "Pilvi", + "fr": "Nuage", + "hr": "oblak", + "hu": "Felhő", + "it": "Nuvola", + "ja": "雲", + "nb_NO": "Sky", + "nl": "Wolk", + "pt_BR": "Nuvem", + "ru": "Облако", + "si": null, + "sk": "Oblak", + "sr": "облак", + "sv": "Moln", + "szl": null, + "tzm": null, + "uk": "Хмара", + "zh_Hans": "云" + } + }, + { + "number": 22, + "emoji": "🔥", + "description": "Fire", + "unicode": "U+1F525", + "translated_descriptions": { + "ar": "نار", + "bg": "Огън", + "ca": "Foc", + "cs": "Oheň", + "de": "Feuer", + "eo": "Fajro", + "es": "Fuego", + "et": "Tuli", + "fi": "Tuli", + "fr": "Feu", + "hr": "vatra", + "hu": "Tűz", + "it": "Fuoco", + "ja": "炎", + "nb_NO": "Flamme", + "nl": "Vuur", + "pt_BR": "Fogo", + "ru": "Огонь", + "si": null, + "sk": "Oheň", + "sr": "ватра", + "sv": "Eld", + "szl": null, + "tzm": "Timessi", + "uk": "Вогонь", + "zh_Hans": "火" + } + }, + { + "number": 23, + "emoji": "🍌", + "description": "Banana", + "unicode": "U+1F34C", + "translated_descriptions": { + "ar": "مَوزَة", + "bg": "Банан", + "ca": "Plàtan", + "cs": "Banán", + "de": "Banane", + "eo": "Banano", + "es": "Plátano", + "et": "Banaan", + "fi": "Banaani", + "fr": "Banane", + "hr": "banana", + "hu": "Banán", + "it": "Banana", + "ja": "バナナ", + "nb_NO": "Banan", + "nl": "Banaan", + "pt_BR": "Banana", + "ru": "Банан", + "si": null, + "sk": "Banán", + "sr": "банана", + "sv": "Banan", + "szl": null, + "tzm": "Tabanant", + "uk": "Банан", + "zh_Hans": "香蕉" + } + }, + { + "number": 24, + "emoji": "🍎", + "description": "Apple", + "unicode": "U+1F34E", + "translated_descriptions": { + "ar": "تُفَّاحَة", + "bg": "Ябълка", + "ca": "Poma", + "cs": "Jablko", + "de": "Apfel", + "eo": "Pomo", + "es": "Manzana", + "et": "Õun", + "fi": "Omena", + "fr": "Pomme", + "hr": "jabuka", + "hu": "Alma", + "it": "Mela", + "ja": "リンゴ", + "nb_NO": "Eple", + "nl": "Appel", + "pt_BR": "Maçã", + "ru": "Яблоко", + "si": null, + "sk": "Červené jablko", + "sr": "јабука", + "sv": "Äpple", + "szl": null, + "tzm": "Tadeffuyt", + "uk": "Яблуко", + "zh_Hans": "苹果" + } + }, + { + "number": 25, + "emoji": "🍓", + "description": "Strawberry", + "unicode": "U+1F353", + "translated_descriptions": { + "ar": "فَراوِلَة", + "bg": "Ягода", + "ca": "Maduixa", + "cs": "Jahoda", + "de": "Erdbeere", + "eo": "Frago", + "es": "Fresa", + "et": "Maasikas", + "fi": "Mansikka", + "fr": "Fraise", + "hr": "jagoda", + "hu": "Eper", + "it": "Fragola", + "ja": "いちご", + "nb_NO": "Jordbær", + "nl": "Aardbei", + "pt_BR": "Morango", + "ru": "Клубника", + "si": null, + "sk": "Jahoda", + "sr": "јагода", + "sv": "Jordgubbe", + "szl": null, + "tzm": null, + "uk": "Полуниця", + "zh_Hans": "草莓" + } + }, + { + "number": 26, + "emoji": "🌽", + "description": "Corn", + "unicode": "U+1F33D", + "translated_descriptions": { + "ar": "ذُرَة", + "bg": "Царевица", + "ca": "Blat de moro", + "cs": "Kukuřice", + "de": "Mais", + "eo": "Maizo", + "es": "Maíz", + "et": "Mais", + "fi": "Maissi", + "fr": "Maïs", + "hr": "kukuruza", + "hu": "Kukorica", + "it": "Mais", + "ja": "とうもろこし", + "nb_NO": "Mais", + "nl": "Maïs", + "pt_BR": "Milho", + "ru": "Кукуруза", + "si": null, + "sk": "Kukuričný klas", + "sr": "кукуруз", + "sv": "Majs", + "szl": null, + "tzm": null, + "uk": "Кукурудза", + "zh_Hans": "玉米" + } + }, + { + "number": 27, + "emoji": "🍕", + "description": "Pizza", + "unicode": "U+1F355", + "translated_descriptions": { + "ar": "بِيتزا", + "bg": "Пица", + "ca": "Pizza", + "cs": "Pizza", + "de": "Pizza", + "eo": "Pico", + "es": "Pizza", + "et": "Pitsa", + "fi": "Pizza", + "fr": "Pizza", + "hr": "pizza", + "hu": "Pizza", + "it": "Pizza", + "ja": "ピザ", + "nb_NO": "Pizza", + "nl": "Pizza", + "pt_BR": "Pizza", + "ru": "Пицца", + "si": null, + "sk": "Pizza", + "sr": "пица", + "sv": "Pizza", + "szl": null, + "tzm": null, + "uk": "Піца", + "zh_Hans": "披萨" + } + }, + { + "number": 28, + "emoji": "🎂", + "description": "Cake", + "unicode": "U+1F382", + "translated_descriptions": { + "ar": "كَعكَة", + "bg": "Торта", + "ca": "Pastís", + "cs": "Dort", + "de": "Kuchen", + "eo": "Torto", + "es": "Tarta", + "et": "Kook", + "fi": "Kakku", + "fr": "Gâteau", + "hr": "torta", + "hu": "Süti", + "it": "Torta", + "ja": "ケーキ", + "nb_NO": "Kake", + "nl": "Taart", + "pt_BR": "Bolo", + "ru": "Торт", + "si": null, + "sk": "Narodeninová torta", + "sr": "торта", + "sv": "Tårta", + "szl": null, + "tzm": null, + "uk": "Пиріг", + "zh_Hans": "蛋糕" + } + }, + { + "number": 29, + "emoji": "❤️", + "description": "Heart", + "unicode": "U+2764U+FE0F", + "translated_descriptions": { + "ar": "قَلب", + "bg": "Сърце", + "ca": "Cor", + "cs": "Srdce", + "de": "Herz", + "eo": "Koro", + "es": "Corazón", + "et": "Süda", + "fi": "Sydän", + "fr": "Cœur", + "hr": "srca", + "hu": "Szív", + "it": "Cuore", + "ja": "ハート", + "nb_NO": "Hjerte", + "nl": "Hart", + "pt_BR": "Coração", + "ru": "Сердце", + "si": null, + "sk": "červené srdce", + "sr": "срце", + "sv": "Hjärta", + "szl": null, + "tzm": "Ul", + "uk": "Серце", + "zh_Hans": "心" + } + }, + { + "number": 30, + "emoji": "😀", + "description": "Smiley", + "unicode": "U+1F600", + "translated_descriptions": { + "ar": "اِبتِسَامَة", + "bg": "Усмивка", + "ca": "Somrient", + "cs": "Smajlík", + "de": "Lächeln", + "eo": "Rideto", + "es": "Emoticono", + "et": "Smaili", + "fi": "Hymynaama", + "fr": "Sourire", + "hr": "smajlića", + "hu": "Mosoly", + "it": "Faccina sorridente", + "ja": "スマイル", + "nb_NO": "Smilefjes", + "nl": "Smiley", + "pt_BR": "Sorriso", + "ru": "Улыбка", + "si": null, + "sk": "Škeriaca sa tvár", + "sr": "смајли", + "sv": "Smiley", + "szl": null, + "tzm": null, + "uk": "Посмішка", + "zh_Hans": "笑脸" + } + }, + { + "number": 31, + "emoji": "🤖", + "description": "Robot", + "unicode": "U+1F916", + "translated_descriptions": { + "ar": "رُوبُوت", + "bg": "Робот", + "ca": "Robot", + "cs": "Robot", + "de": "Roboter", + "eo": "Roboto", + "es": "Robot", + "et": "Robot", + "fi": "Robotti", + "fr": "Robot", + "hr": "robot", + "hu": "Robot", + "it": "Robot", + "ja": "ロボと", + "nb_NO": "Robot", + "nl": "Robot", + "pt_BR": "Robô", + "ru": "Робот", + "si": null, + "sk": "Robot", + "sr": "робот", + "sv": "Robot", + "szl": null, + "tzm": "Aṛubu", + "uk": "Робот", + "zh_Hans": "机器人" + } + }, + { + "number": 32, + "emoji": "🎩", + "description": "Hat", + "unicode": "U+1F3A9", + "translated_descriptions": { + "ar": "قُبَّعَة", + "bg": "Шапка", + "ca": "Barret", + "cs": "Klobouk", + "de": "Hut", + "eo": "Ĉapelo", + "es": "Sombrero", + "et": "Kübar", + "fi": "Hattu", + "fr": "Chapeau", + "hr": "kapa", + "hu": "Kalap", + "it": "Cappello", + "ja": "帽子", + "nb_NO": "Hatt", + "nl": "Hoed", + "pt_BR": "Chapéu", + "ru": "Шляпа", + "si": null, + "sk": "Cilinder", + "sr": "шешир", + "sv": "Hatt", + "szl": null, + "tzm": "Taraza", + "uk": "Капелюх", + "zh_Hans": "帽子" + } + }, + { + "number": 33, + "emoji": "👓", + "description": "Glasses", + "unicode": "U+1F453", + "translated_descriptions": { + "ar": "نَظَّارَة", + "bg": "Очила", + "ca": "Ulleres", + "cs": "Brýle", + "de": "Brille", + "eo": "Okulvitroj", + "es": "Gafas", + "et": "Prillid", + "fi": "Silmälasit", + "fr": "Lunettes", + "hr": "naočale", + "hu": "Szemüveg", + "it": "Occhiali", + "ja": "めがね", + "nb_NO": "Briller", + "nl": "Bril", + "pt_BR": "Óculos", + "ru": "Очки", + "si": null, + "sk": "Okuliare", + "sr": "наочаре", + "sv": "Glasögon", + "szl": null, + "tzm": null, + "uk": "Окуляри", + "zh_Hans": "眼镜" + } + }, + { + "number": 34, + "emoji": "🔧", + "description": "Spanner", + "unicode": "U+1F527", + "translated_descriptions": { + "ar": "مِفتَاحُ رَبط", + "bg": "Гаечен ключ", + "ca": "Clau anglesa", + "cs": "Klíč", + "de": "Schraubenschlüssel", + "eo": "Ŝraŭbŝlosilo", + "es": "Llave inglesa", + "et": "Mutrivõti", + "fi": "Kiintoavain", + "fr": "Clé à molette", + "hr": "ključ", + "hu": "Csavarkulcs", + "it": "Chiave inglese", + "ja": "スパナ", + "nb_NO": "Fastnøkkel", + "nl": "Moersleutel", + "pt_BR": "Chave inglesa", + "ru": "Ключ", + "si": null, + "sk": "Francúzsky kľúč", + "sr": "кључ", + "sv": "Skruvnyckel", + "szl": null, + "tzm": null, + "uk": "Гайковий ключ", + "zh_Hans": "扳手" + } + }, + { + "number": 35, + "emoji": "🎅", + "description": "Santa", + "unicode": "U+1F385", + "translated_descriptions": { + "ar": "سانتا", + "bg": "Дядо Коледа", + "ca": "Pare Noél", + "cs": "Mikuláš", + "de": "Weihnachtsmann", + "eo": "Kristnaska viro", + "es": "Papá Noel", + "et": "Jõuluvana", + "fi": "Joulupukki", + "fr": "Père Noël", + "hr": "deda Mraz", + "hu": "Télapó", + "it": "Babbo Natale", + "ja": "サンタ", + "nb_NO": "Julenisse", + "nl": "Kerstman", + "pt_BR": "Papai-noel", + "ru": "Санта", + "si": null, + "sk": "Santa Claus", + "sr": "деда Мраз", + "sv": "Tomte", + "szl": null, + "tzm": null, + "uk": "Санта Клаус", + "zh_Hans": "圣诞老人" + } + }, + { + "number": 36, + "emoji": "👍", + "description": "Thumbs Up", + "unicode": "U+1F44D", + "translated_descriptions": { + "ar": "رَفعُ إِبهَام", + "bg": "Палец нагоре", + "ca": "Polzes amunt", + "cs": "Palec nahoru", + "de": "Daumen Hoch", + "eo": "Dikfingro supren", + "es": "Pulgar arriba", + "et": "Pöidlad püsti", + "fi": "Peukalo ylös", + "fr": "Pouce en l’air", + "hr": "palac gore", + "hu": "Hüvelykujj fel", + "it": "Pollice alzato", + "ja": "いいね", + "nb_NO": "Tommel Opp", + "nl": "Duim omhoog", + "pt_BR": "Joinha", + "ru": "Большой палец вверх", + "si": null, + "sk": "Palec nahor", + "sr": "палчић горе", + "sv": "Tummen upp", + "szl": null, + "tzm": null, + "uk": "Великий палець вгору", + "zh_Hans": "赞" + } + }, + { + "number": 37, + "emoji": "☂️", + "description": "Umbrella", + "unicode": "U+2602U+FE0F", + "translated_descriptions": { + "ar": "مِظَلَّة", + "bg": "Чадър", + "ca": "Paraigües", + "cs": "Deštník", + "de": "Regenschirm", + "eo": "Ombrelo", + "es": "Paraguas", + "et": "Vihmavari", + "fi": "Sateenvarjo", + "fr": "Parapluie", + "hr": "kišobran", + "hu": "Esernyő", + "it": "Ombrello", + "ja": "傘", + "nb_NO": "Paraply", + "nl": "Paraplu", + "pt_BR": "Guarda-chuva", + "ru": "Зонт", + "si": null, + "sk": "Dáždnik", + "sr": "кишобран", + "sv": "Paraply", + "szl": null, + "tzm": null, + "uk": "Парасолька", + "zh_Hans": "伞" + } + }, + { + "number": 38, + "emoji": "⌛", + "description": "Hourglass", + "unicode": "U+231B", + "translated_descriptions": { + "ar": "سَاعَةٌ رَملِيَّة", + "bg": "Пясъчен часовник", + "ca": "Rellotge de sorra", + "cs": "Přesýpací hodiny", + "de": "Sanduhr", + "eo": "Sablohorloĝo", + "es": "Reloj de arena", + "et": "Liivakell", + "fi": "Tiimalasi", + "fr": "Sablier", + "hr": "pješčani sat", + "hu": "Homokóra", + "it": "Clessidra", + "ja": "砂時計", + "nb_NO": "Timeglass", + "nl": "Zandloper", + "pt_BR": "Ampulheta", + "ru": "Песочные часы", + "si": null, + "sk": "Presýpacie hodiny", + "sr": "пешчаник", + "sv": "Timglas", + "szl": null, + "tzm": null, + "uk": "Пісковий годинник", + "zh_Hans": "沙漏" + } + }, + { + "number": 39, + "emoji": "⏰", + "description": "Clock", + "unicode": "U+23F0", + "translated_descriptions": { + "ar": "سَاعَة", + "bg": "Часовник", + "ca": "Rellotge", + "cs": "Hodiny", + "de": "Uhr", + "eo": "Horloĝo", + "es": "Reloj", + "et": "Kell", + "fi": "Pöytäkello", + "fr": "Réveil", + "hr": "sat", + "hu": "Óra", + "it": "Orologio", + "ja": "時計", + "nb_NO": "Klokke", + "nl": "Wekker", + "pt_BR": "Relógio", + "ru": "Часы", + "si": null, + "sk": "Budík", + "sr": "сат", + "sv": "Klocka", + "szl": null, + "tzm": null, + "uk": "Годинник", + "zh_Hans": "时钟" + } + }, + { + "number": 40, + "emoji": "🎁", + "description": "Gift", + "unicode": "U+1F381", + "translated_descriptions": { + "ar": "هَدِيَّة", + "bg": "Подарък", + "ca": "Regal", + "cs": "Dárek", + "de": "Geschenk", + "eo": "Donaco", + "es": "Regalo", + "et": "Kingitus", + "fi": "Lahja", + "fr": "Cadeau", + "hr": "poklon", + "hu": "Ajándék", + "it": "Regalo", + "ja": "ギフト", + "nb_NO": "Gave", + "nl": "Geschenk", + "pt_BR": "Presente", + "ru": "Подарок", + "si": null, + "sk": "Zabalený darček", + "sr": "поклон", + "sv": "Present", + "szl": null, + "tzm": null, + "uk": "Подарунок", + "zh_Hans": "礼物" + } + }, + { + "number": 41, + "emoji": "💡", + "description": "Light Bulb", + "unicode": "U+1F4A1", + "translated_descriptions": { + "ar": "مِصبَاح", + "bg": "Лампа", + "ca": "Bombeta", + "cs": "Žárovka", + "de": "Glühbirne", + "eo": "Lampo", + "es": "Bombilla", + "et": "Lambipirn", + "fi": "Hehkulamppu", + "fr": "Ampoule", + "hr": "žarulja", + "hu": "Égő", + "it": "Lampadina", + "ja": "電球", + "nb_NO": "Lyspære", + "nl": "Gloeilamp", + "pt_BR": "Lâmpada", + "ru": "Лампочка", + "si": null, + "sk": "Žiarovka", + "sr": "сијалица", + "sv": "Lampa", + "szl": null, + "tzm": null, + "uk": "Лампочка", + "zh_Hans": "灯泡" + } + }, + { + "number": 42, + "emoji": "📕", + "description": "Book", + "unicode": "U+1F4D5", + "translated_descriptions": { + "ar": "كِتَاب", + "bg": "Книга", + "ca": "Llibre", + "cs": "Kniha", + "de": "Buch", + "eo": "Libro", + "es": "Libro", + "et": "Raamat", + "fi": "Kirja", + "fr": "Livre", + "hr": "knjiga", + "hu": "Könyv", + "it": "Libro", + "ja": "本", + "nb_NO": "Bok", + "nl": "Boek", + "pt_BR": "Livro", + "ru": "Книга", + "si": null, + "sk": "Zatvorená kniha", + "sr": "књига", + "sv": "Bok", + "szl": null, + "tzm": "Adlis", + "uk": "Книга", + "zh_Hans": "书" + } + }, + { + "number": 43, + "emoji": "✏️", + "description": "Pencil", + "unicode": "U+270FU+FE0F", + "translated_descriptions": { + "ar": "قَلَمُ رَصاص", + "bg": "Молив", + "ca": "Llapis", + "cs": "Tužka", + "de": "Bleistift", + "eo": "Krajono", + "es": "Lápiz", + "et": "Pliiats", + "fi": "Lyijykynä", + "fr": "Crayon", + "hr": "olovka", + "hu": "Ceruza", + "it": "Matita", + "ja": "鉛筆", + "nb_NO": "Blyant", + "nl": "Potlood", + "pt_BR": "Lápis", + "ru": "Карандаш", + "si": null, + "sk": "Ceruzka", + "sr": "оловка", + "sv": "Penna", + "szl": null, + "tzm": null, + "uk": "Олівець", + "zh_Hans": "铅笔" + } + }, + { + "number": 44, + "emoji": "📎", + "description": "Paperclip", + "unicode": "U+1F4CE", + "translated_descriptions": { + "ar": "مِشبَكُ وَرَق", + "bg": "Кламер", + "ca": "Clip", + "cs": "Sponka", + "de": "Büroklammer", + "eo": "Paperkuntenilo", + "es": "Clip", + "et": "Kirjaklamber", + "fi": "Paperiliitin", + "fr": "Trombone", + "hr": "spajalica", + "hu": "Gémkapocs", + "it": "Graffetta", + "ja": "クリップ", + "nb_NO": "BInders", + "nl": "Papierklemmetje", + "pt_BR": "Clipe de papel", + "ru": "Скрепка", + "si": null, + "sk": "Sponka na papier", + "sr": "спајалица", + "sv": "Gem", + "szl": null, + "tzm": null, + "uk": "Спиначка", + "zh_Hans": "回形针" + } + }, + { + "number": 45, + "emoji": "✂️", + "description": "Scissors", + "unicode": "U+2702U+FE0F", + "translated_descriptions": { + "ar": "مِقَصّ", + "bg": "Ножици", + "ca": "Tisores", + "cs": "Nůžky", + "de": "Schere", + "eo": "Tondilo", + "es": "Tijeras", + "et": "Käärid", + "fi": "Sakset", + "fr": "Ciseaux", + "hr": "škare", + "hu": "Olló", + "it": "Forbici", + "ja": "はさみ", + "nb_NO": "Saks", + "nl": "Schaar", + "pt_BR": "Tesoura", + "ru": "Ножницы", + "si": null, + "sk": "Nožnice", + "sr": "маказе", + "sv": "Sax", + "szl": null, + "tzm": null, + "uk": "Ножиці", + "zh_Hans": "剪刀" + } + }, + { + "number": 46, + "emoji": "🔒", + "description": "Lock", + "unicode": "U+1F512", + "translated_descriptions": { + "ar": "قُفل", + "bg": "Катинар", + "ca": "Cadenat", + "cs": "Zámek", + "de": "Schloss", + "eo": "Seruro", + "es": "Candado", + "et": "Lukk", + "fi": "Lukko", + "fr": "Cadenas", + "hr": "zaključati", + "hu": "Lakat", + "it": "Lucchetto", + "ja": "錠前", + "nb_NO": "Lås", + "nl": "Slot", + "pt_BR": "Cadeado", + "ru": "Замок", + "si": null, + "sk": "Zatvorená zámka", + "sr": "катанац", + "sv": "Lås", + "szl": null, + "tzm": null, + "uk": "Замок", + "zh_Hans": "锁" + } + }, + { + "number": 47, + "emoji": "🔑", + "description": "Key", + "unicode": "U+1F511", + "translated_descriptions": { + "ar": "مِفتَاح", + "bg": "Ключ", + "ca": "Clau", + "cs": "Klíč", + "de": "Schlüssel", + "eo": "Ŝlosilo", + "es": "Llave", + "et": "Võti", + "fi": "Avain", + "fr": "Clé", + "hr": "ključ", + "hu": "Kulcs", + "it": "Chiave", + "ja": "鍵", + "nb_NO": "Nøkkel", + "nl": "Sleutel", + "pt_BR": "Chave", + "ru": "Ключ", + "si": null, + "sk": "Kľúč", + "sr": "кључ", + "sv": "Nyckel", + "szl": null, + "tzm": "Tasarut", + "uk": "Ключ", + "zh_Hans": "钥匙" + } + }, + { + "number": 48, + "emoji": "🔨", + "description": "Hammer", + "unicode": "U+1F528", + "translated_descriptions": { + "ar": "مِطرَقَة", + "bg": "Чук", + "ca": "Martell", + "cs": "Kladivo", + "de": "Hammer", + "eo": "Martelo", + "es": "Martillo", + "et": "Haamer", + "fi": "Vasara", + "fr": "Marteau", + "hr": "čekić", + "hu": "Kalapács", + "it": "Martello", + "ja": "金槌", + "nb_NO": "Hammer", + "nl": "Hamer", + "pt_BR": "Martelo", + "ru": "Молоток", + "si": null, + "sk": "Kladivo", + "sr": "чекић", + "sv": "Hammare", + "szl": null, + "tzm": null, + "uk": "Молоток", + "zh_Hans": "锤子" + } + }, + { + "number": 49, + "emoji": "☎️", + "description": "Telephone", + "unicode": "U+260EU+FE0F", + "translated_descriptions": { + "ar": "تِلِفُون", + "bg": "Телефон", + "ca": "Telèfon", + "cs": "Telefon", + "de": "Telefon", + "eo": "Telefono", + "es": "Telefono", + "et": "Telefon", + "fi": "Puhelin", + "fr": "Téléphone", + "hr": "telefon", + "hu": "Telefon", + "it": "Telefono", + "ja": "電話機", + "nb_NO": "Telefon", + "nl": "Telefoon", + "pt_BR": "Telefone", + "ru": "Телефон", + "si": null, + "sk": "Telefón", + "sr": "телефон", + "sv": "Telefon", + "szl": null, + "tzm": "Atilifun", + "uk": "Телефон", + "zh_Hans": "电话" + } + }, + { + "number": 50, + "emoji": "🏁", + "description": "Flag", + "unicode": "U+1F3C1", + "translated_descriptions": { + "ar": "عَلَم", + "bg": "Флаг", + "ca": "Bandera", + "cs": "Vlajka", + "de": "Flagge", + "eo": "Flago", + "es": "Bandera", + "et": "Lipp", + "fi": "Lippu", + "fr": "Drapeau", + "hr": "zastava", + "hu": "Zászló", + "it": "Bandiera", + "ja": "旗", + "nb_NO": "Flagg", + "nl": "Vlag", + "pt_BR": "Bandeira", + "ru": "Флаг", + "si": null, + "sk": "Kockovaná zástava", + "sr": "застава", + "sv": "Flagga", + "szl": null, + "tzm": "Acenyal", + "uk": "Прапор", + "zh_Hans": "旗帜" + } + }, + { + "number": 51, + "emoji": "🚂", + "description": "Train", + "unicode": "U+1F682", + "translated_descriptions": { + "ar": "قِطَار", + "bg": "Влак", + "ca": "Tren", + "cs": "Vlak", + "de": "Zug", + "eo": "Vagonaro", + "es": "Tren", + "et": "Rong", + "fi": "Juna", + "fr": "Train", + "hr": "vlak", + "hu": "Vonat", + "it": "Treno", + "ja": "電車", + "nb_NO": "Tog", + "nl": "Trein", + "pt_BR": "Trem", + "ru": "Поезд", + "si": null, + "sk": "Rušeň", + "sr": "воз", + "sv": "Tåg", + "szl": null, + "tzm": null, + "uk": "Потяг", + "zh_Hans": "火车" + } + }, + { + "number": 52, + "emoji": "🚲", + "description": "Bicycle", + "unicode": "U+1F6B2", + "translated_descriptions": { + "ar": "دَرّاجَة", + "bg": "Колело", + "ca": "Bicicleta", + "cs": "Kolo", + "de": "Fahrrad", + "eo": "Biciklo", + "es": "Bicicleta", + "et": "Jalgratas", + "fi": "Polkupyörä", + "fr": "Vélo", + "hr": "bicikl", + "hu": "Kerékpár", + "it": "Bicicletta", + "ja": "自転車", + "nb_NO": "Sykkel", + "nl": "Fiets", + "pt_BR": "Bicicleta", + "ru": "Велосипед", + "si": null, + "sk": "Bicykel", + "sr": "бицикл", + "sv": "Cykel", + "szl": null, + "tzm": null, + "uk": "Велосипед", + "zh_Hans": "自行车" + } + }, + { + "number": 53, + "emoji": "✈️", + "description": "Aeroplane", + "unicode": "U+2708U+FE0F", + "translated_descriptions": { + "ar": "طَائِرة", + "bg": "Самолет", + "ca": "Avió", + "cs": "Letadlo", + "de": "Flugzeug", + "eo": "Aviadilo", + "es": "Avión", + "et": "Lennuk", + "fi": "Lentokone", + "fr": "Avion", + "hr": "avion", + "hu": "Repülő", + "it": "Aeroplano", + "ja": "飛行機", + "nb_NO": "Fly", + "nl": "Vliegtuig", + "pt_BR": "Avião", + "ru": "Самолет", + "si": null, + "sk": "Lietadlo", + "sr": "авион", + "sv": "Flygplan", + "szl": null, + "tzm": null, + "uk": "Літак", + "zh_Hans": "飞机" + } + }, + { + "number": 54, + "emoji": "🚀", + "description": "Rocket", + "unicode": "U+1F680", + "translated_descriptions": { + "ar": "صَارُوخ", + "bg": "Ракета", + "ca": "Coet", + "cs": "Raketa", + "de": "Rakete", + "eo": "Raketo", + "es": "Cohete", + "et": "Rakett", + "fi": "Raketti", + "fr": "Fusée", + "hr": "raketa", + "hu": "Rakáta", + "it": "Razzo", + "ja": "ロケット", + "nb_NO": "Rakett", + "nl": "Raket", + "pt_BR": "Foguete", + "ru": "Ракета", + "si": null, + "sk": "Raketa", + "sr": "ракета", + "sv": "Raket", + "szl": null, + "tzm": null, + "uk": "Ракета", + "zh_Hans": "火箭" + } + }, + { + "number": 55, + "emoji": "🏆", + "description": "Trophy", + "unicode": "U+1F3C6", + "translated_descriptions": { + "ar": "كَأسُ النَّصر", + "bg": "Трофей", + "ca": "Trofeu", + "cs": "Pohár", + "de": "Pokal", + "eo": "Trofeo", + "es": "Trofeo", + "et": "Auhind", + "fi": "Palkinto", + "fr": "Trophée", + "hr": "trofej", + "hu": "Trófea", + "it": "Trofeo", + "ja": "トロフィー", + "nb_NO": "Pokal", + "nl": "Trofee", + "pt_BR": "Troféu", + "ru": "Кубок", + "si": null, + "sk": "Trofej", + "sr": "пехар", + "sv": "Trofé", + "szl": null, + "tzm": null, + "uk": "Приз", + "zh_Hans": "奖杯" + } + }, + { + "number": 56, + "emoji": "⚽", + "description": "Ball", + "unicode": "U+26BD", + "translated_descriptions": { + "ar": "كُرَة", + "bg": "Топка", + "ca": "Pilota", + "cs": "Míč", + "de": "Ball", + "eo": "Pilko", + "es": "Bola", + "et": "Pall", + "fi": "Pallo", + "fr": "Ballon", + "hr": "lopta", + "hu": "Labda", + "it": "Palla", + "ja": "ボール", + "nb_NO": "Ball", + "nl": "Bal", + "pt_BR": "Bola", + "ru": "Мяч", + "si": null, + "sk": "Futbal", + "sr": "лопта", + "sv": "Boll", + "szl": null, + "tzm": "Tcama", + "uk": "М'яч", + "zh_Hans": "球" + } + }, + { + "number": 57, + "emoji": "🎸", + "description": "Guitar", + "unicode": "U+1F3B8", + "translated_descriptions": { + "ar": "غيتار", + "bg": "Китара", + "ca": "Guitarra", + "cs": "Kytara", + "de": "Gitarre", + "eo": "Gitaro", + "es": "Guitarra", + "et": "Kitarr", + "fi": "Kitara", + "fr": "Guitare", + "hr": "gitara", + "hu": "Gitár", + "it": "Chitarra", + "ja": "ギター", + "nb_NO": "Gitar", + "nl": "Gitaar", + "pt_BR": "Guitarra", + "ru": "Гитара", + "si": null, + "sk": "Gitara", + "sr": "гитара", + "sv": "Gitarr", + "szl": null, + "tzm": "Agiṭaṛ", + "uk": "Гітара", + "zh_Hans": "吉他" + } + }, + { + "number": 58, + "emoji": "🎺", + "description": "Trumpet", + "unicode": "U+1F3BA", + "translated_descriptions": { + "ar": "بُوق", + "bg": "Тромпет", + "ca": "Trompeta", + "cs": "Trumpeta", + "de": "Trompete", + "eo": "Trumpeto", + "es": "Trompeta", + "et": "Trompet", + "fi": "Trumpetti", + "fr": "Trompette", + "hr": "truba", + "hu": "Trombita", + "it": "Trombetta", + "ja": "トランペット", + "nb_NO": "Trompet", + "nl": "Trompet", + "pt_BR": "Trombeta", + "ru": "Труба", + "si": null, + "sk": "Trúbka", + "sr": "труба", + "sv": "Trumpet", + "szl": null, + "tzm": null, + "uk": "Труба", + "zh_Hans": "喇叭" + } + }, + { + "number": 59, + "emoji": "🔔", + "description": "Bell", + "unicode": "U+1F514", + "translated_descriptions": { + "ar": "جَرَس", + "bg": "Звънец", + "ca": "Campana", + "cs": "Zvonek", + "de": "Glocke", + "eo": "Sonorilo", + "es": "Campana", + "et": "Kelluke", + "fi": "Soittokello", + "fr": "Cloche", + "hr": "zvono", + "hu": "Harang", + "it": "Campana", + "ja": "ベル", + "nb_NO": "Bjelle", + "nl": "Bel", + "pt_BR": "Sino", + "ru": "Колокол", + "si": null, + "sk": "Zvon", + "sr": "звоно", + "sv": "Bjällra", + "szl": null, + "tzm": null, + "uk": "Дзвін", + "zh_Hans": "铃铛" + } + }, + { + "number": 60, + "emoji": "⚓", + "description": "Anchor", + "unicode": "U+2693", + "translated_descriptions": { + "ar": "مِرسَاة", + "bg": "Котва", + "ca": "Àncora", + "cs": "Kotva", + "de": "Anker", + "eo": "Ankro", + "es": "Ancla", + "et": "Ankur", + "fi": "Ankkuri", + "fr": "Ancre", + "hr": "sidro", + "hu": "Horgony", + "it": "Ancora", + "ja": "いかり", + "nb_NO": "Anker", + "nl": "Anker", + "pt_BR": "Âncora", + "ru": "Якорь", + "si": null, + "sk": "Kotva", + "sr": "сидро", + "sv": "Ankare", + "szl": null, + "tzm": null, + "uk": "Якір", + "zh_Hans": "锚" + } + }, + { + "number": 61, + "emoji": "🎧", + "description": "Headphones", + "unicode": "U+1F3A7", + "translated_descriptions": { + "ar": "سَمّاعَة رَأس", + "bg": "Слушалки", + "ca": "Auriculars", + "cs": "Sluchátka", + "de": "Kopfhörer", + "eo": "Kapaŭdilo", + "es": "Cascos", + "et": "Kõrvaklapid", + "fi": "Kuulokkeet", + "fr": "Casque audio", + "hr": "slušalice", + "hu": "Fejhallgató", + "it": "Cuffie", + "ja": "ヘッドホン", + "nb_NO": "Hodetelefoner", + "nl": "Koptelefoon", + "pt_BR": "Fones de ouvido", + "ru": "Наушники", + "si": null, + "sk": "Slúchadlá", + "sr": "слушалице", + "sv": "Hörlurar", + "szl": null, + "tzm": null, + "uk": "Навушники", + "zh_Hans": "耳机" + } + }, + { + "number": 62, + "emoji": "📁", + "description": "Folder", + "unicode": "U+1F4C1", + "translated_descriptions": { + "ar": "مُجَلَّد", + "bg": "Папка", + "ca": "Carpeta", + "cs": "Složka", + "de": "Ordner", + "eo": "Dosierujo", + "es": "Carpeta", + "et": "Kaust", + "fi": "Kansio", + "fr": "Dossier", + "hr": "mapu", + "hu": "Mappa", + "it": "Cartella", + "ja": "フォルダ", + "nb_NO": "Mappe", + "nl": "Map", + "pt_BR": "Pasta", + "ru": "Папка", + "si": null, + "sk": "Fascikel", + "sr": "фасцикла", + "sv": "Mapp", + "szl": null, + "tzm": "Asdaw", + "uk": "Тека", + "zh_Hans": "文件夹" + } + }, + { + "number": 63, + "emoji": "📌", + "description": "Pin", + "unicode": "U+1F4CC", + "translated_descriptions": { + "ar": "دَبُّوس", + "bg": "Кабърче", + "ca": "Xinxeta", + "cs": "Špendlík", + "de": "Stecknadel", + "eo": "Pinglo", + "es": "Alfiler", + "et": "Nööpnõel", + "fi": "Nuppineula", + "fr": "Punaise", + "hr": "pribadača", + "hu": "Rajszeg", + "it": "Puntina", + "ja": "ピン", + "nb_NO": "Tegnestift", + "nl": "Duimspijker", + "pt_BR": "Alfinete", + "ru": "Булавка", + "si": null, + "sk": "Špendlík", + "sr": "чиода", + "sv": "Häftstift", + "szl": null, + "tzm": null, + "uk": "Кнопка", + "zh_Hans": "图钉" + } + } +] -- cgit v1.2.3 From 272cb01b05529971ea38e09bf75d8d8f194a9dd8 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 5 May 2022 14:23:14 +0200 Subject: Fix license identifier --- lib/events/encryptedfile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 0558563f..d0c4a030 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2021 Carl Schwan // -// SPDX-License-Identifier: LGPl-2.1-or-later +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once -- cgit v1.2.3 From b79f67919e05698a8c3daffbe0fe53fa1ce46e54 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 8 May 2022 17:43:08 +0200 Subject: toSnakeCase and EventContent::SingleKeyValue This is a rework of EventContent::SimpleContent previously defined in simplestateevents.h. Quite a few events (and not only state events) have just a single key-value pair in their content - this structure (which is really just a template wrapper around the value) and the accompanying JsonConverter<> specialisation encapsulate the concept to streamline definition of such events. This commit only has simplestateevents.h using it; further commits will use SingleKeyValue in other places. toSnakeCase is a facility function that converts camelCase used for C++ variables into snake_case used in JSON payloads. Combined with the preprocessor trick that makes a string literal from an identifier, this allows to reduce boilerplate code that repeats the same name for fields in C++ event classes and fields in JSON. SingleKeyValue uses it, and there are other cases for it coming. --- CMakeLists.txt | 1 + lib/converters.h | 16 ++++++++++ lib/events/simplestateevents.h | 72 ++++++++++++++++-------------------------- lib/events/single_key_value.h | 27 ++++++++++++++++ 4 files changed, 72 insertions(+), 44 deletions(-) create mode 100644 lib/events/single_key_value.h (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index dc2459e9..537d580e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,7 @@ list(APPEND lib_SRCS lib/events/eventloader.h lib/events/roomevent.h lib/events/roomevent.cpp lib/events/stateevent.h lib/events/stateevent.cpp + lib/events/single_key_value.h lib/events/simplestateevents.h lib/events/eventcontent.h lib/events/eventcontent.cpp lib/events/eventrelation.h lib/events/eventrelation.cpp diff --git a/lib/converters.h b/lib/converters.h index 515c96fd..6515310a 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -414,4 +414,20 @@ inline void addParam(ContT& container, const QString& key, ValT&& value) _impl::AddNode, Force>::impl(container, key, std::forward(value)); } + +// This is a facility function to convert camelCase method/variable names +// used throughout Quotient to snake_case JSON keys - see usage in +// single_key_value.h and event.h (DEFINE_CONTENT_GETTER macro). +inline auto toSnakeCase(QLatin1String s) +{ + QString result { s }; + for (auto it = result.begin(); it != result.end(); ++it) + if (it->isUpper()) { + const auto offset = static_cast(it - result.begin()); + result.insert(offset, '_'); // NB: invalidates iterators + it = result.begin() + offset + 1; + *it = it->toLower(); + } + return result; +} } // namespace Quotient diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 9610574b..33221542 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -4,63 +4,47 @@ #pragma once #include "stateevent.h" +#include "single_key_value.h" namespace Quotient { -namespace EventContent { - template - struct SimpleContent { - using value_type = T; - - // The constructor is templated to enable perfect forwarding - template - SimpleContent(QString keyName, TT&& value) - : value(std::forward(value)), key(std::move(keyName)) - {} - SimpleContent(const QJsonObject& json, QString keyName) - : value(fromJson(json[keyName])), key(std::move(keyName)) - {} - QJsonObject toJson() const - { - return { { key, Quotient::toJson(value) } }; - } - - T value; - const QString key; - }; -} // namespace EventContent - -#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \ - class QUOTIENT_API _Name \ - : public StateEvent> { \ - public: \ - using value_type = content_type::value_type; \ - DEFINE_EVENT_TYPEID(_TypeId, _Name) \ - template \ - explicit _Name(T&& value) \ - : StateEvent(typeId(), matrixTypeId(), QString(), \ - QStringLiteral(#_ContentKey), std::forward(value)) \ - {} \ - explicit _Name(QJsonObject obj) \ - : StateEvent(typeId(), std::move(obj), \ - QStringLiteral(#_ContentKey)) \ - {} \ - auto _ContentKey() const { return content().value; } \ - }; \ - REGISTER_EVENT_TYPE(_Name) \ +#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \ + constexpr auto _Name##Key = #_ContentKey##_ls; \ + class QUOTIENT_API _Name \ + : public StateEvent< \ + EventContent::SingleKeyValue<_ValueType, &_Name##Key>> { \ + public: \ + using value_type = _ValueType; \ + DEFINE_EVENT_TYPEID(_TypeId, _Name) \ + template \ + explicit _Name(T&& value) \ + : StateEvent(TypeId, matrixTypeId(), QString(), \ + std::forward(value)) \ + {} \ + explicit _Name(QJsonObject obj) \ + : StateEvent(TypeId, std::move(obj)) \ + {} \ + auto _ContentKey() const { return content().value; } \ + }; \ + REGISTER_EVENT_TYPE(_Name) \ // End of macro DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic) -DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", QStringList, pinnedEvents) +DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", + QStringList, pinnedEvents) +constexpr auto RoomAliasesEventKey = "aliases"_ls; class [[deprecated( "m.room.aliases events are deprecated by the Matrix spec; use" " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases")]] // -RoomAliasesEvent : public StateEvent> { +RoomAliasesEvent + : public StateEvent< + EventContent::SingleKeyValue> +{ public: DEFINE_EVENT_TYPEID("m.room.aliases", RoomAliasesEvent) explicit RoomAliasesEvent(const QJsonObject& obj) - : StateEvent(typeId(), obj, QStringLiteral("aliases")) + : StateEvent(typeId(), obj) {} QString server() const { return stateKey(); } QStringList aliases() const { return content().value; } diff --git a/lib/events/single_key_value.h b/lib/events/single_key_value.h new file mode 100644 index 00000000..75ca8cd2 --- /dev/null +++ b/lib/events/single_key_value.h @@ -0,0 +1,27 @@ +#pragma once + +#include "converters.h" + +namespace Quotient { + +namespace EventContent { + template + struct SingleKeyValue { + T value; + }; +} // namespace EventContent + +template +struct JsonConverter> { + using content_type = EventContent::SingleKeyValue; + static content_type load(const QJsonValue& jv) + { + return { fromJson(jv.toObject().value(JsonKey)) }; + } + static QJsonObject dump(const content_type& c) + { + return { { JsonKey, toJson(c.value) } }; + } + static inline const auto JsonKey = toSnakeCase(*KeyStr); +}; +} // namespace Quotient -- cgit v1.2.3 From b094a75f704b59f1382b6eec1f3bf0ab51fb0a85 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 4 Jan 2022 10:30:17 +0100 Subject: StateEvent: use non-member JSON converters With the reworked JsonConverter code it is possible to work uniformly with structures that have a member toJson() and a constructor converting from QJsonObject, as well as with structures that rely on an external JsonConverter specialisation. --- lib/events/eventcontent.h | 1 - lib/events/stateevent.h | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index de9a792b..76f20f79 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -34,7 +34,6 @@ namespace EventContent { explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {} virtual ~Base() = default; - // FIXME: make toJson() from converters.* work on base classes QJsonObject toJson() const; public: diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 88da68f8..19905431 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -72,7 +72,7 @@ struct Prev { explicit Prev(const QJsonObject& unsignedJson, ContentParamTs&&... contentParams) : senderId(unsignedJson.value("prev_sender"_ls).toString()) - , content(unsignedJson.value(PrevContentKeyL).toObject(), + , content(fromJson(unsignedJson.value(PrevContentKeyL)), std::forward(contentParams)...) {} @@ -89,7 +89,8 @@ public: explicit StateEvent(Type type, const QJsonObject& fullJson, ContentParamTs&&... contentParams) : StateEventBase(type, fullJson) - , _content(contentJson(), std::forward(contentParams)...) + , _content(fromJson(contentJson()), + std::forward(contentParams)...) { const auto& unsignedData = unsignedJson(); if (unsignedData.contains(PrevContentKeyL)) @@ -101,9 +102,9 @@ public: const QString& stateKey, ContentParamTs&&... contentParams) : StateEventBase(type, matrixType, stateKey) - , _content(std::forward(contentParams)...) + , _content{std::forward(contentParams)...} { - editJson().insert(ContentKey, _content.toJson()); + editJson().insert(ContentKey, toJson(_content)); } const ContentT& content() const { return _content; } @@ -111,7 +112,7 @@ public: void editContent(VisitorT&& visitor) { visitor(_content); - editJson()[ContentKeyL] = _content.toJson(); + editJson()[ContentKeyL] = toJson(_content); } const ContentT* prevContent() const { -- cgit v1.2.3 From 09ecce1744344f1bfbacd31c8f300440a4139be4 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 4 Jan 2022 12:46:34 +0100 Subject: Cleanup; comments reformatting --- lib/events/eventcontent.cpp | 24 +++--- lib/events/eventcontent.h | 198 +++++++++++++++++++++----------------------- 2 files changed, 109 insertions(+), 113 deletions(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 9d7edf20..479d5da0 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -29,12 +29,13 @@ FileInfo::FileInfo(const QFileInfo &fi) } FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, - Omittable file, QString originalFilename) + Omittable encryptedFile, + QString originalFilename) : mimeType(mimeType) , url(move(u)) , payloadSize(payloadSize) , originalName(move(originalFilename)) - , file(file) + , file(move(encryptedFile)) { if (!isValid()) qCWarning(MESSAGES) @@ -44,7 +45,7 @@ FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, } FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, - const Omittable &file, + Omittable encryptedFile, QString originalFilename) : originalInfoJson(infoJson) , mimeType( @@ -52,7 +53,7 @@ FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, , url(move(mxcUrl)) , payloadSize(fromJson(infoJson["size"_ls])) , originalName(move(originalFilename)) - , file(file) + , file(move(encryptedFile)) { if(url.isEmpty() && file.has_value()) { url = file->url; @@ -82,15 +83,16 @@ ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) {} ImageInfo::ImageInfo(const QUrl& mxcUrl, qint64 fileSize, const QMimeType& type, - QSize imageSize, const Omittable &file, const QString& originalFilename) - : FileInfo(mxcUrl, fileSize, type, file, originalFilename) + QSize imageSize, Omittable encryptedFile, + const QString& originalFilename) + : FileInfo(mxcUrl, fileSize, type, move(encryptedFile), originalFilename) , imageSize(imageSize) {} ImageInfo::ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, - const Omittable &file, + Omittable encryptedFile, const QString& originalFilename) - : FileInfo(mxcUrl, infoJson, file, originalFilename) + : FileInfo(mxcUrl, infoJson, move(encryptedFile), originalFilename) , imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt()) {} @@ -103,10 +105,10 @@ void ImageInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert(QStringLiteral("h"), imageSize.height()); } -Thumbnail::Thumbnail(const QJsonObject& infoJson, const Omittable &file) +Thumbnail::Thumbnail(const QJsonObject& infoJson, + Omittable encryptedFile) : ImageInfo(QUrl(infoJson["thumbnail_url"_ls].toString()), - infoJson["thumbnail_info"_ls].toObject(), - file) + infoJson["thumbnail_info"_ls].toObject(), move(encryptedFile)) {} void Thumbnail::fillInfoJson(QJsonObject* infoJson) const diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 76f20f79..4134202a 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -19,16 +19,13 @@ class QFileInfo; namespace Quotient { namespace EventContent { - /** - * A base class for all content types that can be stored - * in a RoomMessageEvent - * - * Each content type class should have a constructor taking - * a QJsonObject and override fillJson() with an implementation - * that will fill the target QJsonObject with stored values. It is - * assumed but not required that a content object can also be created - * from plain data. - */ + //! \brief Base for all content types that can be stored in RoomMessageEvent + //! + //! Each content type class should have a constructor taking + //! a QJsonObject and override fillJson() with an implementation + //! that will fill the target QJsonObject with stored values. It is + //! assumed but not required that a content object can also be created + //! from plain data. class QUOTIENT_API Base { public: explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {} @@ -60,45 +57,55 @@ namespace EventContent { // ImageContent : UrlWithThumbnailContent // VideoContent : PlayableContent> - /** - * A base/mixin class for structures representing an "info" object for - * some content types. These include most attachment types currently in - * the CS API spec. - * - * In order to use it in a content class, derive both from TypedBase - * (or Base) and from FileInfo (or its derivative, such as \p ImageInfo) - * and call fillInfoJson() to fill the "info" subobject. Make sure - * to pass an "info" part of JSON to FileInfo constructor, not the whole - * JSON content, as well as contents of "url" (or a similar key) and - * optionally "filename" node from the main JSON content. Assuming you - * don't do unusual things, you should use \p UrlBasedContent<> instead - * of doing multiple inheritance and overriding Base::fillJson() by hand. - * - * This class is not polymorphic. - */ + //! \brief Mix-in class representing `info` subobject in content JSON + //! + //! This is one of base classes for content types that deal with files or + //! URLs. It stores the file metadata attributes, such as size, MIME type + //! etc. found in the `content/info` subobject of event JSON payloads. + //! Actual content classes derive from this class _and_ TypedBase that + //! provides a polymorphic interface to access data in the mix-in. FileInfo + //! (as well as ImageInfo, that adds image size to the metadata) is NOT + //! polymorphic and is used in a non-polymorphic way to store thumbnail + //! metadata (in a separate instance), next to the metadata on the file + //! itself. + //! + //! If you need to make a new _content_ (not info) class based on files/URLs + //! take UrlBasedContent as the example, i.e.: + //! 1. Double-inherit from this class (or ImageInfo) and TypedBase. + //! 2. Provide a constructor from QJsonObject that will pass the `info` + //! subobject (not the whole content JSON) down to FileInfo/ImageInfo. + //! 3. Override fillJson() to customise the JSON export logic. Make sure + //! to call toInfoJson() from it to produce the payload for the `info` + //! subobject in the JSON payload. + //! + //! \sa ImageInfo, FileContent, ImageContent, AudioContent, VideoContent, + //! UrlBasedContent class QUOTIENT_API FileInfo { public: FileInfo() = default; + //! \brief Construct from a QFileInfo object + //! + //! \param fi a QFileInfo object referring to an existing file explicit FileInfo(const QFileInfo& fi); explicit FileInfo(QUrl mxcUrl, qint64 payloadSize = -1, const QMimeType& mimeType = {}, - Omittable file = none, + Omittable encryptedFile = none, QString originalFilename = {}); + //! \brief Construct from a JSON `info` payload + //! + //! Make sure to pass the `info` subobject of content JSON, not the + //! whole JSON content. FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, - const Omittable &file, + Omittable encryptedFile, QString originalFilename = {}); bool isValid() const; - void fillInfoJson(QJsonObject* infoJson) const; - - /** - * \brief Extract media id from the URL - * - * This can be used, e.g., to construct a QML-facing image:// - * URI as follows: - * \code "image://provider/" + info.mediaId() \endcode - */ + //! \brief Extract media id from the URL + //! + //! This can be used, e.g., to construct a QML-facing image:// + //! URI as follows: + //! \code "image://provider/" + info.mediaId() \endcode QString mediaId() const { return url.authority() + url.path(); } public: @@ -118,19 +125,17 @@ namespace EventContent { return infoJson; } - /** - * A content info class for image content types: image, thumbnail, video - */ + //! \brief A content info class for image/video content types and thumbnails class QUOTIENT_API ImageInfo : public FileInfo { public: ImageInfo() = default; explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); explicit ImageInfo(const QUrl& mxcUrl, qint64 fileSize = -1, const QMimeType& type = {}, QSize imageSize = {}, - const Omittable &file = none, + Omittable encryptedFile = none, const QString& originalFilename = {}); ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, - const Omittable &encryptedFile, + Omittable encryptedFile, const QString& originalFilename = {}); void fillInfoJson(QJsonObject* infoJson) const; @@ -139,22 +144,18 @@ namespace EventContent { QSize imageSize; }; - /** - * An auxiliary class for an info type that carries a thumbnail - * - * This class saves/loads a thumbnail to/from "info" subobject of - * the JSON representation of event content; namely, - * "info/thumbnail_url" and "info/thumbnail_info" fields are used. - */ + //! \brief An auxiliary class for an info type that carries a thumbnail + //! + //! This class saves/loads a thumbnail to/from `info` subobject of + //! the JSON representation of event content; namely, `info/thumbnail_url` + //! and `info/thumbnail_info` fields are used. class QUOTIENT_API Thumbnail : public ImageInfo { public: using ImageInfo::ImageInfo; - Thumbnail(const QJsonObject& infoJson, const Omittable &file = none); + Thumbnail(const QJsonObject& infoJson, + Omittable encryptedFile = none); - /** - * Writes thumbnail information to "thumbnail_info" subobject - * and thumbnail URL to "thumbnail_url" node inside "info". - */ + //! \brief Add thumbnail information to the passed `info` JSON object void fillInfoJson(QJsonObject* infoJson) const; }; @@ -170,18 +171,15 @@ namespace EventContent { using Base::Base; }; - /** - * A base class for content types that have a URL and additional info - * - * Types that derive from this class template take "url" and, - * optionally, "filename" values from the top-level JSON object and - * the rest of information from the "info" subobject, as defined by - * the parameter type. - * - * \tparam InfoT base info class - */ + //! \brief A template class for content types with a URL and additional info + //! + //! Types that derive from this class template take `url` and, + //! optionally, `filename` values from the top-level JSON object and + //! the rest of information from the `info` subobject, as defined by + //! the parameter type. + //! \tparam InfoT base info class - FileInfo or ImageInfo template - class QUOTIENT_API UrlBasedContent : public TypedBase, public InfoT { + class UrlBasedContent : public TypedBase, public InfoT { public: using InfoT::InfoT; explicit UrlBasedContent(const QJsonObject& json) @@ -241,43 +239,39 @@ namespace EventContent { } }; - /** - * Content class for m.image - * - * Available fields: - * - corresponding to the top-level JSON: - * - url - * - filename (extension to the spec) - * - corresponding to the "info" subobject: - * - payloadSize ("size" in JSON) - * - mimeType ("mimetype" in JSON) - * - imageSize (QSize for a combination of "h" and "w" in JSON) - * - thumbnail.url ("thumbnail_url" in JSON) - * - corresponding to the "info/thumbnail_info" subobject: contents of - * thumbnail field, in the same vein as for the main image: - * - payloadSize - * - mimeType - * - imageSize - */ - using ImageContent = UrlWithThumbnailContent; - - /** - * Content class for m.file - * - * Available fields: - * - corresponding to the top-level JSON: - * - url - * - filename - * - corresponding to the "info" subobject: - * - payloadSize ("size" in JSON) - * - mimeType ("mimetype" in JSON) - * - thumbnail.url ("thumbnail_url" in JSON) - * - corresponding to the "info/thumbnail_info" subobject: - * - thumbnail.payloadSize - * - thumbnail.mimeType - * - thumbnail.imageSize (QSize for "h" and "w" in JSON) - */ - using FileContent = UrlWithThumbnailContent; + //! \brief Content class for m.image + //! + //! Available fields: + //! - corresponding to the top-level JSON: + //! - url + //! - filename (extension to the spec) + //! - corresponding to the `info` subobject: + //! - payloadSize (`size` in JSON) + //! - mimeType (`mimetype` in JSON) + //! - imageSize (QSize for a combination of `h` and `w` in JSON) + //! - thumbnail.url (`thumbnail_url` in JSON) + //! - corresponding to the `info/thumbnail_info` subobject: contents of + //! thumbnail field, in the same vein as for the main image: + //! - payloadSize + //! - mimeType + //! - imageSize + using ImageContent = UrlBasedContent; + + //! \brief Content class for m.file + //! + //! Available fields: + //! - corresponding to the top-level JSON: + //! - url + //! - filename + //! - corresponding to the `info` subobject: + //! - payloadSize (`size` in JSON) + //! - mimeType (`mimetype` in JSON) + //! - thumbnail.url (`thumbnail_url` in JSON) + //! - corresponding to the `info/thumbnail_info` subobject: + //! - thumbnail.payloadSize + //! - thumbnail.mimeType + //! - thumbnail.imageSize (QSize for `h` and `w` in JSON) + using FileContent = UrlBasedContent; } // namespace EventContent } // namespace Quotient Q_DECLARE_METATYPE(const Quotient::EventContent::TypedBase*) -- cgit v1.2.3 From d271a171dbec4068e43e8f711d5d2e966a2072ac Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 4 Jan 2022 15:25:49 +0100 Subject: Simplify EventContent a bit Main changes: 1. Base::fillJson() gets a QJsonObject& instead of QJsonObject* - c'mon, there's nothing inherently wrong with using an lvalue reference for a read-write parameter. 2. UrlWithThumbnailContent merged into UrlBasedContent. The original UrlBasedContent was only used to produce a single class, AudioContent, and even that can logically have a thumbnail even if the spec doesn't provision that. And there's no guarantee even for visual content (ImageContent, e.g.) to have thumbnail data; the fallback is already tested. 3. toInfoJson is converted from a template to a couple of overloads that supersede fillInfoJson() member functions in FileInfo/ImageInfo. These overloads are easier on the eye; and clang-tidy no more warns about ImageInfo::fillInfoJson() shadowing FileInfo::fillInfoJson(). 4. Now that UrlWithThumbnail is gone, PlayableContent can directly derive from UrlBasedContent since both its specialisations use it. 5. Instead of FileInfo/ImageInfo, fillInfoJson() has been reinvented within UrlBasedContent so that, in particular, PlayableContent wouldn't need to extract 'info' subobject and then roll it back inside the content JSON object. --- lib/events/encryptionevent.cpp | 9 ++-- lib/events/encryptionevent.h | 2 +- lib/events/eventcontent.cpp | 36 ++++++++-------- lib/events/eventcontent.h | 84 ++++++++++++++----------------------- lib/events/roommemberevent.cpp | 11 +++-- lib/events/roommemberevent.h | 2 +- lib/events/roommessageevent.cpp | 18 ++++---- lib/events/roommessageevent.h | 29 +++++++------ lib/events/roompowerlevelsevent.cpp | 22 +++++----- lib/events/roompowerlevelsevent.h | 2 +- 10 files changed, 98 insertions(+), 117 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 6272c668..47b0a032 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -47,11 +47,10 @@ EncryptionEventContent::EncryptionEventContent(EncryptionType et) } } -void EncryptionEventContent::fillJson(QJsonObject* o) const +void EncryptionEventContent::fillJson(QJsonObject& o) const { - Q_ASSERT(o); if (encryption != EncryptionType::Undefined) - o->insert(AlgorithmKey, algorithm); - o->insert(RotationPeriodMsKey, rotationPeriodMs); - o->insert(RotationPeriodMsgsKey, rotationPeriodMsgs); + o.insert(AlgorithmKey, algorithm); + o.insert(RotationPeriodMsKey, rotationPeriodMs); + o.insert(RotationPeriodMsgsKey, rotationPeriodMsgs); } diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 124ced33..fe76b1af 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -26,7 +26,7 @@ public: int rotationPeriodMsgs; protected: - void fillJson(QJsonObject* o) const override; + void fillJson(QJsonObject& o) const override; }; using EncryptionType = EncryptionEventContent::EncryptionType; diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 479d5da0..6218e3b8 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -15,7 +15,7 @@ using std::move; QJsonObject Base::toJson() const { QJsonObject o; - fillJson(&o); + fillJson(o); return o; } @@ -68,14 +68,15 @@ bool FileInfo::isValid() const && (url.authority() + url.path()).count('/') == 1; } -void FileInfo::fillInfoJson(QJsonObject* infoJson) const +QJsonObject Quotient::EventContent::toInfoJson(const FileInfo& info) { - Q_ASSERT(infoJson); - if (payloadSize != -1) - infoJson->insert(QStringLiteral("size"), payloadSize); - if (mimeType.isValid()) - infoJson->insert(QStringLiteral("mimetype"), mimeType.name()); + QJsonObject infoJson; + if (info.payloadSize != -1) + infoJson.insert(QStringLiteral("size"), info.payloadSize); + if (info.mimeType.isValid()) + infoJson.insert(QStringLiteral("mimetype"), info.mimeType.name()); //TODO add encryptedfile + return infoJson; } ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) @@ -96,13 +97,14 @@ ImageInfo::ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, , imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt()) {} -void ImageInfo::fillInfoJson(QJsonObject* infoJson) const +QJsonObject Quotient::EventContent::toInfoJson(const ImageInfo& info) { - FileInfo::fillInfoJson(infoJson); - if (imageSize.width() != -1) - infoJson->insert(QStringLiteral("w"), imageSize.width()); - if (imageSize.height() != -1) - infoJson->insert(QStringLiteral("h"), imageSize.height()); + auto infoJson = toInfoJson(static_cast(info)); + if (info.imageSize.width() != -1) + infoJson.insert(QStringLiteral("w"), info.imageSize.width()); + if (info.imageSize.height() != -1) + infoJson.insert(QStringLiteral("h"), info.imageSize.height()); + return infoJson; } Thumbnail::Thumbnail(const QJsonObject& infoJson, @@ -111,11 +113,11 @@ Thumbnail::Thumbnail(const QJsonObject& infoJson, infoJson["thumbnail_info"_ls].toObject(), move(encryptedFile)) {} -void Thumbnail::fillInfoJson(QJsonObject* infoJson) const +void Thumbnail::dumpTo(QJsonObject& infoJson) const { if (url.isValid()) - infoJson->insert(QStringLiteral("thumbnail_url"), url.toString()); + infoJson.insert(QStringLiteral("thumbnail_url"), url.toString()); if (!imageSize.isEmpty()) - infoJson->insert(QStringLiteral("thumbnail_info"), - toInfoJson(*this)); + infoJson.insert(QStringLiteral("thumbnail_info"), + toInfoJson(*this)); } diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 4134202a..6aee333d 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -40,7 +40,7 @@ namespace EventContent { Base(const Base&) = default; Base(Base&&) = default; - virtual void fillJson(QJsonObject* o) const = 0; + virtual void fillJson(QJsonObject& o) const = 0; }; // The below structures fairly follow CS spec 11.2.1.6. The overall @@ -50,12 +50,14 @@ namespace EventContent { // A quick classes inheritance structure follows (the definitions are // spread across eventcontent.h and roommessageevent.h): + // UrlBasedContent : InfoT + url and thumbnail data + // PlayableContent : + duration attribute // FileInfo - // FileContent : UrlWithThumbnailContent - // AudioContent : PlayableContent> + // FileContent = UrlBasedContent + // AudioContent = PlayableContent // ImageInfo : FileInfo + imageSize attribute - // ImageContent : UrlWithThumbnailContent - // VideoContent : PlayableContent> + // ImageContent = UrlBasedContent + // VideoContent = PlayableContent //! \brief Mix-in class representing `info` subobject in content JSON //! @@ -117,13 +119,7 @@ namespace EventContent { Omittable file = none; }; - template - QJsonObject toInfoJson(const InfoT& info) - { - QJsonObject infoJson; - info.fillInfoJson(&infoJson); - return infoJson; - } + QUOTIENT_API QJsonObject toInfoJson(const FileInfo& info); //! \brief A content info class for image/video content types and thumbnails class QUOTIENT_API ImageInfo : public FileInfo { @@ -138,12 +134,12 @@ namespace EventContent { Omittable encryptedFile, const QString& originalFilename = {}); - void fillInfoJson(QJsonObject* infoJson) const; - public: QSize imageSize; }; + QUOTIENT_API QJsonObject toInfoJson(const ImageInfo& info); + //! \brief An auxiliary class for an info type that carries a thumbnail //! //! This class saves/loads a thumbnail to/from `info` subobject of @@ -156,7 +152,7 @@ namespace EventContent { Omittable encryptedFile = none); //! \brief Add thumbnail information to the passed `info` JSON object - void fillInfoJson(QJsonObject* infoJson) const; + void dumpTo(QJsonObject& infoJson) const; }; class QUOTIENT_API TypedBase : public Base { @@ -185,57 +181,41 @@ namespace EventContent { explicit UrlBasedContent(const QJsonObject& json) : TypedBase(json) , InfoT(QUrl(json["url"].toString()), json["info"].toObject(), - fromJson>(json["file"]), json["filename"].toString()) + fromJson>(json["file"]), + json["filename"].toString()) + , thumbnail(FileInfo::originalInfoJson) { - // A small hack to facilitate links creation in QML. + // Two small hacks on originalJson to expose mediaIds to QML originalJson.insert("mediaId", InfoT::mediaId()); + originalJson.insert("thumbnailMediaId", thumbnail.mediaId()); } QMimeType type() const override { return InfoT::mimeType; } const FileInfo* fileInfo() const override { return this; } FileInfo* fileInfo() override { return this; } - - protected: - void fillJson(QJsonObject* json) const override - { - Q_ASSERT(json); - if (!InfoT::file.has_value()) { - json->insert("url", InfoT::url.toString()); - } else { - json->insert("file", Quotient::toJson(*InfoT::file)); - } - if (!InfoT::originalName.isEmpty()) - json->insert("filename", InfoT::originalName); - json->insert("info", toInfoJson(*this)); - } - }; - - template - class QUOTIENT_API UrlWithThumbnailContent : public UrlBasedContent { - public: - // NB: when using inherited constructors, thumbnail has to be - // initialised separately - using UrlBasedContent::UrlBasedContent; - explicit UrlWithThumbnailContent(const QJsonObject& json) - : UrlBasedContent(json), thumbnail(InfoT::originalInfoJson) - { - // Another small hack, to simplify making a thumbnail link - UrlBasedContent::originalJson.insert("thumbnailMediaId", - thumbnail.mediaId()); - } - const Thumbnail* thumbnailInfo() const override { return &thumbnail; } public: Thumbnail thumbnail; protected: - void fillJson(QJsonObject* json) const override + virtual void fillInfoJson(QJsonObject& infoJson [[maybe_unused]]) const + {} + + void fillJson(QJsonObject& json) const override { - UrlBasedContent::fillJson(json); - auto infoJson = json->take("info").toObject(); - thumbnail.fillInfoJson(&infoJson); - json->insert("info", infoJson); + if (!InfoT::file.has_value()) { + json.insert("url", InfoT::url.toString()); + } else { + json.insert("file", Quotient::toJson(*InfoT::file)); + } + if (!InfoT::originalName.isEmpty()) + json.insert("filename", InfoT::originalName); + auto infoJson = toInfoJson(*this); + if (thumbnail.isValid()) + thumbnail.dumpTo(infoJson); + fillInfoJson(infoJson); + json.insert("info", infoJson); } }; diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index b4770224..55da5809 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -43,19 +43,18 @@ MemberEventContent::MemberEventContent(const QJsonObject& json) displayName = sanitized(*displayName); } -void MemberEventContent::fillJson(QJsonObject* o) const +void MemberEventContent::fillJson(QJsonObject& o) const { - Q_ASSERT(o); if (membership != Membership::Invalid) - o->insert(QStringLiteral("membership"), + o.insert(QStringLiteral("membership"), MembershipStrings[qCountTrailingZeroBits( std::underlying_type_t(membership))]); if (displayName) - o->insert(QStringLiteral("displayname"), *displayName); + o.insert(QStringLiteral("displayname"), *displayName); if (avatarUrl && avatarUrl->isValid()) - o->insert(QStringLiteral("avatar_url"), avatarUrl->toString()); + o.insert(QStringLiteral("avatar_url"), avatarUrl->toString()); if (!reason.isEmpty()) - o->insert(QStringLiteral("reason"), reason); + o.insert(QStringLiteral("reason"), reason); } bool RoomMemberEvent::changesMembership() const diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index ceb7826b..afbaf825 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -26,7 +26,7 @@ public: QString reason; protected: - void fillJson(QJsonObject* o) const override; + void fillJson(QJsonObject& o) const override; }; using MembershipType [[deprecated("Use Membership instead")]] = Membership; diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index d63352cb..d9d3fbe0 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -302,17 +302,16 @@ TextContent::TextContent(const QJsonObject& json) } } -void TextContent::fillJson(QJsonObject* json) const +void TextContent::fillJson(QJsonObject &json) const { static const auto FormatKey = QStringLiteral("format"); - Q_ASSERT(json); if (mimeType.inherits("text/html")) { - json->insert(FormatKey, HtmlContentTypeId); - json->insert(FormattedBodyKey, body); + json.insert(FormatKey, HtmlContentTypeId); + json.insert(FormattedBodyKey, body); } if (relatesTo) { - json->insert( + json.insert( QStringLiteral("m.relates_to"), relatesTo->type == EventRelation::ReplyType ? QJsonObject { { relatesTo->type, @@ -326,7 +325,7 @@ void TextContent::fillJson(QJsonObject* json) const newContentJson.insert(FormatKey, HtmlContentTypeId); newContentJson.insert(FormattedBodyKey, body); } - json->insert(QStringLiteral("m.new_content"), newContentJson); + json.insert(QStringLiteral("m.new_content"), newContentJson); } } } @@ -347,9 +346,8 @@ QMimeType LocationContent::type() const return QMimeDatabase().mimeTypeForData(geoUri.toLatin1()); } -void LocationContent::fillJson(QJsonObject* o) const +void LocationContent::fillJson(QJsonObject& o) const { - Q_ASSERT(o); - o->insert(QStringLiteral("geo_uri"), geoUri); - o->insert(QStringLiteral("info"), toInfoJson(thumbnail)); + o.insert(QStringLiteral("geo_uri"), geoUri); + o.insert(QStringLiteral("info"), toInfoJson(thumbnail)); } diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 03a51328..6968ad70 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -136,7 +136,7 @@ namespace EventContent { Omittable relatesTo; protected: - void fillJson(QJsonObject* json) const override; + void fillJson(QJsonObject& json) const override; }; /** @@ -164,28 +164,25 @@ namespace EventContent { Thumbnail thumbnail; protected: - void fillJson(QJsonObject* o) const override; + void fillJson(QJsonObject& o) const override; }; /** * A base class for info types that include duration: audio and video */ - template - class QUOTIENT_API PlayableContent : public ContentT { + template + class PlayableContent : public UrlBasedContent { public: - using ContentT::ContentT; + using UrlBasedContent::UrlBasedContent; PlayableContent(const QJsonObject& json) - : ContentT(json) - , duration(ContentT::originalInfoJson["duration"_ls].toInt()) + : UrlBasedContent(json) + , duration(FileInfo::originalInfoJson["duration"_ls].toInt()) {} protected: - void fillJson(QJsonObject* json) const override + void fillInfoJson(QJsonObject& infoJson) const override { - ContentT::fillJson(json); - auto infoJson = json->take("info"_ls).toObject(); infoJson.insert(QStringLiteral("duration"), duration); - json->insert(QStringLiteral("info"), infoJson); } public: @@ -211,7 +208,7 @@ namespace EventContent { * - mimeType * - imageSize */ - using VideoContent = PlayableContent>; + using VideoContent = PlayableContent; /** * Content class for m.audio @@ -224,7 +221,13 @@ namespace EventContent { * - payloadSize ("size" in JSON) * - mimeType ("mimetype" in JSON) * - duration + * - thumbnail.url ("thumbnail_url" in JSON - extension to the spec) + * - corresponding to the "info/thumbnail_info" subobject: contents of + * thumbnail field (extension to the spec): + * - payloadSize + * - mimeType + * - imageSize */ - using AudioContent = PlayableContent>; + using AudioContent = PlayableContent; } // namespace EventContent } // namespace Quotient diff --git a/lib/events/roompowerlevelsevent.cpp b/lib/events/roompowerlevelsevent.cpp index 8d262ddf..1c87fd47 100644 --- a/lib/events/roompowerlevelsevent.cpp +++ b/lib/events/roompowerlevelsevent.cpp @@ -21,17 +21,17 @@ PowerLevelsEventContent::PowerLevelsEventContent(const QJsonObject& json) : { } -void PowerLevelsEventContent::fillJson(QJsonObject* o) const { - o->insert(QStringLiteral("invite"), invite); - o->insert(QStringLiteral("kick"), kick); - o->insert(QStringLiteral("ban"), ban); - o->insert(QStringLiteral("redact"), redact); - o->insert(QStringLiteral("events"), Quotient::toJson(events)); - o->insert(QStringLiteral("events_default"), eventsDefault); - o->insert(QStringLiteral("state_default"), stateDefault); - o->insert(QStringLiteral("users"), Quotient::toJson(users)); - o->insert(QStringLiteral("users_default"), usersDefault); - o->insert(QStringLiteral("notifications"), QJsonObject{{"room", notifications.room}}); +void PowerLevelsEventContent::fillJson(QJsonObject& o) const { + o.insert(QStringLiteral("invite"), invite); + o.insert(QStringLiteral("kick"), kick); + o.insert(QStringLiteral("ban"), ban); + o.insert(QStringLiteral("redact"), redact); + o.insert(QStringLiteral("events"), Quotient::toJson(events)); + o.insert(QStringLiteral("events_default"), eventsDefault); + o.insert(QStringLiteral("state_default"), stateDefault); + o.insert(QStringLiteral("users"), Quotient::toJson(users)); + o.insert(QStringLiteral("users_default"), usersDefault); + o.insert(QStringLiteral("notifications"), QJsonObject{{"room", notifications.room}}); } int RoomPowerLevelsEvent::powerLevelForEvent(const QString &eventId) const { diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h index 415cc814..3ce83763 100644 --- a/lib/events/roompowerlevelsevent.h +++ b/lib/events/roompowerlevelsevent.h @@ -31,7 +31,7 @@ public: Notifications notifications; protected: - void fillJson(QJsonObject* o) const override; + void fillJson(QJsonObject& o) const override; }; class QUOTIENT_API RoomPowerLevelsEvent -- cgit v1.2.3 From 4070706fcc91cd46054b00c5f3a267a9d8c44fb7 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 13:58:47 +0100 Subject: Event content: provide toJson() instead of deriving from EC::Base EventContent::Base has been made primarily for the sake of dynamic polymorphism needed within RoomMessageEvent content (arguably, it might not be really needed even there, but that's a bigger matter for another time). When that polymorphism is not needed, it's easier for reading and maintenance to have toJson() member function (or even specialise JsonConverter<> outside of the content structure) instead of deriving from EC::Base and then still having fillJson() member function. This commit removes EventContent::Base dependency where it's not beneficial. --- lib/events/encryptionevent.cpp | 4 +++- lib/events/encryptionevent.h | 8 +++----- lib/events/roommemberevent.cpp | 6 +++--- lib/events/roommemberevent.h | 7 ++----- lib/events/roompowerlevelsevent.cpp | 10 ++++++---- lib/events/roompowerlevelsevent.h | 8 ++------ 6 files changed, 19 insertions(+), 24 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 47b0a032..6e994cd4 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -47,10 +47,12 @@ EncryptionEventContent::EncryptionEventContent(EncryptionType et) } } -void EncryptionEventContent::fillJson(QJsonObject& o) const +QJsonObject EncryptionEventContent::toJson() const { + QJsonObject o; if (encryption != EncryptionType::Undefined) o.insert(AlgorithmKey, algorithm); o.insert(RotationPeriodMsKey, rotationPeriodMs); o.insert(RotationPeriodMsgsKey, rotationPeriodMsgs); + return o; } diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index fe76b1af..5b5420ec 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -4,12 +4,11 @@ #pragma once -#include "eventcontent.h" #include "stateevent.h" #include "quotient_common.h" namespace Quotient { -class QUOTIENT_API EncryptionEventContent : public EventContent::Base { +class QUOTIENT_API EncryptionEventContent { public: enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined }; @@ -20,13 +19,12 @@ public: {} explicit EncryptionEventContent(const QJsonObject& json); + QJsonObject toJson() const; + EncryptionType encryption; QString algorithm; int rotationPeriodMs; int rotationPeriodMsgs; - -protected: - void fillJson(QJsonObject& o) const override; }; using EncryptionType = EncryptionEventContent::EncryptionType; diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 55da5809..c3be0e00 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -4,8 +4,6 @@ #include "roommemberevent.h" -#include "logging.h" - #include namespace Quotient { @@ -43,8 +41,9 @@ MemberEventContent::MemberEventContent(const QJsonObject& json) displayName = sanitized(*displayName); } -void MemberEventContent::fillJson(QJsonObject& o) const +QJsonObject MemberEventContent::toJson() const { + QJsonObject o; if (membership != Membership::Invalid) o.insert(QStringLiteral("membership"), MembershipStrings[qCountTrailingZeroBits( @@ -55,6 +54,7 @@ void MemberEventContent::fillJson(QJsonObject& o) const o.insert(QStringLiteral("avatar_url"), avatarUrl->toString()); if (!reason.isEmpty()) o.insert(QStringLiteral("reason"), reason); + return o; } bool RoomMemberEvent::changesMembership() const diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index afbaf825..dd33ea6b 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -5,18 +5,18 @@ #pragma once -#include "eventcontent.h" #include "stateevent.h" #include "quotient_common.h" namespace Quotient { -class QUOTIENT_API MemberEventContent : public EventContent::Base { +class QUOTIENT_API MemberEventContent { public: using MembershipType [[deprecated("Use Quotient::Membership instead")]] = Membership; QUO_IMPLICIT MemberEventContent(Membership ms) : membership(ms) {} explicit MemberEventContent(const QJsonObject& json); + QJsonObject toJson() const; Membership membership; /// (Only for invites) Whether the invite is to a direct chat @@ -24,9 +24,6 @@ public: Omittable displayName; Omittable avatarUrl; QString reason; - -protected: - void fillJson(QJsonObject& o) const override; }; using MembershipType [[deprecated("Use Membership instead")]] = Membership; diff --git a/lib/events/roompowerlevelsevent.cpp b/lib/events/roompowerlevelsevent.cpp index 1c87fd47..84a31d55 100644 --- a/lib/events/roompowerlevelsevent.cpp +++ b/lib/events/roompowerlevelsevent.cpp @@ -3,8 +3,6 @@ #include "roompowerlevelsevent.h" -#include - using namespace Quotient; PowerLevelsEventContent::PowerLevelsEventContent(const QJsonObject& json) : @@ -21,7 +19,9 @@ PowerLevelsEventContent::PowerLevelsEventContent(const QJsonObject& json) : { } -void PowerLevelsEventContent::fillJson(QJsonObject& o) const { +QJsonObject PowerLevelsEventContent::toJson() const +{ + QJsonObject o; o.insert(QStringLiteral("invite"), invite); o.insert(QStringLiteral("kick"), kick); o.insert(QStringLiteral("ban"), ban); @@ -31,7 +31,9 @@ void PowerLevelsEventContent::fillJson(QJsonObject& o) const { o.insert(QStringLiteral("state_default"), stateDefault); o.insert(QStringLiteral("users"), Quotient::toJson(users)); o.insert(QStringLiteral("users_default"), usersDefault); - o.insert(QStringLiteral("notifications"), QJsonObject{{"room", notifications.room}}); + o.insert(QStringLiteral("notifications"), + QJsonObject { { "room", notifications.room } }); + return o; } int RoomPowerLevelsEvent::powerLevelForEvent(const QString &eventId) const { diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h index 3ce83763..a1638a27 100644 --- a/lib/events/roompowerlevelsevent.h +++ b/lib/events/roompowerlevelsevent.h @@ -3,17 +3,16 @@ #pragma once -#include "eventcontent.h" #include "stateevent.h" namespace Quotient { -class QUOTIENT_API PowerLevelsEventContent : public EventContent::Base { -public: +struct QUOTIENT_API PowerLevelsEventContent { struct Notifications { int room; }; explicit PowerLevelsEventContent(const QJsonObject& json); + QJsonObject toJson() const; int invite; int kick; @@ -29,9 +28,6 @@ public: int usersDefault; Notifications notifications; - -protected: - void fillJson(QJsonObject& o) const override; }; class QUOTIENT_API RoomPowerLevelsEvent -- 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 ++-- lib/events/event.cpp | 2 +- lib/events/event.h | 14 +++++++------- lib/events/eventloader.h | 4 ++-- lib/events/roomevent.cpp | 10 +++++----- lib/events/roomevent.h | 5 +++++ lib/events/stateevent.cpp | 2 +- lib/events/stateevent.h | 28 ++++++++++++++++++---------- quotest/quotest.cpp | 8 ++++---- 9 files changed, 45 insertions(+), 32 deletions(-) (limited to 'lib/events') 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 { diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 4c304a3c..1f1eebaa 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -29,7 +29,7 @@ Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json) } Event::Event(Type type, event_mtype_t matrixType, const QJsonObject& contentJson) - : Event(type, basicEventJson(matrixType, contentJson)) + : Event(type, basicJson(matrixType, contentJson)) {} Event::~Event() = default; diff --git a/lib/events/event.h b/lib/events/event.h index 113fa3fa..5be2b41b 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -48,13 +48,6 @@ const QString RoomIdKey { RoomIdKeyL }; const QString UnsignedKey { UnsignedKeyL }; const QString StateKeyKey { StateKeyKeyL }; -/// Make a minimal correct Matrix event JSON -inline QJsonObject basicEventJson(const QString& matrixType, - const QJsonObject& content) -{ - return { { TypeKey, matrixType }, { ContentKey, content } }; -} - // === Event types === using event_type_t = QLatin1String; @@ -193,6 +186,13 @@ public: Event& operator=(Event&&) = delete; virtual ~Event(); + /// Make a minimal correct Matrix event JSON + static QJsonObject basicJson(const QString& matrixType, + const QJsonObject& content) + { + return { { TypeKey, matrixType }, { ContentKey, content } }; + } + Type type() const { return _type; } QString matrixType() const; [[deprecated("Use fullJson() and stringify it with QJsonDocument::toJson() " diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index fe624d70..c7b82e8e 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -29,7 +29,7 @@ template inline event_ptr_tt loadEvent(const QString& matrixType, const QJsonObject& content) { - return doLoadEvent(basicEventJson(matrixType, content), + return doLoadEvent(Event::basicJson(matrixType, content), matrixType); } @@ -44,7 +44,7 @@ inline StateEventPtr loadStateEvent(const QString& matrixType, const QString& stateKey = {}) { return doLoadEvent( - basicStateEventJson(matrixType, content, stateKey), matrixType); + StateEventBase::basicJson(matrixType, content, stateKey), matrixType); } template diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 2f482871..ebe72149 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -101,19 +101,19 @@ void RoomEvent::dumpTo(QDebug dbg) const dbg << " (made at " << originTimestamp().toString(Qt::ISODate) << ')'; } -QJsonObject makeCallContentJson(const QString& callId, int version, - QJsonObject content) +QJsonObject CallEventBase::basicJson(const QString& matrixType, + const QString& callId, int version, + QJsonObject content) { content.insert(QStringLiteral("call_id"), callId); content.insert(QStringLiteral("version"), version); - return content; + return RoomEvent::basicJson(matrixType, content); } CallEventBase::CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, int version, const QJsonObject& contentJson) - : RoomEvent(type, matrixType, - makeCallContentJson(callId, version, contentJson)) + : RoomEvent(type, basicJson(type, callId, version, contentJson)) {} CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index a7d6c428..0692ad8a 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -98,6 +98,11 @@ public: QString callId() const { return contentPart("call_id"_ls); } int version() const { return contentPart("version"_ls); } + +protected: + static QJsonObject basicJson(const QString& matrixType, + const QString& callId, int version, + QJsonObject contentJson = {}); }; } // namespace Quotient Q_DECLARE_METATYPE(Quotient::RoomEvent*) diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index e53d47d4..cdf3c449 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -16,7 +16,7 @@ StateEventBase::StateEventBase(Type type, const QJsonObject& json) StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, const QString& stateKey, const QJsonObject& contentJson) - : RoomEvent(type, basicStateEventJson(matrixType, contentJson, stateKey)) + : RoomEvent(type, basicJson(type, contentJson, stateKey)) {} bool StateEventBase::repeatsState() const diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 19905431..47bf6e59 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -7,16 +7,6 @@ namespace Quotient { -/// Make a minimal correct Matrix state event JSON -inline QJsonObject basicStateEventJson(const QString& matrixTypeId, - const QJsonObject& content, - const QString& stateKey = {}) -{ - return { { TypeKey, matrixTypeId }, - { StateKeyKey, stateKey }, - { ContentKey, content } }; -} - class QUOTIENT_API StateEventBase : public RoomEvent { public: static inline EventFactory factory { "StateEvent" }; @@ -27,6 +17,16 @@ public: const QJsonObject& contentJson = {}); ~StateEventBase() override = default; + //! Make a minimal correct Matrix state event JSON + static QJsonObject basicJson(const QString& matrixTypeId, + const QJsonObject& content, + const QString& stateKey = {}) + { + return { { TypeKey, matrixTypeId }, + { StateKeyKey, stateKey }, + { ContentKey, content } }; + } + bool isStateEvent() const override { return true; } QString replacedState() const; void dumpTo(QDebug dbg) const override; @@ -36,6 +36,14 @@ public: using StateEventPtr = event_ptr_tt; using StateEvents = EventsArray; +[[deprecated("Use StateEventBase::basicJson() instead")]] +inline QJsonObject basicStateEventJson(const QString& matrixTypeId, + const QJsonObject& content, + const QString& stateKey = {}) +{ + return StateEventBase::basicJson(matrixTypeId, content, stateKey); +} + //! \brief Override RoomEvent factory with that from StateEventBase if JSON has //! stateKey //! diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 792faabd..861cfba0 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -531,10 +531,10 @@ public: : RoomEvent(typeId(), jo) {} CustomEvent(int testValue) - : RoomEvent(typeId(), - basicEventJson(matrixTypeId(), - QJsonObject { { "testValue"_ls, - toJson(testValue) } })) + : RoomEvent(TypeId, + Event::basicJson(TypeId, + QJsonObject { { "testValue"_ls, + toJson(testValue) } })) {} auto testValue() const { return contentPart("testValue"_ls); } -- cgit v1.2.3 From 41f630cf7be6e806e498cb31711f403bf6919ff8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 7 May 2022 19:23:18 +0200 Subject: Pass matrixType(QString), not Event::Type, to basicJson() Not that it was very important, as the two are basically the same thing (and matrixTypeId() is about to be obsoleted); but formally basicJson() is supposed to get the former, not the latter. --- lib/events/roomevent.cpp | 8 ++++---- lib/events/stateevent.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index ebe72149..707cf4fd 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -105,15 +105,15 @@ QJsonObject CallEventBase::basicJson(const QString& matrixType, const QString& callId, int version, QJsonObject content) { - content.insert(QStringLiteral("call_id"), callId); - content.insert(QStringLiteral("version"), version); - return RoomEvent::basicJson(matrixType, content); + contentJson.insert(QStringLiteral("call_id"), callId); + contentJson.insert(QStringLiteral("version"), version); + return RoomEvent::basicJson(matrixType, contentJson); } CallEventBase::CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, int version, const QJsonObject& contentJson) - : RoomEvent(type, basicJson(type, callId, version, contentJson)) + : RoomEvent(type, basicJson(matrixType, callId, version, contentJson)) {} CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index cdf3c449..0fd489d1 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -16,7 +16,7 @@ StateEventBase::StateEventBase(Type type, const QJsonObject& json) StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, const QString& stateKey, const QJsonObject& contentJson) - : RoomEvent(type, basicJson(type, contentJson, stateKey)) + : RoomEvent(type, basicJson(matrixType, contentJson, stateKey)) {} bool StateEventBase::repeatsState() const -- cgit v1.2.3 From e7a4b5a545b0f59b95ca8097009dbf6eea534db1 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 6 May 2022 22:17:55 +0200 Subject: Generalise DEFINE_SIMPLE_EVENT This macro was defined in accountdataevents.h but adding one more parameter (base class) makes it applicable to pretty much any event with the content that has one key-value pair (though state events already have a non-macro solution in the form of `StateEvent`). Now CustomEvent definition in quotest.cpp can be replaced with a single line. --- lib/events/accountdataevents.h | 31 +++++++++---------------------- lib/events/event.h | 26 ++++++++++++++++++++++++++ quotest/quotest.cpp | 18 +----------------- 3 files changed, 36 insertions(+), 39 deletions(-) (limited to 'lib/events') diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 12f1f00b..ec2f64e3 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -46,27 +46,14 @@ struct JsonObjectConverter { using TagsMap = QHash; -#define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _ContentType, _ContentKey) \ - class QUOTIENT_API _Name : public Event { \ - public: \ - using content_type = _ContentType; \ - DEFINE_EVENT_TYPEID(_TypeId, _Name) \ - explicit _Name(const QJsonObject& obj) : Event(typeId(), obj) {} \ - explicit _Name(const content_type& content) \ - : Event(typeId(), matrixTypeId(), \ - QJsonObject { \ - { QStringLiteral(#_ContentKey), toJson(content) } }) \ - {} \ - auto _ContentKey() const \ - { \ - return contentPart(#_ContentKey##_ls); \ - } \ - }; \ - REGISTER_EVENT_TYPE(_Name) \ - // End of macro - -DEFINE_SIMPLE_EVENT(TagEvent, "m.tag", TagsMap, tags) -DEFINE_SIMPLE_EVENT(ReadMarkerEvent, "m.fully_read", QString, event_id) -DEFINE_SIMPLE_EVENT(IgnoredUsersEvent, "m.ignored_user_list", QSet, +DEFINE_SIMPLE_EVENT(TagEvent, Event, "m.tag", TagsMap, tags) +DEFINE_SIMPLE_EVENT(ReadMarkerEventImpl, Event, "m.fully_read", QString, eventId) +class ReadMarkerEvent : public ReadMarkerEventImpl { +public: + using ReadMarkerEventImpl::ReadMarkerEventImpl; + [[deprecated("Use ReadMarkerEvent::eventId() instead")]] + QString event_id() const { return eventId(); } +}; +DEFINE_SIMPLE_EVENT(IgnoredUsersEvent, Event, "m.ignored_user_list", QSet, ignored_users) } // namespace Quotient diff --git a/lib/events/event.h b/lib/events/event.h index 5be2b41b..a27c0b96 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -278,6 +278,32 @@ using Events = EventsArray; Type_::factory.addMethod(); \ // End of macro +/// \brief Define a new event class with a single key-value pair in the content +/// +/// This macro defines a new event class \p Name_ derived from \p Base_, +/// with Matrix event type \p TypeId_, providing a getter named \p GetterName_ +/// for a single value of type \p ValueType_ inside the event content. +/// To retrieve the value the getter uses a JSON key name that corresponds to +/// its own (getter's) name but written in snake_case. \p GetterName_ must be +/// in camelCase, no quotes (an identifier, not a literal). +#define DEFINE_SIMPLE_EVENT(Name_, Base_, TypeId_, ValueType_, GetterName_) \ + class QUOTIENT_API Name_ : public Base_ { \ + public: \ + using content_type = ValueType_; \ + DEFINE_EVENT_TYPEID(TypeId_, Name_) \ + explicit Name_(const QJsonObject& obj) : Base_(TypeId, obj) {} \ + explicit Name_(const content_type& content) \ + : Name_(Base_::basicJson(TypeId, { { JsonKey, toJson(content) } })) \ + {} \ + auto GetterName_() const \ + { \ + return contentPart(JsonKey); \ + } \ + static inline const auto JsonKey = toSnakeCase(#GetterName_##_ls); \ + }; \ + REGISTER_EVENT_TYPE(Name_) \ + // End of macro + // === is<>(), eventCast<>() and switchOnType<>() === template diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 861cfba0..b14442b9 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -523,23 +523,7 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, return true; } -class CustomEvent : public RoomEvent { -public: - DEFINE_EVENT_TYPEID("quotest.custom", CustomEvent) - - CustomEvent(const QJsonObject& jo) - : RoomEvent(typeId(), jo) - {} - CustomEvent(int testValue) - : RoomEvent(TypeId, - Event::basicJson(TypeId, - QJsonObject { { "testValue"_ls, - toJson(testValue) } })) - {} - - auto testValue() const { return contentPart("testValue"_ls); } -}; -REGISTER_EVENT_TYPE(CustomEvent) +DEFINE_SIMPLE_EVENT(CustomEvent, RoomEvent, "quotest.custom", int, testValue) TEST_IMPL(sendCustomEvent) { -- cgit v1.2.3 From f4a20cc3710ee8f4b1788f73d05466aa0e660d61 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 7 May 2022 19:02:35 +0200 Subject: More cleanup --- lib/events/directchatevent.cpp | 2 -- lib/events/eventcontent.h | 2 +- lib/events/roomevent.cpp | 4 ++-- lib/events/stateevent.cpp | 4 ++-- lib/room.cpp | 9 +++++---- lib/room.h | 3 ++- 6 files changed, 12 insertions(+), 12 deletions(-) (limited to 'lib/events') diff --git a/lib/events/directchatevent.cpp b/lib/events/directchatevent.cpp index 0ee1f7b0..83bb1e32 100644 --- a/lib/events/directchatevent.cpp +++ b/lib/events/directchatevent.cpp @@ -3,8 +3,6 @@ #include "directchatevent.h" -#include - using namespace Quotient; QMultiHash DirectChatEvent::usersToDirectChats() const diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 6aee333d..bbd35618 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -40,7 +40,7 @@ namespace EventContent { Base(const Base&) = default; Base(Base&&) = default; - virtual void fillJson(QJsonObject& o) const = 0; + virtual void fillJson(QJsonObject&) const = 0; }; // The below structures fairly follow CS spec 11.2.1.6. The overall diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 707cf4fd..3ddf5ac4 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -103,7 +103,7 @@ void RoomEvent::dumpTo(QDebug dbg) const QJsonObject CallEventBase::basicJson(const QString& matrixType, const QString& callId, int version, - QJsonObject content) + QJsonObject contentJson) { contentJson.insert(QStringLiteral("call_id"), callId); contentJson.insert(QStringLiteral("version"), version); @@ -116,7 +116,7 @@ CallEventBase::CallEventBase(Type type, event_mtype_t matrixType, : RoomEvent(type, basicJson(matrixType, callId, version, contentJson)) {} -CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) +CallEventBase::CallEventBase(Type type, const QJsonObject& json) : RoomEvent(type, json) { if (callId().isEmpty()) diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 0fd489d1..c343e37f 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -6,9 +6,9 @@ using namespace Quotient; StateEventBase::StateEventBase(Type type, const QJsonObject& json) - : RoomEvent(json.contains(StateKeyKeyL) ? type : unknownEventTypeId(), json) + : RoomEvent(json.contains(StateKeyKeyL) ? type : UnknownEventTypeId, json) { - if (Event::type() == unknownEventTypeId() && !json.contains(StateKeyKeyL)) + if (Event::type() == UnknownEventTypeId && !json.contains(StateKeyKeyL)) qWarning(EVENTS) << "Attempt to create a state event with no stateKey -" "forcing the event type to unknown to avoid damage"; } diff --git a/lib/room.cpp b/lib/room.cpp index 183e242a..4d9f952c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -3064,15 +3064,16 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) QElapsedTimer et; et.start(); if (auto* evt = eventCast(event)) { + const auto& users = evt->users(); d->usersTyping.clear(); - d->usersTyping.reserve(evt->users().size()); // Assume all are members - for (const auto& userId : evt->users()) + d->usersTyping.reserve(users.size()); // Assume all are members + for (const auto& userId : users) if (isMember(userId)) d->usersTyping.append(user(userId)); - if (evt->users().size() > 3 || et.nsecsElapsed() >= profilerMinNsecs()) + if (users.size() > 3 || et.nsecsElapsed() >= profilerMinNsecs()) qCDebug(PROFILER) - << "Processing typing events from" << evt->users().size() + << "Processing typing events from" << users.size() << "user(s) in" << objectName() << "took" << et; emit typingChanged(); } diff --git a/lib/room.h b/lib/room.h index 6ba7feac..7e53aed0 100644 --- a/lib/room.h +++ b/lib/room.h @@ -758,7 +758,8 @@ public: [[deprecated("Use currentState().get() instead; " "make sure to check its result for nullptrs")]] // const Quotient::StateEventBase* - getCurrentState(const QString& evtType, const QString& stateKey = {}) const; + getCurrentState(const QString& evtType, + const QString& stateKey = {}) const; /// Get a state event with the given event type and state key /*! This is a typesafe overload that accepts a C++ event type instead of -- cgit v1.2.3 From 4b35dfd8af196ff9e8669499ea3ed7e4127f5901 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 30 Jan 2022 15:55:06 +0100 Subject: Use std::pair instead of QPair QPair is giving way to its STL counterpart, becoming its alias in Qt 6. --- lib/events/stateevent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 47bf6e59..343e87a5 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -72,7 +72,7 @@ inline bool is(const Event& e) * \sa * https://matrix.org/docs/spec/client_server/unstable.html#types-of-room-events */ -using StateEventKey = QPair; +using StateEventKey = std::pair; template struct Prev { -- cgit v1.2.3 From c42d268db0b40cdba06381fc64a6966a72c90709 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 11 Feb 2022 22:21:25 +0100 Subject: QUO_CONTENT_GETTER To streamline adding of simple getters of content parts. --- lib/events/callanswerevent.h | 6 ++---- lib/events/callcandidatesevent.h | 17 +++-------------- lib/events/callinviteevent.h | 5 +---- lib/events/event.h | 15 +++++++++++++++ lib/events/redactionevent.h | 2 +- lib/events/roomevent.h | 4 ++-- 6 files changed, 24 insertions(+), 25 deletions(-) (limited to 'lib/events') diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index 8ffe60f2..70292a7a 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -17,10 +17,8 @@ public: const QString& sdp); explicit CallAnswerEvent(const QString& callId, const QString& sdp); - int lifetime() const - { - return contentPart("lifetime"_ls); - } // FIXME: Omittable<>? + QUO_CONTENT_GETTER(int, lifetime) // FIXME: Omittable<>? + QString sdp() const { return contentPart("answer"_ls).value("sdp"_ls).toString(); diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h index 74c38f2c..e949f722 100644 --- a/lib/events/callcandidatesevent.h +++ b/lib/events/callcandidatesevent.h @@ -23,20 +23,9 @@ public: { { QStringLiteral("candidates"), candidates } }) {} - QJsonArray candidates() const - { - return contentPart("candidates"_ls); - } - - QString callId() const - { - return contentPart("call_id"); - } - - int version() const - { - return contentPart("version"); - } + QUO_CONTENT_GETTER(QJsonArray, candidates) + QUO_CONTENT_GETTER(QString, callId) + QUO_CONTENT_GETTER(int, version) }; REGISTER_EVENT_TYPE(CallCandidatesEvent) diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index 47362b5c..1b1f4f0f 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -16,10 +16,7 @@ public: explicit CallInviteEvent(const QString& callId, const int lifetime, const QString& sdp); - int lifetime() const - { - return contentPart("lifetime"_ls); - } // FIXME: Omittable<>? + QUO_CONTENT_GETTER(int, lifetime) // FIXME: Omittable<>? QString sdp() const { return contentPart("offer"_ls).value("sdp"_ls).toString(); diff --git a/lib/events/event.h b/lib/events/event.h index a27c0b96..ec21c6aa 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -258,6 +258,21 @@ template using EventsArray = std::vector>; using Events = EventsArray; +//! \brief Define an inline method obtaining a content part +//! +//! This macro adds a const method that extracts a JSON value at the key +//! toSnakeCase(PartName_) (sic) and converts it to the type +//! \p PartType_. Effectively, the generated method is an equivalent of +//! \code +//! contentPart(Quotient::toSnakeCase(#PartName_##_ls)); +//! \endcode +#define QUO_CONTENT_GETTER(PartType_, PartName_) \ + PartType_ PartName_() const \ + { \ + static const auto JsonKey = toSnakeCase(#PartName_##_ls); \ + return contentPart(JsonKey); \ + } + // === Facilities for event class definitions === // This macro should be used in a public section of an event class to diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index be20bf52..1c486a44 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -17,7 +17,7 @@ public: { return fullJson()["redacts"_ls].toString(); } - QString reason() const { return contentPart("reason"_ls); } + QUO_CONTENT_GETTER(QString, reason) }; REGISTER_EVENT_TYPE(RedactionEvent) } // namespace Quotient diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 0692ad8a..7f724689 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -96,8 +96,8 @@ public: ~CallEventBase() override = default; bool isCallEvent() const override { return true; } - QString callId() const { return contentPart("call_id"_ls); } - int version() const { return contentPart("version"_ls); } + QUO_CONTENT_GETTER(QString, callId) + QUO_CONTENT_GETTER(int, version) protected: static QJsonObject basicJson(const QString& matrixType, -- cgit v1.2.3 From b32c1c27ae412d073a7e98bdaf22678bdc66ab23 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 10 May 2022 22:56:02 +0200 Subject: CallAnswerEvent: drop lifetime See https://github.com/matrix-org/matrix-spec/pull/1054. # Conflicts: # lib/events/callanswerevent.cpp # lib/events/callanswerevent.h --- lib/events/callanswerevent.cpp | 11 ----------- lib/events/callanswerevent.h | 4 ---- lib/room.cpp | 7 ++++--- lib/room.h | 5 +++-- 4 files changed, 7 insertions(+), 20 deletions(-) (limited to 'lib/events') diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp index be83d9d0..f75f8ad3 100644 --- a/lib/events/callanswerevent.cpp +++ b/lib/events/callanswerevent.cpp @@ -14,7 +14,6 @@ m.call.answer "type": "answer" }, "call_id": "12345", - "lifetime": 60000, "version": 0 }, "event_id": "$WLGTSEFSEF:localhost", @@ -33,16 +32,6 @@ CallAnswerEvent::CallAnswerEvent(const QJsonObject& obj) qCDebug(EVENTS) << "Call Answer event"; } -CallAnswerEvent::CallAnswerEvent(const QString& callId, const int lifetime, - const QString& sdp) - : CallEventBase( - typeId(), matrixTypeId(), callId, 0, - { { QStringLiteral("lifetime"), lifetime }, - { QStringLiteral("answer"), - QJsonObject { { QStringLiteral("type"), QStringLiteral("answer") }, - { QStringLiteral("sdp"), sdp } } } }) -{} - CallAnswerEvent::CallAnswerEvent(const QString& callId, const QString& sdp) : CallEventBase( typeId(), matrixTypeId(), callId, 0, diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index 70292a7a..4d539b85 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -13,12 +13,8 @@ public: explicit CallAnswerEvent(const QJsonObject& obj); - explicit CallAnswerEvent(const QString& callId, const int lifetime, - const QString& sdp); explicit CallAnswerEvent(const QString& callId, const QString& sdp); - QUO_CONTENT_GETTER(int, lifetime) // FIXME: Omittable<>? - QString sdp() const { return contentPart("answer"_ls).value("sdp"_ls).toString(); diff --git a/lib/room.cpp b/lib/room.cpp index 4ba699b0..993455be 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2232,11 +2232,12 @@ void Room::sendCallCandidates(const QString& callId, d->sendEvent(callId, candidates); } -void Room::answerCall(const QString& callId, const int lifetime, +void Room::answerCall(const QString& callId, [[maybe_unused]] int lifetime, const QString& sdp) { - Q_ASSERT(supportsCalls()); - d->sendEvent(callId, lifetime, sdp); + qCWarning(MAIN) << "To client developer: drop lifetime parameter from " + "Room::answerCall(), it is no more accepted"; + answerCall(callId, sdp); } void Room::answerCall(const QString& callId, const QString& sdp) diff --git a/lib/room.h b/lib/room.h index 7e53aed0..2e2ebf9a 100644 --- a/lib/room.h +++ b/lib/room.h @@ -871,8 +871,9 @@ public Q_SLOTS: void inviteCall(const QString& callId, const int lifetime, const QString& sdp); void sendCallCandidates(const QString& callId, const QJsonArray& candidates); - void answerCall(const QString& callId, const int lifetime, - const QString& sdp); + [[deprecated("Lifetime argument is no more in use here; " + "use 2-arg Room::answerCall() instead")]] + void answerCall(const QString& callId, int lifetime, const QString& sdp); void answerCall(const QString& callId, const QString& sdp); void hangupCall(const QString& callId); -- 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 ++++++++++++++++++++--------------------- lib/connection.h | 6 ++-- lib/converters.h | 8 +++++ lib/e2ee/qolminboundsession.cpp | 4 +-- lib/e2ee/qolminboundsession.h | 2 +- lib/events/callinviteevent.cpp | 2 +- lib/events/callinviteevent.h | 4 +-- lib/events/roomkeyevent.h | 5 ++- lib/room.cpp | 20 ++++++------ lib/room.h | 4 +-- 10 files changed, 70 insertions(+), 57 deletions(-) (limited to 'lib/events') 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 diff --git a/lib/connection.h b/lib/connection.h index 29731593..b75bd5b5 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -317,8 +317,10 @@ public: #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; Database* database(); - UnorderedMap loadRoomMegolmSessions(Room* room); - void saveMegolmSession(Room* room, QOlmInboundGroupSession* session); + UnorderedMap loadRoomMegolmSessions( + const Room* room); + void saveMegolmSession(const Room* room, + const QOlmInboundGroupSession& session); #endif // Quotient_E2EE_ENABLED Q_INVOKABLE Quotient::SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; diff --git a/lib/converters.h b/lib/converters.h index 6515310a..5e3becb8 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -163,6 +163,14 @@ inline qint64 fromJson(const QJsonValue& jv) { return qint64(jv.toDouble()); } template <> inline QString fromJson(const QJsonValue& jv) { return jv.toString(); } +//! Use fromJson and use toLatin1()/toUtf8()/... to make QByteArray +//! +//! QJsonValue can only convert to QString and there's ambiguity whether +//! conversion to QByteArray should use (fast but very limited) toLatin1() or +//! (all encompassing and conforming to the JSON spec but slow) toUtf8(). +template <> +inline QByteArray fromJson(const QJsonValue& jv) = delete; + template <> inline QJsonArray fromJson(const QJsonValue& jv) { return jv.toArray(); } diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 60d871ef..62856831 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -154,9 +154,9 @@ QString QOlmInboundGroupSession::olmSessionId() const { return m_olmSessionId; } -void QOlmInboundGroupSession::setOlmSessionId(const QString& olmSessionId) +void QOlmInboundGroupSession::setOlmSessionId(const QString& newOlmSessionId) { - m_olmSessionId = olmSessionId; + m_olmSessionId = newOlmSessionId; } QString QOlmInboundGroupSession::senderId() const diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 32112b97..13515434 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -44,7 +44,7 @@ public: //! The olm session that this session was received from. //! Required to get the device this session is from. QString olmSessionId() const; - void setOlmSessionId(const QString& setOlmSessionId); + void setOlmSessionId(const QString& newOlmSessionId); //! The sender of this session. QString senderId() const; diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index 11d50768..2f26a1cb 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -33,7 +33,7 @@ CallInviteEvent::CallInviteEvent(const QJsonObject& obj) qCDebug(EVENTS) << "Call Invite event"; } -CallInviteEvent::CallInviteEvent(const QString& callId, const int lifetime, +CallInviteEvent::CallInviteEvent(const QString& callId, int lifetime, const QString& sdp) : CallEventBase( typeId(), matrixTypeId(), callId, 0, diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index 1b1f4f0f..5b4ca0df 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -13,10 +13,10 @@ public: explicit CallInviteEvent(const QJsonObject& obj); - explicit CallInviteEvent(const QString& callId, const int lifetime, + explicit CallInviteEvent(const QString& callId, int lifetime, const QString& sdp); - QUO_CONTENT_GETTER(int, lifetime) // FIXME: Omittable<>? + QUO_CONTENT_GETTER(int, lifetime) QString sdp() const { return contentPart("offer"_ls).value("sdp"_ls).toString(); diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index c4df7936..ed4c9440 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -16,7 +16,10 @@ public: QString algorithm() const { return contentPart("algorithm"_ls); } QString roomId() const { return contentPart(RoomIdKeyL); } QString sessionId() const { return contentPart("session_id"_ls); } - QString sessionKey() const { return contentPart("session_key"_ls); } + QByteArray sessionKey() const + { + return contentPart("session_key"_ls).toLatin1(); + } }; REGISTER_EVENT_TYPE(RoomKeyEvent) } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index 993455be..0a997b9c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -339,14 +339,16 @@ public: #ifdef Quotient_E2EE_ENABLED UnorderedMap groupSessions; - bool addInboundGroupSession(QString sessionId, QString sessionKey, const QString& senderId, const QString& olmSessionId) + bool addInboundGroupSession(QString sessionId, QByteArray sessionKey, + const QString& senderId, + const QString& olmSessionId) { - if (groupSessions.find(sessionId) != groupSessions.end()) { + if (groupSessions.contains(sessionId)) { qCWarning(E2EE) << "Inbound Megolm session" << sessionId << "already exists"; return false; } - auto megolmSession = QOlmInboundGroupSession::create(sessionKey.toLatin1()); + auto megolmSession = QOlmInboundGroupSession::create(sessionKey); if (megolmSession->sessionId() != sessionId) { qCWarning(E2EE) << "Session ID mismatch in m.room_key event"; return false; @@ -354,13 +356,12 @@ public: megolmSession->setSenderId(senderId); megolmSession->setOlmSessionId(olmSessionId); qCWarning(E2EE) << "Adding inbound session"; - connection->saveMegolmSession(q, megolmSession.get()); + connection->saveMegolmSession(q, *megolmSession); groupSessions[sessionId] = std::move(megolmSession); return true; } QString groupSessionDecryptMessage(QByteArray cipher, - const QString& senderKey, const QString& sessionId, const QString& eventId, QDateTime timestamp, @@ -1478,9 +1479,9 @@ RoomEventPtr Room::decryptMessage(const EncryptedEvent& encryptedEvent) return {}; } QString decrypted = d->groupSessionDecryptMessage( - encryptedEvent.ciphertext(), encryptedEvent.senderKey(), - encryptedEvent.sessionId(), encryptedEvent.id(), - encryptedEvent.originTimestamp(), encryptedEvent.senderId()); + encryptedEvent.ciphertext(), encryptedEvent.sessionId(), + encryptedEvent.id(), encryptedEvent.originTimestamp(), + encryptedEvent.senderId()); if (decrypted.isEmpty()) { // qCWarning(E2EE) << "Encrypted message is empty"; return {}; @@ -1509,7 +1510,8 @@ void Room::handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, << roomKeyEvent.algorithm() << "in m.room_key event"; } if (d->addInboundGroupSession(roomKeyEvent.sessionId(), - roomKeyEvent.sessionKey(), senderId, olmSessionId)) { + roomKeyEvent.sessionKey(), senderId, + olmSessionId)) { qCWarning(E2EE) << "added new inboundGroupSession:" << d->groupSessions.size(); auto undecryptedEvents = d->undecryptedEvents[roomKeyEvent.sessionId()]; diff --git a/lib/room.h b/lib/room.h index 2e2ebf9a..6e6071f0 100644 --- a/lib/room.h +++ b/lib/room.h @@ -871,8 +871,8 @@ public Q_SLOTS: void inviteCall(const QString& callId, const int lifetime, const QString& sdp); void sendCallCandidates(const QString& callId, const QJsonArray& candidates); - [[deprecated("Lifetime argument is no more in use here; " - "use 2-arg Room::answerCall() instead")]] + //! \deprecated Lifetime argument is no more passed; use 2-arg + //! Room::answerCall() instead void answerCall(const QString& callId, int lifetime, const QString& sdp); void answerCall(const QString& callId, const QString& sdp); void hangupCall(const QString& callId); -- cgit v1.2.3 From ff54bf2d0979dc6b9b3b77bba827ae7f3baa9f58 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sun, 27 Feb 2022 19:15:16 +0100 Subject: Add constructor for creating roomkeyevents --- lib/events/roomkeyevent.cpp | 13 +++++++++++++ lib/events/roomkeyevent.h | 1 + 2 files changed, 14 insertions(+) (limited to 'lib/events') diff --git a/lib/events/roomkeyevent.cpp b/lib/events/roomkeyevent.cpp index 332be3f7..68962950 100644 --- a/lib/events/roomkeyevent.cpp +++ b/lib/events/roomkeyevent.cpp @@ -10,3 +10,16 @@ RoomKeyEvent::RoomKeyEvent(const QJsonObject &obj) : Event(typeId(), obj) if (roomId().isEmpty()) qCWarning(E2EE) << "Room key event has empty room id"; } + +RoomKeyEvent::RoomKeyEvent(const QString& algorithm, const QString& roomId, const QString& sessionId, const QString& sessionKey, const QString& senderId) + : Event(typeId(), { + {"content", QJsonObject{ + {"algorithm", algorithm}, + {"room_id", roomId}, + {"session_id", sessionId}, + {"session_key", sessionKey}, + }}, + {"sender", senderId}, + {"type", "m.room_key"}, + }) +{} diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index ed4c9440..2bda3086 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -12,6 +12,7 @@ public: DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent) explicit RoomKeyEvent(const QJsonObject& obj); + explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, const QString &sessionId, const QString& sessionKey, const QString& senderId); QString algorithm() const { return contentPart("algorithm"_ls); } QString roomId() const { return contentPart(RoomIdKeyL); } -- cgit v1.2.3 From efa450920e5fc338e771e653ca0889e948d04ee7 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 8 Mar 2022 00:06:36 +0100 Subject: Implement sending encrypted files --- autotests/CMakeLists.txt | 1 + autotests/testfilecrypto.cpp | 17 +++++++++++ autotests/testfilecrypto.h | 12 ++++++++ lib/eventitem.cpp | 10 +++++++ lib/eventitem.h | 3 ++ lib/events/encryptedfile.cpp | 26 +++++++++++++++-- lib/events/encryptedfile.h | 1 + lib/room.cpp | 67 +++++++++++++++++++++++++++++--------------- lib/room.h | 2 +- 9 files changed, 113 insertions(+), 26 deletions(-) create mode 100644 autotests/testfilecrypto.cpp create mode 100644 autotests/testfilecrypto.h (limited to 'lib/events') diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 671d6c08..c11901bf 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -18,4 +18,5 @@ if(${PROJECT_NAME}_ENABLE_E2EE) quotient_add_test(NAME testgroupsession) quotient_add_test(NAME testolmsession) quotient_add_test(NAME testolmutility) + quotient_add_test(NAME testfilecrypto) endif() diff --git a/autotests/testfilecrypto.cpp b/autotests/testfilecrypto.cpp new file mode 100644 index 00000000..e6bec1fe --- /dev/null +++ b/autotests/testfilecrypto.cpp @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2022 Tobias Fella +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "testfilecrypto.h" +#include "events/encryptedfile.h" +#include + +using namespace Quotient; +void TestFileCrypto::encryptDecryptData() +{ + QByteArray data = "ABCDEF"; + auto [file, cipherText] = EncryptedFile::encryptFile(data); + auto decrypted = file.decryptFile(cipherText); + QCOMPARE(data, decrypted); +} +QTEST_APPLESS_MAIN(TestFileCrypto) diff --git a/autotests/testfilecrypto.h b/autotests/testfilecrypto.h new file mode 100644 index 00000000..9096a8c7 --- /dev/null +++ b/autotests/testfilecrypto.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2022 Tobias Fella +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include + +class TestFileCrypto : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void encryptDecryptData(); +}; diff --git a/lib/eventitem.cpp b/lib/eventitem.cpp index a2d65d8d..302ae053 100644 --- a/lib/eventitem.cpp +++ b/lib/eventitem.cpp @@ -26,6 +26,16 @@ void PendingEventItem::setFileUploaded(const QUrl& remoteUrl) setStatus(EventStatus::FileUploaded); } +void PendingEventItem::setEncryptedFile(const EncryptedFile& encryptedFile) +{ + if (auto* rme = getAs()) { + Q_ASSERT(rme->hasFileContent()); + rme->editContent([encryptedFile](EventContent::TypedBase& ec) { + ec.fileInfo()->file = encryptedFile; + }); + } +} + // Not exactly sure why but this helps with the linker not finding // Quotient::EventStatus::staticMetaObject when building Quaternion #include "moc_eventitem.cpp" diff --git a/lib/eventitem.h b/lib/eventitem.h index f04ef323..d8313736 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -9,6 +9,8 @@ #include #include +#include "events/encryptedfile.h" + namespace Quotient { namespace EventStatus { @@ -114,6 +116,7 @@ public: void setDeparted() { setStatus(EventStatus::Departed); } void setFileUploaded(const QUrl& remoteUrl); + void setEncryptedFile(const EncryptedFile& encryptedFile); void setReachedServer(const QString& eventId) { setStatus(EventStatus::ReachedServer); diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index d4a517bd..e90be428 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -8,6 +8,7 @@ #ifdef Quotient_E2EE_ENABLED #include #include +#include "e2ee/qolmutils.h" #endif using namespace Quotient; @@ -27,7 +28,7 @@ QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const { int length; auto* ctx = EVP_CIPHER_CTX_new(); - QByteArray plaintext(ciphertext.size() + EVP_CIPHER_CTX_block_size(ctx) + QByteArray plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH - 1, '\0'); EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, @@ -44,7 +45,7 @@ QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const + length, &length); EVP_CIPHER_CTX_free(ctx); - return plaintext; + return plaintext.left(ciphertext.size()); } #else qWarning(MAIN) << "This build of libQuotient doesn't support E2EE, " @@ -53,6 +54,27 @@ QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const #endif } +std::pair EncryptedFile::encryptFile(const QByteArray &plainText) +{ + QByteArray k = getRandom(32); + auto kBase64 = k.toBase64(); + QByteArray iv = getRandom(16); + JWK key = {"oct"_ls, {"encrypt"_ls, "decrypt"_ls}, "A256CTR"_ls, QString(k.toBase64()).replace(u'/', u'_').replace(u'+', u'-').left(kBase64.indexOf('=')), true}; + + int length; + auto* ctx = EVP_CIPHER_CTX_new(); + QByteArray cipherText(plainText.size(), plainText.size() + EVP_MAX_BLOCK_LENGTH - 1); + EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast(k.data()),reinterpret_cast(iv.data())); + EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), &length, reinterpret_cast(plainText.data()), plainText.size()); + EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + length, &length); + EVP_CIPHER_CTX_free(ctx); + + auto hash = QCryptographicHash::hash(cipherText, QCryptographicHash::Sha256).toBase64(); + auto ivBase64 = iv.toBase64(); + EncryptedFile file = {{}, key, ivBase64.left(ivBase64.indexOf('=')), {{QStringLiteral("sha256"), hash.left(hash.indexOf('='))}}, "v2"_ls}; + return {file, cipherText}; +} + void JsonObjectConverter::dumpTo(QJsonObject& jo, const EncryptedFile& pod) { diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index d0c4a030..2ce35086 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -46,6 +46,7 @@ public: QString v; QByteArray decryptFile(const QByteArray &ciphertext) const; + static std::pair encryptFile(const QByteArray &plainText); }; template <> diff --git a/lib/room.cpp b/lib/room.cpp index 7db9f8e9..763ca31c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -299,8 +299,7 @@ public: RoomEvent* addAsPending(RoomEventPtr&& event); - //TODO deleteWhenFinishedis ugly, find out if there's something nicer - QString doSendEvent(const RoomEvent* pEvent, bool deleteWhenFinished = false); + QString doSendEvent(const RoomEvent* pEvent); void onEventSendingFailure(const QString& txnId, BaseJob* call = nullptr); SetRoomStateWithKeyJob* requestSetState(const QString& evtType, @@ -2076,6 +2075,16 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) qCWarning(MAIN) << q << "has been upgraded, event won't be sent"; return {}; } + + return doSendEvent(addAsPending(std::move(event))); +} + +QString Room::Private::doSendEvent(const RoomEvent* pEvent) +{ + const auto txnId = pEvent->transactionId(); + // TODO, #133: Enqueue the job rather than immediately trigger it. + const RoomEvent* _event = pEvent; + if (q->usesEncryption()) { if (!hasValidMegolmSession() || shouldRotateMegolmSession()) { createMegolmSession(); @@ -2083,10 +2092,8 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) const auto devicesWithoutKey = getDevicesWithoutKey(); sendMegolmSession(devicesWithoutKey); - //TODO check if this is necessary //TODO check if we increment the sent message count - event->setRoomId(id); - const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(event->fullJson()).toJson()); + const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson()); currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); if(std::holds_alternative(encrypted)) { @@ -2098,23 +2105,14 @@ QString Room::Private::sendEvent(RoomEventPtr&& event) encryptedEvent->setTransactionId(connection->generateTxnId()); encryptedEvent->setRoomId(id); encryptedEvent->setSender(connection->userId()); - event->setTransactionId(encryptedEvent->transactionId()); // We show the unencrypted event locally while pending. The echo check will throw the encrypted version out - addAsPending(std::move(event)); - return doSendEvent(encryptedEvent, true); + _event = encryptedEvent; } - return doSendEvent(addAsPending(std::move(event))); -} - -QString Room::Private::doSendEvent(const RoomEvent* pEvent, bool deleteWhenFinished) -{ - const auto txnId = pEvent->transactionId(); - // TODO, #133: Enqueue the job rather than immediately trigger it. if (auto call = connection->callApi(BackgroundRequest, id, - pEvent->matrixType(), txnId, - pEvent->contentJson())) { + _event->matrixType(), txnId, + _event->contentJson())) { Room::connect(call, &BaseJob::sentRequest, q, [this, txnId] { auto it = q->findPendingEvent(txnId); if (it == unsyncedEvents.end()) { @@ -2128,7 +2126,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent, bool deleteWhenFinis Room::connect(call, &BaseJob::failure, q, std::bind(&Room::Private::onEventSendingFailure, this, txnId, call)); - Room::connect(call, &BaseJob::success, q, [this, call, txnId, deleteWhenFinished, pEvent] { + Room::connect(call, &BaseJob::success, q, [this, call, txnId, _event] { auto it = q->findPendingEvent(txnId); if (it != unsyncedEvents.end()) { if (it->deliveryStatus() != EventStatus::ReachedServer) { @@ -2140,8 +2138,8 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent, bool deleteWhenFinis << "already merged"; emit q->messageSent(txnId, call->eventId()); - if (deleteWhenFinished){ - delete pEvent; + if (q->usesEncryption()){ + delete _event; } }); } else @@ -2266,13 +2264,16 @@ QString Room::Private::doPostFile(RoomEventPtr&& msgEvent, const QUrl& localUrl) // Below, the upload job is used as a context object to clean up connections const auto& transferJob = fileTransfers.value(txnId).job; connect(q, &Room::fileTransferCompleted, transferJob, - [this, txnId](const QString& tId, const QUrl&, const QUrl& mxcUri) { + [this, txnId](const QString& tId, const QUrl&, const QUrl& mxcUri, Omittable encryptedFile) { if (tId != txnId) return; const auto it = q->findPendingEvent(txnId); if (it != unsyncedEvents.end()) { it->setFileUploaded(mxcUri); + if (encryptedFile) { + it->setEncryptedFile(*encryptedFile); + } emit q->pendingEventChanged( int(it - unsyncedEvents.begin())); doSendEvent(it->get()); @@ -2508,6 +2509,20 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, Q_ASSERT_X(localFilename.isLocalFile(), __FUNCTION__, "localFilename should point at a local file"); auto fileName = localFilename.toLocalFile(); + Omittable encryptedFile = std::nullopt; +#ifdef Quotient_E2EE_ENABLED + QTemporaryFile tempFile; + if (usesEncryption()) { + tempFile.open(); + QFile file(localFilename.toLocalFile()); + file.open(QFile::ReadOnly); + auto [e, data] = EncryptedFile::encryptFile(file.readAll()); + tempFile.write(data); + tempFile.close(); + fileName = QFileInfo(tempFile).absoluteFilePath(); + encryptedFile = e; + } +#endif auto job = connection()->uploadFile(fileName, overrideContentType); if (isJobPending(job)) { d->fileTransfers[id] = { job, fileName, true }; @@ -2516,9 +2531,15 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, d->fileTransfers[id].update(sent, total); emit fileTransferProgress(id, sent, total); }); - connect(job, &BaseJob::success, this, [this, id, localFilename, job] { + connect(job, &BaseJob::success, this, [this, id, localFilename, job, encryptedFile] { d->fileTransfers[id].status = FileTransferInfo::Completed; - emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri())); + if (encryptedFile) { + auto file = *encryptedFile; + file.url = QUrl(job->contentUri()); + emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri()), file); + } else { + emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri())); + } }); connect(job, &BaseJob::failure, this, std::bind(&Private::failedTransfer, d, id, job->errorString())); diff --git a/lib/room.h b/lib/room.h index 6e6071f0..d5a8366a 100644 --- a/lib/room.h +++ b/lib/room.h @@ -999,7 +999,7 @@ Q_SIGNALS: void newFileTransfer(QString id, QUrl localFile); void fileTransferProgress(QString id, qint64 progress, qint64 total); - void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl); + void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl, Omittable encryptedFile = std::nullopt); void fileTransferFailed(QString id, QString errorMessage = {}); // fileTransferCancelled() is no more here; use fileTransferFailed() and // check the transfer status instead -- cgit v1.2.3 From fcde81c8618fbe10c1cb91c0ec6887a3df705a23 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Tue, 8 Mar 2022 21:44:10 +0100 Subject: Properly create encrypted edits --- lib/events/encryptedevent.cpp | 7 +++++++ lib/events/encryptedevent.h | 2 ++ lib/room.cpp | 3 +++ 3 files changed, 12 insertions(+) (limited to 'lib/events') diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 9d07a35f..3af3d6ff 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -61,3 +61,10 @@ RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const } return loadEvent(eventObject); } + +void EncryptedEvent::setRelation(const QJsonObject& relation) +{ + auto content = editJson()["content"_ls].toObject(); + content["m.relates_to"] = relation; + editJson()["content"] = content; +} diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 72efffd4..ddd5e415 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -56,6 +56,8 @@ public: QString deviceId() const { return contentPart(DeviceIdKeyL); } QString sessionId() const { return contentPart(SessionIdKeyL); } RoomEventPtr createDecrypted(const QString &decrypted) const; + + void setRelation(const QJsonObject& relation); }; REGISTER_EVENT_TYPE(EncryptedEvent) diff --git a/lib/room.cpp b/lib/room.cpp index 763ca31c..a42b7184 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2105,6 +2105,9 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) encryptedEvent->setTransactionId(connection->generateTxnId()); encryptedEvent->setRoomId(id); encryptedEvent->setSender(connection->userId()); + if(pEvent->contentJson().contains("m.relates_to"_ls)) { + encryptedEvent->setRelation(pEvent->contentJson()["m.relates_to"_ls].toObject()); + } // We show the unencrypted event locally while pending. The echo check will throw the encrypted version out _event = encryptedEvent; } -- 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 ++++++++---- lib/connection.h | 3 +-- lib/database.cpp | 18 +++++++++++++++--- lib/database.h | 3 ++- lib/events/encryptedfile.cpp | 4 ++++ lib/room.cpp | 27 ++++++++++++++++----------- 6 files changed, 46 insertions(+), 21 deletions(-) (limited to 'lib/events') 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))); diff --git a/lib/connection.h b/lib/connection.h index 8bed55da..5a1f1e5c 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -329,8 +329,7 @@ public: void saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data); - //This currently assumes that an olm session with (user, device) exists - //TODO make this return an event? + //This assumes that an olm session with (user, device) exists QPair olmEncryptMessage(User* user, const QString& device, const QByteArray& message); void createOlmSession(const QString& theirIdentityKey, const QString& theirOneTimeKey); #endif // Quotient_E2EE_ENABLED diff --git a/lib/database.cpp b/lib/database.cpp index 4a28fd4c..74b56a02 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "e2ee/e2ee.h" #include "e2ee/qolmsession.h" @@ -182,7 +183,7 @@ void Database::saveOlmSession(const QString& senderKey, const QString& sessionId UnorderedMap> Database::loadOlmSessions(const PicklingMode& picklingMode) { - auto query = prepareQuery(QStringLiteral("SELECT * FROM olm_sessions;")); + auto query = prepareQuery(QStringLiteral("SELECT * FROM olm_sessions ORDER BY lastReceived DESC;")); transaction(); execute(query); commit(); @@ -338,7 +339,6 @@ QOlmOutboundGroupSessionPtr Database::loadCurrentOutboundMegolmSession(const QSt void Database::setDevicesReceivedKey(const QString& roomId, QHash devices, const QString& sessionId, int index) { - //TODO this better auto connection = dynamic_cast(parent()); transaction(); for (const auto& user : devices.keys()) { @@ -360,7 +360,7 @@ QHash Database::devicesWithoutKey(Room* room, const QStrin { auto connection = dynamic_cast(parent()); QHash devices; - for (const auto& user : room->users()) {//TODO does this include invited & left? + for (const auto& user : room->users()) { devices[user->id()] = connection->devicesForUser(user); } @@ -375,3 +375,15 @@ QHash Database::devicesWithoutKey(Room* room, const QStrin } return devices; } + +void Database::updateOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle) +{ + auto query = prepareQuery(QStringLiteral("UPDATE olm_sessions SET pickle=:pickle WHERE senderKey=:senderKey AND sessionId=:sessionId;")); + query.bindValue(":pickle", pickle); + query.bindValue(":senderKey", senderKey); + query.bindValue(":sessionId", sessionId); + transaction(); + execute(query); + commit(); +} + diff --git a/lib/database.h b/lib/database.h index 30f2f203..8ddd7b6d 100644 --- a/lib/database.h +++ b/lib/database.h @@ -32,7 +32,7 @@ public: QByteArray accountPickle(); void setAccountPickle(const QByteArray &pickle); void clear(); - void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray &pickle, const QDateTime& timestamp); + void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle, const QDateTime& timestamp); UnorderedMap> loadOlmSessions(const PicklingMode& picklingMode); UnorderedMap loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode); void saveMegolmSession(const QString& roomId, const QString& sessionId, const QByteArray& pickle, const QString& senderId, const QString& olmSessionId); @@ -42,6 +42,7 @@ public: void setOlmSessionLastReceived(const QString& sessionId, const QDateTime& timestamp); QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode); void saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& data); + void updateOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle); // Returns a map User -> [Device] that have not received key yet QHash devicesWithoutKey(Room* room, const QString &sessionId); diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index e90be428..bb4e26c7 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -56,6 +56,7 @@ QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const std::pair EncryptedFile::encryptFile(const QByteArray &plainText) { +#ifdef Quotient_E2EE_ENABLED QByteArray k = getRandom(32); auto kBase64 = k.toBase64(); QByteArray iv = getRandom(16); @@ -73,6 +74,9 @@ std::pair EncryptedFile::encryptFile(const QByteArray auto ivBase64 = iv.toBase64(); EncryptedFile file = {{}, key, ivBase64.left(ivBase64.indexOf('=')), {{QStringLiteral("sha256"), hash.left(hash.indexOf('='))}}, "v2"_ls}; return {file, cipherText}; +#else + return {{}, {}}; +#endif } void JsonObjectConverter::dumpTo(QJsonObject& jo, diff --git a/lib/room.cpp b/lib/room.cpp index a42b7184..0ca8f648 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -449,8 +449,8 @@ public: const auto sessionKey = currentOutboundMegolmSession->sessionKey(); if(std::holds_alternative(sessionKey)) { - qCWarning(E2EE) << "Session error"; - //TODO something + qCWarning(E2EE) << "Failed to load key for new megolm session"; + return; } addInboundGroupSession(q->connection()->olmAccount()->identityKeys().curve25519, currentOutboundMegolmSession->sessionId(), std::get(sessionKey), QString(connection->olmAccount()->identityKeys().ed25519)); } @@ -459,7 +459,6 @@ public: { // Noisy but nice for debugging //qCDebug(E2EE) << "Creating the payload for" << user->id() << device << sessionId << sessionKey.toHex(); - //TODO: store {user->id(), device, sessionId, theirIdentityKey}; required for key requests const auto event = makeEvent("m.megolm.v1.aes-sha2", q->id(), sessionId, sessionKey, q->localUser()->id()); QJsonObject payloadJson = event->fullJson(); payloadJson["recipient"] = user->id(); @@ -504,6 +503,9 @@ public: hash[user->id()] = u; } } + if (hash.isEmpty()) { + return; + } auto job = connection->callApi(hash); connect(job, &BaseJob::success, q, [job, this, sessionId, sessionKey, devices, index](){ Connection::UsersToDevicesToEvents usersToDevicesToEvents; @@ -525,7 +527,6 @@ public: signedData.remove("signatures"); auto signatureMatch = QOlmUtility().ed25519Verify(connection->edKeyForUserDevice(user->id(), device).toLatin1(), QJsonDocument(signedData).toJson(QJsonDocument::Compact), signature); if (std::holds_alternative(signatureMatch)) { - //TODO i think there are more failed signature checks than expected. Investigate qCWarning(E2EE) << "Failed to verify one-time-key signature for" << user->id() << device << ". Skipping this device."; continue; } else { @@ -535,8 +536,10 @@ public: usersToDevicesToEvents[user->id()][device] = payloadForUserDevice(user, device, sessionId, sessionKey); } } - connection->sendToDevices("m.room.encrypted", usersToDevicesToEvents); - connection->database()->setDevicesReceivedKey(q->id(), devices, sessionId, index); + if (!usersToDevicesToEvents.empty()) { + connection->sendToDevices("m.room.encrypted", usersToDevicesToEvents); + connection->database()->setDevicesReceivedKey(q->id(), devices, sessionId, index); + } }); } @@ -545,8 +548,8 @@ public: const auto sessionId = currentOutboundMegolmSession->sessionId(); const auto _sessionKey = currentOutboundMegolmSession->sessionKey(); if(std::holds_alternative(_sessionKey)) { - qCWarning(E2EE) << "Session error"; - //TODO something + qCWarning(E2EE) << "Error loading session key"; + return; } const auto sessionKey = std::get(_sessionKey); const auto senderKey = q->connection()->olmAccount()->identityKeys().curve25519; @@ -581,7 +584,6 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) connect(this, &Room::userAdded, this, [this, connection](){ if(usesEncryption()) { connection->encryptionUpdate(this); - //TODO key at currentIndex to all user devices } }); d->groupSessions = connection->loadRoomMegolmSessions(this); @@ -2086,18 +2088,20 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) const RoomEvent* _event = pEvent; if (q->usesEncryption()) { +#ifndef Quotient_E2EE_ENABLED + qWarning() << "This build of libQuotient does not support E2EE."; + return {}; +#else if (!hasValidMegolmSession() || shouldRotateMegolmSession()) { createMegolmSession(); } const auto devicesWithoutKey = getDevicesWithoutKey(); sendMegolmSession(devicesWithoutKey); - //TODO check if we increment the sent message count const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson()); currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); if(std::holds_alternative(encrypted)) { - //TODO something qWarning(E2EE) << "Error encrypting message" << std::get(encrypted); return {}; } @@ -2110,6 +2114,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) } // We show the unencrypted event locally while pending. The echo check will throw the encrypted version out _event = encryptedEvent; +#endif } if (auto call = -- cgit v1.2.3 From 1b302abce0bfd9fb62cdc721bc7300dc61b1784f Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Thu, 10 Mar 2022 21:47:51 +0100 Subject: Update lib/events/encryptedfile.h --- lib/events/encryptedfile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 2ce35086..022ac91e 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -46,7 +46,7 @@ public: QString v; QByteArray decryptFile(const QByteArray &ciphertext) const; - static std::pair encryptFile(const QByteArray &plainText); + static std::pair encryptFile(const QByteArray& plainText); }; template <> -- 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 +++---- lib/connection.h | 2 +- lib/database.cpp | 3 +-- lib/events/encryptedfile.cpp | 2 +- lib/room.h | 2 +- 5 files changed, 7 insertions(+), 9 deletions(-) (limited to 'lib/events') 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) diff --git a/lib/connection.h b/lib/connection.h index 5a1f1e5c..5b266aad 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -134,7 +134,7 @@ class QUOTIENT_API Connection : public QObject { public: using UsersToDevicesToEvents = - UnorderedMap>>; + UnorderedMap>; enum RoomVisibility { PublishRoom, diff --git a/lib/database.cpp b/lib/database.cpp index 87275e1f..99c6f358 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include "e2ee/e2ee.h" #include "e2ee/qolmsession.h" @@ -140,7 +139,7 @@ void Database::migrateTo4() execute(QStringLiteral("CREATE TABLE sent_megolm_sessions (roomId TEXT, userId TEXT, deviceId TEXT, identityKey TEXT, sessionId TEXT, i INTEGER);")); execute(QStringLiteral("ALTER TABLE outbound_megolm_sessions ADD creationTime TEXT;")); execute(QStringLiteral("ALTER TABLE outbound_megolm_sessions ADD messageCount INTEGER;")); - execute(QStringLiteral("PRAGMA user_version = 3;")); + execute(QStringLiteral("PRAGMA user_version = 4;")); commit(); } diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index bb4e26c7..d35ee28f 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -75,7 +75,7 @@ std::pair EncryptedFile::encryptFile(const QByteArray EncryptedFile file = {{}, key, ivBase64.left(ivBase64.indexOf('=')), {{QStringLiteral("sha256"), hash.left(hash.indexOf('='))}}, "v2"_ls}; return {file, cipherText}; #else - return {{}, {}}; + return {}; #endif } diff --git a/lib/room.h b/lib/room.h index d5a8366a..b1201a6c 100644 --- a/lib/room.h +++ b/lib/room.h @@ -999,7 +999,7 @@ Q_SIGNALS: void newFileTransfer(QString id, QUrl localFile); void fileTransferProgress(QString id, qint64 progress, qint64 total); - void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl, Omittable encryptedFile = std::nullopt); + void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl, Omittable encryptedFile = none); void fileTransferFailed(QString id, QString errorMessage = {}); // fileTransferCancelled() is no more here; use fileTransferFailed() and // check the transfer status instead -- cgit v1.2.3 From 9c4cc1b9b065765843c81a0c555b3afa5122b61e Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Wed, 18 May 2022 22:05:48 +0200 Subject: Update lib/events/encryptedevent.cpp Co-authored-by: Alexey Rusakov --- lib/events/encryptedevent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 3af3d6ff..c97ccc16 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -64,7 +64,7 @@ RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const void EncryptedEvent::setRelation(const QJsonObject& relation) { - auto content = editJson()["content"_ls].toObject(); + auto content = contentJson(); content["m.relates_to"] = relation; editJson()["content"] = content; } -- cgit v1.2.3 From a8076b9a2394150e11381dc8fc2e3af2bbd03f39 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 19 May 2022 21:58:54 +0200 Subject: Fix cipher text buffer initialization --- lib/events/encryptedfile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index d35ee28f..9cc3a0c8 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -64,7 +64,7 @@ std::pair EncryptedFile::encryptFile(const QByteArray int length; auto* ctx = EVP_CIPHER_CTX_new(); - QByteArray cipherText(plainText.size(), plainText.size() + EVP_MAX_BLOCK_LENGTH - 1); + QByteArray cipherText(plainText.size() + EVP_MAX_BLOCK_LENGTH - 1, '\0'); EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast(k.data()),reinterpret_cast(iv.data())); EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), &length, reinterpret_cast(plainText.data()), plainText.size()); EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + length, &length); -- cgit v1.2.3 From 44f34c60fe1f1dde859655bbda86221b6cec4811 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Fri, 20 May 2022 12:41:06 +0200 Subject: Truncate ciphertext buffer to actual size during file encryption The ciphertext for AES CTR is exactly as large as the plaintext (not necessarily a multiple of the blocksize!). By truncating the ciphertext, we do not send bytes that will be decrypted to gibberish. As a side node, we probably do not need to initialize the ciphertext buffer larger than the plaintext size at all, but the OpenSSL docs are a bit vague about that. --- autotests/testfilecrypto.cpp | 4 +++- lib/events/encryptedfile.cpp | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'lib/events') diff --git a/autotests/testfilecrypto.cpp b/autotests/testfilecrypto.cpp index e6bec1fe..5d549b89 100644 --- a/autotests/testfilecrypto.cpp +++ b/autotests/testfilecrypto.cpp @@ -12,6 +12,8 @@ void TestFileCrypto::encryptDecryptData() QByteArray data = "ABCDEF"; auto [file, cipherText] = EncryptedFile::encryptFile(data); auto decrypted = file.decryptFile(cipherText); - QCOMPARE(data, decrypted); + QCOMPARE(cipherText.size(), data.size()); + QCOMPARE(decrypted.size(), data.size()); + QCOMPARE(decrypted, data); } QTEST_APPLESS_MAIN(TestFileCrypto) diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index 9cc3a0c8..140dca7f 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -67,6 +67,7 @@ std::pair EncryptedFile::encryptFile(const QByteArray QByteArray cipherText(plainText.size() + EVP_MAX_BLOCK_LENGTH - 1, '\0'); EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast(k.data()),reinterpret_cast(iv.data())); EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), &length, reinterpret_cast(plainText.data()), plainText.size()); + cipherText.resize(length); EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + length, &length); EVP_CIPHER_CTX_free(ctx); -- cgit v1.2.3 From 59f2b60835752fc87e75f456145d21cc5f77a433 Mon Sep 17 00:00:00 2001 From: Tobias Fella <9750016+TobiasFella@users.noreply.github.com> Date: Fri, 20 May 2022 20:33:12 +0200 Subject: Apply suggestions from code review Co-authored-by: Alexey Rusakov --- autotests/testfilecrypto.cpp | 1 + lib/events/encryptedfile.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/autotests/testfilecrypto.cpp b/autotests/testfilecrypto.cpp index 5d549b89..f9212376 100644 --- a/autotests/testfilecrypto.cpp +++ b/autotests/testfilecrypto.cpp @@ -12,6 +12,7 @@ void TestFileCrypto::encryptDecryptData() QByteArray data = "ABCDEF"; auto [file, cipherText] = EncryptedFile::encryptFile(data); auto decrypted = file.decryptFile(cipherText); + // AES CTR produces ciphertext of the same size as the original QCOMPARE(cipherText.size(), data.size()); QCOMPARE(decrypted.size(), data.size()); QCOMPARE(decrypted, data); diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp index 140dca7f..33ebb514 100644 --- a/lib/events/encryptedfile.cpp +++ b/lib/events/encryptedfile.cpp @@ -64,10 +64,10 @@ std::pair EncryptedFile::encryptFile(const QByteArray int length; auto* ctx = EVP_CIPHER_CTX_new(); - QByteArray cipherText(plainText.size() + EVP_MAX_BLOCK_LENGTH - 1, '\0'); EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast(k.data()),reinterpret_cast(iv.data())); + const auto blockSize = EVP_CIPHER_CTX_block_size(ctx); + QByteArray cipherText(plainText.size() + blockSize - 1, '\0'); EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), &length, reinterpret_cast(plainText.data()), plainText.size()); - cipherText.resize(length); EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + length, &length); EVP_CIPHER_CTX_free(ctx); -- 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. --- CMakeLists.txt | 2 +- autotests/testfilecrypto.cpp | 6 +- autotests/testolmaccount.cpp | 5 +- lib/connection.cpp | 11 ++- lib/connection.h | 5 +- lib/converters.h | 8 ++ lib/eventitem.cpp | 21 ++--- lib/eventitem.h | 9 +- lib/events/encryptedfile.cpp | 119 -------------------------- lib/events/encryptedfile.h | 63 -------------- lib/events/eventcontent.cpp | 68 +++++++-------- lib/events/eventcontent.h | 53 ++++++------ lib/events/filesourceinfo.cpp | 181 ++++++++++++++++++++++++++++++++++++++++ lib/events/filesourceinfo.h | 89 ++++++++++++++++++++ lib/events/roomavatarevent.h | 4 +- lib/events/roommessageevent.cpp | 8 +- lib/events/stickerevent.cpp | 2 +- lib/jobs/downloadfilejob.cpp | 13 +-- lib/jobs/downloadfilejob.h | 5 +- lib/mxcreply.cpp | 10 ++- lib/room.cpp | 85 +++++++++---------- lib/room.h | 3 +- quotest/quotest.cpp | 2 +- 23 files changed, 427 insertions(+), 345 deletions(-) delete mode 100644 lib/events/encryptedfile.cpp delete mode 100644 lib/events/encryptedfile.h create mode 100644 lib/events/filesourceinfo.cpp create mode 100644 lib/events/filesourceinfo.h (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 404ba87c..635efd90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,7 +168,7 @@ list(APPEND lib_SRCS lib/events/roomkeyevent.h lib/events/roomkeyevent.cpp lib/events/stickerevent.h lib/events/stickerevent.cpp lib/events/keyverificationevent.h lib/events/keyverificationevent.cpp - lib/events/encryptedfile.h lib/events/encryptedfile.cpp + lib/events/filesourceinfo.h lib/events/filesourceinfo.cpp lib/jobs/requestdata.h lib/jobs/requestdata.cpp lib/jobs/basejob.h lib/jobs/basejob.cpp lib/jobs/syncjob.h lib/jobs/syncjob.cpp diff --git a/autotests/testfilecrypto.cpp b/autotests/testfilecrypto.cpp index f9212376..b86114a4 100644 --- a/autotests/testfilecrypto.cpp +++ b/autotests/testfilecrypto.cpp @@ -3,14 +3,16 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "testfilecrypto.h" -#include "events/encryptedfile.h" + +#include "events/filesourceinfo.h" + #include using namespace Quotient; void TestFileCrypto::encryptDecryptData() { QByteArray data = "ABCDEF"; - auto [file, cipherText] = EncryptedFile::encryptFile(data); + auto [file, cipherText] = EncryptedFileMetadata::encryptFile(data); auto decrypted = file.decryptFile(cipherText); // AES CTR produces ciphertext of the same size as the original QCOMPARE(cipherText.size(), data.size()); diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index e31ff6d3..b509d12f 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include @@ -156,8 +156,7 @@ void TestOlmAccount::encryptedFile() "sha256": "fdSLu/YkRx3Wyh3KQabP3rd6+SFiKg5lsJZQHtkSAYA" }})"); - EncryptedFile file; - JsonObjectConverter::fillFrom(doc.object(), file); + const auto file = fromJson(doc); QCOMPARE(file.v, "v2"); QCOMPARE(file.iv, "w+sE15fzSc0AAAAAAAAAAA"); 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 diff --git a/lib/connection.h b/lib/connection.h index f8744752..656e597c 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -51,7 +51,7 @@ class SendToDeviceJob; class SendMessageJob; class LeaveRoomJob; class Database; -struct EncryptedFile; +struct EncryptedFileMetadata; class QOlmAccount; class QOlmInboundGroupSession; @@ -601,7 +601,8 @@ public Q_SLOTS: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob* downloadFile(const QUrl& url, const EncryptedFile& file, + DownloadFileJob* downloadFile(const QUrl& url, + const EncryptedFileMetadata& file, const QString& localFilename = {}); #endif /** diff --git a/lib/converters.h b/lib/converters.h index 5e3becb8..49cb1ed9 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -16,6 +16,7 @@ #include #include +#include class QVariant; @@ -224,6 +225,13 @@ struct QUOTIENT_API JsonConverter { static QVariant load(const QJsonValue& jv); }; +template +inline QJsonValue toJson(const std::variant& v) +{ + return std::visit( + [](const auto& value) { return QJsonValue { toJson(value) }; }, v); +} + template struct JsonConverter> { static QJsonValue dump(const Omittable& from) diff --git a/lib/eventitem.cpp b/lib/eventitem.cpp index 302ae053..a2e2a156 100644 --- a/lib/eventitem.cpp +++ b/lib/eventitem.cpp @@ -8,32 +8,23 @@ using namespace Quotient; -void PendingEventItem::setFileUploaded(const QUrl& remoteUrl) +void PendingEventItem::setFileUploaded(const FileSourceInfo& uploadedFileData) { // TODO: eventually we might introduce hasFileContent to RoomEvent, // and unify the code below. if (auto* rme = getAs()) { Q_ASSERT(rme->hasFileContent()); - rme->editContent([remoteUrl](EventContent::TypedBase& ec) { - ec.fileInfo()->url = remoteUrl; + rme->editContent([&uploadedFileData](EventContent::TypedBase& ec) { + ec.fileInfo()->source = uploadedFileData; }); } if (auto* rae = getAs()) { Q_ASSERT(rae->content().fileInfo()); - rae->editContent( - [remoteUrl](EventContent::FileInfo& fi) { fi.url = remoteUrl; }); - } - setStatus(EventStatus::FileUploaded); -} - -void PendingEventItem::setEncryptedFile(const EncryptedFile& encryptedFile) -{ - if (auto* rme = getAs()) { - Q_ASSERT(rme->hasFileContent()); - rme->editContent([encryptedFile](EventContent::TypedBase& ec) { - ec.fileInfo()->file = encryptedFile; + rae->editContent([&uploadedFileData](EventContent::FileInfo& fi) { + fi.source = uploadedFileData; }); } + setStatus(EventStatus::FileUploaded); } // Not exactly sure why but this helps with the linker not finding diff --git a/lib/eventitem.h b/lib/eventitem.h index d8313736..5e001d88 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -3,14 +3,14 @@ #pragma once -#include "events/stateevent.h" #include "quotient_common.h" +#include "events/filesourceinfo.h" +#include "events/stateevent.h" + #include #include -#include "events/encryptedfile.h" - namespace Quotient { namespace EventStatus { @@ -115,8 +115,7 @@ public: QString annotation() const { return _annotation; } void setDeparted() { setStatus(EventStatus::Departed); } - void setFileUploaded(const QUrl& remoteUrl); - void setEncryptedFile(const EncryptedFile& encryptedFile); + void setFileUploaded(const FileSourceInfo &uploadedFileData); void setReachedServer(const QString& eventId) { setStatus(EventStatus::ReachedServer); diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp deleted file mode 100644 index 33ebb514..00000000 --- a/lib/events/encryptedfile.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "encryptedfile.h" -#include "logging.h" - -#ifdef Quotient_E2EE_ENABLED -#include -#include -#include "e2ee/qolmutils.h" -#endif - -using namespace Quotient; - -QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const -{ -#ifdef Quotient_E2EE_ENABLED - auto _key = key.k; - const auto keyBytes = QByteArray::fromBase64( - _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); - const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); - if (sha256 - != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { - qCWarning(E2EE) << "Hash verification failed for file"; - return {}; - } - { - int length; - auto* ctx = EVP_CIPHER_CTX_new(); - QByteArray plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH - - 1, - '\0'); - EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, - reinterpret_cast( - keyBytes.data()), - reinterpret_cast( - QByteArray::fromBase64(iv.toLatin1()).data())); - EVP_DecryptUpdate( - ctx, reinterpret_cast(plaintext.data()), &length, - reinterpret_cast(ciphertext.data()), - ciphertext.size()); - EVP_DecryptFinal_ex(ctx, - reinterpret_cast(plaintext.data()) - + length, - &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext.left(ciphertext.size()); - } -#else - qWarning(MAIN) << "This build of libQuotient doesn't support E2EE, " - "cannot decrypt the file"; - return ciphertext; -#endif -} - -std::pair EncryptedFile::encryptFile(const QByteArray &plainText) -{ -#ifdef Quotient_E2EE_ENABLED - QByteArray k = getRandom(32); - auto kBase64 = k.toBase64(); - QByteArray iv = getRandom(16); - JWK key = {"oct"_ls, {"encrypt"_ls, "decrypt"_ls}, "A256CTR"_ls, QString(k.toBase64()).replace(u'/', u'_').replace(u'+', u'-').left(kBase64.indexOf('=')), true}; - - int length; - auto* ctx = EVP_CIPHER_CTX_new(); - EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast(k.data()),reinterpret_cast(iv.data())); - const auto blockSize = EVP_CIPHER_CTX_block_size(ctx); - QByteArray cipherText(plainText.size() + blockSize - 1, '\0'); - EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), &length, reinterpret_cast(plainText.data()), plainText.size()); - EVP_EncryptFinal_ex(ctx, reinterpret_cast(cipherText.data()) + length, &length); - EVP_CIPHER_CTX_free(ctx); - - auto hash = QCryptographicHash::hash(cipherText, QCryptographicHash::Sha256).toBase64(); - auto ivBase64 = iv.toBase64(); - EncryptedFile file = {{}, key, ivBase64.left(ivBase64.indexOf('=')), {{QStringLiteral("sha256"), hash.left(hash.indexOf('='))}}, "v2"_ls}; - return {file, cipherText}; -#else - return {}; -#endif -} - -void JsonObjectConverter::dumpTo(QJsonObject& jo, - const EncryptedFile& pod) -{ - addParam<>(jo, QStringLiteral("url"), pod.url); - addParam<>(jo, QStringLiteral("key"), pod.key); - addParam<>(jo, QStringLiteral("iv"), pod.iv); - addParam<>(jo, QStringLiteral("hashes"), pod.hashes); - addParam<>(jo, QStringLiteral("v"), pod.v); -} - -void JsonObjectConverter::fillFrom(const QJsonObject& jo, - EncryptedFile& pod) -{ - fromJson(jo.value("url"_ls), pod.url); - fromJson(jo.value("key"_ls), pod.key); - fromJson(jo.value("iv"_ls), pod.iv); - fromJson(jo.value("hashes"_ls), pod.hashes); - fromJson(jo.value("v"_ls), pod.v); -} - -void JsonObjectConverter::dumpTo(QJsonObject &jo, const JWK &pod) -{ - addParam<>(jo, QStringLiteral("kty"), pod.kty); - addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); - addParam<>(jo, QStringLiteral("alg"), pod.alg); - addParam<>(jo, QStringLiteral("k"), pod.k); - addParam<>(jo, QStringLiteral("ext"), pod.ext); -} - -void JsonObjectConverter::fillFrom(const QJsonObject &jo, JWK &pod) -{ - fromJson(jo.value("kty"_ls), pod.kty); - fromJson(jo.value("key_ops"_ls), pod.keyOps); - fromJson(jo.value("alg"_ls), pod.alg); - fromJson(jo.value("k"_ls), pod.k); - fromJson(jo.value("ext"_ls), pod.ext); -} diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h deleted file mode 100644 index 022ac91e..00000000 --- a/lib/events/encryptedfile.h +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "converters.h" - -namespace Quotient { -/** - * JSON Web Key object as specified in - * https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes - * The only currently relevant member is `k`, the rest needs to be set to the defaults specified in the spec. - */ -struct JWK -{ - Q_GADGET - Q_PROPERTY(QString kty MEMBER kty CONSTANT) - Q_PROPERTY(QStringList keyOps MEMBER keyOps CONSTANT) - Q_PROPERTY(QString alg MEMBER alg CONSTANT) - Q_PROPERTY(QString k MEMBER k CONSTANT) - Q_PROPERTY(bool ext MEMBER ext CONSTANT) - -public: - QString kty; - QStringList keyOps; - QString alg; - QString k; - bool ext; -}; - -struct QUOTIENT_API EncryptedFile -{ - Q_GADGET - Q_PROPERTY(QUrl url MEMBER url CONSTANT) - Q_PROPERTY(JWK key MEMBER key CONSTANT) - Q_PROPERTY(QString iv MEMBER iv CONSTANT) - Q_PROPERTY(QHash hashes MEMBER hashes CONSTANT) - Q_PROPERTY(QString v MEMBER v CONSTANT) - -public: - QUrl url; - JWK key; - QString iv; - QHash hashes; - QString v; - - QByteArray decryptFile(const QByteArray &ciphertext) const; - static std::pair encryptFile(const QByteArray& plainText); -}; - -template <> -struct QUOTIENT_API JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const EncryptedFile& pod); - static void fillFrom(const QJsonObject& jo, EncryptedFile& pod); -}; - -template <> -struct QUOTIENT_API JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const JWK& pod); - static void fillFrom(const QJsonObject& jo, JWK& pod); -}; -} // namespace Quotient diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 6218e3b8..36b647cb 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -19,23 +19,21 @@ QJsonObject Base::toJson() const return o; } -FileInfo::FileInfo(const QFileInfo &fi) - : mimeType(QMimeDatabase().mimeTypeForFile(fi)) - , url(QUrl::fromLocalFile(fi.filePath())) - , payloadSize(fi.size()) - , originalName(fi.fileName()) +FileInfo::FileInfo(const QFileInfo& fi) + : source(QUrl::fromLocalFile(fi.filePath())), + mimeType(QMimeDatabase().mimeTypeForFile(fi)), + payloadSize(fi.size()), + originalName(fi.fileName()) { Q_ASSERT(fi.isFile()); } -FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, - Omittable encryptedFile, - QString originalFilename) - : mimeType(mimeType) - , url(move(u)) +FileInfo::FileInfo(FileSourceInfo sourceInfo, qint64 payloadSize, + const QMimeType& mimeType, QString originalFilename) + : source(move(sourceInfo)) + , mimeType(mimeType) , payloadSize(payloadSize) , originalName(move(originalFilename)) - , file(move(encryptedFile)) { if (!isValid()) qCWarning(MESSAGES) @@ -44,28 +42,28 @@ FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, "0.7; for local resources, use FileInfo(QFileInfo) instead"; } -FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, - Omittable encryptedFile, +FileInfo::FileInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, QString originalFilename) - : originalInfoJson(infoJson) + : source(move(sourceInfo)) + , originalInfoJson(infoJson) , mimeType( QMimeDatabase().mimeTypeForName(infoJson["mimetype"_ls].toString())) - , url(move(mxcUrl)) , payloadSize(fromJson(infoJson["size"_ls])) , originalName(move(originalFilename)) - , file(move(encryptedFile)) { - if(url.isEmpty() && file.has_value()) { - url = file->url; - } if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); } bool FileInfo::isValid() const { - return url.scheme() == "mxc" - && (url.authority() + url.path()).count('/') == 1; + const auto& u = url(); + return u.scheme() == "mxc" && (u.authority() + u.path()).count('/') == 1; +} + +QUrl FileInfo::url() const +{ + return getUrlFromSourceInfo(source); } QJsonObject Quotient::EventContent::toInfoJson(const FileInfo& info) @@ -75,7 +73,6 @@ QJsonObject Quotient::EventContent::toInfoJson(const FileInfo& info) infoJson.insert(QStringLiteral("size"), info.payloadSize); if (info.mimeType.isValid()) infoJson.insert(QStringLiteral("mimetype"), info.mimeType.name()); - //TODO add encryptedfile return infoJson; } @@ -83,17 +80,16 @@ ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) : FileInfo(fi), imageSize(imageSize) {} -ImageInfo::ImageInfo(const QUrl& mxcUrl, qint64 fileSize, const QMimeType& type, - QSize imageSize, Omittable encryptedFile, +ImageInfo::ImageInfo(FileSourceInfo sourceInfo, qint64 fileSize, + const QMimeType& type, QSize imageSize, const QString& originalFilename) - : FileInfo(mxcUrl, fileSize, type, move(encryptedFile), originalFilename) + : FileInfo(move(sourceInfo), fileSize, type, originalFilename) , imageSize(imageSize) {} -ImageInfo::ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, - Omittable encryptedFile, +ImageInfo::ImageInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, const QString& originalFilename) - : FileInfo(mxcUrl, infoJson, move(encryptedFile), originalFilename) + : FileInfo(move(sourceInfo), infoJson, originalFilename) , imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt()) {} @@ -107,16 +103,20 @@ QJsonObject Quotient::EventContent::toInfoJson(const ImageInfo& info) return infoJson; } -Thumbnail::Thumbnail(const QJsonObject& infoJson, - Omittable encryptedFile) +Thumbnail::Thumbnail( + const QJsonObject& infoJson, + const Omittable& encryptedFileMetadata) : ImageInfo(QUrl(infoJson["thumbnail_url"_ls].toString()), - infoJson["thumbnail_info"_ls].toObject(), move(encryptedFile)) -{} + infoJson["thumbnail_info"_ls].toObject()) +{ + if (encryptedFileMetadata) + source = *encryptedFileMetadata; +} void Thumbnail::dumpTo(QJsonObject& infoJson) const { - if (url.isValid()) - infoJson.insert(QStringLiteral("thumbnail_url"), url.toString()); + if (url().isValid()) + fillJson(infoJson, { "thumbnail_url"_ls, "thumbnail_file"_ls }, source); if (!imageSize.isEmpty()) infoJson.insert(QStringLiteral("thumbnail_info"), toInfoJson(*this)); diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index bbd35618..23281876 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -6,14 +6,14 @@ // This file contains generic event content definitions, applicable to room // message events as well as other events (e.g., avatars). -#include "encryptedfile.h" +#include "filesourceinfo.h" #include "quotient_export.h" #include +#include #include #include #include -#include class QFileInfo; @@ -50,7 +50,7 @@ namespace EventContent { // A quick classes inheritance structure follows (the definitions are // spread across eventcontent.h and roommessageevent.h): - // UrlBasedContent : InfoT + url and thumbnail data + // UrlBasedContent : InfoT + thumbnail data // PlayableContent : + duration attribute // FileInfo // FileContent = UrlBasedContent @@ -89,34 +89,32 @@ namespace EventContent { //! //! \param fi a QFileInfo object referring to an existing file explicit FileInfo(const QFileInfo& fi); - explicit FileInfo(QUrl mxcUrl, qint64 payloadSize = -1, + explicit FileInfo(FileSourceInfo sourceInfo, qint64 payloadSize = -1, const QMimeType& mimeType = {}, - Omittable encryptedFile = none, QString originalFilename = {}); //! \brief Construct from a JSON `info` payload //! //! Make sure to pass the `info` subobject of content JSON, not the //! whole JSON content. - FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, - Omittable encryptedFile, + FileInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, QString originalFilename = {}); bool isValid() const; + QUrl url() const; //! \brief Extract media id from the URL //! //! This can be used, e.g., to construct a QML-facing image:// //! URI as follows: //! \code "image://provider/" + info.mediaId() \endcode - QString mediaId() const { return url.authority() + url.path(); } + QString mediaId() const { return url().authority() + url().path(); } public: + FileSourceInfo source; QJsonObject originalInfoJson; QMimeType mimeType; - QUrl url; qint64 payloadSize = 0; QString originalName; - Omittable file = none; }; QUOTIENT_API QJsonObject toInfoJson(const FileInfo& info); @@ -126,12 +124,10 @@ namespace EventContent { public: ImageInfo() = default; explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); - explicit ImageInfo(const QUrl& mxcUrl, qint64 fileSize = -1, + explicit ImageInfo(FileSourceInfo sourceInfo, qint64 fileSize = -1, const QMimeType& type = {}, QSize imageSize = {}, - Omittable encryptedFile = none, const QString& originalFilename = {}); - ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, - Omittable encryptedFile, + ImageInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, const QString& originalFilename = {}); public: @@ -144,12 +140,13 @@ namespace EventContent { //! //! This class saves/loads a thumbnail to/from `info` subobject of //! the JSON representation of event content; namely, `info/thumbnail_url` - //! and `info/thumbnail_info` fields are used. + //! (or, in case of an encrypted thumbnail, `info/thumbnail_file`) and + //! `info/thumbnail_info` fields are used. class QUOTIENT_API Thumbnail : public ImageInfo { public: using ImageInfo::ImageInfo; Thumbnail(const QJsonObject& infoJson, - Omittable encryptedFile = none); + const Omittable& encryptedFile = none); //! \brief Add thumbnail information to the passed `info` JSON object void dumpTo(QJsonObject& infoJson) const; @@ -169,10 +166,10 @@ namespace EventContent { //! \brief A template class for content types with a URL and additional info //! - //! Types that derive from this class template take `url` and, - //! optionally, `filename` values from the top-level JSON object and - //! the rest of information from the `info` subobject, as defined by - //! the parameter type. + //! Types that derive from this class template take `url` (or, if the file + //! is encrypted, `file`) and, optionally, `filename` values from + //! the top-level JSON object and the rest of information from the `info` + //! subobject, as defined by the parameter type. //! \tparam InfoT base info class - FileInfo or ImageInfo template class UrlBasedContent : public TypedBase, public InfoT { @@ -181,10 +178,12 @@ namespace EventContent { explicit UrlBasedContent(const QJsonObject& json) : TypedBase(json) , InfoT(QUrl(json["url"].toString()), json["info"].toObject(), - fromJson>(json["file"]), json["filename"].toString()) , thumbnail(FileInfo::originalInfoJson) { + const auto efmJson = json.value("file"_ls).toObject(); + if (!efmJson.isEmpty()) + InfoT::source = fromJson(efmJson); // Two small hacks on originalJson to expose mediaIds to QML originalJson.insert("mediaId", InfoT::mediaId()); originalJson.insert("thumbnailMediaId", thumbnail.mediaId()); @@ -204,11 +203,7 @@ namespace EventContent { void fillJson(QJsonObject& json) const override { - if (!InfoT::file.has_value()) { - json.insert("url", InfoT::url.toString()); - } else { - json.insert("file", Quotient::toJson(*InfoT::file)); - } + Quotient::fillJson(json, { "url"_ls, "file"_ls }, InfoT::source); if (!InfoT::originalName.isEmpty()) json.insert("filename", InfoT::originalName); auto infoJson = toInfoJson(*this); @@ -223,7 +218,7 @@ namespace EventContent { //! //! Available fields: //! - corresponding to the top-level JSON: - //! - url + //! - source (corresponding to `url` or `file` in JSON) //! - filename (extension to the spec) //! - corresponding to the `info` subobject: //! - payloadSize (`size` in JSON) @@ -241,12 +236,12 @@ namespace EventContent { //! //! Available fields: //! - corresponding to the top-level JSON: - //! - url + //! - source (corresponding to `url` or `file` in JSON) //! - filename //! - corresponding to the `info` subobject: //! - payloadSize (`size` in JSON) //! - mimeType (`mimetype` in JSON) - //! - thumbnail.url (`thumbnail_url` in JSON) + //! - thumbnail.source (`thumbnail_url` or `thumbnail_file` in JSON) //! - corresponding to the `info/thumbnail_info` subobject: //! - thumbnail.payloadSize //! - thumbnail.mimeType diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp new file mode 100644 index 00000000..a64c7da8 --- /dev/null +++ b/lib/events/filesourceinfo.cpp @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "filesourceinfo.h" + +#include "logging.h" + +#ifdef Quotient_E2EE_ENABLED +# include "e2ee/qolmutils.h" + +# include + +# include +#endif + +using namespace Quotient; + +QByteArray EncryptedFileMetadata::decryptFile(const QByteArray& ciphertext) const +{ +#ifdef Quotient_E2EE_ENABLED + auto _key = key.k; + const auto keyBytes = QByteArray::fromBase64( + _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); + const auto sha256 = + QByteArray::fromBase64(hashes["sha256"_ls].toLatin1()); + if (sha256 + != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return {}; + } + { + int length; + auto* ctx = EVP_CIPHER_CTX_new(); + QByteArray plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH - 1, '\0'); + EVP_DecryptInit_ex( + ctx, EVP_aes_256_ctr(), nullptr, + reinterpret_cast(keyBytes.data()), + reinterpret_cast( + QByteArray::fromBase64(iv.toLatin1()).data())); + EVP_DecryptUpdate( + ctx, reinterpret_cast(plaintext.data()), &length, + reinterpret_cast(ciphertext.data()), + ciphertext.size()); + EVP_DecryptFinal_ex(ctx, + reinterpret_cast(plaintext.data()) + + length, + &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext.left(ciphertext.size()); + } +#else + qWarning(MAIN) << "This build of libQuotient doesn't support E2EE, " + "cannot decrypt the file"; + return ciphertext; +#endif +} + +std::pair EncryptedFileMetadata::encryptFile( + const QByteArray& plainText) +{ +#ifdef Quotient_E2EE_ENABLED + QByteArray k = getRandom(32); + auto kBase64 = k.toBase64(); + QByteArray iv = getRandom(16); + JWK key = { "oct"_ls, + { "encrypt"_ls, "decrypt"_ls }, + "A256CTR"_ls, + QString(k.toBase64()) + .replace(u'/', u'_') + .replace(u'+', u'-') + .left(kBase64.indexOf('=')), + true }; + + int length; + auto* ctx = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, + reinterpret_cast(k.data()), + reinterpret_cast(iv.data())); + const auto blockSize = EVP_CIPHER_CTX_block_size(ctx); + QByteArray cipherText(plainText.size() + blockSize - 1, '\0'); + EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), + &length, + reinterpret_cast(plainText.data()), + plainText.size()); + EVP_EncryptFinal_ex(ctx, + reinterpret_cast(cipherText.data()) + + length, + &length); + EVP_CIPHER_CTX_free(ctx); + + auto hash = QCryptographicHash::hash(cipherText, QCryptographicHash::Sha256) + .toBase64(); + auto ivBase64 = iv.toBase64(); + EncryptedFileMetadata efm = { {}, + key, + ivBase64.left(ivBase64.indexOf('=')), + { { QStringLiteral("sha256"), + hash.left(hash.indexOf('=')) } }, + "v2"_ls }; + return { efm, cipherText }; +#else + return {}; +#endif +} + +void JsonObjectConverter::dumpTo(QJsonObject& jo, + const EncryptedFileMetadata& pod) +{ + addParam<>(jo, QStringLiteral("url"), pod.url); + addParam<>(jo, QStringLiteral("key"), pod.key); + addParam<>(jo, QStringLiteral("iv"), pod.iv); + addParam<>(jo, QStringLiteral("hashes"), pod.hashes); + addParam<>(jo, QStringLiteral("v"), pod.v); +} + +void JsonObjectConverter::fillFrom(const QJsonObject& jo, + EncryptedFileMetadata& pod) +{ + fromJson(jo.value("url"_ls), pod.url); + fromJson(jo.value("key"_ls), pod.key); + fromJson(jo.value("iv"_ls), pod.iv); + fromJson(jo.value("hashes"_ls), pod.hashes); + fromJson(jo.value("v"_ls), pod.v); +} + +void JsonObjectConverter::dumpTo(QJsonObject& jo, const JWK& pod) +{ + addParam<>(jo, QStringLiteral("kty"), pod.kty); + addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); + addParam<>(jo, QStringLiteral("alg"), pod.alg); + addParam<>(jo, QStringLiteral("k"), pod.k); + addParam<>(jo, QStringLiteral("ext"), pod.ext); +} + +void JsonObjectConverter::fillFrom(const QJsonObject& jo, JWK& pod) +{ + fromJson(jo.value("kty"_ls), pod.kty); + fromJson(jo.value("key_ops"_ls), pod.keyOps); + fromJson(jo.value("alg"_ls), pod.alg); + fromJson(jo.value("k"_ls), pod.k); + fromJson(jo.value("ext"_ls), pod.ext); +} + +template +struct Overloads : FunctorTs... { + using FunctorTs::operator()...; +}; + +template +Overloads(FunctorTs&&...) -> Overloads; + +QUrl Quotient::getUrlFromSourceInfo(const FileSourceInfo& fsi) +{ + return std::visit(Overloads { [](const QUrl& url) { return url; }, + [](const EncryptedFileMetadata& efm) { + return efm.url; + } }, + fsi); +} + +void Quotient::setUrlInSourceInfo(FileSourceInfo& fsi, const QUrl& newUrl) +{ + std::visit(Overloads { [&newUrl](QUrl& url) { url = newUrl; }, + [&newUrl](EncryptedFileMetadata& efm) { + efm.url = newUrl; + } }, + fsi); +} + +void Quotient::fillJson(QJsonObject& jo, + const std::array& jsonKeys, + const FileSourceInfo& fsi) +{ + // NB: Keeping variant_size_v out of the function signature for readability. + // NB2: Can't use jsonKeys directly inside static_assert as its value is + // unknown so the compiler cannot ensure size() is constexpr (go figure...) + static_assert( + std::variant_size_v == decltype(jsonKeys) {}.size()); + jo.insert(jsonKeys[fsi.index()], toJson(fsi)); +} diff --git a/lib/events/filesourceinfo.h b/lib/events/filesourceinfo.h new file mode 100644 index 00000000..885601be --- /dev/null +++ b/lib/events/filesourceinfo.h @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "converters.h" + +#include + +namespace Quotient { +/** + * JSON Web Key object as specified in + * https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes + * The only currently relevant member is `k`, the rest needs to be set to the defaults specified in the spec. + */ +struct JWK +{ + Q_GADGET + Q_PROPERTY(QString kty MEMBER kty CONSTANT) + Q_PROPERTY(QStringList keyOps MEMBER keyOps CONSTANT) + Q_PROPERTY(QString alg MEMBER alg CONSTANT) + Q_PROPERTY(QString k MEMBER k CONSTANT) + Q_PROPERTY(bool ext MEMBER ext CONSTANT) + +public: + QString kty; + QStringList keyOps; + QString alg; + QString k; + bool ext; +}; + +struct QUOTIENT_API EncryptedFileMetadata { + Q_GADGET + Q_PROPERTY(QUrl url MEMBER url CONSTANT) + Q_PROPERTY(JWK key MEMBER key CONSTANT) + Q_PROPERTY(QString iv MEMBER iv CONSTANT) + Q_PROPERTY(QHash hashes MEMBER hashes CONSTANT) + Q_PROPERTY(QString v MEMBER v CONSTANT) + +public: + QUrl url; + JWK key; + QString iv; + QHash hashes; + QString v; + + static std::pair encryptFile( + const QByteArray& plainText); + QByteArray decryptFile(const QByteArray& ciphertext) const; +}; + +template <> +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const EncryptedFileMetadata& pod); + static void fillFrom(const QJsonObject& jo, EncryptedFileMetadata& pod); +}; + +template <> +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const JWK& pod); + static void fillFrom(const QJsonObject& jo, JWK& pod); +}; + +using FileSourceInfo = std::variant; + +QUOTIENT_API QUrl getUrlFromSourceInfo(const FileSourceInfo& fsi); + +QUOTIENT_API void setUrlInSourceInfo(FileSourceInfo& fsi, const QUrl& newUrl); + +// The way FileSourceInfo is stored in JSON requires an extra parameter so +// the original template is not applicable +template <> +void fillJson(QJsonObject&, const FileSourceInfo&) = delete; + +//! \brief Export FileSourceInfo to a JSON object +//! +//! Depending on what is stored inside FileSourceInfo, this function will insert +//! - a key-to-string pair where key is taken from jsonKeys[0] and the string +//! is the URL, if FileSourceInfo stores a QUrl; +//! - a key-to-object mapping where key is taken from jsonKeys[1] and the object +//! is the result of converting EncryptedFileMetadata to JSON, +//! if FileSourceInfo stores EncryptedFileMetadata +QUOTIENT_API void fillJson(QJsonObject& jo, + const std::array& jsonKeys, + const FileSourceInfo& fsi); + +} // namespace Quotient diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index c54b5801..af291696 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -26,10 +26,10 @@ public: const QSize& imageSize = {}, const QString& originalFilename = {}) : RoomAvatarEvent(EventContent::ImageContent { - mxcUrl, fileSize, mimeType, imageSize, none, originalFilename }) + mxcUrl, fileSize, mimeType, imageSize, originalFilename }) {} - QUrl url() const { return content().url; } + QUrl url() const { return content().url(); } }; REGISTER_EVENT_TYPE(RoomAvatarEvent) } // namespace Quotient diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index d9d3fbe0..2a6ae93c 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -148,21 +148,21 @@ TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) auto mimeTypeName = mimeType.name(); if (mimeTypeName.startsWith("image/")) return new ImageContent(localUrl, file.size(), mimeType, - QImageReader(filePath).size(), none, + QImageReader(filePath).size(), file.fileName()); // duration can only be obtained asynchronously and can only be reliably // done by starting to play the file. Left for a future implementation. if (mimeTypeName.startsWith("video/")) return new VideoContent(localUrl, file.size(), mimeType, - QMediaResource(localUrl).resolution(), none, + QMediaResource(localUrl).resolution(), file.fileName()); if (mimeTypeName.startsWith("audio/")) - return new AudioContent(localUrl, file.size(), mimeType, none, + return new AudioContent(localUrl, file.size(), mimeType, file.fileName()); } - return new FileContent(localUrl, file.size(), mimeType, none, file.fileName()); + return new FileContent(localUrl, file.size(), mimeType, file.fileName()); } RoomMessageEvent::RoomMessageEvent(const QString& plainBody, diff --git a/lib/events/stickerevent.cpp b/lib/events/stickerevent.cpp index 628fd154..6d318f0e 100644 --- a/lib/events/stickerevent.cpp +++ b/lib/events/stickerevent.cpp @@ -22,5 +22,5 @@ const EventContent::ImageContent &StickerEvent::image() const QUrl StickerEvent::url() const { - return m_imageContent.url; + return m_imageContent.url(); } diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index d00fc5f4..85c235c7 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -8,8 +8,9 @@ #include #ifdef Quotient_E2EE_ENABLED -# include -# include "events/encryptedfile.h" +# include "events/filesourceinfo.h" + +# include #endif using namespace Quotient; @@ -26,7 +27,7 @@ public: QScopedPointer tempFile; #ifdef Quotient_E2EE_ENABLED - Omittable encryptedFile; + Omittable encryptedFile; #endif }; @@ -49,7 +50,7 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, #ifdef Quotient_E2EE_ENABLED DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, - const EncryptedFile& file, + const EncryptedFileMetadata& file, const QString& localFilename) : GetContentJob(serverName, mediaId) , d(localFilename.isEmpty() ? makeImpl() @@ -126,7 +127,7 @@ BaseJob::Status DownloadFileJob::prepareResult() d->tempFile->seek(0); QByteArray encrypted = d->tempFile->readAll(); - EncryptedFile file = *d->encryptedFile; + EncryptedFileMetadata file = *d->encryptedFile; const auto decrypted = file.decryptFile(encrypted); d->targetFile->write(decrypted); d->tempFile->remove(); @@ -151,7 +152,7 @@ BaseJob::Status DownloadFileJob::prepareResult() d->tempFile->seek(0); const auto encrypted = d->tempFile->readAll(); - EncryptedFile file = *d->encryptedFile; + EncryptedFileMetadata file = *d->encryptedFile; const auto decrypted = file.decryptFile(encrypted); d->tempFile->write(decrypted); } else { diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index ffa3d055..cbbfd244 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -4,7 +4,8 @@ #pragma once #include "csapi/content-repo.h" -#include "events/encryptedfile.h" + +#include "events/filesourceinfo.h" namespace Quotient { class QUOTIENT_API DownloadFileJob : public GetContentJob { @@ -16,7 +17,7 @@ public: const QString& localFilename = {}); #ifdef Quotient_E2EE_ENABLED - DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFile& file, const QString& localFilename = {}); + DownloadFileJob(const QString& serverName, const QString& mediaId, const EncryptedFileMetadata& file, const QString& localFilename = {}); #endif QString targetFileName() const; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 319d514a..b7993ad5 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -8,7 +8,7 @@ #include "room.h" #ifdef Quotient_E2EE_ENABLED -#include "events/encryptedfile.h" +#include "events/filesourceinfo.h" #endif using namespace Quotient; @@ -20,7 +20,7 @@ public: : m_reply(r) {} QNetworkReply* m_reply; - Omittable m_encryptedFile; + Omittable m_encryptedFile; QIODevice* m_device = nullptr; }; @@ -47,7 +47,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) if(!d->m_encryptedFile.has_value()) { d->m_device = d->m_reply; } else { - EncryptedFile file = *d->m_encryptedFile; + EncryptedFileMetadata file = *d->m_encryptedFile; auto buffer = new QBuffer(this); buffer->setData(file.decryptFile(d->m_reply->readAll())); buffer->open(ReadOnly); @@ -64,7 +64,9 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) auto eventIt = room->findInTimeline(eventId); if(eventIt != room->historyEdge()) { auto event = eventIt->viewAs(); - d->m_encryptedFile = event->content()->fileInfo()->file; + if (auto* efm = std::get_if( + &event->content()->fileInfo()->source)) + d->m_encryptedFile = *efm; } #endif } diff --git a/lib/room.cpp b/lib/room.cpp index 7022a49d..20ea1159 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1525,7 +1525,7 @@ QUrl Room::urlToThumbnail(const QString& eventId) const auto* thumbnail = event->content()->thumbnailInfo(); Q_ASSERT(thumbnail != nullptr); return connection()->getUrlForApi( - thumbnail->url, thumbnail->imageSize); + thumbnail->url(), thumbnail->imageSize); } qCDebug(MAIN) << "Event" << eventId << "has no thumbnail"; return {}; @@ -1536,7 +1536,7 @@ QUrl Room::urlToDownload(const QString& eventId) const if (auto* event = d->getEventWithFile(eventId)) { auto* fileInfo = event->content()->fileInfo(); Q_ASSERT(fileInfo != nullptr); - return connection()->getUrlForApi(fileInfo->url); + return connection()->getUrlForApi(fileInfo->url()); } return {}; } @@ -2275,28 +2275,26 @@ QString Room::Private::doPostFile(RoomEventPtr&& msgEvent, const QUrl& localUrl) // Below, the upload job is used as a context object to clean up connections const auto& transferJob = fileTransfers.value(txnId).job; connect(q, &Room::fileTransferCompleted, transferJob, - [this, txnId](const QString& tId, const QUrl&, const QUrl& mxcUri, Omittable encryptedFile) { - if (tId != txnId) - return; + [this, txnId](const QString& tId, const QUrl&, + const FileSourceInfo fileMetadata) { + if (tId != txnId) + return; - const auto it = q->findPendingEvent(txnId); - if (it != unsyncedEvents.end()) { - it->setFileUploaded(mxcUri); - if (encryptedFile) { - it->setEncryptedFile(*encryptedFile); - } - emit q->pendingEventChanged( - int(it - unsyncedEvents.begin())); - doSendEvent(it->get()); - } else { - // Normally in this situation we should instruct - // the media server to delete the file; alas, there's no - // API specced for that. - qCWarning(MAIN) << "File uploaded to" << mxcUri - << "but the event referring to it was " - "cancelled"; - } - }); + const auto it = q->findPendingEvent(txnId); + if (it != unsyncedEvents.end()) { + it->setFileUploaded(fileMetadata); + emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); + doSendEvent(it->get()); + } else { + // Normally in this situation we should instruct + // the media server to delete the file; alas, there's no + // API specced for that. + qCWarning(MAIN) + << "File uploaded to" << getUrlFromSourceInfo(fileMetadata) + << "but the event referring to it was " + "cancelled"; + } + }); connect(q, &Room::fileTransferFailed, transferJob, [this, txnId](const QString& tId) { if (tId != txnId) @@ -2322,13 +2320,13 @@ QString Room::postFile(const QString& plainText, Q_ASSERT(content != nullptr && content->fileInfo() != nullptr); const auto* const fileInfo = content->fileInfo(); Q_ASSERT(fileInfo != nullptr); - QFileInfo localFile { fileInfo->url.toLocalFile() }; + QFileInfo localFile { fileInfo->url().toLocalFile() }; Q_ASSERT(localFile.isFile()); return d->doPostFile( makeEvent( plainText, RoomMessageEvent::rawMsgTypeForFile(localFile), content), - fileInfo->url); + fileInfo->url()); } #if QT_VERSION_MAJOR < 6 @@ -2520,18 +2518,19 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, Q_ASSERT_X(localFilename.isLocalFile(), __FUNCTION__, "localFilename should point at a local file"); auto fileName = localFilename.toLocalFile(); - Omittable encryptedFile { none }; + FileSourceInfo fileMetadata; #ifdef Quotient_E2EE_ENABLED QTemporaryFile tempFile; if (usesEncryption()) { tempFile.open(); QFile file(localFilename.toLocalFile()); file.open(QFile::ReadOnly); - auto [e, data] = EncryptedFile::encryptFile(file.readAll()); + QByteArray data; + std::tie(fileMetadata, data) = + EncryptedFileMetadata::encryptFile(file.readAll()); tempFile.write(data); tempFile.close(); fileName = QFileInfo(tempFile).absoluteFilePath(); - encryptedFile = e; } #endif auto job = connection()->uploadFile(fileName, overrideContentType); @@ -2542,17 +2541,13 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, d->fileTransfers[id].update(sent, total); emit fileTransferProgress(id, sent, total); }); - connect(job, &BaseJob::success, this, [this, id, localFilename, job, encryptedFile] { - d->fileTransfers[id].status = FileTransferInfo::Completed; - if (encryptedFile) { - auto file = *encryptedFile; - file.url = QUrl(job->contentUri()); - emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri()), file); - } else { - emit fileTransferCompleted(id, localFilename, QUrl(job->contentUri()), none); - } - - }); + connect(job, &BaseJob::success, this, + [this, id, localFilename, job, fileMetadata]() mutable { + // The lambda is mutable to change encryptedFileMetadata + d->fileTransfers[id].status = FileTransferInfo::Completed; + setUrlInSourceInfo(fileMetadata, QUrl(job->contentUri())); + emit fileTransferCompleted(id, localFilename, fileMetadata); + }); connect(job, &BaseJob::failure, this, std::bind(&Private::failedTransfer, d, id, job->errorString())); emit newFileTransfer(id, localFilename); @@ -2585,11 +2580,11 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) << "has an empty or malformed mxc URL; won't download"; return; } - const auto fileUrl = fileInfo->url; + const auto fileUrl = fileInfo->url(); auto filePath = localFilename.toLocalFile(); if (filePath.isEmpty()) { // Setup default file path filePath = - fileInfo->url.path().mid(1) % '_' % d->fileNameToDownload(event); + fileInfo->url().path().mid(1) % '_' % d->fileNameToDownload(event); if (filePath.size() > 200) // If too long, elide in the middle filePath.replace(128, filePath.size() - 192, "---"); @@ -2599,9 +2594,9 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) } DownloadFileJob *job = nullptr; #ifdef Quotient_E2EE_ENABLED - if(fileInfo->file.has_value()) { - auto file = *fileInfo->file; - job = connection()->downloadFile(fileUrl, file, filePath); + if (auto* fileMetadata = + std::get_if(&fileInfo->source)) { + job = connection()->downloadFile(fileUrl, *fileMetadata, filePath); } else { #endif job = connection()->downloadFile(fileUrl, filePath); @@ -2619,7 +2614,7 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) connect(job, &BaseJob::success, this, [this, eventId, fileUrl, job] { d->fileTransfers[eventId].status = FileTransferInfo::Completed; emit fileTransferCompleted( - eventId, fileUrl, QUrl::fromLocalFile(job->targetFileName()), none); + eventId, fileUrl, QUrl::fromLocalFile(job->targetFileName())); }); connect(job, &BaseJob::failure, this, std::bind(&Private::failedTransfer, d, eventId, diff --git a/lib/room.h b/lib/room.h index c3bdc4a0..0636c4bb 100644 --- a/lib/room.h +++ b/lib/room.h @@ -999,7 +999,8 @@ Q_SIGNALS: void newFileTransfer(QString id, QUrl localFile); void fileTransferProgress(QString id, qint64 progress, qint64 total); - void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl, Omittable encryptedFile); + void fileTransferCompleted(QString id, QUrl localFile, + FileSourceInfo fileMetadata); void fileTransferFailed(QString id, QString errorMessage = {}); // fileTransferCancelled() is no more here; use fileTransferFailed() and // check the transfer status instead diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 1eed865f..6bcd71cd 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -516,7 +516,7 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, && e.hasFileContent() && e.content()->fileInfo()->originalName == fileName && testDownload(targetRoom->connection()->makeMediaUrl( - e.content()->fileInfo()->url))); + e.content()->fileInfo()->url()))); }, [this, thisTest](const RoomEvent&) { FAIL_TEST(); }); }); -- cgit v1.2.3 From c2d87291dbf8bd240e3e96138ec52aa5da22416b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 26 May 2022 12:50:30 +0200 Subject: Move encryptFile/decryptFile out of EncryptedFileMetadata These are not operations on EncryptedFileMetadata but rather on a combination of EncryptedFileMetadata and ciphertext. If C++ had multimethods these could be bound to such a combination. --- autotests/testfilecrypto.cpp | 4 ++-- lib/events/filesourceinfo.cpp | 11 ++++++----- lib/events/filesourceinfo.h | 9 +++++---- lib/jobs/downloadfilejob.cpp | 4 ++-- lib/mxcreply.cpp | 4 ++-- lib/room.cpp | 3 +-- 6 files changed, 18 insertions(+), 17 deletions(-) (limited to 'lib/events') diff --git a/autotests/testfilecrypto.cpp b/autotests/testfilecrypto.cpp index b86114a4..29521060 100644 --- a/autotests/testfilecrypto.cpp +++ b/autotests/testfilecrypto.cpp @@ -12,8 +12,8 @@ using namespace Quotient; void TestFileCrypto::encryptDecryptData() { QByteArray data = "ABCDEF"; - auto [file, cipherText] = EncryptedFileMetadata::encryptFile(data); - auto decrypted = file.decryptFile(cipherText); + auto [file, cipherText] = encryptFile(data); + auto decrypted = decryptFile(cipherText, file); // AES CTR produces ciphertext of the same size as the original QCOMPARE(cipherText.size(), data.size()); QCOMPARE(decrypted.size(), data.size()); diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp index a64c7da8..43e8e44c 100644 --- a/lib/events/filesourceinfo.cpp +++ b/lib/events/filesourceinfo.cpp @@ -16,14 +16,15 @@ using namespace Quotient; -QByteArray EncryptedFileMetadata::decryptFile(const QByteArray& ciphertext) const +QByteArray Quotient::decryptFile(const QByteArray& ciphertext, + const EncryptedFileMetadata& metadata) { #ifdef Quotient_E2EE_ENABLED - auto _key = key.k; + auto _key = metadata.key.k; const auto keyBytes = QByteArray::fromBase64( _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); const auto sha256 = - QByteArray::fromBase64(hashes["sha256"_ls].toLatin1()); + QByteArray::fromBase64(metadata.hashes["sha256"_ls].toLatin1()); if (sha256 != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { qCWarning(E2EE) << "Hash verification failed for file"; @@ -37,7 +38,7 @@ QByteArray EncryptedFileMetadata::decryptFile(const QByteArray& ciphertext) cons ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast(keyBytes.data()), reinterpret_cast( - QByteArray::fromBase64(iv.toLatin1()).data())); + QByteArray::fromBase64(metadata.iv.toLatin1()).data())); EVP_DecryptUpdate( ctx, reinterpret_cast(plaintext.data()), &length, reinterpret_cast(ciphertext.data()), @@ -56,7 +57,7 @@ QByteArray EncryptedFileMetadata::decryptFile(const QByteArray& ciphertext) cons #endif } -std::pair EncryptedFileMetadata::encryptFile( +std::pair Quotient::encryptFile( const QByteArray& plainText) { #ifdef Quotient_E2EE_ENABLED diff --git a/lib/events/filesourceinfo.h b/lib/events/filesourceinfo.h index 885601be..8f7e3cbe 100644 --- a/lib/events/filesourceinfo.h +++ b/lib/events/filesourceinfo.h @@ -45,12 +45,13 @@ public: QString iv; QHash hashes; QString v; - - static std::pair encryptFile( - const QByteArray& plainText); - QByteArray decryptFile(const QByteArray& ciphertext) const; }; +QUOTIENT_API std::pair encryptFile( + const QByteArray& plainText); +QUOTIENT_API QByteArray decryptFile(const QByteArray& ciphertext, + const EncryptedFileMetadata& metadata); + template <> struct QUOTIENT_API JsonObjectConverter { static void dumpTo(QJsonObject& jo, const EncryptedFileMetadata& pod); diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 85c235c7..032b24f2 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -128,7 +128,7 @@ BaseJob::Status DownloadFileJob::prepareResult() QByteArray encrypted = d->tempFile->readAll(); EncryptedFileMetadata file = *d->encryptedFile; - const auto decrypted = file.decryptFile(encrypted); + const auto decrypted = decryptFile(encrypted, file); d->targetFile->write(decrypted); d->tempFile->remove(); } else { @@ -153,7 +153,7 @@ BaseJob::Status DownloadFileJob::prepareResult() const auto encrypted = d->tempFile->readAll(); EncryptedFileMetadata file = *d->encryptedFile; - const auto decrypted = file.decryptFile(encrypted); + const auto decrypted = decryptFile(encrypted, file); d->tempFile->write(decrypted); } else { #endif diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index b7993ad5..4174cfd8 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -47,9 +47,9 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) if(!d->m_encryptedFile.has_value()) { d->m_device = d->m_reply; } else { - EncryptedFileMetadata file = *d->m_encryptedFile; auto buffer = new QBuffer(this); - buffer->setData(file.decryptFile(d->m_reply->readAll())); + buffer->setData( + decryptFile(d->m_reply->readAll(), *d->m_encryptedFile)); buffer->open(ReadOnly); d->m_device = buffer; } diff --git a/lib/room.cpp b/lib/room.cpp index 0cef1025..4cb01a39 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2524,8 +2524,7 @@ void Room::uploadFile(const QString& id, const QUrl& localFilename, QFile file(localFilename.toLocalFile()); file.open(QFile::ReadOnly); QByteArray data; - std::tie(fileMetadata, data) = - EncryptedFileMetadata::encryptFile(file.readAll()); + std::tie(fileMetadata, data) = encryptFile(file.readAll()); tempFile.write(data); tempFile.close(); fileName = QFileInfo(tempFile).absoluteFilePath(); -- cgit v1.2.3 From c2e9256b1c334bdadcc208429084cbc83496fb4b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 26 May 2022 12:57:23 +0200 Subject: Cleanup and address Sonar warnings --- lib/connection.h | 2 +- lib/events/eventcontent.cpp | 9 ++++---- lib/events/eventcontent.h | 6 +++--- lib/events/filesourceinfo.cpp | 49 ++++++++++++++++++++----------------------- lib/jobs/downloadfilejob.cpp | 10 ++++----- lib/room.cpp | 2 +- 6 files changed, 37 insertions(+), 41 deletions(-) (limited to 'lib/events') diff --git a/lib/connection.h b/lib/connection.h index 72383abb..43e285c1 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -605,7 +605,7 @@ public Q_SLOTS: #ifdef Quotient_E2EE_ENABLED DownloadFileJob* downloadFile(const QUrl& url, - const EncryptedFileMetadata& file, + const EncryptedFileMetadata& fileMetadata, const QString& localFilename = {}); #endif /** diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 36b647cb..8db3b7e3 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -103,14 +103,13 @@ QJsonObject Quotient::EventContent::toInfoJson(const ImageInfo& info) return infoJson; } -Thumbnail::Thumbnail( - const QJsonObject& infoJson, - const Omittable& encryptedFileMetadata) +Thumbnail::Thumbnail(const QJsonObject& infoJson, + const Omittable& efm) : ImageInfo(QUrl(infoJson["thumbnail_url"_ls].toString()), infoJson["thumbnail_info"_ls].toObject()) { - if (encryptedFileMetadata) - source = *encryptedFileMetadata; + if (efm) + source = *efm; } void Thumbnail::dumpTo(QJsonObject& infoJson) const diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 23281876..ea240122 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -146,7 +146,7 @@ namespace EventContent { public: using ImageInfo::ImageInfo; Thumbnail(const QJsonObject& infoJson, - const Omittable& encryptedFile = none); + const Omittable& efm = none); //! \brief Add thumbnail information to the passed `info` JSON object void dumpTo(QJsonObject& infoJson) const; @@ -181,8 +181,8 @@ namespace EventContent { json["filename"].toString()) , thumbnail(FileInfo::originalInfoJson) { - const auto efmJson = json.value("file"_ls).toObject(); - if (!efmJson.isEmpty()) + if (const auto efmJson = json.value("file"_ls).toObject(); + !efmJson.isEmpty()) InfoT::source = fromJson(efmJson); // Two small hacks on originalJson to expose mediaIds to QML originalJson.insert("mediaId", InfoT::mediaId()); diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp index 43e8e44c..11f93d80 100644 --- a/lib/events/filesourceinfo.cpp +++ b/lib/events/filesourceinfo.cpp @@ -20,36 +20,33 @@ QByteArray Quotient::decryptFile(const QByteArray& ciphertext, const EncryptedFileMetadata& metadata) { #ifdef Quotient_E2EE_ENABLED - auto _key = metadata.key.k; - const auto keyBytes = QByteArray::fromBase64( - _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); - const auto sha256 = - QByteArray::fromBase64(metadata.hashes["sha256"_ls].toLatin1()); - if (sha256 + if (QByteArray::fromBase64(metadata.hashes["sha256"_ls].toLatin1()) != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { qCWarning(E2EE) << "Hash verification failed for file"; return {}; } - { - int length; - auto* ctx = EVP_CIPHER_CTX_new(); - QByteArray plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH - 1, '\0'); - EVP_DecryptInit_ex( - ctx, EVP_aes_256_ctr(), nullptr, - reinterpret_cast(keyBytes.data()), - reinterpret_cast( - QByteArray::fromBase64(metadata.iv.toLatin1()).data())); - EVP_DecryptUpdate( - ctx, reinterpret_cast(plaintext.data()), &length, - reinterpret_cast(ciphertext.data()), - ciphertext.size()); - EVP_DecryptFinal_ex(ctx, - reinterpret_cast(plaintext.data()) - + length, - &length); - EVP_CIPHER_CTX_free(ctx); - return plaintext.left(ciphertext.size()); - } + + auto _key = metadata.key.k; + const auto keyBytes = QByteArray::fromBase64( + _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); + int length; + auto* ctx = EVP_CIPHER_CTX_new(); + QByteArray plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH - 1, '\0'); + EVP_DecryptInit_ex( + ctx, EVP_aes_256_ctr(), nullptr, + reinterpret_cast(keyBytes.data()), + reinterpret_cast( + QByteArray::fromBase64(metadata.iv.toLatin1()).data())); + EVP_DecryptUpdate(ctx, reinterpret_cast(plaintext.data()), + &length, + reinterpret_cast(ciphertext.data()), + ciphertext.size()); + EVP_DecryptFinal_ex(ctx, + reinterpret_cast(plaintext.data()) + + length, + &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext.left(ciphertext.size()); #else qWarning(MAIN) << "This build of libQuotient doesn't support E2EE, " "cannot decrypt the file"; diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 99c2bf9b..759d52c9 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -139,11 +139,11 @@ BaseJob::Status DownloadFileJob::prepareResult() #endif d->targetFile->close(); if (!d->targetFile->remove()) { - qCWarning(JOBS) << "Failed to remove the target file placeholder"; + qWarning(JOBS) << "Failed to remove the target file placeholder"; return { FileError, "Couldn't finalise the download" }; } if (!d->tempFile->rename(d->targetFile->fileName())) { - qCWarning(JOBS) << "Failed to rename" << d->tempFile->fileName() + qWarning(JOBS) << "Failed to rename" << d->tempFile->fileName() << "to" << d->targetFile->fileName(); return { FileError, "Couldn't finalise the download" }; } @@ -157,12 +157,12 @@ BaseJob::Status DownloadFileJob::prepareResult() decryptFile(*d->tempFile, *d->encryptedFileMetadata, tempTempFile); d->tempFile->close(); if (!d->tempFile->remove()) { - qCWarning(JOBS) + qWarning(JOBS) << "Failed to remove the decrypted file placeholder"; return { FileError, "Couldn't finalise the download" }; } if (!tempTempFile.rename(d->tempFile->fileName())) { - qCWarning(JOBS) << "Failed to rename" << tempTempFile.fileName() + qWarning(JOBS) << "Failed to rename" << tempTempFile.fileName() << "to" << d->tempFile->fileName(); return { FileError, "Couldn't finalise the download" }; } @@ -173,6 +173,6 @@ BaseJob::Status DownloadFileJob::prepareResult() } #endif } - qCDebug(JOBS) << "Saved a file as" << targetFileName(); + qDebug(JOBS) << "Saved a file as" << targetFileName(); return Success; } diff --git a/lib/room.cpp b/lib/room.cpp index 4cb01a39..26fe80e3 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2274,7 +2274,7 @@ QString Room::Private::doPostFile(RoomEventPtr&& msgEvent, const QUrl& localUrl) const auto& transferJob = fileTransfers.value(txnId).job; connect(q, &Room::fileTransferCompleted, transferJob, [this, txnId](const QString& tId, const QUrl&, - const FileSourceInfo fileMetadata) { + const FileSourceInfo& fileMetadata) { if (tId != txnId) return; -- 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. --- autotests/testolmaccount.cpp | 80 +++++++-------- lib/connection.cpp | 227 ++++++++++++++++++++++++++++++++++--------- lib/connection.h | 34 +++---- lib/csapi/keys.h | 4 +- lib/e2ee/qolmsession.cpp | 5 +- lib/e2ee/qolmsession.h | 2 +- lib/events/roomkeyevent.h | 4 +- lib/room.cpp | 113 +-------------------- 8 files changed, 249 insertions(+), 220 deletions(-) (limited to 'lib/events') diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index b509d12f..3fb8ac24 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -378,47 +378,47 @@ void TestOlmAccount::claimKeys() // Alice retrieves bob's keys & claims one signed one-time key. QHash deviceKeys; deviceKeys[bob->userId()] = QStringList(); - auto job = alice->callApi(deviceKeys); - connect(job, &BaseJob::result, this, [bob, alice, job, this] { - const auto& bobDevices = job->deviceKeys().value(bob->userId()); - QVERIFY(!bobDevices.empty()); - - // Retrieve the identity key for the current device. - const auto& bobEd25519 = - bobDevices.value(bob->deviceId()).keys["ed25519:" + bob->deviceId()]; - - const auto currentDevice = bobDevices[bob->deviceId()]; - - // Verify signature. - QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(), - bob->userId())); - - QHash> oneTimeKeys; - oneTimeKeys[bob->userId()] = QHash(); - oneTimeKeys[bob->userId()][bob->deviceId()] = SignedCurve25519Key; - - auto job = alice->callApi(oneTimeKeys); - connect(job, &BaseJob::result, this, [bob, bobEd25519, job] { - const auto userId = bob->userId(); - const auto deviceId = bob->deviceId(); - - // The device exists. - QCOMPARE(job->oneTimeKeys().size(), 1); - QCOMPARE(job->oneTimeKeys().value(userId).size(), 1); - - // The key is the one bob sent. - const auto& oneTimeKey = - job->oneTimeKeys().value(userId).value(deviceId); - QVERIFY(oneTimeKey.canConvert()); - - const auto varMap = oneTimeKey.toMap(); - QVERIFY(std::any_of(varMap.constKeyValueBegin(), - varMap.constKeyValueEnd(), [](const auto& kv) { - return kv.first.startsWith( - SignedCurve25519Key); - })); - }); + auto queryKeysJob = alice->callApi(deviceKeys); + QSignalSpy requestSpy2(queryKeysJob, &BaseJob::result); + QVERIFY(requestSpy2.wait(10000)); + + const auto& bobDevices = queryKeysJob->deviceKeys().value(bob->userId()); + QVERIFY(!bobDevices.empty()); + + const auto currentDevice = bobDevices[bob->deviceId()]; + + // Verify signature. + QVERIFY(verifyIdentitySignature(currentDevice, bob->deviceId(), + bob->userId())); + // Retrieve the identity key for the current device. + const auto& bobEd25519 = + bobDevices.value(bob->deviceId()).keys["ed25519:" + bob->deviceId()]; + + QHash> oneTimeKeys; + oneTimeKeys[bob->userId()] = QHash(); + oneTimeKeys[bob->userId()][bob->deviceId()] = SignedCurve25519Key; + + auto claimKeysJob = alice->callApi(oneTimeKeys); + connect(claimKeysJob, &BaseJob::result, this, [bob, bobEd25519, claimKeysJob] { + const auto userId = bob->userId(); + const auto deviceId = bob->deviceId(); + + // The device exists. + QCOMPARE(claimKeysJob->oneTimeKeys().size(), 1); + QCOMPARE(claimKeysJob->oneTimeKeys().value(userId).size(), 1); + + // The key is the one bob sent. + const auto& oneTimeKeys = + claimKeysJob->oneTimeKeys().value(userId).value(deviceId); + for (auto it = oneTimeKeys.begin(); it != oneTimeKeys.end(); ++it) { + if (it.key().startsWith(SignedCurve25519Key) + && it.value().isObject()) + return; + } + QFAIL("The claimed one time key is not in /claim response"); }); + QSignalSpy completionSpy(claimKeysJob, &BaseJob::result); + QVERIFY(completionSpy.wait(10000)); } void TestOlmAccount::claimMultipleKeys() 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( diff --git a/lib/connection.h b/lib/connection.h index a2824744..5b806350 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -319,22 +319,26 @@ public: #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; Database* database() const; + PicklingMode picklingMode() const; UnorderedMap loadRoomMegolmSessions( - const Room* room); + const Room* room) const; void saveMegolmSession(const Room* room, - const QOlmInboundGroupSession& session); + const QOlmInboundGroupSession& session) const; bool hasOlmSession(const QString& user, const QString& deviceId) const; QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession( const QString& roomId) const; - void saveCurrentOutboundMegolmSession(const QString& roomId, const QOlmOutboundGroupSession &session) const; - - //This assumes that an olm session with (user, device) exists - std::pair olmEncryptMessage( - const QString& userId, const QString& device, - const QByteArray& message) const; - void createOlmSession(const QString& theirIdentityKey, - const QString& theirOneTimeKey) const; + void saveCurrentOutboundMegolmSession( + const QString& roomId, const QOlmOutboundGroupSession& session) const; + + void sendSessionKeyToDevices(const QString& roomId, + const QByteArray& sessionId, + const QByteArray& sessionKey, + const QMultiHash& devices, + int index); + + QJsonObject decryptNotification(const QJsonObject ¬ification); + QStringList devicesForUser(const QString& userId) const; #endif // Quotient_E2EE_ENABLED Q_INVOKABLE Quotient::SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; @@ -695,16 +699,8 @@ public Q_SLOTS: #ifdef Quotient_E2EE_ENABLED void encryptionUpdate(Room *room); - PicklingMode picklingMode() const; - QJsonObject decryptNotification(const QJsonObject ¬ification); - - QStringList devicesForUser(const QString& userId) const; - QString curveKeyForUserDevice(const QString& userId, - const QString& device) const; - QString edKeyForUserDevice(const QString& userId, - const QString& device) const; - bool isKnownCurveKey(const QString& userId, const QString& curveKey) const; #endif + Q_SIGNALS: /// \brief Initial server resolution has failed /// diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h index ce1ca9ed..bcf1ad41 100644 --- a/lib/csapi/keys.h +++ b/lib/csapi/keys.h @@ -207,9 +207,9 @@ public: /// /// See the [key algorithms](/client-server-api/#key-algorithms) section for /// information on the Key Object format. - QHash> oneTimeKeys() const + QHash> oneTimeKeys() const { - return loadFromJson>>( + return loadFromJson>>( "one_time_keys"_ls); } }; diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 2b149aac..2a98d5d8 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -96,12 +96,13 @@ QOlmExpected QOlmSession::createOutboundSession( return std::make_unique(olmOutboundSession); } -QOlmExpected QOlmSession::pickle(const PicklingMode &mode) +QOlmExpected QOlmSession::pickle(const PicklingMode &mode) const { QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); QByteArray key = toKey(mode); const auto error = olm_pickle_session(m_session, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()); + pickledBuf.data(), + pickledBuf.length()); if (error == olm_error()) { return lastError(m_session); diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index faae16ef..021092c7 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -31,7 +31,7 @@ public: const QString& theirOneTimeKey); //! Serialises an `QOlmSession` to encrypted Base64. - QOlmExpected pickle(const PicklingMode &mode); + QOlmExpected pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. static QOlmExpected unpickle( diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 2bda3086..3093db41 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -12,7 +12,9 @@ public: DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent) explicit RoomKeyEvent(const QJsonObject& obj); - explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, const QString &sessionId, const QString& sessionKey, const QString& senderId); + explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, + const QString& sessionId, const QString& sessionKey, + const QString& senderId); QString algorithm() const { return contentPart("algorithm"_ls); } QString roomId() const { return contentPart(RoomIdKeyL); } diff --git a/lib/room.cpp b/lib/room.cpp index 07d03467..6ec06aa8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -457,28 +457,6 @@ public: addInboundGroupSession(currentOutboundMegolmSession->sessionId(), *sessionKey, q->localUser()->id(), "SELF"_ls); } - std::unique_ptr payloadForUserDevice(QString user, const QString& device, const QByteArray& sessionId, const QByteArray& sessionKey) - { - // Noisy but nice for debugging - //qCDebug(E2EE) << "Creating the payload for" << user->id() << device << sessionId << sessionKey.toHex(); - const auto event = makeEvent("m.megolm.v1.aes-sha2", q->id(), sessionId, sessionKey, q->localUser()->id()); - QJsonObject payloadJson = event->fullJson(); - payloadJson["recipient"] = user; - payloadJson["sender"] = connection->user()->id(); - QJsonObject recipientObject; - recipientObject["ed25519"] = connection->edKeyForUserDevice(user, device); - payloadJson["recipient_keys"] = recipientObject; - QJsonObject senderObject; - senderObject["ed25519"] = QString(connection->olmAccount()->identityKeys().ed25519); - payloadJson["keys"] = senderObject; - payloadJson["sender_device"] = connection->deviceId(); - auto cipherText = connection->olmEncryptMessage(user, device, QJsonDocument(payloadJson).toJson(QJsonDocument::Compact)); - QJsonObject encrypted; - encrypted[connection->curveKeyForUserDevice(user, device)] = QJsonObject{{"type", cipherText.first}, {"body", QString(cipherText.second)}}; - - return makeEvent(encrypted, connection->olmAccount()->identityKeys().curve25519); - } - QMultiHash getDevicesWithoutKey() const { QMultiHash devices; @@ -490,91 +468,7 @@ public: id, devices, currentOutboundMegolmSession->sessionId()); } - bool createOlmSession(const QString& user, const QString& device, - const QJsonObject& oneTimeKeyObject) const - { - static QOlmUtility verifier; - qDebug(E2EE) << "Creating a new session for" << user << device; - if (oneTimeKeyObject.isEmpty()) { - qWarning(E2EE) << "No one time key for" << user << device; - return false; - } - const auto oneTimeKeyForId = *oneTimeKeyObject.constBegin(); - const auto signature = - oneTimeKeyForId["signatures"][user]["ed25519:"_ls % device] - .toString() - .toLatin1(); - auto signedObject = oneTimeKeyForId.toObject(); - signedObject.remove("unsigned"_ls); - signedObject.remove("signatures"_ls); - const auto signedData = - QJsonDocument(signedObject).toJson(QJsonDocument::Compact); - if (!verifier.ed25519Verify( - connection->edKeyForUserDevice(user, device).toLatin1(), - signedData, signature)) { - qWarning(E2EE) << "Failed to verify one-time-key signature for" - << user << device << ". Skipping this device."; - return false; - } - const auto recipientCurveKey = - connection->curveKeyForUserDevice(user, device); - connection->createOlmSession(recipientCurveKey, - oneTimeKeyForId["key"].toString()); - return true; - } - - void sendRoomKeyToDevices(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 (!connection->hasOlmSession(userId, deviceId)) { - hash[userId].insert(deviceId, "signed_curve25519"_ls); - qDebug(E2EE) - << "Adding" << userId << deviceId << "to keys to claim"; - } - - if (hash.isEmpty()) - return; - - auto job = connection->callApi(hash); - connect(job, &BaseJob::success, q, - [job, this, sessionId, sessionKey, devices, index] { - Connection::UsersToDevicesToEvents usersToDevicesToEvents; - const auto data = job->jsonData(); - for (const auto& [user, device] : asKeyValueRange(devices)) { - if (!connection->hasOlmSession(user, device) - && !createOlmSession( - user, device, - data["one_time_keys"][user][device].toObject())) - continue; - - usersToDevicesToEvents[user][device] = - payloadForUserDevice(user, device, sessionId, - sessionKey); - } - if (!usersToDevicesToEvents.empty()) { - connection->sendToDevices("m.room.encrypted"_ls, - usersToDevicesToEvents); - QVector> receivedDevices; - receivedDevices.reserve(devices.size()); - for (const auto& [user, device] : asKeyValueRange(devices)) - receivedDevices.push_back( - { user, device, - connection->curveKeyForUserDevice(user, device) }); - - connection->database()->setDevicesReceivedKey(id, - receivedDevices, - sessionId, index); - } - }); - } - - void sendMegolmSession(const QMultiHash& devices) { + void sendMegolmSession(const QMultiHash& devices) const { // Save the session to this device const auto sessionId = currentOutboundMegolmSession->sessionId(); const auto sessionKey = currentOutboundMegolmSession->sessionKey(); @@ -584,8 +478,9 @@ public: } // Send the session to other people - sendRoomKeyToDevices(sessionId, *sessionKey, devices, - currentOutboundMegolmSession->sessionMessageIndex()); + connection->sendSessionKeyToDevices( + id, sessionId, *sessionKey, devices, + currentOutboundMegolmSession->sessionMessageIndex()); } #endif // Quotient_E2EE_ENABLED -- cgit v1.2.3 From 5a63f8a18645d612decdcc853335df0682c41d03 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 18 Jun 2022 21:29:27 +0200 Subject: Drop make_array(); use std::to_array() where needed make_array() has been introduced to cover for shortcomings on macOS and Windows. These shortcomings are no more there, so we can just use the standardrlibrary. --- lib/e2ee/e2ee.h | 5 +++-- lib/events/encryptionevent.cpp | 4 +--- lib/jobs/basejob.cpp | 20 ++++++++++---------- lib/quotient_common.h | 27 +++++++-------------------- 4 files changed, 21 insertions(+), 35 deletions(-) (limited to 'lib/events') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index f97eb27a..1efd0f16 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -37,8 +37,9 @@ constexpr auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls; inline bool isSupportedAlgorithm(const QString& algorithm) { - static constexpr auto SupportedAlgorithms = - make_array(OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey); + static constexpr std::array SupportedAlgorithms { + OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey + }; return std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(), algorithm) != SupportedAlgorithms.cend(); diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 6e994cd4..eb15f38e 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -9,9 +9,7 @@ #include namespace Quotient { -static const std::array encryptionStrings = { - { MegolmV1AesSha2AlgoKey } -}; +static constexpr std::array encryptionStrings { MegolmV1AesSha2AlgoKey }; template <> struct JsonConverter { diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index fe70911e..da645a2d 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -138,9 +138,8 @@ public: QTimer timer; QTimer retryTimer; - static constexpr std::array errorStrategy { - { { 90s, 5s }, { 90s, 10s }, { 120s, 30s } } - }; + static constexpr auto errorStrategy = std::to_array( + { { 90s, 5s }, { 90s, 10s }, { 120s, 30s } }); int maxRetries = int(errorStrategy.size()); int retriesTaken = 0; @@ -152,10 +151,8 @@ public: [[nodiscard]] QString dumpRequest() const { - // FIXME: use std::array {} when Apple stdlib gets deduction guides for it - static const auto verbs = - make_array(QStringLiteral("GET"), QStringLiteral("PUT"), - QStringLiteral("POST"), QStringLiteral("DELETE")); + static const std::array verbs { "GET"_ls, "PUT"_ls, "POST"_ls, + "DELETE"_ls }; const auto verbWord = verbs.at(size_t(verb)); return verbWord % ' ' % (reply ? reply->url().toString(QUrl::RemoveQuery) @@ -748,11 +745,14 @@ QString BaseJob::statusCaption() const } } -int BaseJob::error() const { return d->status.code; } +int BaseJob::error() const { + return d->status.code; } -QString BaseJob::errorString() const { return d->status.message; } +QString BaseJob::errorString() const { + return d->status.message; } -QUrl BaseJob::errorUrl() const { return d->errorUrl; } +QUrl BaseJob::errorUrl() const { + return d->errorUrl; } void BaseJob::setStatus(Status s) { diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 8bcd5ca6..136e9f79 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -41,7 +41,6 @@ Q_ENUM_NS_IMPL(Enum) \ Q_FLAG_NS(Flags) -// Apple Clang hasn't caught up with explicit(bool) yet #if __cpp_conditional_explicit >= 201806L #define QUO_IMPLICIT explicit(false) #else @@ -54,19 +53,6 @@ namespace Quotient { Q_NAMESPACE_EXPORT(QUOTIENT_API) -// std::array {} needs explicit template parameters on macOS because -// Apple stdlib doesn't have deduction guides for std::array. C++20 has -// to_array() but that can't be borrowed, this time because of MSVC: -// https://developercommunity.visualstudio.com/t/vc-ice-p1-initc-line-3652-from-stdto-array/1464038 -// Therefore a simpler (but also slightly more wobbly - it resolves the element -// type using std::common_type<>) make_array facility is implemented here. -template -constexpr auto make_array(Ts&&... items) -{ - return std::array, sizeof...(items)>( - { std::forward(items)... }); -} - // TODO: code like this should be generated from the CS API definition //! \brief Membership states @@ -87,9 +73,10 @@ enum class Membership : unsigned int { }; QUO_DECLARE_FLAGS_NS(MembershipMask, Membership) -constexpr auto MembershipStrings = make_array( - // The order MUST be the same as the order in the original enum - "join", "leave", "invite", "knock", "ban"); +constexpr std::array MembershipStrings { + // The order MUST be the same as the order in the Membership enum + "join", "leave", "invite", "knock", "ban" +}; //! \brief Local user join-state names //! @@ -105,10 +92,10 @@ enum class JoinState : std::underlying_type_t { }; QUO_DECLARE_FLAGS_NS(JoinStates, JoinState) -[[maybe_unused]] constexpr auto JoinStateStrings = make_array( +[[maybe_unused]] constexpr std::array JoinStateStrings { MembershipStrings[0], MembershipStrings[1], MembershipStrings[2], MembershipStrings[3] /* same as MembershipStrings, sans "ban" */ -); +}; //! \brief Network job running policy flags //! @@ -135,7 +122,7 @@ enum RoomType { }; Q_ENUM_NS(RoomType) -[[maybe_unused]] constexpr auto RoomTypeStrings = make_array("m.space"); +[[maybe_unused]] constexpr std::array RoomTypeStrings { "m.space" }; } // namespace Quotient Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) -- cgit v1.2.3 From 7ef84728ab3744192583eb587a4585c576f5a176 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 18 Jun 2022 21:39:42 +0200 Subject: Move C++-only macros to util.h This pertains to QUO_IMPLICIT and DECL_DEPRECATED_ENUMERATOR - both can be used with no connection to Qt meta-type system (which is what quotient_common.h is for). --- lib/e2ee/e2ee.h | 3 +-- lib/events/encryptionevent.cpp | 2 -- lib/events/encryptionevent.h | 1 - lib/quotient_common.h | 9 --------- lib/util.h | 9 +++++++++ 5 files changed, 10 insertions(+), 14 deletions(-) (limited to 'lib/events') diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 1efd0f16..9501b263 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -8,7 +8,6 @@ #include "converters.h" #include "expected.h" #include "qolmerrors.h" -#include "quotient_common.h" #include #include @@ -71,7 +70,7 @@ struct IdentityKeys }; //! Struct representing the one-time keys. -struct QUOTIENT_API UnsignedOneTimeKeys +struct UnsignedOneTimeKeys { QHash> keys; diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index eb15f38e..1654d6f3 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -6,8 +6,6 @@ #include "e2ee/e2ee.h" -#include - namespace Quotient { static constexpr std::array encryptionStrings { MegolmV1AesSha2AlgoKey }; diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 5b5420ec..c73e5598 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -5,7 +5,6 @@ #pragma once #include "stateevent.h" -#include "quotient_common.h" namespace Quotient { class QUOTIENT_API EncryptionEventContent { diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 136e9f79..e087e7d3 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -41,15 +41,6 @@ Q_ENUM_NS_IMPL(Enum) \ Q_FLAG_NS(Flags) -#if __cpp_conditional_explicit >= 201806L -#define QUO_IMPLICIT explicit(false) -#else -#define QUO_IMPLICIT -#endif - -#define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \ - Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended - namespace Quotient { Q_NAMESPACE_EXPORT(QUOTIENT_API) diff --git a/lib/util.h b/lib/util.h index 5dd69d74..d1623881 100644 --- a/lib/util.h +++ b/lib/util.h @@ -37,6 +37,15 @@ static_assert(false, "Use Q_DISABLE_MOVE instead; Quotient enables it across all QT_WARNING_POP #endif +#if __cpp_conditional_explicit >= 201806L +#define QUO_IMPLICIT explicit(false) +#else +#define QUO_IMPLICIT +#endif + +#define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \ + Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended + /// \brief Copy an object with slicing /// /// Unintended slicing is bad, which why there's a C++ Core Guideline that -- cgit v1.2.3 From 34ff85b715b377c4b2a8e30b1af8327aa7928e36 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 18 Jun 2022 10:35:48 +0200 Subject: Move out Overloads to util.h ...instead of tucking the template in filesourceinfo.cpp where it surely will be forgotten. --- lib/events/filesourceinfo.cpp | 9 +-------- lib/util.h | 13 +++++++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) (limited to 'lib/events') diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp index 11f93d80..e8b6794b 100644 --- a/lib/events/filesourceinfo.cpp +++ b/lib/events/filesourceinfo.cpp @@ -5,6 +5,7 @@ #include "filesourceinfo.h" #include "logging.h" +#include "util.h" #ifdef Quotient_E2EE_ENABLED # include "e2ee/qolmutils.h" @@ -140,14 +141,6 @@ void JsonObjectConverter::fillFrom(const QJsonObject& jo, JWK& pod) fromJson(jo.value("ext"_ls), pod.ext); } -template -struct Overloads : FunctorTs... { - using FunctorTs::operator()...; -}; - -template -Overloads(FunctorTs&&...) -> Overloads; - QUrl Quotient::getUrlFromSourceInfo(const FileSourceInfo& fsi) { return std::visit(Overloads { [](const QUrl& url) { return url; }, diff --git a/lib/util.h b/lib/util.h index d1623881..8a30f457 100644 --- a/lib/util.h +++ b/lib/util.h @@ -170,6 +170,19 @@ constexpr ImplPtr ZeroImpl() return { nullptr, [](ImplType*) { /* nullptr doesn't need deletion */ } }; } +//! \brief Multiplex several functors in one +//! +//! This is a well-known trick to wrap several lambdas into a single functor +//! class that can be passed to std::visit. +//! \sa https://en.cppreference.com/w/cpp/utility/variant/visit +template +struct Overloads : FunctorTs... { + using FunctorTs::operator()...; +}; + +template +Overloads(FunctorTs&&...) -> Overloads; + /** Convert what looks like a URL or a Matrix ID to an HTML hyperlink */ QUOTIENT_API void linkifyUrls(QString& htmlEscapedText); -- cgit v1.2.3 From 1fa81ac5dc9259b7368e0487e0424c76bc87053e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 21 Jun 2022 14:42:59 +0200 Subject: Fix a few clang-tidy/GCC warnings --- lib/database.cpp | 10 +++++----- lib/events/accountdataevents.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'lib/events') diff --git a/lib/database.cpp b/lib/database.cpp index 193ff54e..ed7bd794 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -28,10 +28,10 @@ Database::Database(const QString& matrixId, const QString& deviceId, QObject* pa database().open(); switch(version()) { - case 0: migrateTo1(); - case 1: migrateTo2(); - case 2: migrateTo3(); - case 3: migrateTo4(); + case 0: migrateTo1(); [[fallthrough]]; + case 1: migrateTo2(); [[fallthrough]]; + case 2: migrateTo3(); [[fallthrough]]; + case 3: migrateTo4(); } } @@ -39,7 +39,7 @@ int Database::version() { auto query = execute(QStringLiteral("PRAGMA user_version;")); if (query.next()) { - bool ok; + bool ok = false; int value = query.value(0).toInt(&ok); qCDebug(DATABASE) << "Database version" << value; if (ok) diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index ec2f64e3..24c3353c 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -32,7 +32,7 @@ struct JsonObjectConverter { if (orderJv.isDouble()) rec.order = fromJson(orderJv); if (orderJv.isString()) { - bool ok; + bool ok = false; rec.order = orderJv.toString().toFloat(&ok); if (!ok) rec.order = none; -- cgit v1.2.3 From 6a2cec476b72d44ecf1cd05e47724d325a46f246 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Jun 2022 10:17:26 +0200 Subject: Address a few more Sonar warnings --- lib/events/eventcontent.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index ea240122..1e2f3615 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -36,6 +36,10 @@ namespace EventContent { public: QJsonObject originalJson; + // You can't assign those classes + Base& operator=(const Base&) = delete; + Base& operator=(Base&&) = delete; + protected: Base(const Base&) = default; Base(Base&&) = default; @@ -145,8 +149,8 @@ namespace EventContent { class QUOTIENT_API Thumbnail : public ImageInfo { public: using ImageInfo::ImageInfo; - Thumbnail(const QJsonObject& infoJson, - const Omittable& efm = none); + explicit Thumbnail(const QJsonObject& infoJson, + const Omittable& efm = none); //! \brief Add thumbnail information to the passed `info` JSON object void dumpTo(QJsonObject& infoJson) const; -- cgit v1.2.3 From 6a7e4f883ec22ef26c1d10ba1544b0afd1a0f4d2 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Jun 2022 20:43:04 +0200 Subject: Streamline RoomPowerLevelsEvent backoffice code Also: leave a link at the place in the spec with power level defaults to make it clear they are not invented out of thin air. --- lib/events/roompowerlevelsevent.cpp | 38 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roompowerlevelsevent.cpp b/lib/events/roompowerlevelsevent.cpp index 84a31d55..d9bd010b 100644 --- a/lib/events/roompowerlevelsevent.cpp +++ b/lib/events/roompowerlevelsevent.cpp @@ -5,6 +5,8 @@ using namespace Quotient; +// The default values used below are defined in +// https://spec.matrix.org/v1.3/client-server-api/#mroompower_levels PowerLevelsEventContent::PowerLevelsEventContent(const QJsonObject& json) : invite(json["invite"_ls].toInt(50)), kick(json["kick"_ls].toInt(50)), @@ -16,8 +18,7 @@ PowerLevelsEventContent::PowerLevelsEventContent(const QJsonObject& json) : users(fromJson>(json["users"_ls])), usersDefault(json["users_default"_ls].toInt(0)), notifications(Notifications{json["notifications"_ls].toObject()["room"_ls].toInt(50)}) -{ -} +{} QJsonObject PowerLevelsEventContent::toJson() const { @@ -36,32 +37,17 @@ QJsonObject PowerLevelsEventContent::toJson() const return o; } -int RoomPowerLevelsEvent::powerLevelForEvent(const QString &eventId) const { - auto e = events(); - - if (e.contains(eventId)) { - return e[eventId]; - } - - return eventsDefault(); +int RoomPowerLevelsEvent::powerLevelForEvent(const QString& eventId) const +{ + return events().value(eventId, eventsDefault()); } -int RoomPowerLevelsEvent::powerLevelForState(const QString &eventId) const { - auto e = events(); - - if (e.contains(eventId)) { - return e[eventId]; - } - - return stateDefault(); +int RoomPowerLevelsEvent::powerLevelForState(const QString& eventId) const +{ + return events().value(eventId, stateDefault()); } -int RoomPowerLevelsEvent::powerLevelForUser(const QString &userId) const { - auto u = users(); - - if (u.contains(userId)) { - return u[userId]; - } - - return usersDefault(); +int RoomPowerLevelsEvent::powerLevelForUser(const QString& userId) const +{ + return users().value(userId, usersDefault()); } -- cgit v1.2.3 From 19746de7426aeb67eb90e8c48448356691e270b0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 9 Jun 2022 10:19:14 +0200 Subject: Use QUO_CONTENT_GETTER In keyverificationevent.*, this massively shortens repetitive getter definitions; the remaining few non-trivial ones are moved to keyverificationevent.h, dropping the respective .cpp file and therefore the dedicated translation unit. In roomkeyevent.h, it's just shorter. --- CMakeLists.txt | 2 +- lib/events/keyverificationevent.cpp | 164 ------------------------------------ lib/events/keyverificationevent.h | 80 +++++++++++------- lib/events/roomkeyevent.h | 6 +- 4 files changed, 55 insertions(+), 197 deletions(-) delete mode 100644 lib/events/keyverificationevent.cpp (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index a7a80a73..94055b7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,7 +166,7 @@ list(APPEND lib_SRCS lib/events/encryptedevent.h lib/events/encryptedevent.cpp lib/events/roomkeyevent.h lib/events/roomkeyevent.cpp lib/events/stickerevent.h lib/events/stickerevent.cpp - lib/events/keyverificationevent.h lib/events/keyverificationevent.cpp + lib/events/keyverificationevent.h lib/events/filesourceinfo.h lib/events/filesourceinfo.cpp lib/jobs/requestdata.h lib/jobs/requestdata.cpp lib/jobs/basejob.h lib/jobs/basejob.cpp diff --git a/lib/events/keyverificationevent.cpp b/lib/events/keyverificationevent.cpp deleted file mode 100644 index 4803955d..00000000 --- a/lib/events/keyverificationevent.cpp +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "keyverificationevent.h" - -using namespace Quotient; - -KeyVerificationRequestEvent::KeyVerificationRequestEvent(const QJsonObject &obj) - : Event(typeId(), obj) -{} - -QString KeyVerificationRequestEvent::fromDevice() const -{ - return contentPart("from_device"_ls); -} - -QString KeyVerificationRequestEvent::transactionId() const -{ - return contentPart("transaction_id"_ls); -} - -QStringList KeyVerificationRequestEvent::methods() const -{ - return contentPart("methods"_ls); -} - -uint64_t KeyVerificationRequestEvent::timestamp() const -{ - return contentPart("timestamp"_ls); -} - -KeyVerificationStartEvent::KeyVerificationStartEvent(const QJsonObject &obj) - : Event(typeId(), obj) -{} - -QString KeyVerificationStartEvent::fromDevice() const -{ - return contentPart("from_device"_ls); -} - -QString KeyVerificationStartEvent::transactionId() const -{ - return contentPart("transaction_id"_ls); -} - -QString KeyVerificationStartEvent::method() const -{ - return contentPart("method"_ls); -} - -Omittable KeyVerificationStartEvent::nextMethod() const -{ - return contentPart>("method_ls"); -} - -QStringList KeyVerificationStartEvent::keyAgreementProtocols() const -{ - Q_ASSERT(method() == QStringLiteral("m.sas.v1")); - return contentPart("key_agreement_protocols"_ls); -} - -QStringList KeyVerificationStartEvent::hashes() const -{ - Q_ASSERT(method() == QStringLiteral("m.sas.v1")); - return contentPart("hashes"_ls); - -} - -QStringList KeyVerificationStartEvent::messageAuthenticationCodes() const -{ - Q_ASSERT(method() == QStringLiteral("m.sas.v1")); - return contentPart("message_authentication_codes"_ls); -} - -QString KeyVerificationStartEvent::shortAuthenticationString() const -{ - return contentPart("short_authentification_string"_ls); -} - -KeyVerificationAcceptEvent::KeyVerificationAcceptEvent(const QJsonObject &obj) - : Event(typeId(), obj) -{} - -QString KeyVerificationAcceptEvent::transactionId() const -{ - return contentPart("transaction_id"_ls); -} - -QString KeyVerificationAcceptEvent::method() const -{ - return contentPart("method"_ls); -} - -QString KeyVerificationAcceptEvent::keyAgreementProtocol() const -{ - return contentPart("key_agreement_protocol"_ls); -} - -QString KeyVerificationAcceptEvent::hashData() const -{ - return contentPart("hash"_ls); -} - -QStringList KeyVerificationAcceptEvent::shortAuthenticationString() const -{ - return contentPart("short_authentification_string"_ls); -} - -QString KeyVerificationAcceptEvent::commitement() const -{ - return contentPart("commitment"_ls); -} - -KeyVerificationCancelEvent::KeyVerificationCancelEvent(const QJsonObject &obj) - : Event(typeId(), obj) -{} - -QString KeyVerificationCancelEvent::transactionId() const -{ - return contentPart("transaction_id"_ls); -} - -QString KeyVerificationCancelEvent::reason() const -{ - return contentPart("reason"_ls); -} - -QString KeyVerificationCancelEvent::code() const -{ - return contentPart("code"_ls); -} - -KeyVerificationKeyEvent::KeyVerificationKeyEvent(const QJsonObject &obj) - : Event(typeId(), obj) -{} - -QString KeyVerificationKeyEvent::transactionId() const -{ - return contentPart("transaction_id"_ls); -} - -QString KeyVerificationKeyEvent::key() const -{ - return contentPart("key"_ls); -} - -KeyVerificationMacEvent::KeyVerificationMacEvent(const QJsonObject &obj) - : Event(typeId(), obj) -{} - -QString KeyVerificationMacEvent::transactionId() const -{ - return contentPart("transaction_id"_ls); -} - -QString KeyVerificationMacEvent::keys() const -{ - return contentPart("keys"_ls); -} - -QHash KeyVerificationMacEvent::mac() const -{ - return contentPart>("mac"_ls); -} diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index 497e56a2..78457e0c 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -14,20 +14,20 @@ public: explicit KeyVerificationRequestEvent(const QJsonObject& obj); /// The device ID which is initiating the request. - QString fromDevice() const; + QUO_CONTENT_GETTER(QString, fromDevice) /// An opaque identifier for the verification request. Must /// be unique with respect to the devices involved. - QString transactionId() const; + QUO_CONTENT_GETTER(QString, transactionId) /// The verification methods supported by the sender. - QStringList methods() const; + QUO_CONTENT_GETTER(QStringList, methods) /// The POSIX timestamp in milliseconds for when the request was /// made. If the request is in the future by more than 5 minutes or /// more than 10 minutes in the past, the message should be ignored /// by the receiver. - uint64_t timestamp() const; + QUO_CONTENT_GETTER(uint64_t, timestamp) }; REGISTER_EVENT_TYPE(KeyVerificationRequestEvent) @@ -39,36 +39,52 @@ public: explicit KeyVerificationStartEvent(const QJsonObject &obj); /// The device ID which is initiating the process. - QString fromDevice() const; + QUO_CONTENT_GETTER(QString, fromDevice) /// An opaque identifier for the verification request. Must /// be unique with respect to the devices involved. - QString transactionId() const; + QUO_CONTENT_GETTER(QString, transactionId) /// The verification method to use. - QString method() const; + QUO_CONTENT_GETTER(QString, method) /// Optional method to use to verify the other user's key with. - Omittable nextMethod() const; + QUO_CONTENT_GETTER(Omittable, nextMethod) // SAS.V1 methods /// The key agreement protocols the sending device understands. /// \note Only exist if method is m.sas.v1 - QStringList keyAgreementProtocols() const; + QStringList keyAgreementProtocols() const + { + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + return contentPart("key_agreement_protocols"_ls); + } /// The hash methods the sending device understands. /// \note Only exist if method is m.sas.v1 - QStringList hashes() const; + QStringList hashes() const + { + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + return contentPart("hashes"_ls); + } /// The message authentication codes that the sending device understands. /// \note Only exist if method is m.sas.v1 - QStringList messageAuthenticationCodes() const; + QStringList messageAuthenticationCodes() const + { + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + return contentPart("message_authentication_codes"_ls); + } /// The SAS methods the sending device (and the sending device's /// user) understands. /// \note Only exist if method is m.sas.v1 - QString shortAuthenticationString() const; + QString shortAuthenticationString() const + { + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + return contentPart("short_authentification_string"_ls); + } }; REGISTER_EVENT_TYPE(KeyVerificationStartEvent) @@ -81,30 +97,33 @@ public: explicit KeyVerificationAcceptEvent(const QJsonObject& obj); /// An opaque identifier for the verification process. - QString transactionId() const; + QUO_CONTENT_GETTER(QString, transactionId) /// The verification method to use. Must be 'm.sas.v1'. - QString method() const; + QUO_CONTENT_GETTER(QString, method) /// The key agreement protocol the device is choosing to use, out of /// the options in the m.key.verification.start message. - QString keyAgreementProtocol() const; + QUO_CONTENT_GETTER(QString, keyAgreementProtocol) /// The hash method the device is choosing to use, out of the /// options in the m.key.verification.start message. - QString hashData() const; + QString hashData() const + { + return contentPart("hash"_ls); + } /// The message authentication code the device is choosing to use, out /// of the options in the m.key.verification.start message. - QString messageAuthenticationCode() const; + QUO_CONTENT_GETTER(QString, messageAuthenticationCode) /// The SAS methods both devices involved in the verification process understand. - QStringList shortAuthenticationString() const; + QUO_CONTENT_GETTER(QStringList, shortAuthenticationString) /// The hash (encoded as unpadded base64) of the concatenation of the /// device's ephemeral public key (encoded as unpadded base64) and the /// canonical JSON representation of the m.key.verification.start message. - QString commitement() const; + QUO_CONTENT_GETTER(QString, commitment) }; REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent) @@ -115,14 +134,14 @@ public: explicit KeyVerificationCancelEvent(const QJsonObject &obj); /// An opaque identifier for the verification process. - QString transactionId() const; + QUO_CONTENT_GETTER(QString, transactionId) /// A human readable description of the code. The client should only /// rely on this string if it does not understand the code. - QString reason() const; + QUO_CONTENT_GETTER(QString, reason) /// The error code for why the process/request was cancelled by the user. - QString code() const; + QUO_CONTENT_GETTER(QString, code) }; REGISTER_EVENT_TYPE(KeyVerificationCancelEvent) @@ -134,11 +153,11 @@ public: explicit KeyVerificationKeyEvent(const QJsonObject &obj); - /// An opaque identifier for the verification process. - QString transactionId() const; + /// An opaque identifier for the verification process. + QUO_CONTENT_GETTER(QString, transactionId) /// The device's ephemeral public key, encoded as unpadded base64. - QString key() const; + QUO_CONTENT_GETTER(QString, key) }; REGISTER_EVENT_TYPE(KeyVerificationKeyEvent) @@ -149,13 +168,16 @@ public: explicit KeyVerificationMacEvent(const QJsonObject &obj); - /// An opaque identifier for the verification process. - QString transactionId() const; + /// An opaque identifier for the verification process. + QUO_CONTENT_GETTER(QString, transactionId) /// The device's ephemeral public key, encoded as unpadded base64. - QString keys() const; + QUO_CONTENT_GETTER(QString, keys) - QHash mac() const; + QHash mac() const + { + return contentPart>("mac"_ls); + } }; REGISTER_EVENT_TYPE(KeyVerificationMacEvent) } // namespace Quotient diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 3093db41..9eb2854b 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -16,9 +16,9 @@ public: const QString& sessionId, const QString& sessionKey, const QString& senderId); - QString algorithm() const { return contentPart("algorithm"_ls); } - QString roomId() const { return contentPart(RoomIdKeyL); } - QString sessionId() const { return contentPart("session_id"_ls); } + QUO_CONTENT_GETTER(QString, algorithm) + QUO_CONTENT_GETTER(QString, roomId) + QUO_CONTENT_GETTER(QString, sessionId) QByteArray sessionKey() const { return contentPart("session_key"_ls).toLatin1(); -- cgit v1.2.3 From 143ac38aabab90b40fbe73b489b91fb159e66b6b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 23 Jun 2022 08:34:53 +0200 Subject: Streamline Room::P::shouldRotateMegolmSession() Now there's only 1 instead of 5 lookups of the same EncryptionEvent, and the code is shorter. --- lib/events/encryptionevent.h | 2 ++ lib/room.cpp | 30 ++++++++++-------------------- 2 files changed, 12 insertions(+), 20 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index c73e5598..945b17e7 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -52,6 +52,8 @@ public: QString algorithm() const { return content().algorithm; } int rotationPeriodMs() const { return content().rotationPeriodMs; } int rotationPeriodMsgs() const { return content().rotationPeriodMsgs; } + + bool useEncryption() const { return !algorithm().isEmpty(); } }; REGISTER_EVENT_TYPE(EncryptionEvent) } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index 2d4cfeb4..35a89cc6 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -411,10 +411,17 @@ public: bool shouldRotateMegolmSession() const { - if (!q->usesEncryption()) { + const auto* encryptionConfig = currentState.get(); + if (!encryptionConfig || !encryptionConfig->useEncryption()) return false; - } - return currentOutboundMegolmSession->messageCount() >= rotationMessageCount() || currentOutboundMegolmSession->creationTime().addMSecs(rotationInterval()) < QDateTime::currentDateTime(); + + const auto rotationInterval = encryptionConfig->rotationPeriodMs(); + const auto rotationMessageCount = encryptionConfig->rotationPeriodMsgs(); + return currentOutboundMegolmSession->messageCount() + >= rotationMessageCount + || currentOutboundMegolmSession->creationTime().addMSecs( + rotationInterval) + < QDateTime::currentDateTime(); } bool hasValidMegolmSession() const @@ -425,23 +432,6 @@ public: return currentOutboundMegolmSession != nullptr; } - /// Time in milliseconds after which the outgoing megolmsession should be replaced - unsigned int rotationInterval() const - { - if (!q->usesEncryption()) { - return 0; - } - return q->getCurrentState()->rotationPeriodMs(); - } - - // Number of messages sent by this user after which the outgoing megolm session should be replaced - int rotationMessageCount() const - { - if (!q->usesEncryption()) { - return 0; - } - return q->getCurrentState()->rotationPeriodMsgs(); - } void createMegolmSession() { qCDebug(E2EE) << "Creating new outbound megolm session for room " << q->objectName(); -- cgit v1.2.3 From 8580043a64941e1c1f132737e4b50280ac17812a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Jun 2022 23:11:20 +0200 Subject: Make EventContent::Base() move constructor noexcept --- lib/events/eventcontent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 1e2f3615..7611d077 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -42,7 +42,7 @@ namespace EventContent { protected: Base(const Base&) = default; - Base(Base&&) = default; + Base(Base&&) noexcept = default; virtual void fillJson(QJsonObject&) const = 0; }; -- cgit v1.2.3 From bc8e01df4286f5f8ff9103fbebad801f355db689 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 3 Jul 2022 22:14:13 +0200 Subject: Shorten switchOnType, function_traits and connect* ...thanks to C++20 awesomeness. A notable change is that wrap_in_function() (and respectively function_traits<>::function_type) and fn_return_t alias are gone. The former are no more needed because connectUntil/connectSingleShot no more use std::function. The latter has been relatively underused and with the optimisation of switchOnType hereby, could be completely replaced with std::invoke_result_t. Rewriting connect* functions using constexpr and auto parameters made the implementation 30% more compact and much easier to understand (though still with a couple of - now thoroughly commented - tricky places). Dropping std::function<> from it may also bring some (quite modest, likely) performance benefits. --- lib/events/event.h | 64 +++++++------------ lib/function_traits.cpp | 3 + lib/function_traits.h | 36 +++++------ lib/qt_connection_util.h | 160 +++++++++++++++++------------------------------ 4 files changed, 101 insertions(+), 162 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index ec21c6aa..a966e613 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -341,56 +341,34 @@ inline auto eventCast(const BasePtrT& eptr) : nullptr; } -// A trivial generic catch-all "switch" -template -inline auto switchOnType(const BaseEventT& event, FnT&& fn) - -> decltype(fn(event)) -{ - return fn(event); -} - namespace _impl { - // Using bool instead of auto below because auto apparently upsets MSVC - template - constexpr bool needs_downcast = - std::is_base_of_v>> - && !std::is_same_v>>; -} - -// A trivial type-specific "switch" for a void function -template -inline auto switchOnType(const BaseT& event, FnT&& fn) - -> std::enable_if_t<_impl::needs_downcast - && std::is_void_v>> -{ - using event_type = fn_arg_t; - if (is>(event)) - fn(static_cast(event)); + template + concept Invocable_With_Downcast = + std::derived_from>, BaseT>; } -// A trivial type-specific "switch" for non-void functions with an optional -// default value; non-voidness is guarded by defaultValue type -template -inline auto switchOnType(const BaseT& event, FnT&& fn, - fn_return_t&& defaultValue = {}) - -> std::enable_if_t<_impl::needs_downcast, fn_return_t> +template +inline auto switchOnType(const BaseT& event, TailT&& tail) { - using event_type = fn_arg_t; - if (is>(event)) - return fn(static_cast(event)); - return std::move(defaultValue); + if constexpr (std::is_invocable_v) { + return tail(event); + } else if constexpr (_impl::Invocable_With_Downcast) { + using event_type = fn_arg_t; + if (is>(event)) + return tail(static_cast(event)); + return std::invoke_result_t(); // Default-constructed + } else { // Treat it as a value to return + return std::forward(tail); + } } -// A switch for a chain of 2 or more functions -template -inline std::common_type_t, fn_return_t> -switchOnType(const BaseT& event, FnT1&& fn1, FnT2&& fn2, FnTs&&... fns) +template +inline auto switchOnType(const BaseT& event, FnT1&& fn1, FnTs&&... fns) { using event_type1 = fn_arg_t; if (is>(event)) - return fn1(static_cast(event)); - return switchOnType(event, std::forward(fn2), - std::forward(fns)...); + return fn1(static_cast(event)); + return switchOnType(event, std::forward(fns)...); } template @@ -405,8 +383,8 @@ inline auto visit(const BaseT& event, FnTs&&... fns) // TODO: replace with ranges::for_each once all standard libraries have it template inline auto visitEach(RangeT&& events, FnTs&&... fns) - -> std::enable_if_t(fns)...))>> + requires std::is_void_v< + decltype(switchOnType(**begin(events), std::forward(fns)...))> { for (auto&& evtPtr: events) switchOnType(*evtPtr, std::forward(fns)...); diff --git a/lib/function_traits.cpp b/lib/function_traits.cpp index 6542101a..e3d27122 100644 --- a/lib/function_traits.cpp +++ b/lib/function_traits.cpp @@ -7,6 +7,9 @@ using namespace Quotient; +template +using fn_return_t = typename function_traits::return_type; + int f_(); static_assert(std::is_same_v, int>, "Test fn_return_t<>"); diff --git a/lib/function_traits.h b/lib/function_traits.h index 83b8e425..143ed162 100644 --- a/lib/function_traits.h +++ b/lib/function_traits.h @@ -8,7 +8,7 @@ namespace Quotient { namespace _impl { - template + template struct fn_traits {}; } @@ -21,73 +21,73 @@ namespace _impl { */ template struct function_traits - : public _impl::fn_traits> {}; + : public _impl::fn_traits> {}; // Specialisation for a function template struct function_traits { using return_type = ReturnT; using arg_types = std::tuple; - // See also the comment for wrap_in_function() in qt_connection_util.h - using function_type = std::function; }; namespace _impl { - template + template struct fn_object_traits; // Specialisation for a lambda function template - struct fn_object_traits + struct fn_object_traits : function_traits {}; // Specialisation for a const lambda function template - struct fn_object_traits + struct fn_object_traits : function_traits {}; // Specialisation for function objects with (non-overloaded) operator() // (this includes non-generic lambdas) template - struct fn_traits - : public fn_object_traits {}; + requires requires { &T::operator(); } + struct fn_traits + : public fn_object_traits {}; // Specialisation for a member function in a non-functor class template - struct fn_traits + struct fn_traits : function_traits {}; // Specialisation for a const member function template - struct fn_traits + struct fn_traits : function_traits {}; // Specialisation for a constref member function template - struct fn_traits + struct fn_traits : function_traits {}; // Specialisation for a prvalue member function template - struct fn_traits + struct fn_traits : function_traits {}; // Specialisation for a pointer-to-member template - struct fn_traits + struct fn_traits : function_traits {}; // Specialisation for a const pointer-to-member template - struct fn_traits + struct fn_traits : function_traits {}; } // namespace _impl -template -using fn_return_t = typename function_traits::return_type; - template using fn_arg_t = std::tuple_element_t::arg_types>; +template +constexpr auto fn_arg_count_v = + std::tuple_size_v::arg_types>; + } // namespace Quotient diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index 86593cc8..7477273f 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -9,101 +9,68 @@ namespace Quotient { namespace _impl { - template - using decorated_slot_tt = - std::function; + enum ConnectionType { SingleShot, Until }; - template - inline QMetaObject::Connection - connectDecorated(SenderT* sender, SignalT signal, ContextT* context, - decorated_slot_tt decoratedSlot, - Qt::ConnectionType connType) + template + inline auto connect(auto* sender, auto signal, auto* context, auto slotLike, + Qt::ConnectionType connType) { - auto pc = std::make_unique(); - auto& c = *pc; // Resolve a reference before pc is moved to lambda - - // Perfect forwarding doesn't work through signal-slot connections - - // arguments are always copied (at best - COWed) to the context of - // the slot. Therefore the slot decorator receives const ArgTs&... - // rather than ArgTs&&... - // TODO (C++20): std::bind_front() instead of lambda. - c = QObject::connect(sender, signal, context, - [pc = std::move(pc), - decoratedSlot = std::move(decoratedSlot)](const ArgTs&... args) { - Q_ASSERT(*pc); // If it's been triggered, it should exist - decoratedSlot(*pc, args...); + std::unique_ptr pConn = + std::make_unique(); + auto& c = *pConn; // Save the reference before pConn is moved from + c = QObject::connect( + sender, signal, context, + [slotLike, pConn = std::move(pConn)](const auto&... args) + // The requires-expression below is necessary to prevent Qt + // from eagerly trying to fill the lambda with more arguments + // than slotLike() (i.e., the original slot) can handle + requires requires { slotLike(args...); } { + static_assert(CType == Until || CType == SingleShot, + "Unsupported disconnection type"); + if constexpr (CType == SingleShot) { + // Disconnect early to avoid re-triggers during slotLike() + QObject::disconnect(*pConn); + // Qt kindly keeps slot objects until they do their job, + // even if they disconnect themselves in the process (see + // how doActivate() in qobject.cpp handles c->slotObj). + slotLike(args...); + } else if constexpr (CType == Until) { + if (slotLike(args...)) + QObject::disconnect(*pConn); + } }, connType); return c; } - template - inline QMetaObject::Connection - connectUntil(SenderT* sender, SignalT signal, ContextT* context, - std::function functor, - Qt::ConnectionType connType) - { - return connectDecorated(sender, signal, context, - decorated_slot_tt( - [functor = std::move(functor)](QMetaObject::Connection& c, - const ArgTs&... args) { - if (functor(args...)) - QObject::disconnect(c); - }), - connType); - } - template - inline QMetaObject::Connection - connectSingleShot(SenderT* sender, SignalT signal, ContextT* context, - std::function slot, - Qt::ConnectionType connType) - { - return connectDecorated(sender, signal, context, - decorated_slot_tt( - [slot = std::move(slot)](QMetaObject::Connection& c, - const ArgTs&... args) { - QObject::disconnect(c); - slot(args...); - }), - connType); - } - // TODO: get rid of it as soon as Apple Clang gets proper deduction guides - // for std::function<> - // ...or consider using QtPrivate magic used by QObject::connect() - // ...for inspiration, also check a possible std::not_fn implementation - // at https://en.cppreference.com/w/cpp/utility/functional/not_fn - template - inline auto wrap_in_function(FnT&& f) - { - return typename function_traits::function_type(std::forward(f)); - } + template + concept PmfSlot = + (fn_arg_count_v > 0 + && std::derived_from>>); } // namespace _impl -/*! \brief Create a connection that self-disconnects when its "slot" returns true - * - * A slot accepted by connectUntil() is different from classic Qt slots - * in that its return value must be bool, not void. The slot's return value - * controls whether the connection should be kept; if the slot returns false, - * the connection remains; upon returning true, the slot is disconnected from - * the signal. Because of a different slot signature connectUntil() doesn't - * accept member functions as QObject::connect or Quotient::connectSingleShot - * do; you should pass a lambda or a pre-bound member function to it. - */ -template -inline auto connectUntil(SenderT* sender, SignalT signal, ContextT* context, - const FunctorT& slot, +//! \brief Create a connection that self-disconnects when its slot returns true +//! +//! A slot accepted by connectUntil() is different from classic Qt slots +//! in that its return value must be bool, not void. Because of that different +//! signature connectUntil() doesn't accept member functions in the way +//! QObject::connect or Quotient::connectSingleShot do; you should pass a lambda +//! or a pre-bound member function to it. +//! \return whether the connection should be dropped; false means that the +//! connection remains; upon returning true, the slot is disconnected +//! from the signal. +inline auto connectUntil(auto* sender, auto signal, auto* context, + auto smartSlot, Qt::ConnectionType connType = Qt::AutoConnection) { - return _impl::connectUntil(sender, signal, context, _impl::wrap_in_function(slot), - connType); + return _impl::connect<_impl::Until>(sender, signal, context, smartSlot, + connType); } -/// Create a connection that self-disconnects after triggering on the signal -template -inline auto connectSingleShot(SenderT* sender, SignalT signal, - ContextT* context, const FunctorT& slot, +//! Create a connection that self-disconnects after triggering on the signal +template +inline auto connectSingleShot(auto* sender, auto signal, ContextT* context, + SlotT slot, Qt::ConnectionType connType = Qt::AutoConnection) { #if QT_VERSION_MAJOR >= 6 @@ -111,25 +78,16 @@ inline auto connectSingleShot(SenderT* sender, SignalT signal, Qt::ConnectionType(connType | Qt::SingleShotConnection)); #else - return _impl::connectSingleShot( - sender, signal, context, _impl::wrap_in_function(slot), connType); -} - -// Specialisation for usual Qt slots passed as pointers-to-members. -template -inline auto connectSingleShot(SenderT* sender, SignalT signal, - ReceiverT* receiver, - void (SlotObjectT::*slot)(ArgTs...), - Qt::ConnectionType connType = Qt::AutoConnection) -{ - // TODO: when switching to C++20, use std::bind_front() instead - return _impl::connectSingleShot(sender, signal, receiver, - _impl::wrap_in_function( - [receiver, slot](const ArgTs&... args) { - (receiver->*slot)(args...); - }), - connType); + // In case for usual Qt slots passed as pointers-to-members the receiver + // object has to be pre-bound to the slot to make it self-contained + if constexpr (_impl::PmfSlot) { + return _impl::connect<_impl::SingleShot>(sender, signal, context, + std::bind_front(slot, context), + connType); + } else { + return _impl::connect<_impl::SingleShot>(sender, signal, context, slot, + connType); + } #endif } -- cgit v1.2.3 From 64948b6840032b04ef00bfe207baa29dd445d141 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 7 Jul 2022 18:32:48 +0200 Subject: Avoid std::derived_from and std::bind_front Apple Clang doesn't have those yet. --- lib/events/event.h | 2 +- lib/qt_connection_util.h | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index a966e613..05eb51e9 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -344,7 +344,7 @@ inline auto eventCast(const BasePtrT& eptr) namespace _impl { template concept Invocable_With_Downcast = - std::derived_from>, BaseT>; + std::is_base_of_v>>; } template diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index 7477273f..edcdc572 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -46,7 +46,7 @@ namespace _impl { template concept PmfSlot = (fn_arg_count_v > 0 - && std::derived_from>>); + && std::is_base_of_v>, ReceiverT>); } // namespace _impl //! \brief Create a connection that self-disconnects when its slot returns true @@ -78,12 +78,21 @@ inline auto connectSingleShot(auto* sender, auto signal, ContextT* context, Qt::ConnectionType(connType | Qt::SingleShotConnection)); #else - // In case for usual Qt slots passed as pointers-to-members the receiver + // In case of classic Qt pointer-to-member-function slots the receiver // object has to be pre-bound to the slot to make it self-contained if constexpr (_impl::PmfSlot) { + auto&& boundSlot = +# if __cpp_lib_bind_front // Needs Apple Clang 13 (other platforms are fine) + std::bind_front(slot, context); +# else + [context, slot](const auto&... args) + requires requires { (context->*slot)(args...); } + { + (context->*slot)(args...); + }; +# endif return _impl::connect<_impl::SingleShot>(sender, signal, context, - std::bind_front(slot, context), - connType); + std::move(boundSlot), connType); } else { return _impl::connect<_impl::SingleShot>(sender, signal, context, slot, connType); -- cgit v1.2.3 From 2245a44aca8d51e331cf1211ff6e2b38c57e5246 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 8 Jun 2022 14:26:30 +0200 Subject: Pack up StickerEvent in the header file Dropping yet another translation unit. --- CMakeLists.txt | 2 +- lib/events/stickerevent.cpp | 26 -------------------------- lib/events/stickerevent.h | 19 +++++++++++++++---- 3 files changed, 16 insertions(+), 31 deletions(-) delete mode 100644 lib/events/stickerevent.cpp (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 94055b7f..b2c4ff83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,7 +165,7 @@ list(APPEND lib_SRCS lib/events/encryptionevent.h lib/events/encryptionevent.cpp lib/events/encryptedevent.h lib/events/encryptedevent.cpp lib/events/roomkeyevent.h lib/events/roomkeyevent.cpp - lib/events/stickerevent.h lib/events/stickerevent.cpp + lib/events/stickerevent.h lib/events/keyverificationevent.h lib/events/filesourceinfo.h lib/events/filesourceinfo.cpp lib/jobs/requestdata.h lib/jobs/requestdata.cpp diff --git a/lib/events/stickerevent.cpp b/lib/events/stickerevent.cpp deleted file mode 100644 index 6d318f0e..00000000 --- a/lib/events/stickerevent.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// SDPX-FileCopyrightText: 2020 Carl Schwan -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "stickerevent.h" - -using namespace Quotient; - -StickerEvent::StickerEvent(const QJsonObject &obj) - : RoomEvent(typeId(), obj) - , m_imageContent(EventContent::ImageContent(obj["content"_ls].toObject())) -{} - -QString StickerEvent::body() const -{ - return contentPart("body"_ls); -} - -const EventContent::ImageContent &StickerEvent::image() const -{ - return m_imageContent; -} - -QUrl StickerEvent::url() const -{ - return m_imageContent.url(); -} diff --git a/lib/events/stickerevent.h b/lib/events/stickerevent.h index 0957dca3..e378422d 100644 --- a/lib/events/stickerevent.h +++ b/lib/events/stickerevent.h @@ -16,21 +16,32 @@ class QUOTIENT_API StickerEvent : public RoomEvent public: DEFINE_EVENT_TYPEID("m.sticker", StickerEvent) - explicit StickerEvent(const QJsonObject &obj); + explicit StickerEvent(const QJsonObject& obj) + : RoomEvent(TypeId, obj) + , m_imageContent( + EventContent::ImageContent(obj["content"_ls].toObject())) + {} /// \brief A textual representation or associated description of the /// sticker image. /// /// This could be the alt text of the original image, or a message to /// accompany and further describe the sticker. - QString body() const; + QUO_CONTENT_GETTER(QString, body) /// \brief Metadata about the image referred to in url including a /// thumbnail representation. - const EventContent::ImageContent &image() const; + const EventContent::ImageContent& image() const + { + return m_imageContent; + } /// \brief The URL to the sticker image. This must be a valid mxc:// URI. - QUrl url() const; + QUrl url() const + { + return m_imageContent.url(); + } + private: EventContent::ImageContent m_imageContent; }; -- cgit v1.2.3 From 607489a2e6a3e3238eac0178f5c7bbc70f178f46 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Jun 2022 20:43:50 +0200 Subject: Fix deprecation messages [skip ci] --- lib/events/eventrelation.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/events') diff --git a/lib/events/eventrelation.h b/lib/events/eventrelation.h index e445ee42..2a841cf1 100644 --- a/lib/events/eventrelation.h +++ b/lib/events/eventrelation.h @@ -34,11 +34,11 @@ struct QUOTIENT_API EventRelation { return { ReplacementType, std::move(eventId) }; } - [[deprecated("Use ReplyRelation variable instead")]] + [[deprecated("Use ReplyType variable instead")]] static constexpr auto Reply() { return ReplyType; } - [[deprecated("Use AnnotationRelation variable instead")]] // + [[deprecated("Use AnnotationType variable instead")]] // static constexpr auto Annotation() { return AnnotationType; } - [[deprecated("Use ReplacementRelation variable instead")]] // + [[deprecated("Use ReplacementType variable instead")]] // static constexpr auto Replacement() { return ReplacementType; } }; -- cgit v1.2.3 From ab2d78de6a9d33e1470ae9db2ed6ed9565c817e5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 11 Jul 2022 10:08:44 +0200 Subject: fromJson()/toJson() refactoring fromJson() is generalised to accept any JSON-like type while passing QJsonObject to JsonConverter<>::load (instead of doLoad). This allows to (still) rely on JsonConverter<> as a customisation point while providing an opportunity to overload fromJson for custom types in a pointed way (specifically, by providing the overload for `fromJson(const QJsonObject&)`), instead of having to go with full-blown JsonConverter<> specialisation. This will be used in a further commit to simplify ReceiptEvent definition. Using if constexpr in combination with constraints (`requires()`) - the first such case in Quotient codebase - allowed to put the entire logic in a single JsonConverter<>::load() body instead of having a facility JsonExporter<> class for SFINAE. Aside from that, fromJson is entirely dropped because it's not supposed to be used that way (it's no-op after all); reflecting that, Event::unsignedPart() and Event::contentPart() no more default to QJsonValue as the expected return type, you have to explicitly provide the type instead (and as one can see from the changes in the commit, it's actually better that way since it's better to validate the value inside JSON - e.g. check QString or QJsonObject for emptiness - than the QJsonValue envelope which may still wrap an empty value). toJson() is also generalised, replacing 3 functions with one that has a constexpr if to discern between two kinds of types. --- lib/converters.h | 89 ++++++++++++++++++------------------------- lib/events/encryptedevent.cpp | 10 +++-- lib/events/event.h | 4 +- lib/events/roomevent.cpp | 6 +-- lib/events/stateevent.cpp | 2 +- 5 files changed, 50 insertions(+), 61 deletions(-) (limited to 'lib/events') diff --git a/lib/converters.h b/lib/converters.h index c445442c..9f42d712 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -28,23 +28,8 @@ struct JsonObjectConverter { static void fillFrom(const QJsonObject&, T&) = delete; }; -namespace _impl { - template - struct JsonExporter { - static QJsonObject dump(const T& data) - { - QJsonObject jo; - JsonObjectConverter::dumpTo(jo, data); - return jo; - } - }; - - template - struct JsonExporter< - T, std::enable_if_t>> { - static auto dump(const T& data) { return data.toJson(); } - }; -} +template +PodT fromJson(const JsonT&); //! \brief The switchboard for extra conversion algorithms behind from/toJson //! @@ -62,13 +47,23 @@ namespace _impl { //! that they are not supported and it's not feasible to support those by means //! of overloading toJson() and specialising fromJson(). template -struct JsonConverter : _impl::JsonExporter { +struct JsonConverter { // Unfortunately, if constexpr doesn't work with dump() and T::toJson // because trying to check invocability of T::toJson hits a hard // (non-SFINAE) compilation error if the member is not there. Hence a bit // more verbose SFINAE construct in _impl::JsonExporter. + static auto dump(const T& data) + { + if constexpr (requires() { data.toJson(); }) + return data.toJson(); + else { + QJsonObject jo; + JsonObjectConverter::dumpTo(jo, data); + return jo; + } + } - static T doLoad(const QJsonObject& jo) + static T load(const QJsonObject& jo) { // 'else' below are required to suppress code generation for unused // branches - 'return' is not enough @@ -82,65 +77,57 @@ struct JsonConverter : _impl::JsonExporter { return pod; } } - static T load(const QJsonValue& jv) { return doLoad(jv.toObject()); } - static T load(const QJsonDocument& jd) { return doLoad(jd.object()); } + // By default, revert to fromJson() so that one could provide a single + // fromJson specialisation instead of specialising + // the entire JsonConverter; if a different type of JSON value is needed + // (e.g., an array), specialising JsonConverter is inevitable + static T load(QJsonValueRef jvr) { return fromJson(QJsonValue(jvr)); } + static T load(const QJsonValue& jv) { return fromJson(jv.toObject()); } + static T load(const QJsonDocument& jd) { return fromJson(jd.object()); } }; template - requires (!std::is_constructible_v) inline auto toJson(const T& pod) // -> can return anything from which QJsonValue or, in some cases, QJsonDocument // is constructible { - return JsonConverter::dump(pod); + if constexpr (std::is_constructible_v) + return pod; // No-op if QJsonValue can be directly constructed + else + return JsonConverter::dump(pod); } -inline auto toJson(const QJsonObject& jo) { return jo; } -inline auto toJson(const QJsonValue& jv) { return jv; } - template inline void fillJson(QJsonObject& json, const T& data) { JsonObjectConverter::dumpTo(json, data); } -template -inline T fromJson(const QJsonValue& jv) +template +inline PodT fromJson(const JsonT& json) { - return JsonConverter::load(jv); + // JsonT here can be whatever the respective JsonConverter specialisation + // accepts but by default it's QJsonValue, QJsonDocument, or QJsonObject + return JsonConverter::load(json); } -template<> -inline QJsonValue fromJson(const QJsonValue& jv) { return jv; } - -template -inline T fromJson(const QJsonDocument& jd) -{ - return JsonConverter::load(jd); -} - -// Convenience fromJson() overloads that deduce T instead of requiring -// the coder to explicitly type it. They still enforce the +// Convenience fromJson() overload that deduces PodT instead of requiring +// the coder to explicitly type it. It still enforces the // overwrite-everything semantics of fromJson(), unlike fillFromJson() -template -inline void fromJson(const QJsonValue& jv, T& pod) -{ - pod = jv.isUndefined() ? T() : fromJson(jv); -} - -template -inline void fromJson(const QJsonDocument& jd, T& pod) +template +inline void fromJson(const JsonT& json, PodT& pod) { - pod = fromJson(jd); + pod = fromJson(json); } template inline void fillFromJson(const QJsonValue& jv, T& pod) { - if (jv.isObject()) + if constexpr (requires() { JsonObjectConverter::fillFrom({}, pod); }) { JsonObjectConverter::fillFrom(jv.toObject(), pod); - else if (!jv.isUndefined()) + return; + } else if (!jv.isUndefined()) pod = fromJson(jv); } diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index c97ccc16..ec00ad4c 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -49,14 +49,16 @@ RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const eventObject["event_id"] = id(); eventObject["sender"] = senderId(); eventObject["origin_server_ts"] = originTimestamp().toMSecsSinceEpoch(); - if (const auto relatesToJson = contentPart("m.relates_to"_ls); !relatesToJson.isUndefined()) { + if (const auto relatesToJson = contentPart("m.relates_to"_ls); + !relatesToJson.isEmpty()) { auto content = eventObject["content"].toObject(); - content["m.relates_to"] = relatesToJson.toObject(); + content["m.relates_to"] = relatesToJson; eventObject["content"] = content; } - if (const auto redactsJson = unsignedPart("redacts"_ls); !redactsJson.isUndefined()) { + if (const auto redactsJson = unsignedPart("redacts"_ls); + !redactsJson.isEmpty()) { auto unsign = eventObject["unsigned"].toObject(); - unsign["redacts"] = redactsJson.toString(); + unsign["redacts"] = redactsJson; eventObject["unsigned"] = unsign; } return loadEvent(eventObject); diff --git a/lib/events/event.h b/lib/events/event.h index 05eb51e9..da6cf3c7 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -212,7 +212,7 @@ public: const QJsonObject contentJson() const; - template + template const T contentPart(KeyT&& key) const { return fromJson(contentJson()[std::forward(key)]); @@ -227,7 +227,7 @@ public: const QJsonObject unsignedJson() const; - template + template const T unsignedPart(KeyT&& key) const { return fromJson(unsignedJson()[std::forward(key)]); diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 3ddf5ac4..e695e0ec 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -15,9 +15,9 @@ RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, RoomEvent::RoomEvent(Type type, const QJsonObject& json) : Event(type, json) { - if (const auto redaction = unsignedPart(RedactedCauseKeyL); - redaction.isObject()) - _redactedBecause = makeEvent(redaction.toObject()); + if (const auto redaction = unsignedPart(RedactedCauseKeyL); + !redaction.isEmpty()) + _redactedBecause = makeEvent(redaction); } RoomEvent::~RoomEvent() = default; // Let the smart pointer do its job diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index c343e37f..43dfd6e8 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -21,7 +21,7 @@ StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, bool StateEventBase::repeatsState() const { - const auto prevContentJson = unsignedPart(PrevContentKeyL); + const auto prevContentJson = unsignedPart(PrevContentKeyL); return fullJson().value(ContentKeyL) == prevContentJson; } -- cgit v1.2.3 From 44aeb57e196bcd9e041c498a212f17b0dcd1244f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Jun 2022 02:52:35 +0200 Subject: Refactor things around EncryptionEvent[Content] EncryptionEvent was marked as Q_GADGET only for the sake of defining EncryptionType inside of it as Q_ENUM, with aliases also available under Quotient:: and EncryptionEventContent. This is a legacy from pre-Q_ENUM_NS times. However, event types are not really made to be proper Q_GADGETs: Q_GADGET implies access by value or reference but event types are uncopyable for the former and QML is ill-equipped for the latter. This commit moves EncryptionType definition to where other such enumerations reside - on the namespace level in quotient_common.h; and the other two places are now deprecated; and EncryptionEvent is no more Q_GADGET. With fromJson/toJson refactored in the previous commit there's no more need to specialise JsonConverter<>: specialising fromJson() is just enough. Moving EncryptionType to quotient_common.h exposed the clash of two Undefined enumerators (in RoomType and EncryptionType), warranting both enumerations to become scoped (which they ought to be, anyway). And while we're at it, the base type of enumerations is specified explicitly, as MSVC apparently uses a signed base type (int?) by default, unlike other compilers, and the upcoming enum converters will assume an unsigned base type. Finally, using fillFromJson() instead of fromJson() in the EncryptionEventContent constructor allowed to make default values explicit in the header file, rather than buried in the initialisation code. --- lib/events/encryptionevent.cpp | 50 ++++++++++++++++++++---------------------- lib/events/encryptionevent.h | 35 ++++++++++++----------------- lib/quotient_common.h | 12 +++++++--- lib/room.cpp | 4 ++-- 4 files changed, 49 insertions(+), 52 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 1654d6f3..8872447b 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -6,47 +6,45 @@ #include "e2ee/e2ee.h" -namespace Quotient { +using namespace Quotient; + static constexpr std::array encryptionStrings { MegolmV1AesSha2AlgoKey }; template <> -struct JsonConverter { - static EncryptionType load(const QJsonValue& jv) - { - const auto& encryptionString = jv.toString(); - for (auto it = encryptionStrings.begin(); it != encryptionStrings.end(); - ++it) - if (encryptionString == *it) - return EncryptionType(it - encryptionStrings.begin()); - - if (!encryptionString.isEmpty()) - qCWarning(EVENTS) << "Unknown EncryptionType: " << encryptionString; - return EncryptionType::Undefined; - } -}; -} // namespace Quotient - -using namespace Quotient; +EncryptionType Quotient::fromJson(const QJsonValue& jv) +{ + const auto& encryptionString = jv.toString(); + for (auto it = encryptionStrings.begin(); it != encryptionStrings.end(); + ++it) + if (encryptionString == *it) + return EncryptionType(it - encryptionStrings.begin()); + + if (!encryptionString.isEmpty()) + qCWarning(EVENTS) << "Unknown EncryptionType: " << encryptionString; + return EncryptionType::Undefined; +} EncryptionEventContent::EncryptionEventContent(const QJsonObject& json) - : encryption(fromJson(json[AlgorithmKeyL])) + : encryption(fromJson(json[AlgorithmKeyL])) , algorithm(sanitized(json[AlgorithmKeyL].toString())) - , rotationPeriodMs(json[RotationPeriodMsKeyL].toInt(604800000)) - , rotationPeriodMsgs(json[RotationPeriodMsgsKeyL].toInt(100)) -{} +{ + // NB: fillFromJson only fills the variable if the JSON key exists + fillFromJson(json[RotationPeriodMsKeyL], rotationPeriodMs); + fillFromJson(json[RotationPeriodMsgsKeyL], rotationPeriodMsgs); +} -EncryptionEventContent::EncryptionEventContent(EncryptionType et) +EncryptionEventContent::EncryptionEventContent(Quotient::EncryptionType et) : encryption(et) { - if(encryption != Undefined) { - algorithm = encryptionStrings[encryption]; + if(encryption != Quotient::EncryptionType::Undefined) { + algorithm = encryptionStrings[static_cast(encryption)]; } } QJsonObject EncryptionEventContent::toJson() const { QJsonObject o; - if (encryption != EncryptionType::Undefined) + if (encryption != Quotient::EncryptionType::Undefined) o.insert(AlgorithmKey, algorithm); o.insert(RotationPeriodMsKey, rotationPeriodMs); o.insert(RotationPeriodMsgsKey, rotationPeriodMsgs); diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 945b17e7..91452c3f 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -4,51 +4,44 @@ #pragma once +#include "quotient_common.h" #include "stateevent.h" namespace Quotient { class QUOTIENT_API EncryptionEventContent { public: - enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined }; + using EncryptionType + [[deprecated("Use Quotient::EncryptionType instead")]] = + Quotient::EncryptionType; - QUO_IMPLICIT EncryptionEventContent(EncryptionType et); - [[deprecated("This constructor will require explicit EncryptionType soon")]] // - explicit EncryptionEventContent() - : EncryptionEventContent(Undefined) - {} + // NOLINTNEXTLINE(google-explicit-constructor) + QUO_IMPLICIT EncryptionEventContent(Quotient::EncryptionType et); explicit EncryptionEventContent(const QJsonObject& json); QJsonObject toJson() const; - EncryptionType encryption; - QString algorithm; - int rotationPeriodMs; - int rotationPeriodMsgs; + Quotient::EncryptionType encryption; + QString algorithm {}; + int rotationPeriodMs = 604'800'000; + int rotationPeriodMsgs = 100; }; -using EncryptionType = EncryptionEventContent::EncryptionType; - class QUOTIENT_API EncryptionEvent : public StateEvent { - Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent) - using EncryptionType = EncryptionEventContent::EncryptionType; - Q_ENUM(EncryptionType) + using EncryptionType + [[deprecated("Use Quotient::EncryptionType instead")]] = + Quotient::EncryptionType; explicit EncryptionEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} - [[deprecated("This constructor will require an explicit parameter soon")]] // -// explicit EncryptionEvent() -// : EncryptionEvent(QJsonObject()) -// {} explicit EncryptionEvent(EncryptionEventContent&& content) : StateEvent(typeId(), matrixTypeId(), QString(), std::move(content)) {} - EncryptionType encryption() const { return content().encryption; } - + Quotient::EncryptionType encryption() const { return content().encryption; } QString algorithm() const { return content().algorithm; } int rotationPeriodMs() const { return content().rotationPeriodMs; } int rotationPeriodMsgs() const { return content().rotationPeriodMsgs; } diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 233bcaa1..7fec9274 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -107,14 +107,20 @@ enum UriResolveResult : int8_t { }; Q_ENUM_NS(UriResolveResult) -enum RoomType { - Space, - Undefined, +enum class RoomType : uint8_t { + Space = 0, + Undefined = 0xFF, }; Q_ENUM_NS(RoomType) [[maybe_unused]] constexpr std::array RoomTypeStrings { "m.space" }; +enum class EncryptionType : uint8_t { + MegolmV1AesSha2 = 0, + Undefined = 0xFF, +}; +Q_ENUM_NS(EncryptionType) + } // namespace Quotient Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::JoinStates) diff --git a/lib/room.cpp b/lib/room.cpp index 2625105c..f67451ce 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -3062,7 +3062,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return false; } if (oldEncEvt - && oldEncEvt->encryption() != EncryptionEventContent::Undefined) { + && oldEncEvt->encryption() != EncryptionType::Undefined) { qCWarning(STATE) << "The room is already encrypted but a new" " room encryption event arrived - ignoring"; return false; @@ -3508,5 +3508,5 @@ void Room::activateEncryption() qCWarning(E2EE) << "Room" << objectName() << "is already encrypted"; return; } - setState(EncryptionEventContent::MegolmV1AesSha2); + setState(EncryptionType::MegolmV1AesSha2); } -- cgit v1.2.3 From 9a65bde0fa438ee4ad101b58721b77182ae1ae70 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 9 May 2022 12:02:30 +0200 Subject: Make AliasesEventContent a simple structure JSON conversions are moved out of the class, obviating the need to define the plain data constructor and gaining default-constructibility along the way - previously the default constructor was preempted by user-defined ones. --- lib/events/roomcanonicalaliasevent.h | 43 ++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 24 deletions(-) (limited to 'lib/events') diff --git a/lib/events/roomcanonicalaliasevent.h b/lib/events/roomcanonicalaliasevent.h index bb8654e5..e599699d 100644 --- a/lib/events/roomcanonicalaliasevent.h +++ b/lib/events/roomcanonicalaliasevent.h @@ -7,35 +7,30 @@ #include "stateevent.h" namespace Quotient { -namespace EventContent{ - class AliasesEventContent { - - public: - - template - AliasesEventContent(T1&& canonicalAlias, T2&& altAliases) - : canonicalAlias(std::forward(canonicalAlias)) - , altAliases(std::forward(altAliases)) - { } - - AliasesEventContent(const QJsonObject& json) - : canonicalAlias(fromJson(json["alias"])) - , altAliases(fromJson(json["alt_aliases"])) - { } - - auto toJson() const - { - QJsonObject jo; - addParam(jo, QStringLiteral("alias"), canonicalAlias); - addParam(jo, QStringLiteral("alt_aliases"), altAliases); - return jo; - } - +namespace EventContent { + struct AliasesEventContent { QString canonicalAlias; QStringList altAliases; }; } // namespace EventContent +template<> +inline EventContent::AliasesEventContent fromJson(const QJsonObject& jo) +{ + return EventContent::AliasesEventContent { + fromJson(jo["alias"_ls]), + fromJson(jo["alt_aliases"_ls]) + }; +} +template<> +inline auto toJson(const EventContent::AliasesEventContent& c) +{ + QJsonObject jo; + addParam(jo, QStringLiteral("alias"), c.canonicalAlias); + addParam(jo, QStringLiteral("alt_aliases"), c.altAliases); + return jo; +} + class RoomCanonicalAliasEvent : public StateEvent { public: -- cgit v1.2.3 From e7dee15531ad357bd33ac546fb1b9332a5c1260c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 9 Jun 2022 08:40:27 +0200 Subject: converters.*: facilities to convert enums This introduces enumTo/FromJsonString() and flagTo/FromJsonString(), four facility functions to simplify conversion between C++ enums and JSON, and refactors a couple of places where it's useful. --- lib/converters.cpp | 14 +++++++ lib/converters.h | 92 ++++++++++++++++++++++++++++++++++++++++++ lib/events/roomcreateevent.cpp | 19 +++------ lib/events/roommemberevent.cpp | 19 +++------ 4 files changed, 116 insertions(+), 28 deletions(-) (limited to 'lib/events') diff --git a/lib/converters.cpp b/lib/converters.cpp index 444ca4f6..b0e3a4b6 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -2,9 +2,23 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "converters.h" +#include "logging.h" #include +void Quotient::_impl::warnUnknownEnumValue(const QString& stringValue, + const char* enumTypeName) +{ + qWarning(EVENTS).noquote() + << "Unknown" << enumTypeName << "value:" << stringValue; +} + +void Quotient::_impl::reportEnumOutOfBounds(uint32_t v, const char* enumTypeName) +{ + qCritical(MAIN).noquote() + << "Value" << v << "is out of bounds for enumeration" << enumTypeName; +} + QJsonValue Quotient::JsonConverter::dump(const QVariant& v) { return QJsonValue::fromVariant(v); diff --git a/lib/converters.h b/lib/converters.h index 9f42d712..bf456af9 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -131,6 +131,98 @@ inline void fillFromJson(const QJsonValue& jv, T& pod) pod = fromJson(jv); } +namespace _impl { + void warnUnknownEnumValue(const QString& stringValue, + const char* enumTypeName); + void reportEnumOutOfBounds(uint32_t v, const char* enumTypeName); +} + +//! \brief Facility string-to-enum converter +//! +//! This is to simplify enum loading from JSON - just specialise +//! Quotient::fromJson() and call this function from it, passing (aside from +//! the JSON value for the enum - that must be a string, not an int) any +//! iterable container of string'y values (const char*, QLatin1String, etc.) +//! matching respective enum values, 0-based. +//! \sa enumToJsonString +template +EnumT enumFromJsonString(const QString& s, const EnumStringValuesT& enumValues, + EnumT defaultValue) +{ + static_assert(std::is_unsigned_v>); + if (const auto it = std::find(cbegin(enumValues), cend(enumValues), s); + it != cend(enumValues)) + return EnumT(it - cbegin(enumValues)); + + if (!s.isEmpty()) + _impl::warnUnknownEnumValue(s, qt_getEnumName(EnumT())); + return defaultValue; +} + +//! \brief Facility enum-to-string converter +//! +//! This does the same as enumFromJsonString, the other way around. +//! \note The source enumeration must not have gaps in values, or \p enumValues +//! has to match those gaps (i.e., if the source enumeration is defined +//! as { Value1 = 1, Value2 = 3, Value3 = 5 } then \p enumValues +//! should be defined as { "", "Value1", "", "Value2", "", "Value3" +//! } (mind the gap at value 0, in particular). +//! \sa enumFromJsonString +template +QString enumToJsonString(EnumT v, const EnumStringValuesT& enumValues) +{ + static_assert(std::is_unsigned_v>); + if (v < size(enumValues)) + return enumValues[v]; + + _impl::reportEnumOutOfBounds(static_cast(v), + qt_getEnumName(EnumT())); + Q_ASSERT(false); + return {}; +} + +//! \brief Facility converter for flags +//! +//! This is very similar to enumFromJsonString, except that the target +//! enumeration is assumed to be of a 'flag' kind - i.e. its values must be +//! a power-of-two sequence starting from 1, without gaps, so exactly 1,2,4,8,16 +//! and so on. +//! \note Unlike enumFromJsonString, the values start from 1 and not from 0, +//! with 0 being used for an invalid value by default. +//! \note This function does not support flag combinations. +//! \sa QUO_DECLARE_FLAGS, QUO_DECLARE_FLAGS_NS +template +FlagT flagFromJsonString(const QString& s, const FlagStringValuesT& flagValues, + FlagT defaultValue = FlagT(0U)) +{ + // Enums based on signed integers don't make much sense for flag types + static_assert(std::is_unsigned_v>); + if (const auto it = std::find(cbegin(flagValues), cend(flagValues), s); + it != cend(flagValues)) + return FlagT(1U << (it - cbegin(flagValues))); + + if (!s.isEmpty()) + _impl::warnUnknownEnumValue(s, qt_getEnumName(FlagT())); + return defaultValue; +} + +template +QString flagToJsonString(FlagT v, const FlagStringValuesT& flagValues) +{ + static_assert(std::is_unsigned_v>); + if (const auto offset = + qCountTrailingZeroBits(std::underlying_type_t(v)); + offset < size(flagValues)) // + { + return flagValues[offset]; + } + + _impl::reportEnumOutOfBounds(static_cast(v), + qt_getEnumName(FlagT())); + Q_ASSERT(false); + return {}; +} + // JsonConverter<> specialisations template<> diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index bb6de648..3b5024d5 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -6,20 +6,11 @@ using namespace Quotient; template <> -struct Quotient::JsonConverter { - static RoomType load(const QJsonValue& jv) - { - const auto& roomTypeString = jv.toString(); - for (auto it = RoomTypeStrings.begin(); it != RoomTypeStrings.end(); - ++it) - if (roomTypeString == *it) - return RoomType(it - RoomTypeStrings.begin()); - - if (!roomTypeString.isEmpty()) - qCWarning(EVENTS) << "Unknown Room Type: " << roomTypeString; - return RoomType::Undefined; - } -}; +RoomType Quotient::fromJson(const QJsonValue& jv) +{ + return enumFromJsonString(jv.toString(), RoomTypeStrings, + RoomType::Undefined); +} bool RoomCreateEvent::isFederated() const { diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index c3be0e00..953ff8ae 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -11,18 +11,10 @@ template <> struct JsonConverter { static Membership load(const QJsonValue& jv) { - const auto& ms = jv.toString(); - if (ms.isEmpty()) - { - qCWarning(EVENTS) << "Empty membership state"; - return Membership::Invalid; - } - const auto it = - std::find(MembershipStrings.begin(), MembershipStrings.end(), ms); - if (it != MembershipStrings.end()) - return Membership(1U << (it - MembershipStrings.begin())); - - qCWarning(EVENTS) << "Unknown Membership value: " << ms; + if (const auto& ms = jv.toString(); !ms.isEmpty()) + return flagFromJsonString(ms, MembershipStrings); + + qCWarning(EVENTS) << "Empty membership state"; return Membership::Invalid; } }; @@ -46,8 +38,7 @@ QJsonObject MemberEventContent::toJson() const QJsonObject o; if (membership != Membership::Invalid) o.insert(QStringLiteral("membership"), - MembershipStrings[qCountTrailingZeroBits( - std::underlying_type_t(membership))]); + flagToJsonString(membership, MembershipStrings)); if (displayName) o.insert(QStringLiteral("displayname"), *displayName); if (avatarUrl && avatarUrl->isValid()) -- cgit v1.2.3 From e172699e9a20737c4abb9bd96609f48e65415961 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Jun 2022 13:17:22 +0200 Subject: eventcontent.h: Use C++17 nested namespaces notation --- lib/events/eventcontent.h | 464 +++++++++++++++++++++++----------------------- 1 file changed, 231 insertions(+), 233 deletions(-) (limited to 'lib/events') diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 7611d077..af26c0a4 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -17,240 +17,238 @@ class QFileInfo; -namespace Quotient { -namespace EventContent { - //! \brief Base for all content types that can be stored in RoomMessageEvent +namespace Quotient::EventContent { +//! \brief Base for all content types that can be stored in RoomMessageEvent +//! +//! Each content type class should have a constructor taking +//! a QJsonObject and override fillJson() with an implementation +//! that will fill the target QJsonObject with stored values. It is +//! assumed but not required that a content object can also be created +//! from plain data. +class QUOTIENT_API Base { +public: + explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {} + virtual ~Base() = default; + + QJsonObject toJson() const; + +public: + QJsonObject originalJson; + + // You can't assign those classes + Base& operator=(const Base&) = delete; + Base& operator=(Base&&) = delete; + +protected: + Base(const Base&) = default; + Base(Base&&) noexcept = default; + + virtual void fillJson(QJsonObject&) const = 0; +}; + +// The below structures fairly follow CS spec 11.2.1.6. The overall +// set of attributes for each content types is a superset of the spec +// but specific aggregation structure is altered. See doc comments to +// each type for the list of available attributes. + +// A quick classes inheritance structure follows (the definitions are +// spread across eventcontent.h and roommessageevent.h): +// UrlBasedContent : InfoT + thumbnail data +// PlayableContent : + duration attribute +// FileInfo +// FileContent = UrlBasedContent +// AudioContent = PlayableContent +// ImageInfo : FileInfo + imageSize attribute +// ImageContent = UrlBasedContent +// VideoContent = PlayableContent + +//! \brief Mix-in class representing `info` subobject in content JSON +//! +//! This is one of base classes for content types that deal with files or +//! URLs. It stores the file metadata attributes, such as size, MIME type +//! etc. found in the `content/info` subobject of event JSON payloads. +//! Actual content classes derive from this class _and_ TypedBase that +//! provides a polymorphic interface to access data in the mix-in. FileInfo +//! (as well as ImageInfo, that adds image size to the metadata) is NOT +//! polymorphic and is used in a non-polymorphic way to store thumbnail +//! metadata (in a separate instance), next to the metadata on the file +//! itself. +//! +//! If you need to make a new _content_ (not info) class based on files/URLs +//! take UrlBasedContent as the example, i.e.: +//! 1. Double-inherit from this class (or ImageInfo) and TypedBase. +//! 2. Provide a constructor from QJsonObject that will pass the `info` +//! subobject (not the whole content JSON) down to FileInfo/ImageInfo. +//! 3. Override fillJson() to customise the JSON export logic. Make sure +//! to call toInfoJson() from it to produce the payload for the `info` +//! subobject in the JSON payload. +//! +//! \sa ImageInfo, FileContent, ImageContent, AudioContent, VideoContent, +//! UrlBasedContent +class QUOTIENT_API FileInfo { +public: + FileInfo() = default; + //! \brief Construct from a QFileInfo object //! - //! Each content type class should have a constructor taking - //! a QJsonObject and override fillJson() with an implementation - //! that will fill the target QJsonObject with stored values. It is - //! assumed but not required that a content object can also be created - //! from plain data. - class QUOTIENT_API Base { - public: - explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {} - virtual ~Base() = default; - - QJsonObject toJson() const; - - public: - QJsonObject originalJson; - - // You can't assign those classes - Base& operator=(const Base&) = delete; - Base& operator=(Base&&) = delete; - - protected: - Base(const Base&) = default; - Base(Base&&) noexcept = default; - - virtual void fillJson(QJsonObject&) const = 0; - }; - - // The below structures fairly follow CS spec 11.2.1.6. The overall - // set of attributes for each content types is a superset of the spec - // but specific aggregation structure is altered. See doc comments to - // each type for the list of available attributes. - - // A quick classes inheritance structure follows (the definitions are - // spread across eventcontent.h and roommessageevent.h): - // UrlBasedContent : InfoT + thumbnail data - // PlayableContent : + duration attribute - // FileInfo - // FileContent = UrlBasedContent - // AudioContent = PlayableContent - // ImageInfo : FileInfo + imageSize attribute - // ImageContent = UrlBasedContent - // VideoContent = PlayableContent - - //! \brief Mix-in class representing `info` subobject in content JSON + //! \param fi a QFileInfo object referring to an existing file + explicit FileInfo(const QFileInfo& fi); + explicit FileInfo(FileSourceInfo sourceInfo, qint64 payloadSize = -1, + const QMimeType& mimeType = {}, + QString originalFilename = {}); + //! \brief Construct from a JSON `info` payload //! - //! This is one of base classes for content types that deal with files or - //! URLs. It stores the file metadata attributes, such as size, MIME type - //! etc. found in the `content/info` subobject of event JSON payloads. - //! Actual content classes derive from this class _and_ TypedBase that - //! provides a polymorphic interface to access data in the mix-in. FileInfo - //! (as well as ImageInfo, that adds image size to the metadata) is NOT - //! polymorphic and is used in a non-polymorphic way to store thumbnail - //! metadata (in a separate instance), next to the metadata on the file - //! itself. - //! - //! If you need to make a new _content_ (not info) class based on files/URLs - //! take UrlBasedContent as the example, i.e.: - //! 1. Double-inherit from this class (or ImageInfo) and TypedBase. - //! 2. Provide a constructor from QJsonObject that will pass the `info` - //! subobject (not the whole content JSON) down to FileInfo/ImageInfo. - //! 3. Override fillJson() to customise the JSON export logic. Make sure - //! to call toInfoJson() from it to produce the payload for the `info` - //! subobject in the JSON payload. - //! - //! \sa ImageInfo, FileContent, ImageContent, AudioContent, VideoContent, - //! UrlBasedContent - class QUOTIENT_API FileInfo { - public: - FileInfo() = default; - //! \brief Construct from a QFileInfo object - //! - //! \param fi a QFileInfo object referring to an existing file - explicit FileInfo(const QFileInfo& fi); - explicit FileInfo(FileSourceInfo sourceInfo, qint64 payloadSize = -1, - const QMimeType& mimeType = {}, - QString originalFilename = {}); - //! \brief Construct from a JSON `info` payload - //! - //! Make sure to pass the `info` subobject of content JSON, not the - //! whole JSON content. - FileInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, - QString originalFilename = {}); - - bool isValid() const; - QUrl url() const; - - //! \brief Extract media id from the URL - //! - //! This can be used, e.g., to construct a QML-facing image:// - //! URI as follows: - //! \code "image://provider/" + info.mediaId() \endcode - QString mediaId() const { return url().authority() + url().path(); } - - public: - FileSourceInfo source; - QJsonObject originalInfoJson; - QMimeType mimeType; - qint64 payloadSize = 0; - QString originalName; - }; - - QUOTIENT_API QJsonObject toInfoJson(const FileInfo& info); - - //! \brief A content info class for image/video content types and thumbnails - class QUOTIENT_API ImageInfo : public FileInfo { - public: - ImageInfo() = default; - explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); - explicit ImageInfo(FileSourceInfo sourceInfo, qint64 fileSize = -1, - const QMimeType& type = {}, QSize imageSize = {}, - const QString& originalFilename = {}); - ImageInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, - const QString& originalFilename = {}); - - public: - QSize imageSize; - }; - - QUOTIENT_API QJsonObject toInfoJson(const ImageInfo& info); - - //! \brief An auxiliary class for an info type that carries a thumbnail - //! - //! This class saves/loads a thumbnail to/from `info` subobject of - //! the JSON representation of event content; namely, `info/thumbnail_url` - //! (or, in case of an encrypted thumbnail, `info/thumbnail_file`) and - //! `info/thumbnail_info` fields are used. - class QUOTIENT_API Thumbnail : public ImageInfo { - public: - using ImageInfo::ImageInfo; - explicit Thumbnail(const QJsonObject& infoJson, - const Omittable& efm = none); - - //! \brief Add thumbnail information to the passed `info` JSON object - void dumpTo(QJsonObject& infoJson) const; - }; - - class QUOTIENT_API TypedBase : public Base { - public: - virtual QMimeType type() const = 0; - virtual const FileInfo* fileInfo() const { return nullptr; } - virtual FileInfo* fileInfo() { return nullptr; } - virtual const Thumbnail* thumbnailInfo() const { return nullptr; } - - protected: - explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} - using Base::Base; - }; - - //! \brief A template class for content types with a URL and additional info - //! - //! Types that derive from this class template take `url` (or, if the file - //! is encrypted, `file`) and, optionally, `filename` values from - //! the top-level JSON object and the rest of information from the `info` - //! subobject, as defined by the parameter type. - //! \tparam InfoT base info class - FileInfo or ImageInfo - template - class UrlBasedContent : public TypedBase, public InfoT { - public: - using InfoT::InfoT; - explicit UrlBasedContent(const QJsonObject& json) - : TypedBase(json) - , InfoT(QUrl(json["url"].toString()), json["info"].toObject(), - json["filename"].toString()) - , thumbnail(FileInfo::originalInfoJson) - { - if (const auto efmJson = json.value("file"_ls).toObject(); - !efmJson.isEmpty()) - InfoT::source = fromJson(efmJson); - // Two small hacks on originalJson to expose mediaIds to QML - originalJson.insert("mediaId", InfoT::mediaId()); - originalJson.insert("thumbnailMediaId", thumbnail.mediaId()); - } - - QMimeType type() const override { return InfoT::mimeType; } - const FileInfo* fileInfo() const override { return this; } - FileInfo* fileInfo() override { return this; } - const Thumbnail* thumbnailInfo() const override { return &thumbnail; } - - public: - Thumbnail thumbnail; - - protected: - virtual void fillInfoJson(QJsonObject& infoJson [[maybe_unused]]) const - {} - - void fillJson(QJsonObject& json) const override - { - Quotient::fillJson(json, { "url"_ls, "file"_ls }, InfoT::source); - if (!InfoT::originalName.isEmpty()) - json.insert("filename", InfoT::originalName); - auto infoJson = toInfoJson(*this); - if (thumbnail.isValid()) - thumbnail.dumpTo(infoJson); - fillInfoJson(infoJson); - json.insert("info", infoJson); - } - }; - - //! \brief Content class for m.image - //! - //! Available fields: - //! - corresponding to the top-level JSON: - //! - source (corresponding to `url` or `file` in JSON) - //! - filename (extension to the spec) - //! - corresponding to the `info` subobject: - //! - payloadSize (`size` in JSON) - //! - mimeType (`mimetype` in JSON) - //! - imageSize (QSize for a combination of `h` and `w` in JSON) - //! - thumbnail.url (`thumbnail_url` in JSON) - //! - corresponding to the `info/thumbnail_info` subobject: contents of - //! thumbnail field, in the same vein as for the main image: - //! - payloadSize - //! - mimeType - //! - imageSize - using ImageContent = UrlBasedContent; - - //! \brief Content class for m.file + //! Make sure to pass the `info` subobject of content JSON, not the + //! whole JSON content. + FileInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, + QString originalFilename = {}); + + bool isValid() const; + QUrl url() const; + + //! \brief Extract media id from the URL //! - //! Available fields: - //! - corresponding to the top-level JSON: - //! - source (corresponding to `url` or `file` in JSON) - //! - filename - //! - corresponding to the `info` subobject: - //! - payloadSize (`size` in JSON) - //! - mimeType (`mimetype` in JSON) - //! - thumbnail.source (`thumbnail_url` or `thumbnail_file` in JSON) - //! - corresponding to the `info/thumbnail_info` subobject: - //! - thumbnail.payloadSize - //! - thumbnail.mimeType - //! - thumbnail.imageSize (QSize for `h` and `w` in JSON) - using FileContent = UrlBasedContent; -} // namespace EventContent -} // namespace Quotient + //! This can be used, e.g., to construct a QML-facing image:// + //! URI as follows: + //! \code "image://provider/" + info.mediaId() \endcode + QString mediaId() const { return url().authority() + url().path(); } + +public: + FileSourceInfo source; + QJsonObject originalInfoJson; + QMimeType mimeType; + qint64 payloadSize = 0; + QString originalName; +}; + +QUOTIENT_API QJsonObject toInfoJson(const FileInfo& info); + +//! \brief A content info class for image/video content types and thumbnails +class QUOTIENT_API ImageInfo : public FileInfo { +public: + ImageInfo() = default; + explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); + explicit ImageInfo(FileSourceInfo sourceInfo, qint64 fileSize = -1, + const QMimeType& type = {}, QSize imageSize = {}, + const QString& originalFilename = {}); + ImageInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson, + const QString& originalFilename = {}); + +public: + QSize imageSize; +}; + +QUOTIENT_API QJsonObject toInfoJson(const ImageInfo& info); + +//! \brief An auxiliary class for an info type that carries a thumbnail +//! +//! This class saves/loads a thumbnail to/from `info` subobject of +//! the JSON representation of event content; namely, `info/thumbnail_url` +//! (or, in case of an encrypted thumbnail, `info/thumbnail_file`) and +//! `info/thumbnail_info` fields are used. +class QUOTIENT_API Thumbnail : public ImageInfo { +public: + using ImageInfo::ImageInfo; + explicit Thumbnail(const QJsonObject& infoJson, + const Omittable& efm = none); + + //! \brief Add thumbnail information to the passed `info` JSON object + void dumpTo(QJsonObject& infoJson) const; +}; + +class QUOTIENT_API TypedBase : public Base { +public: + virtual QMimeType type() const = 0; + virtual const FileInfo* fileInfo() const { return nullptr; } + virtual FileInfo* fileInfo() { return nullptr; } + virtual const Thumbnail* thumbnailInfo() const { return nullptr; } + +protected: + explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} + using Base::Base; +}; + +//! \brief A template class for content types with a URL and additional info +//! +//! Types that derive from this class template take `url` (or, if the file +//! is encrypted, `file`) and, optionally, `filename` values from +//! the top-level JSON object and the rest of information from the `info` +//! subobject, as defined by the parameter type. +//! \tparam InfoT base info class - FileInfo or ImageInfo +template +class UrlBasedContent : public TypedBase, public InfoT { +public: + using InfoT::InfoT; + explicit UrlBasedContent(const QJsonObject& json) + : TypedBase(json) + , InfoT(QUrl(json["url"].toString()), json["info"].toObject(), + json["filename"].toString()) + , thumbnail(FileInfo::originalInfoJson) + { + if (const auto efmJson = json.value("file"_ls).toObject(); + !efmJson.isEmpty()) + InfoT::source = fromJson(efmJson); + // Two small hacks on originalJson to expose mediaIds to QML + originalJson.insert("mediaId", InfoT::mediaId()); + originalJson.insert("thumbnailMediaId", thumbnail.mediaId()); + } + + QMimeType type() const override { return InfoT::mimeType; } + const FileInfo* fileInfo() const override { return this; } + FileInfo* fileInfo() override { return this; } + const Thumbnail* thumbnailInfo() const override { return &thumbnail; } + +public: + Thumbnail thumbnail; + +protected: + virtual void fillInfoJson(QJsonObject& infoJson [[maybe_unused]]) const + {} + + void fillJson(QJsonObject& json) const override + { + Quotient::fillJson(json, { "url"_ls, "file"_ls }, InfoT::source); + if (!InfoT::originalName.isEmpty()) + json.insert("filename", InfoT::originalName); + auto infoJson = toInfoJson(*this); + if (thumbnail.isValid()) + thumbnail.dumpTo(infoJson); + fillInfoJson(infoJson); + json.insert("info", infoJson); + } +}; + +//! \brief Content class for m.image +//! +//! Available fields: +//! - corresponding to the top-level JSON: +//! - source (corresponding to `url` or `file` in JSON) +//! - filename (extension to the spec) +//! - corresponding to the `info` subobject: +//! - payloadSize (`size` in JSON) +//! - mimeType (`mimetype` in JSON) +//! - imageSize (QSize for a combination of `h` and `w` in JSON) +//! - thumbnail.url (`thumbnail_url` in JSON) +//! - corresponding to the `info/thumbnail_info` subobject: contents of +//! thumbnail field, in the same vein as for the main image: +//! - payloadSize +//! - mimeType +//! - imageSize +using ImageContent = UrlBasedContent; + +//! \brief Content class for m.file +//! +//! Available fields: +//! - corresponding to the top-level JSON: +//! - source (corresponding to `url` or `file` in JSON) +//! - filename +//! - corresponding to the `info` subobject: +//! - payloadSize (`size` in JSON) +//! - mimeType (`mimetype` in JSON) +//! - thumbnail.source (`thumbnail_url` or `thumbnail_file` in JSON) +//! - corresponding to the `info/thumbnail_info` subobject: +//! - thumbnail.payloadSize +//! - thumbnail.mimeType +//! - thumbnail.imageSize (QSize for `h` and `w` in JSON) +using FileContent = UrlBasedContent; +} // namespace Quotient::EventContent Q_DECLARE_METATYPE(const Quotient::EventContent::TypedBase*) -- cgit v1.2.3 From a329538c617bad01ffd35a83a0b684f90993dd0d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 26 Jul 2022 14:26:05 +0200 Subject: Add missing QUOTIENT_API pieces The upcoming event type infrastructure finally helps to detect those omissions more or less reliably (for event types only though). --- lib/events/redactionevent.h | 2 +- lib/events/roomcanonicalaliasevent.h | 2 +- lib/events/simplestateevents.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/events') diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index 1c486a44..63617e54 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -6,7 +6,7 @@ #include "roomevent.h" namespace Quotient { -class RedactionEvent : public RoomEvent { +class QUOTIENT_API RedactionEvent : public RoomEvent { public: DEFINE_EVENT_TYPEID("m.room.redaction", RedactionEvent) diff --git a/lib/events/roomcanonicalaliasevent.h b/lib/events/roomcanonicalaliasevent.h index e599699d..60ca68ac 100644 --- a/lib/events/roomcanonicalaliasevent.h +++ b/lib/events/roomcanonicalaliasevent.h @@ -31,7 +31,7 @@ inline auto toJson(const EventContent::AliasesEventContent& c) return jo; } -class RoomCanonicalAliasEvent +class QUOTIENT_API RoomCanonicalAliasEvent : public StateEvent { public: DEFINE_EVENT_TYPEID("m.room.canonical_alias", RoomCanonicalAliasEvent) diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 33221542..43c1da69 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -34,7 +34,7 @@ DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", QStringList, pinnedEvents) constexpr auto RoomAliasesEventKey = "aliases"_ls; -class [[deprecated( +class QUOTIENT_API [[deprecated( "m.room.aliases events are deprecated by the Matrix spec; use" " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases")]] // RoomAliasesEvent -- cgit v1.2.3 From d422b1fd87c48572ae1fc162ce239196e3254752 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 26 Jul 2022 16:56:41 +0200 Subject: Hopefully fix building with GCC The last commit broke it. --- lib/events/simplestateevents.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/events') diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 43c1da69..c6b91931 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -34,10 +34,10 @@ DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", QStringList, pinnedEvents) constexpr auto RoomAliasesEventKey = "aliases"_ls; -class QUOTIENT_API [[deprecated( +class [[deprecated( "m.room.aliases events are deprecated by the Matrix spec; use" " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases")]] // -RoomAliasesEvent +QUOTIENT_API RoomAliasesEvent : public StateEvent< EventContent::SingleKeyValue> { -- cgit v1.2.3 From 7754947d7f758ddcb31d4ff3ea79435fb1c171e9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 26 Jul 2022 17:18:36 +0200 Subject: Another fix attempt --- lib/events/simplestateevents.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'lib/events') diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index c6b91931..46e1b3a7 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -34,13 +34,12 @@ DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", QStringList, pinnedEvents) constexpr auto RoomAliasesEventKey = "aliases"_ls; -class [[deprecated( +class Q_DECL_DEPRECATED_X( "m.room.aliases events are deprecated by the Matrix spec; use" - " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases")]] // -QUOTIENT_API RoomAliasesEvent + " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases") + QUOTIENT_API RoomAliasesEvent : public StateEvent< - EventContent::SingleKeyValue> -{ + EventContent::SingleKeyValue> { public: DEFINE_EVENT_TYPEID("m.room.aliases", RoomAliasesEvent) explicit RoomAliasesEvent(const QJsonObject& obj) -- cgit v1.2.3 From 36344a6e0283f924b72cb2b25001bdf212a7e707 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 26 Jul 2022 23:06:50 +0200 Subject: ...and the definitive fix --- lib/events/simplestateevents.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'lib/events') diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 46e1b3a7..a8eaab56 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -34,10 +34,7 @@ DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", QStringList, pinnedEvents) constexpr auto RoomAliasesEventKey = "aliases"_ls; -class Q_DECL_DEPRECATED_X( - "m.room.aliases events are deprecated by the Matrix spec; use" - " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases") - QUOTIENT_API RoomAliasesEvent +class QUOTIENT_API RoomAliasesEvent : public StateEvent< EventContent::SingleKeyValue> { public: @@ -45,7 +42,13 @@ public: explicit RoomAliasesEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} + Q_DECL_DEPRECATED_X( + "m.room.aliases events are deprecated by the Matrix spec; use" + " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases") QString server() const { return stateKey(); } + Q_DECL_DEPRECATED_X( + "m.room.aliases events are deprecated by the Matrix spec; use" + " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases") QStringList aliases() const { return content().value; } }; } // namespace Quotient -- 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 +++++++++++++++--------------- lib/events/event.h | 29 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 15 deletions(-) (limited to 'lib/events') 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 } diff --git a/lib/events/event.h b/lib/events/event.h index da6cf3c7..043d4f54 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -332,6 +332,13 @@ inline bool isUnknown(const Event& e) return e.type() == UnknownEventTypeId; } +//! \brief Cast the event pointer down in a type-safe way +//! +//! Checks that the event \p eptr points to actually is of the requested type +//! and returns a (plain) pointer to the event downcast to that type. \p eptr +//! can be either "dumb" (BaseEventT*) or "smart" (`event_ptr_tt<>`). This +//! overload doesn't affect the event ownership - if the original pointer owns +//! the event it must outlive the downcast pointer to keep it from dangling. template inline auto eventCast(const BasePtrT& eptr) -> decltype(static_cast(&*eptr)) @@ -341,6 +348,28 @@ inline auto eventCast(const BasePtrT& eptr) : nullptr; } +//! \brief Cast the event pointer down in a type-safe way, with moving +//! +//! Checks that the event \p eptr points to actually is of the requested type; +//! if (and only if) it is, releases the pointer, downcasts it to the requested +//! event type and returns a new smart pointer wrapping the downcast one. +//! Unlike the non-moving eventCast() overload, this one only accepts a smart +//! pointer, and that smart pointer should be an rvalue (either a temporary, +//! or as a result of std::move()). The ownership, respectively, is transferred +//! to the new pointer; the original smart pointer is reset to nullptr, as is +//! normal for `unique_ptr<>::release()`. +//! \note If \p eptr's event type does not match \p EventT it retains ownership +//! after calling this overload; if it is a temporary, this normally +//! leads to the event getting deleted along with the end of +//! the temporary's lifetime. +template +inline auto eventCast(event_ptr_tt&& eptr) +{ + return eptr && is>(*eptr) + ? event_ptr_tt(static_cast(eptr.release())) + : nullptr; +} + namespace _impl { template concept Invocable_With_Downcast = -- cgit v1.2.3 From d4b8f54f764bec5758c8f672d4ab05d59e02c269 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 30 Jul 2022 07:37:15 +0200 Subject: moving eventCast(): disallow passing nullptr This is aligned with the non-moving version. --- lib/events/event.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index 043d4f54..b7454337 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -343,9 +343,9 @@ template inline auto eventCast(const BasePtrT& eptr) -> decltype(static_cast(&*eptr)) { - Q_ASSERT(eptr); - return is>(*eptr) ? static_cast(&*eptr) - : nullptr; + return eptr && is>(*eptr) + ? static_cast(&*eptr) + : nullptr; } //! \brief Cast the event pointer down in a type-safe way, with moving -- cgit v1.2.3 From 150df8ce08976ac7b8c25dbed1b965b7c2f65249 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 1 Aug 2022 12:11:58 +0200 Subject: Pull out common JsonConverter code to JsonObjectUnpacker --- lib/converters.h | 20 ++++++++++++-------- lib/events/eventloader.h | 11 ++++------- 2 files changed, 16 insertions(+), 15 deletions(-) (limited to 'lib/events') diff --git a/lib/converters.h b/lib/converters.h index bf456af9..c1d1e9f9 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -31,6 +31,17 @@ struct JsonObjectConverter { template PodT fromJson(const JsonT&); +template +struct JsonObjectUnpacker { + // By default, revert to fromJson() so that one could provide a single + // fromJson specialisation instead of specialising + // the entire JsonConverter; if a different type of JSON value is needed + // (e.g., an array), specialising JsonConverter is inevitable + static T load(QJsonValueRef jvr) { return fromJson(QJsonValue(jvr)); } + static T load(const QJsonValue& jv) { return fromJson(jv.toObject()); } + static T load(const QJsonDocument& jd) { return fromJson(jd.object()); } +}; + //! \brief The switchboard for extra conversion algorithms behind from/toJson //! //! This template is mainly intended for partial conversion specialisations @@ -47,7 +58,7 @@ PodT fromJson(const JsonT&); //! that they are not supported and it's not feasible to support those by means //! of overloading toJson() and specialising fromJson(). template -struct JsonConverter { +struct JsonConverter : JsonObjectUnpacker { // Unfortunately, if constexpr doesn't work with dump() and T::toJson // because trying to check invocability of T::toJson hits a hard // (non-SFINAE) compilation error if the member is not there. Hence a bit @@ -77,13 +88,6 @@ struct JsonConverter { return pod; } } - // By default, revert to fromJson() so that one could provide a single - // fromJson specialisation instead of specialising - // the entire JsonConverter; if a different type of JSON value is needed - // (e.g., an array), specialising JsonConverter is inevitable - static T load(QJsonValueRef jvr) { return fromJson(QJsonValue(jvr)); } - static T load(const QJsonValue& jv) { return fromJson(jv.toObject()); } - static T load(const QJsonDocument& jd) { return fromJson(jd.object()); } }; template diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index c7b82e8e..7dde9786 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -48,14 +48,11 @@ inline StateEventPtr loadStateEvent(const QString& matrixType, } template -struct JsonConverter> { - static auto load(const QJsonValue& jv) +struct JsonConverter> + : JsonObjectUnpacker> { + static auto load(const QJsonObject& jo) { - return loadEvent(jv.toObject()); - } - static auto load(const QJsonDocument& jd) - { - return loadEvent(jd.object()); + return loadEvent(jo); } }; } // namespace Quotient -- cgit v1.2.3 From 88dcdd3ef0af4030a963b08bf8c5bff0784c320d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 1 Aug 2022 13:51:58 +0200 Subject: Fix FTBFS --- lib/converters.h | 1 + lib/events/eventloader.h | 2 ++ 2 files changed, 3 insertions(+) (limited to 'lib/events') diff --git a/lib/converters.h b/lib/converters.h index c1d1e9f9..688f7bbd 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -74,6 +74,7 @@ struct JsonConverter : JsonObjectUnpacker { } } + using JsonObjectUnpacker::load; static T load(const QJsonObject& jo) { // 'else' below are required to suppress code generation for unused diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index 7dde9786..15271ab1 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -50,9 +50,11 @@ inline StateEventPtr loadStateEvent(const QString& matrixType, template struct JsonConverter> : JsonObjectUnpacker> { + using JsonObjectUnpacker>::load; static auto load(const QJsonObject& jo) { return loadEvent(jo); } }; + } // namespace Quotient -- cgit v1.2.3 From 3214feeb031fa231c7c42c21c53410302966e32e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 29 Jul 2022 18:13:14 +0200 Subject: eventloader.h: use basicJson() in a uniform way There's no particular reason the order of parameters in StateEventBase::basicJson() should be as it was, and (the only) loadStateEvent() usage in room.cpp suggests the unified order is more convenient. Besides, this order is aligned with that in the StateEventBase constructor. --- lib/events/eventloader.h | 35 ++++++++++------------------------- lib/events/stateevent.cpp | 2 +- lib/events/stateevent.h | 8 ++++---- lib/room.cpp | 5 +++-- 4 files changed, 18 insertions(+), 32 deletions(-) (limited to 'lib/events') diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index 15271ab1..4c639efa 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -19,32 +19,17 @@ inline event_ptr_tt loadEvent(const QJsonObject& fullJson) return doLoadEvent(fullJson, fullJson[TypeKeyL].toString()); } -/*! Create an event from a type string and content JSON - * - * Use this factory template to resolve the C++ type from the Matrix - * type string in \p matrixType and create an event of that type that has - * its content part set to \p content. - */ -template -inline event_ptr_tt loadEvent(const QString& matrixType, - const QJsonObject& content) -{ - return doLoadEvent(Event::basicJson(matrixType, content), - matrixType); -} - -/*! Create a state event from a type string, content JSON and state key - * - * Use this factory to resolve the C++ type from the Matrix type string - * in \p matrixType and create a state event of that type with content part - * set to \p content and state key set to \p stateKey (empty by default). - */ -inline StateEventPtr loadStateEvent(const QString& matrixType, - const QJsonObject& content, - const QString& stateKey = {}) +//! \brief Create an event from a type string and content JSON +//! +//! Use this template to resolve the C++ type from the Matrix type string in +//! \p matrixType and create an event of that type by passing all parameters +//! to BaseEventT::basicJson(). +template +inline event_ptr_tt loadEvent( + const QString& matrixType, const BasicJsonParamTs&... basicJsonParams) { - return doLoadEvent( - StateEventBase::basicJson(matrixType, content, stateKey), matrixType); + return doLoadEvent( + BaseEventT::basicJson(matrixType, basicJsonParams...), matrixType); } template diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 43dfd6e8..1df24df0 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -16,7 +16,7 @@ StateEventBase::StateEventBase(Type type, const QJsonObject& json) StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, const QString& stateKey, const QJsonObject& contentJson) - : RoomEvent(type, basicJson(matrixType, contentJson, stateKey)) + : RoomEvent(type, basicJson(type, stateKey, contentJson)) {} bool StateEventBase::repeatsState() const diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 343e87a5..9f1d7118 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -19,12 +19,12 @@ public: //! Make a minimal correct Matrix state event JSON static QJsonObject basicJson(const QString& matrixTypeId, - const QJsonObject& content, - const QString& stateKey = {}) + const QString& stateKey = {}, + const QJsonObject& contentJson = {}) { return { { TypeKey, matrixTypeId }, { StateKeyKey, stateKey }, - { ContentKey, content } }; + { ContentKey, contentJson } }; } bool isStateEvent() const override { return true; } @@ -41,7 +41,7 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, const QJsonObject& content, const QString& stateKey = {}) { - return StateEventBase::basicJson(matrixTypeId, content, stateKey); + return StateEventBase::basicJson(matrixTypeId, stateKey, content); } //! \brief Override RoomEvent factory with that from StateEventBase if JSON has diff --git a/lib/room.cpp b/lib/room.cpp index f5d8709b..7fd41a4f 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -219,8 +219,9 @@ public: // In the absence of a real event, make a stub as-if an event // with empty content has been received. Event classes should be // prepared for empty/invalid/malicious content anyway. - stubbedState.emplace(evtKey, loadStateEvent(evtKey.first, {}, - evtKey.second)); + stubbedState.emplace(evtKey, + loadEvent(evtKey.first, + evtKey.second)); qCDebug(STATE) << "A new stub event created for key {" << evtKey.first << evtKey.second << "}"; qCDebug(STATE) << "Stubbed state size:" << stubbedState.size(); -- 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 +++++++++++++++++------------------ lib/events/roomkeyevent.cpp | 21 +++++++++------------ lib/events/roomkeyevent.h | 3 +-- 3 files changed, 27 insertions(+), 32 deletions(-) (limited to 'lib/events') 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; diff --git a/lib/events/roomkeyevent.cpp b/lib/events/roomkeyevent.cpp index 68962950..3a8601d1 100644 --- a/lib/events/roomkeyevent.cpp +++ b/lib/events/roomkeyevent.cpp @@ -5,21 +5,18 @@ using namespace Quotient; -RoomKeyEvent::RoomKeyEvent(const QJsonObject &obj) : Event(typeId(), obj) +RoomKeyEvent::RoomKeyEvent(const QJsonObject &obj) : Event(TypeId, obj) { if (roomId().isEmpty()) qCWarning(E2EE) << "Room key event has empty room id"; } -RoomKeyEvent::RoomKeyEvent(const QString& algorithm, const QString& roomId, const QString& sessionId, const QString& sessionKey, const QString& senderId) - : Event(typeId(), { - {"content", QJsonObject{ - {"algorithm", algorithm}, - {"room_id", roomId}, - {"session_id", sessionId}, - {"session_key", sessionKey}, - }}, - {"sender", senderId}, - {"type", "m.room_key"}, - }) +RoomKeyEvent::RoomKeyEvent(const QString& algorithm, const QString& roomId, + const QString& sessionId, const QString& sessionKey) + : Event(TypeId, basicJson(TypeId, { + { "algorithm", algorithm }, + { "room_id", roomId }, + { "session_id", sessionId }, + { "session_key", sessionKey }, + })) {} diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 9eb2854b..0dfdf383 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -13,8 +13,7 @@ public: explicit RoomKeyEvent(const QJsonObject& obj); explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, - const QString& sessionId, const QString& sessionKey, - const QString& senderId); + const QString& sessionId, const QString& sessionKey); QUO_CONTENT_GETTER(QString, algorithm) QUO_CONTENT_GETTER(QString, roomId) -- 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 ++++++++++++---------------- lib/connection.h | 4 +- lib/events/keyverificationevent.h | 77 ++++++++++++++++- lib/keyverificationsession.cpp | 173 ++++++++++++++------------------------ lib/keyverificationsession.h | 8 +- 5 files changed, 198 insertions(+), 191 deletions(-) (limited to 'lib/events') 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 diff --git a/lib/connection.h b/lib/connection.h index 3a4ee798..5fdc525d 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -336,8 +336,8 @@ public: bool hasOlmSession(const QString& user, const QString& deviceId) const; // This assumes that an olm session already exists. If it doesn't, no message is sent. - void sendToDevice(const QString& userId, const QString& deviceId, - event_ptr_tt event, bool encrypted); + void sendToDevice(const QString& targetUserId, const QString& targetDeviceId, + Event event, bool encrypted); /// Returns true if this megolm session comes from a verified device bool isVerifiedSession(const QString& megolmSessionId) const; diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index cdbd5d74..f635d07b 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -18,6 +18,16 @@ public: explicit KeyVerificationRequestEvent(const QJsonObject& obj) : Event(TypeId, obj) {} + KeyVerificationRequestEvent(const QString& transactionId, + const QString& fromDevice, + const QStringList& methods, + const QDateTime& timestamp) + : KeyVerificationRequestEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId }, + { "from_device"_ls, fromDevice }, + { "methods"_ls, toJson(methods) }, + { "timestamp"_ls, toJson(timestamp) } })) + {} /// The device ID which is initiating the request. QUO_CONTENT_GETTER(QString, fromDevice) @@ -44,6 +54,14 @@ public: explicit KeyVerificationReadyEvent(const QJsonObject& obj) : Event(TypeId, obj) {} + KeyVerificationReadyEvent(const QString& transactionId, + const QString& fromDevice, + const QStringList& methods) + : KeyVerificationReadyEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId }, + { "from_device"_ls, fromDevice }, + { "methods"_ls, toJson(methods) } })) + {} /// The device ID which is accepting the request. QUO_CONTENT_GETTER(QString, fromDevice) @@ -62,9 +80,23 @@ class QUOTIENT_API KeyVerificationStartEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent) - explicit KeyVerificationStartEvent(const QJsonObject &obj) + explicit KeyVerificationStartEvent(const QJsonObject& obj) : Event(TypeId, obj) {} + KeyVerificationStartEvent(const QString& transactionId, + const QString& fromDevice) + : KeyVerificationStartEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId }, + { "from_device"_ls, fromDevice }, + { "method"_ls, SasV1Method }, + { "hashes"_ls, QJsonArray{ "sha256"_ls } }, + { "key_agreement_protocols"_ls, + QJsonArray{ "curve25519-hkdf-sha256"_ls } }, + { "message_authentication_codes"_ls, + QJsonArray{ "hkdf-hmac-sha256"_ls } }, + { "short_authentication_string"_ls, + QJsonArray{ "decimal"_ls, "emoji"_ls } } })) + {} /// The device ID which is initiating the process. QUO_CONTENT_GETTER(QString, fromDevice) @@ -125,6 +157,18 @@ public: explicit KeyVerificationAcceptEvent(const QJsonObject& obj) : Event(TypeId, obj) {} + KeyVerificationAcceptEvent(const QString& transactionId, + const QString& commitment) + : KeyVerificationAcceptEvent(basicJson( + TypeId, { { "transaction_id"_ls, transactionId }, + { "method"_ls, SasV1Method }, + { "key_agreement_protocol"_ls, "curve25519-hkdf-sha256" }, + { "hash"_ls, "sha256" }, + { "message_authentication_code"_ls, "hkdf-hmac-sha256" }, + { "short_authentication_string"_ls, + QJsonArray{ "decimal"_ls, "emoji"_ls, } }, + { "commitment"_ls, commitment } })) + {} /// An opaque identifier for the verification process. QUO_CONTENT_GETTER(QString, transactionId) @@ -161,9 +205,18 @@ class QUOTIENT_API KeyVerificationCancelEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent) - explicit KeyVerificationCancelEvent(const QJsonObject &obj) + explicit KeyVerificationCancelEvent(const QJsonObject& obj) : Event(TypeId, obj) {} + KeyVerificationCancelEvent(const QString& transactionId, + const QString& reason) + : KeyVerificationCancelEvent( + basicJson(TypeId, { + { "transaction_id"_ls, transactionId }, + { "reason"_ls, reason }, + { "code"_ls, reason } // Not a typo + })) + {} /// An opaque identifier for the verification process. QUO_CONTENT_GETTER(QString, transactionId) @@ -183,9 +236,14 @@ class QUOTIENT_API KeyVerificationKeyEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent) - explicit KeyVerificationKeyEvent(const QJsonObject &obj) + explicit KeyVerificationKeyEvent(const QJsonObject& obj) : Event(TypeId, obj) {} + KeyVerificationKeyEvent(const QString& transactionId, const QString& key) + : KeyVerificationKeyEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId }, + { "key"_ls, key } })) + {} /// An opaque identifier for the verification process. QUO_CONTENT_GETTER(QString, transactionId) @@ -200,9 +258,16 @@ class QUOTIENT_API KeyVerificationMacEvent : public Event { public: DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent) - explicit KeyVerificationMacEvent(const QJsonObject &obj) + explicit KeyVerificationMacEvent(const QJsonObject& obj) : Event(TypeId, obj) {} + KeyVerificationMacEvent(const QString& transactionId, const QString& keys, + const QJsonObject& mac) + : KeyVerificationMacEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId }, + { "keys"_ls, keys }, + { "mac"_ls, mac } })) + {} /// An opaque identifier for the verification process. QUO_CONTENT_GETTER(QString, transactionId) @@ -224,6 +289,10 @@ public: explicit KeyVerificationDoneEvent(const QJsonObject& obj) : Event(TypeId, obj) {} + explicit KeyVerificationDoneEvent(const QString& transactionId) + : KeyVerificationDoneEvent( + basicJson(TypeId, { { "transaction_id"_ls, transactionId } })) + {} /// The same transactionId as before QUO_CONTENT_GETTER(QString, transactionId) diff --git a/lib/keyverificationsession.cpp b/lib/keyverificationsession.cpp index 1ee489ea..caf5071a 100644 --- a/lib/keyverificationsession.cpp +++ b/lib/keyverificationsession.cpp @@ -20,6 +20,19 @@ using namespace Quotient; using namespace std::chrono; +const QStringList supportedMethods = { SasV1Method }; + +QStringList commonSupportedMethods(const QStringList& remoteMethods) +{ + QStringList result; + for (const auto& method : remoteMethods) { + if (supportedMethods.contains(method)) { + result += method; + } + } + return result; +} + KeyVerificationSession::KeyVerificationSession( QString remoteUserId, const KeyVerificationRequestEvent& event, Connection* connection, bool encrypted) @@ -93,7 +106,7 @@ void KeyVerificationSession::init(milliseconds timeout) QTimer::singleShot(timeout, this, [this] { cancelVerification(TIMEOUT); }); - m_sas = olm_sas(new uint8_t[olm_sas_size()]); + m_sas = olm_sas(new std::byte[olm_sas_size()]); auto randomSize = olm_create_sas_random_length(m_sas); auto random = getRandom(randomSize); olm_create_sas(m_sas, random.data(), randomSize); @@ -104,7 +117,8 @@ void KeyVerificationSession::init(milliseconds timeout) KeyVerificationSession::~KeyVerificationSession() { - delete[] reinterpret_cast(m_sas); + olm_clear_sas(m_sas); + delete[] reinterpret_cast(m_sas); } void KeyVerificationSession::handleKey(const KeyVerificationKeyEvent& event) @@ -160,18 +174,22 @@ void KeyVerificationSession::handleKey(const KeyVerificationKeyEvent& event) emit keyReceived(); } -QByteArray KeyVerificationSession::macInfo(bool verifying, const QString& key) -{ - return (verifying ? "MATRIX_KEY_VERIFICATION_MAC%3%4%1%2%5%6"_ls : "MATRIX_KEY_VERIFICATION_MAC%1%2%3%4%5%6"_ls).arg(m_connection->userId()).arg(m_connection->deviceId()).arg(m_remoteUserId).arg(m_remoteDeviceId).arg(m_transactionId).arg(key).toLatin1(); -} - -QString KeyVerificationSession::calculateMac(const QString& input, bool verifying, const QString& keyId) +QString KeyVerificationSession::calculateMac(const QString& input, + bool verifying, + const QString& keyId) { QByteArray inputBytes = input.toLatin1(); QByteArray outputBytes(olm_sas_mac_length(m_sas), '\0'); - olm_sas_calculate_mac(m_sas, inputBytes.data(), inputBytes.size(), macInfo(verifying, keyId).data(), macInfo(verifying, keyId).size(), outputBytes.data(), outputBytes.size()); - auto output = QString(outputBytes); - return output.left(output.indexOf('=')); + const auto macInfo = + (verifying ? "MATRIX_KEY_VERIFICATION_MAC%3%4%1%2%5%6"_ls + : "MATRIX_KEY_VERIFICATION_MAC%1%2%3%4%5%6"_ls) + .arg(m_connection->userId(), m_connection->deviceId(), + m_remoteUserId, m_remoteDeviceId, m_transactionId, keyId) + .toLatin1(); + olm_sas_calculate_mac(m_sas, inputBytes.data(), inputBytes.size(), + macInfo.data(), macInfo.size(), outputBytes.data(), + outputBytes.size()); + return QString::fromLatin1(outputBytes.data(), outputBytes.indexOf('=')); } void KeyVerificationSession::sendMac() @@ -184,56 +202,37 @@ void KeyVerificationSession::sendMac() auto key = m_connection->olmAccount()->deviceKeys().keys[edKeyId]; mac[edKeyId] = calculateMac(key, false, edKeyId); - auto event = makeEvent(QJsonObject { - {"type", "m.key.verification.mac"}, - {"content", QJsonObject{ - {"transaction_id", m_transactionId}, - {"keys", keys}, - {"mac", mac}, - }} - }); - m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, + KeyVerificationMacEvent(m_transactionId, keys, + mac), + m_encrypted); setState (macReceived ? DONE : WAITINGFORMAC); } void KeyVerificationSession::sendDone() { - auto event = makeEvent(QJsonObject { - {"type", "m.key.verification.done"}, - {"content", QJsonObject{ - {"transaction_id", m_transactionId}, - }} - }); - m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, + KeyVerificationDoneEvent(m_transactionId), + m_encrypted); } void KeyVerificationSession::sendKey() { QByteArray keyBytes(olm_sas_pubkey_length(m_sas), '\0'); olm_sas_get_pubkey(m_sas, keyBytes.data(), keyBytes.size()); - QString key = QString(keyBytes); - auto event = makeEvent(QJsonObject { - {"type", "m.key.verification.key"}, - {"content", QJsonObject{ - {"transaction_id", m_transactionId}, - {"key", key}, - }} - }); - m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, + KeyVerificationKeyEvent(m_transactionId, + keyBytes), + m_encrypted); } void KeyVerificationSession::cancelVerification(Error error) { - auto event = makeEvent(QJsonObject { - {"type", "m.key.verification.cancel"}, - {"content", QJsonObject{ - {"code", errorToString(error)}, - {"reason", errorToString(error)}, - {"transaction_id", m_transactionId} - }} - }); - m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, + KeyVerificationCancelEvent(m_transactionId, + errorToString(error)), + m_encrypted); setState(CANCELED); setError(error); emit finished(); @@ -249,15 +248,11 @@ void KeyVerificationSession::sendReady() return; } - auto event = makeEvent(QJsonObject { - {"type", "m.key.verification.ready"}, - {"content", QJsonObject { - {"from_device", m_connection->deviceId()}, - {"methods", toJson(methods)}, - {"transaction_id", m_transactionId}, - }} - }); - m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + m_connection->sendToDevice( + m_remoteUserId, m_remoteDeviceId, + KeyVerificationReadyEvent(m_transactionId, m_connection->deviceId(), + methods), + m_encrypted); setState(READY); if (methods.size() == 1) { @@ -268,20 +263,10 @@ void KeyVerificationSession::sendReady() void KeyVerificationSession::sendStartSas() { startSentByUs = true; - auto event = makeEvent(QJsonObject { - {"type", "m.key.verification.start"}, - {"content", QJsonObject { - {"from_device", m_connection->deviceId()}, - {"hashes", QJsonArray {"sha256"}}, - {"key_agreement_protocols", QJsonArray { "curve25519-hkdf-sha256" }}, - {"message_authentication_codes", QJsonArray { "hkdf-hmac-sha256" }}, - {"method", "m.sas.v1"}, - {"short_authentication_string", QJsonArray { "decimal", "emoji" }}, - {"transaction_id", m_transactionId}, - }} - }); - m_startEvent = QJsonDocument(event->contentJson()).toJson(QJsonDocument::Compact); - m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + KeyVerificationStartEvent event(m_transactionId, m_connection->deviceId()); + m_startEvent = QJsonDocument(event.contentJson()).toJson(QJsonDocument::Compact); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, + std::move(event), m_encrypted); setState(WAITINGFORACCEPT); } @@ -324,22 +309,10 @@ void KeyVerificationSession::handleStart(const KeyVerificationStartEvent& event) auto commitment = QString(QCryptographicHash::hash((QString(publicKey) % canonicalEvent).toLatin1(), QCryptographicHash::Sha256).toBase64()); commitment = commitment.left(commitment.indexOf('=')); - auto acceptEvent = makeEvent(QJsonObject { - {"type", "m.key.verification.accept"}, - {"content", QJsonObject { - {"commitment", commitment}, - {"hash", "sha256"}, - {"key_agreement_protocol", "curve25519-hkdf-sha256"}, - {"message_authentication_code", "hkdf-hmac-sha256"}, - {"method", "m.sas.v1"}, - {"short_authentication_string", QJsonArray { - "decimal", - "emoji", - }}, - {"transaction_id", m_transactionId}, - }} - }); - m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(acceptEvent), m_encrypted); + m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, + KeyVerificationAcceptEvent(m_transactionId, + commitment), + m_encrypted); setState(ACCEPTED); } @@ -417,17 +390,12 @@ QList KeyVerificationSession::sasEmojis() const void KeyVerificationSession::sendRequest() { - QJsonArray methods = toJson(m_supportedMethods); - auto event = makeEvent(QJsonObject { - {"type", "m.key.verification.request"}, - {"content", QJsonObject { - {"from_device", m_connection->deviceId()}, - {"transaction_id", m_transactionId}, - {"methods", methods}, - {"timestamp", QDateTime::currentMSecsSinceEpoch()}, - }}, - }); - m_connection->sendToDevice(m_remoteUserId, m_remoteDeviceId, std::move(event), m_encrypted); + m_connection->sendToDevice( + m_remoteUserId, m_remoteDeviceId, + KeyVerificationRequestEvent(m_transactionId, m_connection->deviceId(), + supportedMethods, + QDateTime::currentDateTime()), + m_encrypted); setState(WAITINGFORREADY); } @@ -453,7 +421,7 @@ void KeyVerificationSession::setError(Error error) emit errorChanged(); } -QString KeyVerificationSession::errorToString(Error error) const +QString KeyVerificationSession::errorToString(Error error) { switch(error) { case NONE: @@ -485,7 +453,7 @@ QString KeyVerificationSession::errorToString(Error error) const } } -KeyVerificationSession::Error KeyVerificationSession::stringToError(const QString& error) const +KeyVerificationSession::Error KeyVerificationSession::stringToError(const QString& error) { if (error == "m.timeout"_ls) { return REMOTE_TIMEOUT; @@ -514,14 +482,3 @@ KeyVerificationSession::Error KeyVerificationSession::stringToError(const QStrin } return NONE; } - -QStringList KeyVerificationSession::commonSupportedMethods(const QStringList& remoteMethods) const -{ - QStringList result; - for (const auto& method : remoteMethods) { - if (m_supportedMethods.contains(method)) { - result += method; - } - } - return result; -} diff --git a/lib/keyverificationsession.h b/lib/keyverificationsession.h index 2756fa0a..73c9384e 100644 --- a/lib/keyverificationsession.h +++ b/lib/keyverificationsession.h @@ -128,10 +128,8 @@ private: void init(std::chrono::milliseconds timeout); void setState(State state); void setError(Error error); - QStringList commonSupportedMethods(const QStringList& remoteSupportedMethods) const; - QString errorToString(Error error) const; - Error stringToError(const QString& error) const; - QStringList m_supportedMethods = { "m.sas.v1"_ls }; + static QString errorToString(Error error); + static Error stringToError(const QString& error); QByteArray macInfo(bool verifying, const QString& key = "KEY_IDS"_ls); QString calculateMac(const QString& input, bool verifying, const QString& keyId= "KEY_IDS"_ls); @@ -139,4 +137,4 @@ private: std::pair emojiForCode(int code); }; -} +} // namespace Quotient -- cgit v1.2.3 From 5efbf75c8719407549e4bcfcf4c5329b37630990 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 3 Sep 2022 15:26:07 +0200 Subject: KeyVerificationDoneEvent: fix copy-pasta in DEFINE_EVENT_TYPEID --- lib/events/keyverificationevent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index f635d07b..5b587522 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -284,7 +284,7 @@ REGISTER_EVENT_TYPE(KeyVerificationMacEvent) class QUOTIENT_API KeyVerificationDoneEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.done", KeyVerificationRequestEvent) + DEFINE_EVENT_TYPEID("m.key.verification.done", KeyVerificationDoneEvent) explicit KeyVerificationDoneEvent(const QJsonObject& obj) : Event(TypeId, obj) -- cgit v1.2.3 From dbc78d185c4bafe03b458f9eeb7ef3af35ce2eb2 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 21 Jun 2022 07:33:43 +0200 Subject: SingleKeyValue: allow seamless construction from the underlying type SingleKeyValue is a tiny wrapper and supposed to be discreet. Having to explicitly (even if only with braces) construct its objects stands in the way of readability on the consuming side of the code and sometimes prevents direct initialisation of event objects without constructors getting some kind of ContentParamTs parameter pack where a single content_type argument would suffice otherwise. --- lib/events/single_key_value.h | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib/events') diff --git a/lib/events/single_key_value.h b/lib/events/single_key_value.h index 75ca8cd2..5edff3b1 100644 --- a/lib/events/single_key_value.h +++ b/lib/events/single_key_value.h @@ -7,6 +7,15 @@ namespace Quotient { namespace EventContent { template struct SingleKeyValue { + // NOLINTBEGIN(google-explicit-constructor): that check should learn + // about explicit(false) + QUO_IMPLICIT SingleKeyValue(const T& v = {}) + : value { v } + {} + QUO_IMPLICIT SingleKeyValue(T&& v) + : value { std::move(v) } + {} + // NOLINTEND(google-explicit-constructor) T value; }; } // namespace EventContent -- 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 --- CMakeLists.txt | 2 +- lib/connection.cpp | 2 +- lib/connection.h | 2 +- lib/events/accountdataevents.h | 18 +++++++++++------ lib/events/event.h | 46 ++++++++++++++++++++++-------------------- lib/events/reactionevent.h | 17 ++-------------- lib/events/typingevent.cpp | 11 ---------- lib/events/typingevent.h | 10 +-------- lib/room.cpp | 2 +- quotest/quotest.cpp | 3 ++- 10 files changed, 45 insertions(+), 68 deletions(-) delete mode 100644 lib/events/typingevent.cpp (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index f8667645..06e754aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,7 +154,7 @@ list(APPEND lib_SRCS lib/events/roomcanonicalaliasevent.h lib/events/roomavatarevent.h lib/events/roompowerlevelsevent.h lib/events/roompowerlevelsevent.cpp - lib/events/typingevent.h lib/events/typingevent.cpp + lib/events/typingevent.h lib/events/accountdataevents.h lib/events/receiptevent.h lib/events/receiptevent.cpp lib/events/reactionevent.h 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) diff --git a/lib/connection.h b/lib/connection.h index 39921938..66ed8b68 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -111,7 +111,7 @@ auto defaultUserFactory(Connection* c, const QString& id) // are stored with no regard to their state. using DirectChatsMap = QMultiHash; using DirectChatUsersMap = QMultiHash; -using IgnoredUsersList = IgnoredUsersEvent::content_type; +using IgnoredUsersList = IgnoredUsersEvent::value_type; class QUOTIENT_API Connection : public QObject { Q_OBJECT diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 24c3353c..324ce449 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -4,7 +4,6 @@ #pragma once #include "event.h" -#include "util.h" namespace Quotient { constexpr auto FavouriteTag [[maybe_unused]] = "m.favourite"_ls; @@ -46,14 +45,21 @@ struct JsonObjectConverter { using TagsMap = QHash; -DEFINE_SIMPLE_EVENT(TagEvent, Event, "m.tag", TagsMap, tags) -DEFINE_SIMPLE_EVENT(ReadMarkerEventImpl, Event, "m.fully_read", QString, eventId) +DEFINE_SIMPLE_EVENT(TagEvent, Event, "m.tag", TagsMap, tags, "tags") +DEFINE_SIMPLE_EVENT(ReadMarkerEventImpl, Event, "m.fully_read", QString, + eventId, "event_id") class ReadMarkerEvent : public ReadMarkerEventImpl { public: using ReadMarkerEventImpl::ReadMarkerEventImpl; [[deprecated("Use ReadMarkerEvent::eventId() instead")]] - QString event_id() const { return eventId(); } + auto event_id() const { return eventId(); } +}; +DEFINE_SIMPLE_EVENT(IgnoredUsersEventImpl, Event, "m.ignored_user_list", + QSet, ignoredUsers, "ignored_users") +class IgnoredUsersEvent : public IgnoredUsersEventImpl { +public: + using IgnoredUsersEventImpl::IgnoredUsersEventImpl; + [[deprecated("Use IgnoredUsersEvent::ignoredUsers() instead")]] + auto ignored_users() const { return ignoredUsers(); } }; -DEFINE_SIMPLE_EVENT(IgnoredUsersEvent, Event, "m.ignored_user_list", QSet, - ignored_users) } // namespace Quotient diff --git a/lib/events/event.h b/lib/events/event.h index b7454337..711f8fd4 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -6,6 +6,7 @@ #include "converters.h" #include "logging.h" #include "function_traits.h" +#include "single_key_value.h" namespace Quotient { // === event_ptr_tt<> and type casting facilities === @@ -258,6 +259,13 @@ template using EventsArray = std::vector>; using Events = EventsArray; +#define QUO_CONTENT_GETTER_X(PartType_, PartName_, JsonKey_) \ + PartType_ PartName_() const \ + { \ + static const auto PartName_##JsonKey = JsonKey_; \ + return contentPart(PartName_##JsonKey); \ + } + //! \brief Define an inline method obtaining a content part //! //! This macro adds a const method that extracts a JSON value at the key @@ -266,12 +274,8 @@ using Events = EventsArray; //! \code //! contentPart(Quotient::toSnakeCase(#PartName_##_ls)); //! \endcode -#define QUO_CONTENT_GETTER(PartType_, PartName_) \ - PartType_ PartName_() const \ - { \ - static const auto JsonKey = toSnakeCase(#PartName_##_ls); \ - return contentPart(JsonKey); \ - } +#define QUO_CONTENT_GETTER(PartType_, PartName_) \ + QUO_CONTENT_GETTER_X(PartType_, PartName_, toSnakeCase(#PartName_##_ls)) // === Facilities for event class definitions === @@ -301,22 +305,20 @@ using Events = EventsArray; /// To retrieve the value the getter uses a JSON key name that corresponds to /// its own (getter's) name but written in snake_case. \p GetterName_ must be /// in camelCase, no quotes (an identifier, not a literal). -#define DEFINE_SIMPLE_EVENT(Name_, Base_, TypeId_, ValueType_, GetterName_) \ - class QUOTIENT_API Name_ : public Base_ { \ - public: \ - using content_type = ValueType_; \ - DEFINE_EVENT_TYPEID(TypeId_, Name_) \ - explicit Name_(const QJsonObject& obj) : Base_(TypeId, obj) {} \ - explicit Name_(const content_type& content) \ - : Name_(Base_::basicJson(TypeId, { { JsonKey, toJson(content) } })) \ - {} \ - auto GetterName_() const \ - { \ - return contentPart(JsonKey); \ - } \ - static inline const auto JsonKey = toSnakeCase(#GetterName_##_ls); \ - }; \ - REGISTER_EVENT_TYPE(Name_) \ +#define DEFINE_SIMPLE_EVENT(Name_, Base_, TypeId_, ValueType_, GetterName_, \ + JsonKey_) \ + class QUOTIENT_API Name_ : public Base_ { \ + public: \ + DEFINE_EVENT_TYPEID(TypeId_, Name_) \ + using value_type = ValueType_; \ + explicit Name_(const QJsonObject& obj) : Base_(TypeId, obj) {} \ + explicit Name_(const value_type& v) \ + : Name_(Base_::basicJson(TypeId, { { JsonKey, toJson(v) } })) \ + {} \ + QUO_CONTENT_GETTER_X(ValueType_, GetterName_, JsonKey) \ + static inline const auto JsonKey = toSnakeCase(#GetterName_##_ls); \ + }; \ + REGISTER_EVENT_TYPE(Name_) \ // End of macro // === is<>(), eventCast<>() and switchOnType<>() === diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index b3cb3ca7..8d873441 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -8,20 +8,7 @@ namespace Quotient { -class QUOTIENT_API ReactionEvent : public RoomEvent { -public: - DEFINE_EVENT_TYPEID("m.reaction", ReactionEvent) - - explicit ReactionEvent(const EventRelation& value) - : RoomEvent(typeId(), matrixTypeId(), - { { QStringLiteral("m.relates_to"), toJson(value) } }) - {} - explicit ReactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) {} - EventRelation relation() const - { - return contentPart(RelatesToKey); - } -}; -REGISTER_EVENT_TYPE(ReactionEvent) +DEFINE_SIMPLE_EVENT(ReactionEvent, RoomEvent, "m.reaction", EventRelation, + relation, "m.relates_to") } // namespace Quotient diff --git a/lib/events/typingevent.cpp b/lib/events/typingevent.cpp deleted file mode 100644 index 7e5d7ee6..00000000 --- a/lib/events/typingevent.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "typingevent.h" - -using namespace Quotient; - -QStringList TypingEvent::users() const -{ - return contentPart("user_ids"_ls); -} diff --git a/lib/events/typingevent.h b/lib/events/typingevent.h index 522f7e42..b56475af 100644 --- a/lib/events/typingevent.h +++ b/lib/events/typingevent.h @@ -6,13 +6,5 @@ #include "event.h" namespace Quotient { -class QUOTIENT_API TypingEvent : public Event { -public: - DEFINE_EVENT_TYPEID("m.typing", TypingEvent) - - explicit TypingEvent(const QJsonObject& obj) : Event(typeId(), obj) {} - - QStringList users() const; -}; -REGISTER_EVENT_TYPE(TypingEvent) +DEFINE_SIMPLE_EVENT(TypingEvent, Event, "m.typing", QStringList, users, "user_ids") } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index ebc6e6af..4cae2333 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -3294,7 +3294,7 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event) } if (auto* evt = eventCast(event)) - changes |= d->setFullyReadMarker(evt->event_id()); + changes |= d->setFullyReadMarker(evt->eventId()); // For all account data events auto& currentData = d->accountData[event->matrixType()]; diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index b6c855bb..3860ae1e 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -524,7 +524,8 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, return true; } -DEFINE_SIMPLE_EVENT(CustomEvent, RoomEvent, "quotest.custom", int, testValue) +DEFINE_SIMPLE_EVENT(CustomEvent, RoomEvent, "quotest.custom", int, testValue, + "test_value") TEST_IMPL(sendCustomEvent) { -- cgit v1.2.3 From a18f505fe7ca66556d66538a7c9b9ff31d2c1b29 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 9 Aug 2022 18:42:24 +0200 Subject: EventMetaType, QUO_EVENT, QUO_BASE_EVENT The new metatype framework replaces EventFactory/DEFINE_EVENT_TYPEID/REGISTER_EVENT_TYPE; it is faster, more functional and extensible. Of note: - EventMetaType mostly reproduces the logic of EventFactory but supports custom base event types not just for loading (that part EventFactory also supported) but also for matching - previously you had to have Event::is*Event() for base type matching. Now Quotient::is() can match against both base and leaf types. - Instead of DEFINE_EVENT_TYPEID and REGISTER_EVENT_TYPE there's now a single macro, QUO_EVENT, intended for use in the way similar to Q_OBJECT. Actually, the entire framework borrows heavily from QMetaObject and Q_OBJECT. Making event types full-fledged QObjects is still not considered because half of QObject functions would not be applicable (e.g. signals/slots) while another half (in particular, using Matrix type ids to select event types) would still have to be done on top of QObject. And QML can just access events as const QJsonObjects which is arguably more lightweight as well. - QUO_BASE_EVENT is a new macro replacing EventFactory object definitions. This was necessary for the same reason why Q_OBJECT is a macro: aside from a static object definition, this macro introduces a virtual function override to resolve the metatype at runtime. This very mechanism is used to make event type matching/casting as quick as possible - QUO_BASE_EVENT and QUO_EVENT use the C++20 __VA_OPT__ feature that is only available with the new MSVC preprocessor (see https://docs.microsoft.com/en-us/cpp/preprocessor/preprocessor-experimental-overview); the respective switch was added to CMakeLists.txt. --- CMakeLists.txt | 2 +- lib/events/callanswerevent.h | 3 +- lib/events/callcandidatesevent.h | 4 +- lib/events/callhangupevent.h | 4 +- lib/events/callinviteevent.h | 4 +- lib/events/directchatevent.h | 3 +- lib/events/encryptedevent.h | 4 +- lib/events/encryptionevent.h | 3 +- lib/events/event.cpp | 41 +++- lib/events/event.h | 355 ++++++++++++++++++++++++----------- lib/events/keyverificationevent.h | 26 +-- lib/events/receiptevent.h | 3 +- lib/events/redactionevent.h | 3 +- lib/events/roomavatarevent.h | 3 +- lib/events/roomcanonicalaliasevent.h | 3 +- lib/events/roomcreateevent.h | 3 +- lib/events/roomevent.h | 16 +- lib/events/roomkeyevent.h | 3 +- lib/events/roommemberevent.h | 12 +- lib/events/roommessageevent.h | 4 +- lib/events/roompowerlevelsevent.h | 3 +- lib/events/roomtombstoneevent.h | 3 +- lib/events/simplestateevents.h | 5 +- lib/events/stateevent.h | 30 +-- lib/events/stickerevent.h | 3 +- 25 files changed, 326 insertions(+), 217 deletions(-) (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 06e754aa..9983f860 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ endif(CMAKE_BUILD_TYPE) message(STATUS "Using compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}" ) include(CheckCXXCompilerFlag) if (MSVC) - add_compile_options(/EHsc /W4 + add_compile_options(/Zc:preprocessor /EHsc /W4 /wd4100 /wd4127 /wd4242 /wd4244 /wd4245 /wd4267 /wd4365 /wd4456 /wd4459 /wd4464 /wd4505 /wd4514 /wd4571 /wd4619 /wd4623 /wd4625 /wd4626 /wd4706 /wd4710 /wd4774 /wd4820 /wd4946 /wd5026 /wd5027) diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index 4d539b85..70ca1c7e 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -9,7 +9,7 @@ namespace Quotient { class QUOTIENT_API CallAnswerEvent : public CallEventBase { public: - DEFINE_EVENT_TYPEID("m.call.answer", CallAnswerEvent) + QUO_EVENT(CallAnswerEvent, "m.call.answer") explicit CallAnswerEvent(const QJsonObject& obj); @@ -20,5 +20,4 @@ public: return contentPart("answer"_ls).value("sdp"_ls).toString(); } }; -REGISTER_EVENT_TYPE(CallAnswerEvent) } // namespace Quotient diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h index e949f722..cb96f358 100644 --- a/lib/events/callcandidatesevent.h +++ b/lib/events/callcandidatesevent.h @@ -11,7 +11,7 @@ namespace Quotient { class CallCandidatesEvent : public CallEventBase { public: - DEFINE_EVENT_TYPEID("m.call.candidates", CallCandidatesEvent) + QUO_EVENT(CallCandidatesEvent, "m.call.candidates") explicit CallCandidatesEvent(const QJsonObject& obj) : CallEventBase(typeId(), obj) @@ -27,6 +27,4 @@ public: QUO_CONTENT_GETTER(QString, callId) QUO_CONTENT_GETTER(int, version) }; - -REGISTER_EVENT_TYPE(CallCandidatesEvent) } // namespace Quotient diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index b0017c59..e4d9bb78 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -9,7 +9,7 @@ namespace Quotient { class QUOTIENT_API CallHangupEvent : public CallEventBase { public: - DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent) + QUO_EVENT(CallHangupEvent, "m.call.hangup") explicit CallHangupEvent(const QJsonObject& obj) : CallEventBase(typeId(), obj) @@ -18,6 +18,4 @@ public: : CallEventBase(typeId(), matrixTypeId(), callId, 0) {} }; - -REGISTER_EVENT_TYPE(CallHangupEvent) } // namespace Quotient diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index 5b4ca0df..f96f416d 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -9,7 +9,7 @@ namespace Quotient { class QUOTIENT_API CallInviteEvent : public CallEventBase { public: - DEFINE_EVENT_TYPEID("m.call.invite", CallInviteEvent) + QUO_EVENT(CallInviteEvent, "m.call.invite") explicit CallInviteEvent(const QJsonObject& obj); @@ -22,6 +22,4 @@ public: return contentPart("offer"_ls).value("sdp"_ls).toString(); } }; - -REGISTER_EVENT_TYPE(CallInviteEvent) } // namespace Quotient diff --git a/lib/events/directchatevent.h b/lib/events/directchatevent.h index 2018d3d6..942edba4 100644 --- a/lib/events/directchatevent.h +++ b/lib/events/directchatevent.h @@ -8,11 +8,10 @@ namespace Quotient { class QUOTIENT_API DirectChatEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.direct", DirectChatEvent) + QUO_EVENT(DirectChatEvent, "m.direct") explicit DirectChatEvent(const QJsonObject& obj) : Event(typeId(), obj) {} QMultiHash usersToDirectChats() const; }; -REGISTER_EVENT_TYPE(DirectChatEvent) } // namespace Quotient diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index ddd5e415..22e51cb8 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -27,7 +27,7 @@ namespace Quotient { */ class QUOTIENT_API EncryptedEvent : public RoomEvent { public: - DEFINE_EVENT_TYPEID("m.room.encrypted", EncryptedEvent) + QUO_EVENT(EncryptedEvent, "m.room.encrypted") /* In case with Olm, the encrypted content of the event is * a map from the recipient Curve25519 identity key to ciphertext @@ -59,6 +59,4 @@ public: void setRelation(const QJsonObject& relation); }; -REGISTER_EVENT_TYPE(EncryptedEvent) - } // namespace Quotient diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 91452c3f..60e77451 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -28,7 +28,7 @@ public: class QUOTIENT_API EncryptionEvent : public StateEvent { public: - DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent) + QUO_EVENT(EncryptionEvent, "m.room.encryption") using EncryptionType [[deprecated("Use Quotient::EncryptionType instead")]] = @@ -48,5 +48,4 @@ public: bool useEncryption() const { return !algorithm().isEmpty(); } }; -REGISTER_EVENT_TYPE(EncryptionEvent) } // namespace Quotient diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 1f1eebaa..595e20a5 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -4,6 +4,7 @@ #include "event.h" #include "logging.h" +#include "stateevent.h" #include @@ -11,12 +12,38 @@ using namespace Quotient; QString EventTypeRegistry::getMatrixType(event_type_t typeId) { return typeId; } -void _impl::EventFactoryBase::logAddingMethod(event_type_t TypeId, - size_t newSize) +void AbstractEventMetaType::addDerived(AbstractEventMetaType* newType) { - qDebug(EVENTS) << "Adding factory method for" << TypeId << "events;" - << newSize << "methods will be in the" << name - << "chain"; + if (const auto existing = + std::find_if(derivedTypes.cbegin(), derivedTypes.cend(), + [&newType](const AbstractEventMetaType* t) { + return t->matrixId == newType->matrixId; + }); + existing != derivedTypes.cend()) + { + if (*existing == newType) + return; + // Two different metatype objects claim the same Matrix type id; this + // is not normal, so give as much information as possible to diagnose + if ((*existing)->className == newType->className) { + qCritical(EVENTS) + << newType->className << "claims" << newType->matrixId + << "repeatedly; check that it's exported across translation " + "units or shared objects"; + Q_ASSERT(false); // That situation is plain wrong + return; // So maybe std::terminate() even? + } + qWarning(EVENTS).nospace() + << newType->matrixId << " is already mapped to " + << (*existing)->className << " before " << newType->className + << "; unless the two have different isValid() conditions, the " + "latter class will never be used"; + } + derivedTypes.emplace_back(newType); + qDebug(EVENTS).nospace() + << newType->matrixId << " -> " << newType->className << "; " + << derivedTypes.size() << " derived type(s) registered for " + << className; } Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json) @@ -48,6 +75,10 @@ const QJsonObject Event::unsignedJson() const return fullJson()[UnsignedKeyL].toObject(); } +bool Event::isStateEvent() const { return is(); } + +bool Event::isCallEvent() const { return is(); } + void Event::dumpTo(QDebug dbg) const { dbg << QJsonDocument(contentJson()).toJson(QJsonDocument::Compact); diff --git a/lib/events/event.h b/lib/events/event.h index 711f8fd4..ea827244 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -85,91 +85,167 @@ inline event_ptr_tt makeEvent(ArgTs&&... args) return std::make_unique(std::forward(args)...); } -namespace _impl { - class QUOTIENT_API EventFactoryBase { - public: - EventFactoryBase(const EventFactoryBase&) = delete; - - protected: // This class is only to inherit from - explicit EventFactoryBase(const char* name) - : name(name) - {} - void logAddingMethod(event_type_t TypeId, size_t newSize); - - private: - const char* const name; - }; -} // namespace _impl - -//! \brief A family of event factories to create events from CS API responses -//! -//! Each of these factories, as instantiated by event base types (Event, -//! RoomEvent etc.) is capable of producing an event object derived from -//! \p BaseEventT, using the JSON payload and the event type passed to its -//! make() method. Don't use these directly to make events; use loadEvent() -//! overloads as the frontend for these. Never instantiate new factories -//! outside of base event classes. -//! \sa loadEvent, setupFactory, Event::factory, RoomEvent::factory, -//! StateEventBase::factory -template -class EventFactory : public _impl::EventFactoryBase { -private: - using method_t = event_ptr_tt (*)(const QJsonObject&, - const QString&); - std::vector methods {}; +// === EventMetaType === - template - static event_ptr_tt makeIfMatches(const QJsonObject& json, - const QString& matrixType) +class Event; + +template +bool is(const Event& e); + +//! \brief The base class for event metatypes +//! +//! You should not normally have to use this directly, unless you need to devise +//! a whole new kind of event metatypes. +class QUOTIENT_API AbstractEventMetaType { +public: + // The public fields here are const and are not to be changeable anyway. + // NOLINTBEGIN(misc-non-private-member-variables-in-classes) + const char* const className; + const event_type_t matrixId; + const AbstractEventMetaType* const baseType = nullptr; + // NOLINTEND(misc-non-private-member-variables-in-classes) + + explicit AbstractEventMetaType(const char* className) + : className(className) + {} + explicit AbstractEventMetaType(const char* className, event_type_t matrixId, + AbstractEventMetaType& nearestBase) + : className(className), matrixId(matrixId), baseType(&nearestBase) { - // If your matrix event type is not all ASCII, it's your problem - // (see https://github.com/matrix-org/matrix-doc/pull/2758) - return EventT::TypeId == matrixType ? makeEvent(json) : nullptr; + nearestBase.addDerived(this); } + void addDerived(AbstractEventMetaType *newType); + + virtual ~AbstractEventMetaType() = default; + +protected: + // Allow template specialisations to call into one another + template + friend class EventMetaType; + + // The returned value indicates whether a generic object has to be created + // on the top level when `event` is empty, instead of returning nullptr + virtual bool doLoadFrom(const QJsonObject& fullJson, const QString& type, + Event*& event) const = 0; + +private: + std::vector derivedTypes{}; + Q_DISABLE_COPY_MOVE(AbstractEventMetaType) +}; + +// Any event metatype is unique (note Q_DISABLE_COPY_MOVE above) so can be +// identified by its address +inline bool operator==(const AbstractEventMetaType& lhs, + const AbstractEventMetaType& rhs) +{ + return &lhs == &rhs; +} + +//! \brief A family of event meta-types to load and match events +//! +//! TL;DR for the loadFrom() story: +//! - for base event types, use QUO_BASE_EVENT and, if you have additional +//! validation (e.g., JSON has to contain a certain key - see StateEventBase +//! for a real example), define it in the static EventT::isValid() member +//! function accepting QJsonObject and returning bool. +//! - for leaf (specific) event types - simply use QUO_EVENT and it will do +//! everything necessary, including the TypeId definition. +//! \sa QUO_EVENT, QUO_BASE_EVENT +template +class QUOTIENT_API EventMetaType : public AbstractEventMetaType { + // Above: can't constrain EventT to be EventClass because it's incomplete + // at the point of EventMetaType instantiation. public: - explicit EventFactory(const char* fName) - : EventFactoryBase { fName } - {} + using AbstractEventMetaType::AbstractEventMetaType; - //! \brief Add a method to create events of a given type + //! \brief Try to load an event from JSON, with dynamic type resolution //! - //! Adds a standard factory method (makeIfMatches) for \p EventT so that - //! event objects of this type can be created dynamically by loadEvent. - //! The caller is responsible for ensuring this method is called only - //! once per type. - //! \sa loadEvent, Quotient::loadEvent - template - const auto& addMethod() + //! The generic logic defined in this class template and invoked applies to + //! all event types defined in the library and boils down to the following: + //! 1. + //! a. If EventT has TypeId defined (which normally is a case of + //! all leaf - specific - event types, via QUO_EVENT macro) and + //! \p type doesn't exactly match it, nullptr is immediately returned. + //! b. In absence of TypeId, an event type is assumed to be a base; + //! its derivedTypes are examined, and this algorithm is applied + //! recursively on each. + //! 2. Optional validation: if EventT (or, due to the way inheritance works, + //! any of its base event types) has a static isValid() predicate and + //! the event JSON does not satisfy it, nullptr is immediately returned + //! to the upper level or to the loadFrom() caller. This is how existence + //! of `state_key` is checked in any type derived from StateEventBase. + //! 3. If step 1b above returned non-nullptr, immediately return it. + //! 4. + //! a. If EventT::isValid() or EventT::TypeId (either, or both) exist and + //! are satisfied (see steps 1a and 2 above), an object of this type + //! is created from the passed JSON and returned. In case of a base + //! event type, this will be a generic (aka "unknown") event. + //! b. If neither exists, a generic event is only created and returned + //! when on the top level (i.e., outside of recursion into + //! derivedTypes); lower levels return nullptr instead and the type + //! lookup continues. The latter is a case of a derived base event + //! metatype (e.g. RoomEvent) called from its base event metatype + //! (i.e., Event). If no matching type derived from RoomEvent is found, + //! the nested lookup returns nullptr rather than a generic RoomEvent, + //! so that other types derived from Event could be examined. + event_ptr_tt loadFrom(const QJsonObject& fullJson, + const QString& type) const { - const auto m = &makeIfMatches; - const auto it = std::find(methods.cbegin(), methods.cend(), m); - if (it != methods.cend()) - return *it; - logAddingMethod(EventT::TypeId, methods.size() + 1); - return methods.emplace_back(m); + Event* event = nullptr; + const bool goodEnough = doLoadFrom(fullJson, type, event); + if (!event && goodEnough) + return event_ptr_tt{ makeEvent(fullJson) }; + return event_ptr_tt{ static_cast(event) }; } - auto loadEvent(const QJsonObject& json, const QString& matrixType) +private: + bool doLoadFrom(const QJsonObject& fullJson, const QString& type, + Event*& event) const override + { + if constexpr (requires { EventT::TypeId; }) { + if (EventT::TypeId != type) + return false; + } else { + for (const auto& p : derivedTypes) { + p->doLoadFrom(fullJson, type, event); + if (event) { + Q_ASSERT(is(*event)); + return false; + } + } + } + if constexpr (requires { EventT::isValid; }) { + if (!EventT::isValid(fullJson)) + return false; + } else if constexpr (!requires { EventT::TypeId; }) + return true; // Create a generic event object if on the top level + event = makeEvent(fullJson); + return false; + } + static auto makeEvent(const QJsonObject& fullJson) { - for (const auto& f : methods) - if (auto e = f(json, matrixType)) - return e; - return makeEvent(UnknownEventTypeId, json); + if constexpr (requires { EventT::TypeId; }) + return new EventT(fullJson); + else + return new EventT(UnknownEventTypeId, fullJson); } }; -//! \brief Point of customisation to dynamically load events -//! -//! The default specialisation of this calls BaseEventT::factory.loadEvent() -//! and if that fails (i.e. returns nullptr) creates an unknown event of -//! BaseEventT. Other specialisations may reuse other factories, add validations -//! common to BaseEventT events, and so on. -template -event_ptr_tt doLoadEvent(const QJsonObject& json, - const QString& matrixType) +template +constexpr const auto& mostSpecificMetaType() { - return BaseEventT::factory.loadEvent(json, matrixType); + if constexpr (requires { EventT::MetaType; }) + return EventT::MetaType; + else + return EventT::BaseMetaType; +} + +template +inline event_ptr_tt doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + return mostSpecificMetaType().loadFrom(json, matrixType); } // === Event === @@ -177,7 +253,12 @@ event_ptr_tt doLoadEvent(const QJsonObject& json, class QUOTIENT_API Event { public: using Type = event_type_t; - static inline EventFactory factory { "Event" }; + static inline EventMetaType BaseMetaType { "Event" }; + virtual const AbstractEventMetaType& metaType() const + { + return BaseMetaType; + } + explicit Event(Type type, const QJsonObject& json); explicit Event(Type type, event_mtype_t matrixType, @@ -194,8 +275,26 @@ public: return { { TypeKey, matrixType }, { ContentKey, content } }; } - Type type() const { return _type; } + //! \brief Event Matrix type, as identified by its metatype object + //! + //! For generic/unknown events it will contain a descriptive/generic string + //! defined by the respective base event type (that can be empty). + //! \sa matrixType + Type type() const { return metaType().matrixId; } + + //! \brief Exact Matrix type stored in JSON + //! + //! Coincides with the result of type() (but is slower) for events defined + //! in C++ (not necessarily in the library); for generic/unknown events + //! the returned value will be different. QString matrixType() const; + + template + bool is() const + { + return Quotient::is(*this); + } + [[deprecated("Use fullJson() and stringify it with QJsonDocument::toJson() " "or by other means")]] QByteArray originalJson() const; @@ -237,13 +336,17 @@ public: friend QUOTIENT_API QDebug operator<<(QDebug dbg, const Event& e) { QDebugStateSaver _dss { dbg }; - dbg.noquote().nospace() << e.matrixType() << '(' << e.type() << "): "; + dbg.noquote().nospace() + << e.matrixType() << '(' << e.metaType().className << "): "; e.dumpTo(dbg); return dbg; } - virtual bool isStateEvent() const { return false; } - virtual bool isCallEvent() const { return false; } + // State events are quite special in Matrix; so isStateEvent() is here, + // as an exception. For other base events, Event::is<>() and + // Quotient::is<>() should be used; don't add is* methods here + bool isStateEvent() const; + [[deprecated("Use is() instead")]] bool isCallEvent() const; protected: QJsonObject& editJson() { return _json; } @@ -259,11 +362,64 @@ template using EventsArray = std::vector>; using Events = EventsArray; +// === Facilities for event class definitions === + +//! \brief Supply event metatype information in base event types +//! +//! Use this macro in a public section of your base event class to provide +//! type identity and enable dynamic loading of generic events of that type. +//! Do _not_ add this macro if your class is an intermediate wrapper and is not +//! supposed to be instantiated on its own. Provides BaseMetaType static field +//! initialised by parameters passed to the macro, and a metaType() override +//! pointing to that BaseMetaType. +//! \sa EventMetaType, EventMetaType::SuppressLoadDerived +#define QUO_BASE_EVENT(CppType_, ...) \ + static inline EventMetaType BaseMetaType{ \ + #CppType_ __VA_OPT__(,) __VA_ARGS__ }; \ + const AbstractEventMetaType& metaType() const override \ + { \ + return BaseMetaType; \ + } \ + // End of macro + +//! Supply event metatype information in (specific) event types +//! +//! Use this macro in a public section of your event class to provide type +//! identity and enable dynamic loading of generic events of that type. +//! Do _not_ use this macro if your class is an intermediate wrapper and is not +//! supposed to be instantiated on its own. Provides MetaType static field +//! initialised as described below; a metaType() override pointing to it; and +//! the TypeId static field that is equal to MetaType.matrixId. +//! +//! The first two macro parameters are used as the first two EventMetaType +//! constructor parameters; the third EventMetaType parameter is always +//! BaseMetaType; and additional base types can be passed in extra macro +//! parameters if you need to include the same event type in more than one +//! event factory hierarchy (e.g., EncryptedEvent). +//! \sa EventMetaType +#define QUO_EVENT(CppType_, MatrixType_, ...) \ + static inline const auto& TypeId = MatrixType_##_ls; \ + static inline const EventMetaType MetaType{ \ + #CppType_, TypeId, BaseMetaType __VA_OPT__(,) __VA_ARGS__ \ + }; \ + const AbstractEventMetaType& metaType() const override \ + { \ + return MetaType; \ + } \ + [[deprecated("Use " #CppType_ "::TypeId directly instead")]] \ + static constexpr const char* matrixTypeId() { return MatrixType_; } \ + [[deprecated("Use " #CppType_ "::TypeId directly instead")]] \ + static event_type_t typeId() { return TypeId; } \ + // End of macro + +//! \deprecated This is the old name for what is now known as QUO_EVENT +#define DEFINE_EVENT_TYPEID(Type_, Id_) QUO_EVENT(Type_, Id_) + #define QUO_CONTENT_GETTER_X(PartType_, PartName_, JsonKey_) \ - PartType_ PartName_() const \ - { \ - static const auto PartName_##JsonKey = JsonKey_; \ - return contentPart(PartName_##JsonKey); \ + PartType_ PartName_() const \ + { \ + static const auto PartName_##JsonKey = JsonKey_; \ + return contentPart(PartName_##JsonKey); \ } //! \brief Define an inline method obtaining a content part @@ -277,25 +433,9 @@ using Events = EventsArray; #define QUO_CONTENT_GETTER(PartType_, PartName_) \ QUO_CONTENT_GETTER_X(PartType_, PartName_, toSnakeCase(#PartName_##_ls)) -// === Facilities for event class definitions === - -// This macro should be used in a public section of an event class to -// provide matrixTypeId() and typeId(). -#define DEFINE_EVENT_TYPEID(Id_, Type_) \ - static constexpr event_type_t TypeId = Id_##_ls; \ - [[deprecated("Use " #Type_ "::TypeId directly instead")]] \ - static constexpr event_mtype_t matrixTypeId() { return Id_; } \ - [[deprecated("Use " #Type_ "::TypeId directly instead")]] \ - static event_type_t typeId() { return TypeId; } \ - // End of macro - -// This macro should be put after an event class definition (in .h or .cpp) -// to enable its deserialisation from a /sync and other -// polymorphic event arrays -#define REGISTER_EVENT_TYPE(Type_) \ - [[maybe_unused]] inline const auto& factoryMethodFor##Type_ = \ - Type_::factory.addMethod(); \ - // End of macro +//! \deprecated This macro was used after an event class definition +//! to enable its dynamic loading; it is completely superseded by QUO_EVENT +#define REGISTER_EVENT_TYPE(Type_) /// \brief Define a new event class with a single key-value pair in the content /// @@ -309,7 +449,7 @@ using Events = EventsArray; JsonKey_) \ class QUOTIENT_API Name_ : public Base_ { \ public: \ - DEFINE_EVENT_TYPEID(TypeId_, Name_) \ + QUO_EVENT(Name_, TypeId_) \ using value_type = ValueType_; \ explicit Name_(const QJsonObject& obj) : Base_(TypeId, obj) {} \ explicit Name_(const value_type& v) \ @@ -318,7 +458,6 @@ using Events = EventsArray; QUO_CONTENT_GETTER_X(ValueType_, GetterName_, JsonKey) \ static inline const auto JsonKey = toSnakeCase(#GetterName_##_ls); \ }; \ - REGISTER_EVENT_TYPE(Name_) \ // End of macro // === is<>(), eventCast<>() and switchOnType<>() === @@ -326,12 +465,16 @@ using Events = EventsArray; template inline bool is(const Event& e) { - return e.type() == typeId(); -} - -inline bool isUnknown(const Event& e) -{ - return e.type() == UnknownEventTypeId; + if constexpr (requires { EventT::MetaType; }) { + return &e.metaType() == &EventT::MetaType; + } else { + const auto* p = &e.metaType(); + do { + if (p == &EventT::BaseMetaType) + return true; + } while ((p = p->baseType) != nullptr); + return false; + } } //! \brief Cast the event pointer down in a type-safe way diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index 5b587522..5b5a518f 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -13,7 +13,7 @@ static constexpr auto SasV1Method = "m.sas.v1"_ls; /// Typically sent as a to-device event. class QUOTIENT_API KeyVerificationRequestEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.request", KeyVerificationRequestEvent) + QUO_EVENT(KeyVerificationRequestEvent, "m.key.verification.request") explicit KeyVerificationRequestEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -45,11 +45,10 @@ public: /// by the receiver. QUO_CONTENT_GETTER(QDateTime, timestamp) }; -REGISTER_EVENT_TYPE(KeyVerificationRequestEvent) class QUOTIENT_API KeyVerificationReadyEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.ready", KeyVerificationReadyEvent) + QUO_EVENT(KeyVerificationReadyEvent, "m.key.verification.ready") explicit KeyVerificationReadyEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -72,13 +71,11 @@ public: /// The verification methods supported by the sender. QUO_CONTENT_GETTER(QStringList, methods) }; -REGISTER_EVENT_TYPE(KeyVerificationReadyEvent) - /// Begins a key verification process. class QUOTIENT_API KeyVerificationStartEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent) + QUO_EVENT(KeyVerificationStartEvent, "m.key.verification.start") explicit KeyVerificationStartEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -146,13 +143,12 @@ public: return contentPart("short_authentification_string"_ls); } }; -REGISTER_EVENT_TYPE(KeyVerificationStartEvent) /// Accepts a previously sent m.key.verification.start message. /// Typically sent as a to-device event. class QUOTIENT_API KeyVerificationAcceptEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.accept", KeyVerificationAcceptEvent) + QUO_EVENT(KeyVerificationAcceptEvent, "m.key.verification.accept") explicit KeyVerificationAcceptEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -199,11 +195,10 @@ public: /// canonical JSON representation of the m.key.verification.start message. QUO_CONTENT_GETTER(QString, commitment) }; -REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent) class QUOTIENT_API KeyVerificationCancelEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent) + QUO_EVENT(KeyVerificationCancelEvent, "m.key.verification.cancel") explicit KeyVerificationCancelEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -228,13 +223,12 @@ public: /// The error code for why the process/request was cancelled by the user. QUO_CONTENT_GETTER(QString, code) }; -REGISTER_EVENT_TYPE(KeyVerificationCancelEvent) /// Sends the ephemeral public key for a device to the partner device. /// Typically sent as a to-device event. class QUOTIENT_API KeyVerificationKeyEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent) + QUO_EVENT(KeyVerificationKeyEvent, "m.key.verification.key") explicit KeyVerificationKeyEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -251,12 +245,11 @@ public: /// The device's ephemeral public key, encoded as unpadded base64. QUO_CONTENT_GETTER(QString, key) }; -REGISTER_EVENT_TYPE(KeyVerificationKeyEvent) /// Sends the MAC of a device's key to the partner device. class QUOTIENT_API KeyVerificationMacEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent) + QUO_EVENT(KeyVerificationMacEvent, "m.key.verification.mac") explicit KeyVerificationMacEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -280,11 +273,10 @@ public: return contentPart>("mac"_ls); } }; -REGISTER_EVENT_TYPE(KeyVerificationMacEvent) class QUOTIENT_API KeyVerificationDoneEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.key.verification.done", KeyVerificationDoneEvent) + QUO_EVENT(KeyVerificationDoneEvent, "m.key.verification.done") explicit KeyVerificationDoneEvent(const QJsonObject& obj) : Event(TypeId, obj) @@ -297,6 +289,4 @@ public: /// The same transactionId as before QUO_CONTENT_GETTER(QString, transactionId) }; -REGISTER_EVENT_TYPE(KeyVerificationDoneEvent) - } // namespace Quotient diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index 5e077e47..a02f4592 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -21,11 +21,10 @@ using EventsWithReceipts = QVector; class QUOTIENT_API ReceiptEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent) + QUO_EVENT(ReceiptEvent, "m.receipt") explicit ReceiptEvent(const EventsWithReceipts& ewrs); explicit ReceiptEvent(const QJsonObject& obj) : Event(typeId(), obj) {} EventsWithReceipts eventsWithReceipts() const; }; -REGISTER_EVENT_TYPE(ReceiptEvent) } // namespace Quotient diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index 63617e54..c193054a 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -8,7 +8,7 @@ namespace Quotient { class QUOTIENT_API RedactionEvent : public RoomEvent { public: - DEFINE_EVENT_TYPEID("m.room.redaction", RedactionEvent) + QUO_EVENT(RedactionEvent, "m.room.redaction") explicit RedactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) {} @@ -19,5 +19,4 @@ public: } QUO_CONTENT_GETTER(QString, reason) }; -REGISTER_EVENT_TYPE(RedactionEvent) } // namespace Quotient diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index af291696..2ebe29bf 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -14,7 +14,7 @@ class QUOTIENT_API RoomAvatarEvent // without a thumbnail. But The Spec says there be thumbnails, and // we follow The Spec. public: - DEFINE_EVENT_TYPEID("m.room.avatar", RoomAvatarEvent) + QUO_EVENT(RoomAvatarEvent, "m.room.avatar") explicit RoomAvatarEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} explicit RoomAvatarEvent(const EventContent::ImageContent& avatar) @@ -31,5 +31,4 @@ public: QUrl url() const { return content().url(); } }; -REGISTER_EVENT_TYPE(RoomAvatarEvent) } // namespace Quotient diff --git a/lib/events/roomcanonicalaliasevent.h b/lib/events/roomcanonicalaliasevent.h index 60ca68ac..e1c7888e 100644 --- a/lib/events/roomcanonicalaliasevent.h +++ b/lib/events/roomcanonicalaliasevent.h @@ -34,7 +34,7 @@ inline auto toJson(const EventContent::AliasesEventContent& c) class QUOTIENT_API RoomCanonicalAliasEvent : public StateEvent { public: - DEFINE_EVENT_TYPEID("m.room.canonical_alias", RoomCanonicalAliasEvent) + QUO_EVENT(RoomCanonicalAliasEvent, "m.room.canonical_alias") explicit RoomCanonicalAliasEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) @@ -55,5 +55,4 @@ public: QString alias() const { return content().canonicalAlias; } QStringList altAliases() const { return content().altAliases; } }; -REGISTER_EVENT_TYPE(RoomCanonicalAliasEvent) } // namespace Quotient diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index 989030ac..f22752b4 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -9,7 +9,7 @@ namespace Quotient { class QUOTIENT_API RoomCreateEvent : public StateEventBase { public: - DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent) + QUO_EVENT(RoomCreateEvent, "m.room.create") explicit RoomCreateEvent(const QJsonObject& obj) : StateEventBase(typeId(), obj) @@ -26,5 +26,4 @@ public: bool isUpgrade() const; RoomType roomType() const; }; -REGISTER_EVENT_TYPE(RoomCreateEvent) } // namespace Quotient diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 9461340b..532e72e2 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -10,10 +10,11 @@ namespace Quotient { class RedactionEvent; -/** This class corresponds to m.room.* events */ +// That check could look into Event and find most stuff already deleted... +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) class QUOTIENT_API RoomEvent : public Event { public: - static inline EventFactory factory { "RoomEvent" }; + QUO_BASE_EVENT(RoomEvent, {}, Event::BaseMetaType) // RedactionEvent is an incomplete type here so we cannot inline // constructors and destructors and we cannot use 'using'. @@ -80,21 +81,14 @@ using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; using RoomEventsRange = Range; -template <> -inline EventPtr doLoadEvent(const QJsonObject& json, const QString& matrixType) -{ - if (matrixType == "m.room.encrypted") - return RoomEvent::factory.loadEvent(json, matrixType); - return Event::factory.loadEvent(json, matrixType); -} - class QUOTIENT_API CallEventBase : public RoomEvent { public: + QUO_BASE_EVENT(CallEventBase, "m.call.*"_ls, RoomEvent::BaseMetaType) + CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, int version, const QJsonObject& contentJson = {}); CallEventBase(Type type, const QJsonObject& json); ~CallEventBase() override = default; - bool isCallEvent() const override { return true; } QUO_CONTENT_GETTER(QString, callId) QUO_CONTENT_GETTER(int, version) diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 0dfdf383..6883a2a5 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -9,7 +9,7 @@ namespace Quotient { class QUOTIENT_API RoomKeyEvent : public Event { public: - DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent) + QUO_EVENT(RoomKeyEvent, "m.room_key") explicit RoomKeyEvent(const QJsonObject& obj); explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, @@ -23,5 +23,4 @@ public: return contentPart("session_key"_ls).toLatin1(); } }; -REGISTER_EVENT_TYPE(RoomKeyEvent) } // namespace Quotient diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index dd33ea6b..c690586e 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -31,7 +31,7 @@ using MembershipType [[deprecated("Use Membership instead")]] = Membership; class QUOTIENT_API RoomMemberEvent : public StateEvent { Q_GADGET public: - DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent) + QUO_EVENT(RoomMemberEvent, "m.room.member") using MembershipType [[deprecated("Use Quotient::Membership instead")]] = Membership; @@ -79,14 +79,4 @@ public: bool isRename() const; bool isAvatarUpdate() const; }; - -template <> -inline event_ptr_tt -doLoadEvent(const QJsonObject& json, const QString& matrixType) -{ - if (matrixType == QLatin1String(RoomMemberEvent::matrixTypeId())) - return makeEvent(json); - return makeEvent(unknownEventTypeId(), json); -} -REGISTER_EVENT_TYPE(RoomMemberEvent) } // namespace Quotient diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 6968ad70..889fc4dc 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -20,7 +20,7 @@ namespace MessageEventContent = EventContent; // Back-compatibility class QUOTIENT_API RoomMessageEvent : public RoomEvent { Q_GADGET public: - DEFINE_EVENT_TYPEID("m.room.message", RoomMessageEvent) + QUO_EVENT(RoomMessageEvent, "m.room.message") enum class MsgType { Text, @@ -94,7 +94,7 @@ private: Q_ENUM(MsgType) }; -REGISTER_EVENT_TYPE(RoomMessageEvent) + using MessageEventType = RoomMessageEvent::MsgType; namespace EventContent { diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h index a1638a27..7ac12db0 100644 --- a/lib/events/roompowerlevelsevent.h +++ b/lib/events/roompowerlevelsevent.h @@ -33,7 +33,7 @@ struct QUOTIENT_API PowerLevelsEventContent { class QUOTIENT_API RoomPowerLevelsEvent : public StateEvent { public: - DEFINE_EVENT_TYPEID("m.room.power_levels", RoomPowerLevelsEvent) + QUO_EVENT(RoomPowerLevelsEvent, "m.room.power_levels") explicit RoomPowerLevelsEvent(PowerLevelsEventContent&& content) : StateEvent(typeId(), matrixTypeId(), QString(), std::move(content)) @@ -61,5 +61,4 @@ public: int powerLevelForState(const QString& eventId) const; int powerLevelForUser(const QString& userId) const; }; -REGISTER_EVENT_TYPE(RoomPowerLevelsEvent) } // namespace Quotient diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h index 15d26923..97586587 100644 --- a/lib/events/roomtombstoneevent.h +++ b/lib/events/roomtombstoneevent.h @@ -8,7 +8,7 @@ namespace Quotient { class QUOTIENT_API RoomTombstoneEvent : public StateEventBase { public: - DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent) + QUO_EVENT(RoomTombstoneEvent, "m.room.tombstone") explicit RoomTombstoneEvent(const QJsonObject& obj) : StateEventBase(typeId(), obj) @@ -17,5 +17,4 @@ public: QString serverMessage() const; QString successorRoomId() const; }; -REGISTER_EVENT_TYPE(RoomTombstoneEvent) } // namespace Quotient diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index a8eaab56..c79d03b0 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -14,7 +14,7 @@ namespace Quotient { EventContent::SingleKeyValue<_ValueType, &_Name##Key>> { \ public: \ using value_type = _ValueType; \ - DEFINE_EVENT_TYPEID(_TypeId, _Name) \ + QUO_EVENT(_Name, _TypeId) \ template \ explicit _Name(T&& value) \ : StateEvent(TypeId, matrixTypeId(), QString(), \ @@ -25,7 +25,6 @@ namespace Quotient { {} \ auto _ContentKey() const { return content().value; } \ }; \ - REGISTER_EVENT_TYPE(_Name) \ // End of macro DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) @@ -38,7 +37,7 @@ class QUOTIENT_API RoomAliasesEvent : public StateEvent< EventContent::SingleKeyValue> { public: - DEFINE_EVENT_TYPEID("m.room.aliases", RoomAliasesEvent) + QUO_EVENT(RoomAliasesEvent, "m.room.aliases") explicit RoomAliasesEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 9f1d7118..74876803 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -9,7 +9,12 @@ namespace Quotient { class QUOTIENT_API StateEventBase : public RoomEvent { public: - static inline EventFactory factory { "StateEvent" }; + QUO_BASE_EVENT(StateEventBase, "json.contains('state_key')"_ls, + RoomEvent::BaseMetaType) + static bool isValid(const QJsonObject& fullJson) + { + return fullJson.contains(StateKeyKeyL); + } StateEventBase(Type type, const QJsonObject& json); StateEventBase(Type type, event_mtype_t matrixType, @@ -27,7 +32,6 @@ public: { ContentKey, contentJson } }; } - bool isStateEvent() const override { return true; } QString replacedState() const; void dumpTo(QDebug dbg) const override; @@ -44,28 +48,6 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, return StateEventBase::basicJson(matrixTypeId, stateKey, content); } -//! \brief Override RoomEvent factory with that from StateEventBase if JSON has -//! stateKey -//! -//! This means in particular that an event with a type known to RoomEvent but -//! having stateKey set (even to an empty value) will be treated as a state -//! event and most likely end up as unknown (consider, e.g., m.room.message -//! that has stateKey set). -template <> -inline RoomEventPtr doLoadEvent(const QJsonObject& json, - const QString& matrixType) -{ - if (json.contains(StateKeyKeyL)) - return StateEventBase::factory.loadEvent(json, matrixType); - return RoomEvent::factory.loadEvent(json, matrixType); -} - -template <> -inline bool is(const Event& e) -{ - return e.isStateEvent(); -} - /** * A combination of event type and state key uniquely identifies a piece * of state in Matrix. diff --git a/lib/events/stickerevent.h b/lib/events/stickerevent.h index e378422d..67905481 100644 --- a/lib/events/stickerevent.h +++ b/lib/events/stickerevent.h @@ -14,7 +14,7 @@ namespace Quotient { class QUOTIENT_API StickerEvent : public RoomEvent { public: - DEFINE_EVENT_TYPEID("m.sticker", StickerEvent) + QUO_EVENT(StickerEvent, "m.sticker") explicit StickerEvent(const QJsonObject& obj) : RoomEvent(TypeId, obj) @@ -45,5 +45,4 @@ public: private: EventContent::ImageContent m_imageContent; }; -REGISTER_EVENT_TYPE(StickerEvent) } // namespace Quotient -- 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. --- CMakeLists.txt | 2 +- lib/connection.cpp | 1 - lib/events/callanswerevent.cpp | 15 +-- lib/events/callanswerevent.h | 5 +- lib/events/callcandidatesevent.h | 9 +- lib/events/callhangupevent.h | 12 +-- lib/events/callinviteevent.cpp | 9 +- lib/events/callinviteevent.h | 5 +- lib/events/directchatevent.h | 2 +- lib/events/encryptedevent.cpp | 22 ++--- lib/events/encryptionevent.h | 10 +- lib/events/event.cpp | 7 +- lib/events/event.h | 174 +++++++++++++++++++++++------------ lib/events/eventloader.h | 40 +------- lib/events/keyverificationevent.h | 32 ++----- lib/events/receiptevent.cpp | 16 ++-- lib/events/receiptevent.h | 13 ++- lib/events/redactionevent.h | 3 +- lib/events/roomavatarevent.h | 19 +--- lib/events/roomcanonicalaliasevent.h | 20 +--- lib/events/roomcreateevent.h | 4 +- lib/events/roomevent.cpp | 17 +--- lib/events/roomevent.h | 22 +++-- lib/events/roomkeyevent.cpp | 22 ----- lib/events/roomkeyevent.h | 11 ++- lib/events/roommemberevent.h | 22 +---- lib/events/roommessageevent.cpp | 7 +- lib/events/roompowerlevelsevent.h | 9 +- lib/events/roomtombstoneevent.h | 4 +- lib/events/simplestateevents.h | 42 ++++----- lib/events/stateevent.cpp | 14 ++- lib/events/stateevent.h | 115 ++++++++++++++--------- lib/room.cpp | 2 +- lib/roomstateview.h | 114 ++++++++++++++++++----- lib/syncdata.cpp | 2 - 35 files changed, 405 insertions(+), 418 deletions(-) delete mode 100644 lib/events/roomkeyevent.cpp (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 9983f860..d4cf52d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,7 +165,7 @@ list(APPEND lib_SRCS lib/events/directchatevent.h lib/events/directchatevent.cpp lib/events/encryptionevent.h lib/events/encryptionevent.cpp lib/events/encryptedevent.h lib/events/encryptedevent.cpp - lib/events/roomkeyevent.h lib/events/roomkeyevent.cpp + lib/events/roomkeyevent.h lib/events/stickerevent.h lib/events/filesourceinfo.h lib/events/filesourceinfo.cpp lib/jobs/requestdata.h lib/jobs/requestdata.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" diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp index f75f8ad3..89dcd7fd 100644 --- a/lib/events/callanswerevent.cpp +++ b/lib/events/callanswerevent.cpp @@ -26,16 +26,9 @@ m.call.answer using namespace Quotient; -CallAnswerEvent::CallAnswerEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) -{ - qCDebug(EVENTS) << "Call Answer event"; -} - CallAnswerEvent::CallAnswerEvent(const QString& callId, const QString& sdp) - : CallEventBase( - typeId(), matrixTypeId(), callId, 0, - { { QStringLiteral("answer"), - QJsonObject { { QStringLiteral("type"), QStringLiteral("answer") }, - { QStringLiteral("sdp"), sdp } } } }) + : EventTemplate(callId, { { QStringLiteral("answer"), + QJsonObject { { QStringLiteral("type"), + QStringLiteral("answer") }, + { QStringLiteral("sdp"), sdp } } } }) {} diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index 70ca1c7e..c5ad14df 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -7,11 +7,12 @@ #include "roomevent.h" namespace Quotient { -class QUOTIENT_API CallAnswerEvent : public CallEventBase { +class QUOTIENT_API CallAnswerEvent + : public EventTemplate { public: QUO_EVENT(CallAnswerEvent, "m.call.answer") - explicit CallAnswerEvent(const QJsonObject& obj); + using EventTemplate::EventTemplate; explicit CallAnswerEvent(const QString& callId, const QString& sdp); diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h index cb96f358..f5d2f815 100644 --- a/lib/events/callcandidatesevent.h +++ b/lib/events/callcandidatesevent.h @@ -9,18 +9,15 @@ #include "roomevent.h" namespace Quotient { -class CallCandidatesEvent : public CallEventBase { +class CallCandidatesEvent : public EventTemplate { public: QUO_EVENT(CallCandidatesEvent, "m.call.candidates") - explicit CallCandidatesEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) - {} + using EventTemplate::EventTemplate; explicit CallCandidatesEvent(const QString& callId, const QJsonArray& candidates) - : CallEventBase(typeId(), matrixTypeId(), callId, 0, - { { QStringLiteral("candidates"), candidates } }) + : EventTemplate(callId, { { QStringLiteral("candidates"), candidates } }) {} QUO_CONTENT_GETTER(QJsonArray, candidates) diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index e4d9bb78..f0b131b9 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -7,15 +7,11 @@ #include "roomevent.h" namespace Quotient { -class QUOTIENT_API CallHangupEvent : public CallEventBase { +class QUOTIENT_API CallHangupEvent + : public EventTemplate { public: QUO_EVENT(CallHangupEvent, "m.call.hangup") - - explicit CallHangupEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) - {} - explicit CallHangupEvent(const QString& callId) - : CallEventBase(typeId(), matrixTypeId(), callId, 0) - {} + using EventTemplate::EventTemplate; }; +//REGISTER_EVENT_TYPE(CallHangupEvent) } // namespace Quotient diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index 2f26a1cb..0232275b 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -27,16 +27,9 @@ m.call.invite using namespace Quotient; -CallInviteEvent::CallInviteEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) -{ - qCDebug(EVENTS) << "Call Invite event"; -} - CallInviteEvent::CallInviteEvent(const QString& callId, int lifetime, const QString& sdp) - : CallEventBase( - typeId(), matrixTypeId(), callId, 0, + : EventTemplate(callId, { { QStringLiteral("lifetime"), lifetime }, { QStringLiteral("offer"), QJsonObject { { QStringLiteral("type"), QStringLiteral("offer") }, diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index f96f416d..fc22f7e1 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -7,11 +7,12 @@ #include "roomevent.h" namespace Quotient { -class QUOTIENT_API CallInviteEvent : public CallEventBase { +class QUOTIENT_API CallInviteEvent + : public EventTemplate { public: QUO_EVENT(CallInviteEvent, "m.call.invite") - explicit CallInviteEvent(const QJsonObject& obj); + using EventTemplate::EventTemplate; explicit CallInviteEvent(const QString& callId, int lifetime, const QString& sdp); diff --git a/lib/events/directchatevent.h b/lib/events/directchatevent.h index 942edba4..0756d816 100644 --- a/lib/events/directchatevent.h +++ b/lib/events/directchatevent.h @@ -10,7 +10,7 @@ class QUOTIENT_API DirectChatEvent : public Event { public: QUO_EVENT(DirectChatEvent, "m.direct") - explicit DirectChatEvent(const QJsonObject& obj) : Event(typeId(), obj) {} + using Event::Event; QMultiHash usersToDirectChats() const; }; diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index ec00ad4c..c539d5b2 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -2,33 +2,29 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" -#include "roommessageevent.h" -#include "events/eventloader.h" using namespace Quotient; EncryptedEvent::EncryptedEvent(const QJsonObject& ciphertext, const QString& senderKey) - : RoomEvent(typeId(), matrixTypeId(), - { { AlgorithmKeyL, OlmV1Curve25519AesSha2AlgoKey }, + : RoomEvent({ { AlgorithmKeyL, OlmV1Curve25519AesSha2AlgoKey }, { CiphertextKeyL, ciphertext }, { SenderKeyKeyL, senderKey } }) {} EncryptedEvent::EncryptedEvent(QByteArray ciphertext, const QString& senderKey, const QString& deviceId, const QString& sessionId) - : RoomEvent(typeId(), matrixTypeId(), - { - { AlgorithmKeyL, MegolmV1AesSha2AlgoKey }, - { CiphertextKeyL, QString(ciphertext) }, - { DeviceIdKeyL, deviceId }, - { SenderKeyKeyL, senderKey }, - { SessionIdKeyL, sessionId }, - }) + : RoomEvent({ + { AlgorithmKeyL, MegolmV1AesSha2AlgoKey }, + { CiphertextKeyL, QString(ciphertext) }, + { DeviceIdKeyL, deviceId }, + { SenderKeyKeyL, senderKey }, + { SessionIdKeyL, sessionId }, + }) {} EncryptedEvent::EncryptedEvent(const QJsonObject& obj) - : RoomEvent(typeId(), obj) + : RoomEvent(obj) { qCDebug(E2EE) << "Encrypted event from" << senderId(); } diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 60e77451..4bf7459c 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -26,7 +26,8 @@ public: int rotationPeriodMsgs = 100; }; -class QUOTIENT_API EncryptionEvent : public StateEvent { +class QUOTIENT_API EncryptionEvent + : public KeylessStateEventBase { public: QUO_EVENT(EncryptionEvent, "m.room.encryption") @@ -34,12 +35,7 @@ public: [[deprecated("Use Quotient::EncryptionType instead")]] = Quotient::EncryptionType; - explicit EncryptionEvent(const QJsonObject& obj) - : StateEvent(typeId(), obj) - {} - explicit EncryptionEvent(EncryptionEventContent&& content) - : StateEvent(typeId(), matrixTypeId(), QString(), std::move(content)) - {} + using KeylessStateEventBase::KeylessStateEventBase; Quotient::EncryptionType encryption() const { return content().encryption; } QString algorithm() const { return content().algorithm; } diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 595e20a5..2843e1dc 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -46,7 +46,8 @@ void AbstractEventMetaType::addDerived(AbstractEventMetaType* newType) << className; } -Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json) +Event::Event(const QJsonObject& json) + : _json(json) { if (!json.contains(ContentKeyL) && !json.value(UnsignedKeyL).toObject().contains(RedactedCauseKeyL)) { @@ -55,10 +56,6 @@ Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json) } } -Event::Event(Type type, event_mtype_t matrixType, const QJsonObject& contentJson) - : Event(type, basicJson(matrixType, contentJson)) -{} - Event::~Event() = default; QString Event::matrixType() const { return fullJson()[TypeKeyL].toString(); } diff --git a/lib/events/event.h b/lib/events/event.h index ea827244..8a8d64b0 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -9,7 +9,7 @@ #include "single_key_value.h" namespace Quotient { -// === event_ptr_tt<> and type casting facilities === +// === event_ptr_tt<> and basic type casting facilities === template using event_ptr_tt = std::unique_ptr; @@ -49,42 +49,18 @@ const QString RoomIdKey { RoomIdKeyL }; const QString UnsignedKey { UnsignedKeyL }; const QString StateKeyKey { StateKeyKeyL }; -// === Event types === - using event_type_t = QLatin1String; -using event_mtype_t = const char*; - -class QUOTIENT_API EventTypeRegistry { -public: - ~EventTypeRegistry() = default; - [[deprecated("event_type_t is a string now, use it directly instead")]] +// TODO: Remove in 0.8 +struct QUOTIENT_API EventTypeRegistry { + [[deprecated("event_type_t is a string since libQuotient 0.7, use it directly instead")]] static QString getMatrixType(event_type_t typeId); -private: - EventTypeRegistry() = default; + EventTypeRegistry() = delete; + ~EventTypeRegistry() = default; Q_DISABLE_COPY_MOVE(EventTypeRegistry) }; -template -constexpr event_type_t typeId() -{ - return std::decay_t::TypeId; -} - -constexpr event_type_t UnknownEventTypeId = "?"_ls; -[[deprecated("Use UnknownEventTypeId")]] -constexpr event_type_t unknownEventTypeId() { return UnknownEventTypeId; } - -// === Event creation facilities === - -//! Create an event of arbitrary type from its arguments -template -inline event_ptr_tt makeEvent(ArgTs&&... args) -{ - return std::make_unique(std::forward(args)...); -} - // === EventMetaType === class Event; @@ -195,7 +171,7 @@ public: Event* event = nullptr; const bool goodEnough = doLoadFrom(fullJson, type, event); if (!event && goodEnough) - return event_ptr_tt{ makeEvent(fullJson) }; + return event_ptr_tt{ new EventT(fullJson) }; return event_ptr_tt{ static_cast(event) }; } @@ -220,18 +196,20 @@ private: return false; } else if constexpr (!requires { EventT::TypeId; }) return true; // Create a generic event object if on the top level - event = makeEvent(fullJson); + event = new EventT(fullJson); return false; } - static auto makeEvent(const QJsonObject& fullJson) - { - if constexpr (requires { EventT::TypeId; }) - return new EventT(fullJson); - else - return new EventT(UnknownEventTypeId, fullJson); - } }; +// === Event creation facilities === + +//! \brief Create an event of arbitrary type from its arguments +template +inline event_ptr_tt makeEvent(ArgTs&&... args) +{ + return std::make_unique(std::forward(args)...); +} + template constexpr const auto& mostSpecificMetaType() { @@ -241,13 +219,43 @@ constexpr const auto& mostSpecificMetaType() return EventT::BaseMetaType; } -template -inline event_ptr_tt doLoadEvent(const QJsonObject& json, - const QString& matrixType) +//! \brief Create an event with proper type from a JSON object +//! +//! Use this factory template to detect the type from the JSON object +//! contents (the detected event type should derive from the template +//! parameter type) and create an event object of that type. +template +inline event_ptr_tt loadEvent(const QJsonObject& fullJson) { - return mostSpecificMetaType().loadFrom(json, matrixType); + return mostSpecificMetaType().loadFrom( + fullJson, fullJson[TypeKeyL].toString()); } +//! \brief Create an event from a type string and content JSON +//! +//! Use this template to resolve the C++ type from the Matrix type string in +//! \p matrixType and create an event of that type by passing all parameters +//! to BaseEventT::basicJson(). +template +inline event_ptr_tt loadEvent(const QString& matrixType, + const auto&... otherBasicJsonParams) +{ + return mostSpecificMetaType().loadFrom( + EventT::basicJson(matrixType, otherBasicJsonParams...), matrixType); +} + +template +struct JsonConverter> + : JsonObjectUnpacker> { + // No dump() to avoid any ambiguity on whether a given export to JSON uses + // fullJson() or only contentJson() + using JsonObjectUnpacker>::load; + static auto load(const QJsonObject& jo) + { + return loadEvent(jo); + } +}; + // === Event === class QUOTIENT_API Event { @@ -259,10 +267,8 @@ public: return BaseMetaType; } + explicit Event(const QJsonObject& json); - explicit Event(Type type, const QJsonObject& json); - explicit Event(Type type, event_mtype_t matrixType, - const QJsonObject& contentJson = {}); Q_DISABLE_COPY(Event) Event(Event&&) = default; Event& operator=(Event&&) = delete; @@ -312,6 +318,11 @@ public: const QJsonObject contentJson() const; + //! \brief Get a part of the content object, assuming a given type + //! + //! This retrieves the value under `content.` from the event JSON and + //! then converts it to \p T using fromJson(). + //! \sa contentJson, fromJson template const T contentPart(KeyT&& key) const { @@ -327,6 +338,11 @@ public: const QJsonObject unsignedJson() const; + //! \brief Get a part of the unsigned object, assuming a given type + //! + //! This retrieves the value under `unsigned.` from the event JSON and + //! then converts it to \p T using fromJson(). + //! \sa unsignedJson, fromJson template const T unsignedPart(KeyT&& key) const { @@ -353,7 +369,6 @@ protected: virtual void dumpTo(QDebug dbg) const; private: - Type _type; QJsonObject _json; }; using EventPtr = event_ptr_tt; @@ -364,6 +379,45 @@ using Events = EventsArray; // === Facilities for event class definitions === +//! \brief A template base class to derive your event type from +//! +//! This simple class template generates commonly used event constructor +//! signatures and the content() method with the appropriate return type. +//! The generic version here is only used with non-trivial \p ContentT (if you +//! don't need to create an event from its content structure, just go and derive +//! straight from the respective \p EventBaseT instead of using EventTemplate); +//! specialisations may override that and provide useful semantics even without +//! \p ContentT (see EventTemplate, e.g.). +//! +//! The template uses CRTP to pick the event type id from the actual class; +//! it will fail to compile if \p EventT doesn't provide TypeId. It also uses +//! the base event type's basicJson(); if you need extra keys to be inserted +//! you may want to bypass this template as writing the code to that effect in +//! your class will likely be clearer and more concise. +//! \sa https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern +//! \sa DEFINE_SIMPLE_EVENT +template +class EventTemplate : public BaseEventT { +public: + static_assert( + !std::is_same_v, + "If you see this, you tried to use EventTemplate with the default" + " ContentT type, which is void. This default is only used with explicit" + " specialisations (see CallEventBase, e.g.). Otherwise, if you don't" + " intend to use the content part of EventTemplate then you don't need" + " EventTemplate; just use the base event class directly"); + using content_type = ContentT; + + explicit EventTemplate(const QJsonObject& json) + : BaseEventT(json) + {} + explicit EventTemplate(const ContentT& c) + : BaseEventT(EventT::basicJson(EventT::TypeId, toJson(c))) + {} + + ContentT content() const { return fromJson(this->contentJson()); } +}; + //! \brief Supply event metatype information in base event types //! //! Use this macro in a public section of your base event class to provide @@ -445,19 +499,19 @@ using Events = EventsArray; /// To retrieve the value the getter uses a JSON key name that corresponds to /// its own (getter's) name but written in snake_case. \p GetterName_ must be /// in camelCase, no quotes (an identifier, not a literal). -#define DEFINE_SIMPLE_EVENT(Name_, Base_, TypeId_, ValueType_, GetterName_, \ - JsonKey_) \ - class QUOTIENT_API Name_ : public Base_ { \ - public: \ - QUO_EVENT(Name_, TypeId_) \ - using value_type = ValueType_; \ - explicit Name_(const QJsonObject& obj) : Base_(TypeId, obj) {} \ - explicit Name_(const value_type& v) \ - : Name_(Base_::basicJson(TypeId, { { JsonKey, toJson(v) } })) \ - {} \ - QUO_CONTENT_GETTER_X(ValueType_, GetterName_, JsonKey) \ - static inline const auto JsonKey = toSnakeCase(#GetterName_##_ls); \ - }; \ +#define DEFINE_SIMPLE_EVENT(Name_, Base_, TypeId_, ValueType_, GetterName_, \ + JsonKey_) \ + constexpr auto Name_##ContentKey = JsonKey_##_ls; \ + class QUOTIENT_API Name_ \ + : public EventTemplate< \ + Name_, Base_, \ + EventContent::SingleKeyValue> { \ + public: \ + QUO_EVENT(Name_, TypeId_) \ + using value_type = ValueType_; \ + using EventTemplate::EventTemplate; \ + QUO_CONTENT_GETTER_X(ValueType_, GetterName_, Name_##ContentKey) \ + }; \ // End of macro // === is<>(), eventCast<>() and switchOnType<>() === diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index 4c639efa..b4ac154c 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -6,40 +6,8 @@ #include "stateevent.h" namespace Quotient { - -/*! Create an event with proper type from a JSON object - * - * Use this factory template to detect the type from the JSON object - * contents (the detected event type should derive from the template - * parameter type) and create an event object of that type. - */ -template -inline event_ptr_tt loadEvent(const QJsonObject& fullJson) -{ - return doLoadEvent(fullJson, fullJson[TypeKeyL].toString()); -} - -//! \brief Create an event from a type string and content JSON -//! -//! Use this template to resolve the C++ type from the Matrix type string in -//! \p matrixType and create an event of that type by passing all parameters -//! to BaseEventT::basicJson(). -template -inline event_ptr_tt loadEvent( - const QString& matrixType, const BasicJsonParamTs&... basicJsonParams) -{ - return doLoadEvent( - BaseEventT::basicJson(matrixType, basicJsonParams...), matrixType); +struct [[deprecated( + "This header is obsolete since libQuotient 0.7; include a header with" + " the respective event type definition instead")]] EventLoaderH; +StateEventPtr eventLoaderH(EventLoaderH&); } - -template -struct JsonConverter> - : JsonObjectUnpacker> { - using JsonObjectUnpacker>::load; - static auto load(const QJsonObject& jo) - { - return loadEvent(jo); - } -}; - -} // namespace Quotient diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index 5b5a518f..0ffd8b2c 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -15,9 +15,7 @@ class QUOTIENT_API KeyVerificationRequestEvent : public Event { public: QUO_EVENT(KeyVerificationRequestEvent, "m.key.verification.request") - explicit KeyVerificationRequestEvent(const QJsonObject& obj) - : Event(TypeId, obj) - {} + using Event::Event; KeyVerificationRequestEvent(const QString& transactionId, const QString& fromDevice, const QStringList& methods, @@ -50,9 +48,7 @@ class QUOTIENT_API KeyVerificationReadyEvent : public Event { public: QUO_EVENT(KeyVerificationReadyEvent, "m.key.verification.ready") - explicit KeyVerificationReadyEvent(const QJsonObject& obj) - : Event(TypeId, obj) - {} + using Event::Event; KeyVerificationReadyEvent(const QString& transactionId, const QString& fromDevice, const QStringList& methods) @@ -77,9 +73,7 @@ class QUOTIENT_API KeyVerificationStartEvent : public Event { public: QUO_EVENT(KeyVerificationStartEvent, "m.key.verification.start") - explicit KeyVerificationStartEvent(const QJsonObject& obj) - : Event(TypeId, obj) - {} + using Event::Event; KeyVerificationStartEvent(const QString& transactionId, const QString& fromDevice) : KeyVerificationStartEvent( @@ -150,9 +144,7 @@ class QUOTIENT_API KeyVerificationAcceptEvent : public Event { public: QUO_EVENT(KeyVerificationAcceptEvent, "m.key.verification.accept") - explicit KeyVerificationAcceptEvent(const QJsonObject& obj) - : Event(TypeId, obj) - {} + using Event::Event; KeyVerificationAcceptEvent(const QString& transactionId, const QString& commitment) : KeyVerificationAcceptEvent(basicJson( @@ -200,9 +192,7 @@ class QUOTIENT_API KeyVerificationCancelEvent : public Event { public: QUO_EVENT(KeyVerificationCancelEvent, "m.key.verification.cancel") - explicit KeyVerificationCancelEvent(const QJsonObject& obj) - : Event(TypeId, obj) - {} + using Event::Event; KeyVerificationCancelEvent(const QString& transactionId, const QString& reason) : KeyVerificationCancelEvent( @@ -230,9 +220,7 @@ class QUOTIENT_API KeyVerificationKeyEvent : public Event { public: QUO_EVENT(KeyVerificationKeyEvent, "m.key.verification.key") - explicit KeyVerificationKeyEvent(const QJsonObject& obj) - : Event(TypeId, obj) - {} + using Event::Event; KeyVerificationKeyEvent(const QString& transactionId, const QString& key) : KeyVerificationKeyEvent( basicJson(TypeId, { { "transaction_id"_ls, transactionId }, @@ -251,9 +239,7 @@ class QUOTIENT_API KeyVerificationMacEvent : public Event { public: QUO_EVENT(KeyVerificationMacEvent, "m.key.verification.mac") - explicit KeyVerificationMacEvent(const QJsonObject& obj) - : Event(TypeId, obj) - {} + using Event::Event; KeyVerificationMacEvent(const QString& transactionId, const QString& keys, const QJsonObject& mac) : KeyVerificationMacEvent( @@ -278,9 +264,7 @@ class QUOTIENT_API KeyVerificationDoneEvent : public Event { public: QUO_EVENT(KeyVerificationDoneEvent, "m.key.verification.done") - explicit KeyVerificationDoneEvent(const QJsonObject& obj) - : Event(TypeId, obj) - {} + using Event::Event; explicit KeyVerificationDoneEvent(const QString& transactionId) : KeyVerificationDoneEvent( basicJson(TypeId, { { "transaction_id"_ls, transactionId } })) diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp index 7f06d99f..d8f9fa0b 100644 --- a/lib/events/receiptevent.cpp +++ b/lib/events/receiptevent.cpp @@ -28,7 +28,7 @@ using namespace Quotient; // map lookups are not used and vectors are massively faster. Same goes for // de-/serialization of ReceiptsForEvent::receipts. // (XXX: would this be generally preferred across CS API JSON maps?..) -QJsonObject toJson(const EventsWithReceipts& ewrs) +QJsonObject Quotient::toJson(const EventsWithReceipts& ewrs) { QJsonObject json; for (const auto& e : ewrs) { @@ -41,20 +41,16 @@ QJsonObject toJson(const EventsWithReceipts& ewrs) return json; } -ReceiptEvent::ReceiptEvent(const EventsWithReceipts &ewrs) - : Event(typeId(), matrixTypeId(), toJson(ewrs)) -{} - -EventsWithReceipts ReceiptEvent::eventsWithReceipts() const +template<> +EventsWithReceipts Quotient::fromJson(const QJsonObject& json) { EventsWithReceipts result; - const auto& contents = contentJson(); - result.reserve(contents.size()); - for (auto eventIt = contents.begin(); eventIt != contents.end(); ++eventIt) { + result.reserve(json.size()); + for (auto eventIt = json.begin(); eventIt != json.end(); ++eventIt) { if (eventIt.key().isEmpty()) { qCWarning(EPHEMERAL) << "ReceiptEvent has an empty event id, skipping"; - qCDebug(EPHEMERAL) << "ReceiptEvent content follows:\n" << contents; + qCDebug(EPHEMERAL) << "ReceiptEvent content follows:\n" << json; continue; } const auto reads = diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index a02f4592..b87e00f6 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -19,12 +19,17 @@ struct ReceiptsForEvent { }; using EventsWithReceipts = QVector; -class QUOTIENT_API ReceiptEvent : public Event { +template <> +QUOTIENT_API EventsWithReceipts fromJson(const QJsonObject& json); +QUOTIENT_API QJsonObject toJson(const EventsWithReceipts& ewrs); + +class QUOTIENT_API ReceiptEvent + : public EventTemplate { public: QUO_EVENT(ReceiptEvent, "m.receipt") - explicit ReceiptEvent(const EventsWithReceipts& ewrs); - explicit ReceiptEvent(const QJsonObject& obj) : Event(typeId(), obj) {} + using EventTemplate::EventTemplate; - EventsWithReceipts eventsWithReceipts() const; + [[deprecated("Use content() instead")]] + EventsWithReceipts eventsWithReceipts() const { return content(); } }; } // namespace Quotient diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index c193054a..a2e0b73b 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -10,8 +10,7 @@ class QUOTIENT_API RedactionEvent : public RoomEvent { public: QUO_EVENT(RedactionEvent, "m.room.redaction") - explicit RedactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) - {} + using RoomEvent::RoomEvent; QString redactedEvent() const { diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index 2ebe29bf..1986f852 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -8,26 +8,15 @@ namespace Quotient { class QUOTIENT_API RoomAvatarEvent - : public StateEvent { + : public KeylessStateEventBase { // It's a bit of an overkill to use a full-fledged ImageContent // because in reality m.room.avatar usually only has a single URL, // without a thumbnail. But The Spec says there be thumbnails, and - // we follow The Spec. + // we follow The Spec (and ImageContent is very convenient to reuse here). public: QUO_EVENT(RoomAvatarEvent, "m.room.avatar") - explicit RoomAvatarEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) - {} - explicit RoomAvatarEvent(const EventContent::ImageContent& avatar) - : StateEvent(typeId(), matrixTypeId(), QString(), avatar) - {} - // A replica of EventContent::ImageInfo constructor - explicit RoomAvatarEvent(const QUrl& mxcUrl, qint64 fileSize = -1, - QMimeType mimeType = {}, - const QSize& imageSize = {}, - const QString& originalFilename = {}) - : RoomAvatarEvent(EventContent::ImageContent { - mxcUrl, fileSize, mimeType, imageSize, originalFilename }) - {} + using KeylessStateEventBase::KeylessStateEventBase; QUrl url() const { return content().url(); } }; diff --git a/lib/events/roomcanonicalaliasevent.h b/lib/events/roomcanonicalaliasevent.h index e1c7888e..c73bc92a 100644 --- a/lib/events/roomcanonicalaliasevent.h +++ b/lib/events/roomcanonicalaliasevent.h @@ -32,25 +32,11 @@ inline auto toJson(const EventContent::AliasesEventContent& c) } class QUOTIENT_API RoomCanonicalAliasEvent - : public StateEvent { + : public KeylessStateEventBase { public: QUO_EVENT(RoomCanonicalAliasEvent, "m.room.canonical_alias") - - explicit RoomCanonicalAliasEvent(const QJsonObject& obj) - : StateEvent(typeId(), obj) - { } - - explicit RoomCanonicalAliasEvent(const QString& canonicalAlias, - const QStringList& altAliases = {}) - : StateEvent(typeId(), matrixTypeId(), {}, - canonicalAlias, altAliases) - { } - - explicit RoomCanonicalAliasEvent(QString&& canonicalAlias, - QStringList&& altAliases = {}) - : StateEvent(typeId(), matrixTypeId(), {}, - std::move(canonicalAlias), std::move(altAliases)) - { } + using KeylessStateEventBase::KeylessStateEventBase; QString alias() const { return content().canonicalAlias; } QStringList altAliases() const { return content().altAliases; } diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index f22752b4..2709258f 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -11,9 +11,7 @@ class QUOTIENT_API RoomCreateEvent : public StateEventBase { public: QUO_EVENT(RoomCreateEvent, "m.room.create") - explicit RoomCreateEvent(const QJsonObject& obj) - : StateEventBase(typeId(), obj) - {} + using StateEventBase::StateEventBase; struct Predecessor { QString roomId; diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index e695e0ec..bd06f5c5 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -8,12 +8,7 @@ using namespace Quotient; -RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, - const QJsonObject& contentJson) - : Event(type, matrixType, contentJson) -{} - -RoomEvent::RoomEvent(Type type, const QJsonObject& json) : Event(type, json) +RoomEvent::RoomEvent(const QJsonObject& json) : Event(json) { if (const auto redaction = unsignedPart(RedactedCauseKeyL); !redaction.isEmpty()) @@ -110,14 +105,8 @@ QJsonObject CallEventBase::basicJson(const QString& matrixType, return RoomEvent::basicJson(matrixType, contentJson); } -CallEventBase::CallEventBase(Type type, event_mtype_t matrixType, - const QString& callId, int version, - const QJsonObject& contentJson) - : RoomEvent(type, basicJson(matrixType, callId, version, contentJson)) -{} - -CallEventBase::CallEventBase(Type type, const QJsonObject& json) - : RoomEvent(type, json) +CallEventBase::CallEventBase(const QJsonObject& json) + : RoomEvent(json) { if (callId().isEmpty()) qCWarning(EVENTS) << id() << "is a call event with an empty call id"; diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 532e72e2..830f1d30 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -17,10 +17,8 @@ public: QUO_BASE_EVENT(RoomEvent, {}, Event::BaseMetaType) // RedactionEvent is an incomplete type here so we cannot inline - // constructors and destructors and we cannot use 'using'. - RoomEvent(Type type, event_mtype_t matrixType, - const QJsonObject& contentJson = {}); - RoomEvent(Type type, const QJsonObject& json); + // constructors using it and also destructors (with 'using', in particular). + explicit RoomEvent(const QJsonObject& json); ~RoomEvent() override; QString id() const; @@ -85,10 +83,7 @@ class QUOTIENT_API CallEventBase : public RoomEvent { public: QUO_BASE_EVENT(CallEventBase, "m.call.*"_ls, RoomEvent::BaseMetaType) - CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, - int version, const QJsonObject& contentJson = {}); - CallEventBase(Type type, const QJsonObject& json); - ~CallEventBase() override = default; + explicit CallEventBase(const QJsonObject& json); QUO_CONTENT_GETTER(QString, callId) QUO_CONTENT_GETTER(int, version) @@ -98,6 +93,17 @@ protected: const QString& callId, int version, QJsonObject contentJson = {}); }; + +template +class EventTemplate : public CallEventBase { +public: + using CallEventBase::CallEventBase; + explicit EventTemplate(const QString& callId, + const QJsonObject& contentJson = {}) + : EventTemplate(basicJson(EventT::TypeId, callId, 0, contentJson)) + {} +}; + } // namespace Quotient Q_DECLARE_METATYPE(Quotient::RoomEvent*) Q_DECLARE_METATYPE(const Quotient::RoomEvent*) diff --git a/lib/events/roomkeyevent.cpp b/lib/events/roomkeyevent.cpp deleted file mode 100644 index 3a8601d1..00000000 --- a/lib/events/roomkeyevent.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Alexey Andreyev -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "roomkeyevent.h" - -using namespace Quotient; - -RoomKeyEvent::RoomKeyEvent(const QJsonObject &obj) : Event(TypeId, obj) -{ - if (roomId().isEmpty()) - qCWarning(E2EE) << "Room key event has empty room id"; -} - -RoomKeyEvent::RoomKeyEvent(const QString& algorithm, const QString& roomId, - const QString& sessionId, const QString& sessionKey) - : Event(TypeId, basicJson(TypeId, { - { "algorithm", algorithm }, - { "room_id", roomId }, - { "session_id", sessionId }, - { "session_key", sessionKey }, - })) -{} diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 6883a2a5..dad5df8b 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -11,9 +11,16 @@ class QUOTIENT_API RoomKeyEvent : public Event public: QUO_EVENT(RoomKeyEvent, "m.room_key") - explicit RoomKeyEvent(const QJsonObject& obj); + using Event::Event; explicit RoomKeyEvent(const QString& algorithm, const QString& roomId, - const QString& sessionId, const QString& sessionKey); + const QString& sessionId, const QString& sessionKey) + : Event(basicJson(TypeId, { + { "algorithm", algorithm }, + { "room_id", roomId }, + { "session_id", sessionId }, + { "session_key", sessionKey }, + })) + {} QUO_CONTENT_GETTER(QString, algorithm) QUO_CONTENT_GETTER(QString, roomId) diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index c690586e..9f063136 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -28,7 +28,8 @@ public: using MembershipType [[deprecated("Use Membership instead")]] = Membership; -class QUOTIENT_API RoomMemberEvent : public StateEvent { +class QUOTIENT_API RoomMemberEvent + : public KeyedStateEventBase { Q_GADGET public: QUO_EVENT(RoomMemberEvent, "m.room.member") @@ -36,24 +37,7 @@ public: using MembershipType [[deprecated("Use Quotient::Membership instead")]] = Membership; - explicit RoomMemberEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) - {} - RoomMemberEvent(const QString& userId, MemberEventContent&& content) - : StateEvent(typeId(), matrixTypeId(), userId, std::move(content)) - {} - - //! \brief A special constructor to create unknown RoomMemberEvents - //! - //! This is needed in order to use RoomMemberEvent as a "base event class" - //! in cases like GetMembersByRoomJob when RoomMemberEvents (rather than - //! RoomEvents or StateEvents) are resolved from JSON. For such cases - //! loadEvent\<> requires an underlying class to have a specialisation of - //! EventFactory\<> and be constructible with unknownTypeId() instead of - //! its genuine id. Don't use directly. - //! \sa EventFactory, loadEvent, GetMembersByRoomJob - RoomMemberEvent(Type type, const QJsonObject& fullJson) - : StateEvent(type, fullJson) - {} + using KeyedStateEventBase::KeyedStateEventBase; Membership membership() const { return content().membership; } QString userId() const { return stateKey(); } diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 2a6ae93c..db5afaf1 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -128,8 +128,9 @@ QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, RoomMessageEvent::RoomMessageEvent(const QString& plainBody, const QString& jsonMsgType, TypedBase* content) - : RoomEvent(typeId(), matrixTypeId(), - assembleContentJson(plainBody, jsonMsgType, content)) + : RoomEvent(RoomEvent::basicJson(TypeId, + assembleContentJson(plainBody, jsonMsgType, + content))) , _content(content) {} @@ -175,7 +176,7 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, #endif RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) - : RoomEvent(typeId(), obj), _content(nullptr) + : RoomEvent(obj), _content(nullptr) { if (isRedacted()) return; diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h index 7ac12db0..6150980a 100644 --- a/lib/events/roompowerlevelsevent.h +++ b/lib/events/roompowerlevelsevent.h @@ -31,16 +31,11 @@ struct QUOTIENT_API PowerLevelsEventContent { }; class QUOTIENT_API RoomPowerLevelsEvent - : public StateEvent { + : public KeylessStateEventBase { public: QUO_EVENT(RoomPowerLevelsEvent, "m.room.power_levels") - explicit RoomPowerLevelsEvent(PowerLevelsEventContent&& content) - : StateEvent(typeId(), matrixTypeId(), QString(), std::move(content)) - {} - explicit RoomPowerLevelsEvent(const QJsonObject& obj) - : StateEvent(typeId(), obj) - {} + using KeylessStateEventBase::KeylessStateEventBase; int invite() const { return content().invite; } int kick() const { return content().kick; } diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h index 97586587..95743e32 100644 --- a/lib/events/roomtombstoneevent.h +++ b/lib/events/roomtombstoneevent.h @@ -10,9 +10,7 @@ class QUOTIENT_API RoomTombstoneEvent : public StateEventBase { public: QUO_EVENT(RoomTombstoneEvent, "m.room.tombstone") - explicit RoomTombstoneEvent(const QJsonObject& obj) - : StateEventBase(typeId(), obj) - {} + using StateEventBase::StateEventBase; QString serverMessage() const; QString successorRoomId() const; diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index c79d03b0..d84dc1b1 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -7,25 +7,18 @@ #include "single_key_value.h" namespace Quotient { -#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \ - constexpr auto _Name##Key = #_ContentKey##_ls; \ - class QUOTIENT_API _Name \ - : public StateEvent< \ - EventContent::SingleKeyValue<_ValueType, &_Name##Key>> { \ - public: \ - using value_type = _ValueType; \ - QUO_EVENT(_Name, _TypeId) \ - template \ - explicit _Name(T&& value) \ - : StateEvent(TypeId, matrixTypeId(), QString(), \ - std::forward(value)) \ - {} \ - explicit _Name(QJsonObject obj) \ - : StateEvent(TypeId, std::move(obj)) \ - {} \ - auto _ContentKey() const { return content().value; } \ - }; \ - // End of macro +#define DEFINE_SIMPLE_STATE_EVENT(Name_, TypeId_, ValueType_, ContentKey_) \ + constexpr auto Name_##Key = #ContentKey_##_ls; \ + class QUOTIENT_API Name_ \ + : public KeylessStateEventBase< \ + Name_, EventContent::SingleKeyValue> { \ + public: \ + using value_type = ValueType_; \ + QUO_EVENT(Name_, TypeId_) \ + using KeylessStateEventBase::KeylessStateEventBase; \ + auto ContentKey_() const { return content().value; } \ + }; \ +// End of macro DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic) @@ -34,13 +27,14 @@ DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", constexpr auto RoomAliasesEventKey = "aliases"_ls; class QUOTIENT_API RoomAliasesEvent - : public StateEvent< - EventContent::SingleKeyValue> { + : public KeyedStateEventBase< + RoomAliasesEvent, + EventContent::SingleKeyValue> +{ public: QUO_EVENT(RoomAliasesEvent, "m.room.aliases") - explicit RoomAliasesEvent(const QJsonObject& obj) - : StateEvent(typeId(), obj) - {} + using KeyedStateEventBase::KeyedStateEventBase; + Q_DECL_DEPRECATED_X( "m.room.aliases events are deprecated by the Matrix spec; use" " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases") diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 1df24df0..e117f8a0 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -5,18 +5,16 @@ using namespace Quotient; -StateEventBase::StateEventBase(Type type, const QJsonObject& json) - : RoomEvent(json.contains(StateKeyKeyL) ? type : UnknownEventTypeId, json) +StateEventBase::StateEventBase(const QJsonObject& json) + : RoomEvent(json) { - if (Event::type() == UnknownEventTypeId && !json.contains(StateKeyKeyL)) - qWarning(EVENTS) << "Attempt to create a state event with no stateKey -" - "forcing the event type to unknown to avoid damage"; + Q_ASSERT_X(json.contains(StateKeyKeyL), __FUNCTION__, + "Attempt to create a state event without state key"); } -StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, - const QString& stateKey, +StateEventBase::StateEventBase(Event::Type type, const QString& stateKey, const QJsonObject& contentJson) - : RoomEvent(type, basicJson(type, stateKey, contentJson)) + : RoomEvent(basicJson(type, stateKey, contentJson)) {} bool StateEventBase::repeatsState() const diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 74876803..911972f2 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -16,11 +16,17 @@ public: return fullJson.contains(StateKeyKeyL); } - StateEventBase(Type type, const QJsonObject& json); - StateEventBase(Type type, event_mtype_t matrixType, - const QString& stateKey = {}, - const QJsonObject& contentJson = {}); - ~StateEventBase() override = default; + //! \brief Static setting of whether a given even type uses state keys + //! + //! Most event types don't use a state key; overriding this to `true` + //! for a given type changes the calls across Quotient to include state key + //! in their signatures; otherwise, state key is still accessible but + //! constructors and calls in, e.g., RoomStateView don't include it. + static constexpr auto needsStateKey = false; + + explicit StateEventBase(const QJsonObject& json); + explicit StateEventBase(Type type, const QString& stateKey = {}, + const QJsonObject& contentJson = {}); //! Make a minimal correct Matrix state event JSON static QJsonObject basicJson(const QString& matrixTypeId, @@ -56,64 +62,85 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, */ using StateEventKey = std::pair; -template -struct Prev { - template - explicit Prev(const QJsonObject& unsignedJson, - ContentParamTs&&... contentParams) - : senderId(unsignedJson.value("prev_sender"_ls).toString()) - , content(fromJson(unsignedJson.value(PrevContentKeyL)), - std::forward(contentParams)...) - {} - - QString senderId; - ContentT content; -}; - -template -class StateEvent : public StateEventBase { +template +class EventTemplate + : public StateEventBase { public: using content_type = ContentT; + struct Prev { + explicit Prev() = default; + explicit Prev(const QJsonObject& unsignedJson) + : senderId(fromJson(unsignedJson["prev_sender"_ls])) + , content( + fromJson>(unsignedJson[PrevContentKeyL])) + {} + + QString senderId; + Omittable content; + }; + + explicit EventTemplate(const QJsonObject& fullJson) + : StateEventBase(fullJson) + , _content(fromJson(Event::contentJson())) + , _prev(unsignedJson()) + {} template - explicit StateEvent(Type type, const QJsonObject& fullJson, - ContentParamTs&&... contentParams) - : StateEventBase(type, fullJson) - , _content(fromJson(contentJson()), - std::forward(contentParams)...) - { - const auto& unsignedData = unsignedJson(); - if (unsignedData.contains(PrevContentKeyL)) - _prev = std::make_unique>( - unsignedData, std::forward(contentParams)...); - } - template - explicit StateEvent(Type type, event_mtype_t matrixType, - const QString& stateKey, - ContentParamTs&&... contentParams) - : StateEventBase(type, matrixType, stateKey) - , _content{std::forward(contentParams)...} + explicit EventTemplate(const QString& stateKey, + ContentParamTs&&... contentParams) + : StateEventBase(EventT::TypeId, stateKey) + , _content { std::forward(contentParams)... } { editJson().insert(ContentKey, toJson(_content)); } const ContentT& content() const { return _content; } + template void editContent(VisitorT&& visitor) { visitor(_content); editJson()[ContentKeyL] = toJson(_content); } - const ContentT* prevContent() const - { - return _prev ? &_prev->content : nullptr; - } - QString prevSenderId() const { return _prev ? _prev->senderId : QString(); } + const Omittable& prevContent() const { return _prev.content; } + QString prevSenderId() const { return _prev.senderId; } private: ContentT _content; - std::unique_ptr> _prev; + Prev _prev; }; + +template +class KeyedStateEventBase + : public EventTemplate { +public: + static constexpr auto needsStateKey = true; + + using EventTemplate::EventTemplate; +}; + +template +concept Keyed_State_Event = EvT::needsStateKey; + +template +class KeylessStateEventBase + : public EventTemplate { +private: + using base_type = EventTemplate; + +public: + explicit KeylessStateEventBase(const QJsonObject& fullJson) + : base_type(fullJson) + {} + template + explicit KeylessStateEventBase(ContentParamTs&&... contentParams) + : base_type(QString(), std::forward(contentParams)...) + {} +}; + +template +concept Keyless_State_Event = !EvT::needsStateKey; + } // namespace Quotient Q_DECLARE_METATYPE(Quotient::StateEventBase*) Q_DECLARE_METATYPE(const Quotient::StateEventBase*) diff --git a/lib/room.cpp b/lib/room.cpp index 4cae2333..f11b03e1 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2321,7 +2321,7 @@ void Room::setTopic(const QString& newTopic) bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re) { - if (le->type() != re->type()) + if (le->metaType() != re->metaType()) return false; if (!re->id().isEmpty()) diff --git a/lib/roomstateview.h b/lib/roomstateview.h index 29cce00e..13b375f2 100644 --- a/lib/roomstateview.h +++ b/lib/roomstateview.h @@ -11,6 +11,16 @@ namespace Quotient { class Room; +// NB: Both concepts below expect EvT::needsStateKey to exist so you can't +// express one via negation of the other (there's still an invalid case of +// a non-state event where needsStateKey is not even defined). + +template >> +concept Keyed_State_Fn = EvT::needsStateKey; + +template >> +concept Keyless_State_Fn = !EvT::needsStateKey; + class QUOTIENT_API RoomStateView : private QHash { Q_GADGET @@ -36,31 +46,48 @@ public: //! \brief Get a state event with the given event type and state key //! //! This is a typesafe overload that accepts a C++ event type instead of - //! its Matrix name. - //! \warning In libQuotient 0.7 the return type changed to an Omittable with - //! a reference wrapper inside - you have to check that it - //! has_value() before using. Alternatively you can now use - //! queryCurrentState() to access state safely. - template + //! its Matrix name. It is only defined for events with state key (i.e., + //! derived from KeyedStateEvent). + template const EvT* get(const QString& stateKey = {}) const { - static_assert(std::is_base_of_v); - if (const auto* evt = get(EvT::matrixTypeId(), stateKey)) { - Q_ASSERT(evt->matrixType() == EvT::matrixTypeId() + if (const auto* evt = get(EvT::TypeId, stateKey)) { + Q_ASSERT(evt->matrixType() == EvT::TypeId && evt->stateKey() == stateKey); return eventCast(evt); } return nullptr; } + //! \brief Get a state event with the given event type + //! + //! This is a typesafe overload that accepts a C++ event type instead of + //! its Matrix name. This overload only defined for events that do not use + //! state key (i.e., derived from KeylessStateEvent). + template + const EvT* get() const + { + if (const auto* evt = get(EvT::TypeId)) { + Q_ASSERT(evt->matrixType() == EvT::TypeId); + return eventCast(evt); + } + return nullptr; + } + using QHash::contains; bool contains(const QString& evtType, const QString& stateKey = {}) const; - template + template bool contains(const QString& stateKey = {}) const { - return contains(EvT::matrixTypeId(), stateKey); + return contains(EvT::TypeId, stateKey); + } + + template + bool contains() const + { + return contains(EvT::TypeId); } //! \brief Get the content of the current state event with the given @@ -78,48 +105,85 @@ public: const QVector eventsOfType(const QString& evtType) const; + //! \brief Run a function on a state event with the given type and key + //! + //! Use this overload when there's no predefined event type or the event + //! type is unknown at compile time. + //! \return an Omittable with either the result of the function call, or + //! with `none` if the event is not found or the function fails template auto query(const QString& evtType, const QString& stateKey, FnT&& fn) const { return lift(std::forward(fn), get(evtType, stateKey)); } - template + //! \brief Run a function on a state event with the given type and key + //! + //! This is an overload for keyed state events (those that have + //! `needsStateKey == true`) with type defined at compile time. + //! \return an Omittable with either the result of the function call, or + //! with `none` if the event is not found or the function fails + template auto query(const QString& stateKey, FnT&& fn) const { using EventT = std::decay_t>; - static_assert(std::is_base_of_v); return lift(std::forward(fn), get(stateKey)); } + //! \brief Run a function on a keyless state event with the given type + //! + //! This is an overload for keyless state events (those having + //! `needsStateKey == false`) with type defined at compile time. + //! \return an Omittable with either the result of the function call, or + //! with `none` if the event is not found or the function fails + template + auto query(FnT&& fn) const + { + using EventT = std::decay_t>; + return lift(std::forward(fn), get()); + } + + //! \brief Same as query() but with a fallback value + //! + //! This is a shortcut for `query().value_or()`, passing respective + //! arguments to the respective functions. This is an overload for the case + //! when the event type cannot be fixed at compile time. + //! \return the result of \p fn execution, or \p fallback if the requested + //! event doesn't exist or the function fails template auto queryOr(const QString& evtType, const QString& stateKey, FnT&& fn, FallbackT&& fallback) const { - return lift(std::forward(fn), get(evtType, stateKey)) + return query(evtType, stateKey, std::forward(fn)) .value_or(std::forward(fallback)); } - template - auto query(FnT&& fn) const - { - return query({}, std::forward(fn)); - } - + //! \brief Same as query() but with a fallback value + //! + //! This is a shortcut for `query().value_or()`, passing respective + //! arguments to the respective functions. This is an overload for the case + //! when the event type cannot be fixed at compile time. + //! \return the result of \p fn execution, or \p fallback if the requested + //! event doesn't exist or the function fails template auto queryOr(const QString& stateKey, FnT&& fn, FallbackT&& fallback) const { - using EventT = std::decay_t>; - static_assert(std::is_base_of_v); - return lift(std::forward(fn), get(stateKey)) + return query(stateKey, std::forward(fn)) .value_or(std::forward(fallback)); } + //! \brief Same as query() but with a fallback value + //! + //! This is a shortcut for `query().value_or()`, passing respective + //! arguments to the respective functions. This is an overload for the case + //! when the event type cannot be fixed at compile time. + //! \return the result of \p fn execution, or \p fallback if the requested + //! event doesn't exist or the function fails template auto queryOr(FnT&& fn, FallbackT&& fallback) const { - return queryOr({}, std::forward(fn), - std::forward(fallback)); + return query(std::forward(fn)) + .value_or(std::forward(fallback)); } private: diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index 93416bc4..eb6c932b 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -3,8 +3,6 @@ #include "syncdata.h" -#include "events/eventloader.h" - #include #include -- cgit v1.2.3 From 3fa9afc796788e0c7f44db15c21a5ba1294d3b2d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 25 Jul 2022 17:05:59 +0200 Subject: Remove #include "logging.h" from event.h We don't expose logging internals to the outside world. --- lib/events/encryptedevent.cpp | 1 + lib/events/encryptionevent.cpp | 1 + lib/events/event.h | 1 - lib/events/roommemberevent.cpp | 3 +-- lib/events/stateevent.cpp | 1 + lib/syncdata.cpp | 2 ++ 6 files changed, 6 insertions(+), 3 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index c539d5b2..e9b4a585 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" +#include "logging.h" using namespace Quotient; diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 8872447b..b1b04984 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptionevent.h" +#include "logging.h" #include "e2ee/e2ee.h" diff --git a/lib/events/event.h b/lib/events/event.h index 8a8d64b0..4b715813 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -4,7 +4,6 @@ #pragma once #include "converters.h" -#include "logging.h" #include "function_traits.h" #include "single_key_value.h" diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 953ff8ae..4e7eae1b 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -3,8 +3,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "roommemberevent.h" - -#include +#include "logging.h" namespace Quotient { template <> diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index e117f8a0..204044bb 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "stateevent.h" +#include "logging.h" using namespace Quotient; diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index eb6c932b..ec7203af 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -3,6 +3,8 @@ #include "syncdata.h" +#include "logging.h" + #include #include -- cgit v1.2.3 From 80499cc7619bb857c284e6e89db728ccee9c61f6 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 3 Sep 2022 19:12:42 +0200 Subject: More cleanup --- lib/events/encryptedevent.cpp | 7 ++++--- lib/events/encryptedevent.h | 7 ++++--- lib/events/event.h | 2 +- lib/events/roommessageevent.cpp | 5 ++--- lib/expected.h | 7 ++++--- lib/room.cpp | 16 ++++++++-------- lib/room.h | 7 +++---- quotest/quotest.cpp | 2 +- 8 files changed, 27 insertions(+), 26 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index e9b4a585..94b44901 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -6,14 +6,15 @@ using namespace Quotient; -EncryptedEvent::EncryptedEvent(const QJsonObject& ciphertext, +EncryptedEvent::EncryptedEvent(const QJsonObject& ciphertexts, const QString& senderKey) : RoomEvent({ { AlgorithmKeyL, OlmV1Curve25519AesSha2AlgoKey }, - { CiphertextKeyL, ciphertext }, + { CiphertextKeyL, ciphertexts }, { SenderKeyKeyL, senderKey } }) {} -EncryptedEvent::EncryptedEvent(QByteArray ciphertext, const QString& senderKey, +EncryptedEvent::EncryptedEvent(const QByteArray& ciphertext, + const QString& senderKey, const QString& deviceId, const QString& sessionId) : RoomEvent({ { AlgorithmKeyL, MegolmV1AesSha2AlgoKey }, diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 22e51cb8..02d4c7aa 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -32,11 +32,12 @@ public: /* In case with Olm, the encrypted content of the event is * a map from the recipient Curve25519 identity key to ciphertext * information */ - explicit EncryptedEvent(const QJsonObject& ciphertext, + explicit EncryptedEvent(const QJsonObject& ciphertexts, const QString& senderKey); /* In case with Megolm, device_id and session_id are required */ - explicit EncryptedEvent(QByteArray ciphertext, const QString& senderKey, - const QString& deviceId, const QString& sessionId); + explicit EncryptedEvent(const QByteArray& ciphertext, + const QString& senderKey, const QString& deviceId, + const QString& sessionId); explicit EncryptedEvent(const QJsonObject& obj); QString algorithm() const; diff --git a/lib/events/event.h b/lib/events/event.h index 4b715813..ea5a2554 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -269,7 +269,7 @@ public: explicit Event(const QJsonObject& json); Q_DISABLE_COPY(Event) - Event(Event&&) = default; + Event(Event&&) noexcept = default; Event& operator=(Event&&) = delete; virtual ~Event(); diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index db5afaf1..df4840b3 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -128,9 +128,8 @@ QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, RoomMessageEvent::RoomMessageEvent(const QString& plainBody, const QString& jsonMsgType, TypedBase* content) - : RoomEvent(RoomEvent::basicJson(TypeId, - assembleContentJson(plainBody, jsonMsgType, - content))) + : RoomEvent( + basicJson(TypeId, assembleContentJson(plainBody, jsonMsgType, content))) , _content(content) {} diff --git a/lib/expected.h b/lib/expected.h index 7b9e7f1d..81e186ea 100644 --- a/lib/expected.h +++ b/lib/expected.h @@ -21,11 +21,12 @@ public: using error_type = E; Expected() = default; - explicit Expected(const Expected&) = default; - explicit Expected(Expected&&) noexcept = default; + Expected(const Expected&) = default; + Expected(Expected&&) noexcept = default; + ~Expected() = default; template > - Expected(X&& x) + QUO_IMPLICIT Expected(X&& x) // NOLINT(google-explicit-constructor) : data(std::forward(x)) {} diff --git a/lib/room.cpp b/lib/room.cpp index f11b03e1..042d38ac 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -26,13 +26,13 @@ #include "csapi/inviting.h" #include "csapi/kicking.h" #include "csapi/leaving.h" +#include "csapi/read_markers.h" #include "csapi/receipts.h" #include "csapi/redaction.h" #include "csapi/room_send.h" #include "csapi/room_state.h" #include "csapi/room_upgrades.h" #include "csapi/rooms.h" -#include "csapi/read_markers.h" #include "csapi/tags.h" #include "events/callanswerevent.h" @@ -44,15 +44,15 @@ #include "events/receiptevent.h" #include "events/redactionevent.h" #include "events/roomavatarevent.h" +#include "events/roomcanonicalaliasevent.h" #include "events/roomcreateevent.h" #include "events/roommemberevent.h" +#include "events/roompowerlevelsevent.h" #include "events/roomtombstoneevent.h" #include "events/simplestateevents.h" #include "events/typingevent.h" -#include "events/roompowerlevelsevent.h" #include "jobs/downloadfilejob.h" #include "jobs/mediathumbnailjob.h" -#include "events/roomcanonicalaliasevent.h" #include #include @@ -1353,7 +1353,7 @@ void Room::setTags(TagsMap newTags, ActionScope applyOn) d->setTags(move(newTags)); connection()->callApi( - localUser()->id(), id(), TagEvent::matrixTypeId(), + localUser()->id(), id(), TagEvent::TypeId, TagEvent(d->tags).contentJson()); if (propagate) { @@ -2635,10 +2635,10 @@ RoomEventPtr makeRedacted(const RoomEvent& target, QStringLiteral("membership") }; // clang-format on - static const std::pair keepContentKeysMap[] { - { RoomMemberEvent::typeId(), { QStringLiteral("membership") } }, - { RoomCreateEvent::typeId(), { QStringLiteral("creator") } }, - { RoomPowerLevelsEvent::typeId(), + static const std::pair keepContentKeysMap[]{ + { RoomMemberEvent::TypeId, { QStringLiteral("membership") } }, + { RoomCreateEvent::TypeId, { QStringLiteral("creator") } }, + { RoomPowerLevelsEvent::TypeId, { QStringLiteral("ban"), QStringLiteral("events"), QStringLiteral("events_default"), QStringLiteral("kick"), QStringLiteral("redact"), QStringLiteral("state_default"), diff --git a/lib/room.h b/lib/room.h index 44504691..b454bfbc 100644 --- a/lib/room.h +++ b/lib/room.h @@ -770,11 +770,10 @@ public: "make sure to check its result for nullptrs")]] // const EvT* getCurrentState(const QString& stateKey = {}) const { - QT_IGNORE_DEPRECATIONS( - const auto* evt = eventCast( - getCurrentState(EvT::matrixTypeId(), stateKey));) + QT_IGNORE_DEPRECATIONS(const auto* evt = eventCast( + getCurrentState(EvT::TypeId, stateKey));) Q_ASSERT(evt); - Q_ASSERT(evt->matrixTypeId() == EvT::matrixTypeId() + Q_ASSERT(evt->matrixType() == EvT::TypeId && evt->stateKey() == stateKey); return evt; } diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 3860ae1e..3ac6404a 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -163,7 +163,7 @@ bool TestSuite::validatePendingEvent(const QString& txnId) return it != targetRoom->pendingEvents().end() && it->deliveryStatus() == EventStatus::Submitted && (*it)->transactionId() == txnId && is(**it) - && (*it)->matrixType() == EventT::matrixTypeId(); + && (*it)->matrixType() == EventT::TypeId; } void TestSuite::finishTest(const TestToken& token, bool condition, -- cgit v1.2.3 From 8e58d28ca0517aeeb43c99bd97ec9ba5ada11c95 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 1 Aug 2022 08:09:40 +0200 Subject: CallEventBase -> CallEvent; pack up all call events These are small enough to comfortably reside in a single translation unit. --- CMakeLists.txt | 5 +- autotests/callcandidateseventtest.cpp | 2 +- lib/eventitem.h | 5 +- lib/events/callanswerevent.cpp | 34 ------------ lib/events/callanswerevent.h | 24 --------- lib/events/callcandidatesevent.h | 27 ---------- lib/events/callevents.cpp | 82 +++++++++++++++++++++++++++++ lib/events/callevents.h | 99 +++++++++++++++++++++++++++++++++++ lib/events/callhangupevent.h | 17 ------ lib/events/callinviteevent.cpp | 37 ------------- lib/events/callinviteevent.h | 26 --------- lib/events/event.cpp | 3 +- lib/events/event.h | 8 +-- lib/events/roomevent.cpp | 16 ------ lib/events/roomevent.h | 25 --------- lib/room.cpp | 7 +-- 16 files changed, 194 insertions(+), 223 deletions(-) delete mode 100644 lib/events/callanswerevent.cpp delete mode 100644 lib/events/callanswerevent.h delete mode 100644 lib/events/callcandidatesevent.h create mode 100644 lib/events/callevents.cpp create mode 100644 lib/events/callevents.h delete mode 100644 lib/events/callhangupevent.h delete mode 100644 lib/events/callinviteevent.cpp delete mode 100644 lib/events/callinviteevent.h (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index d4cf52d0..1c4d9545 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,10 +158,7 @@ list(APPEND lib_SRCS lib/events/accountdataevents.h lib/events/receiptevent.h lib/events/receiptevent.cpp lib/events/reactionevent.h - lib/events/callinviteevent.h lib/events/callinviteevent.cpp - lib/events/callcandidatesevent.h - lib/events/callanswerevent.h lib/events/callanswerevent.cpp - lib/events/callhangupevent.h + lib/events/callevents.h lib/events/callevents.cpp lib/events/directchatevent.h lib/events/directchatevent.cpp lib/events/encryptionevent.h lib/events/encryptionevent.cpp lib/events/encryptedevent.h lib/events/encryptedevent.cpp diff --git a/autotests/callcandidateseventtest.cpp b/autotests/callcandidateseventtest.cpp index 0d5a543b..b37dd109 100644 --- a/autotests/callcandidateseventtest.cpp +++ b/autotests/callcandidateseventtest.cpp @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "events/callcandidatesevent.h" +#include "events/callevents.h" #include diff --git a/lib/eventitem.h b/lib/eventitem.h index 90d9f458..2e55a724 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -5,6 +5,7 @@ #include "quotient_common.h" +#include "events/callevents.h" #include "events/filesourceinfo.h" #include "events/stateevent.h" @@ -101,9 +102,9 @@ inline const StateEventBase* EventItemBase::viewAs() const } template <> -inline const CallEventBase* EventItemBase::viewAs() const +inline const CallEvent* EventItemBase::viewAs() const { - return evt->isCallEvent() ? weakPtrCast(evt) : nullptr; + return evt->is() ? weakPtrCast(evt) : nullptr; } class QUOTIENT_API PendingEventItem : public EventItemBase { diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp deleted file mode 100644 index 89dcd7fd..00000000 --- a/lib/events/callanswerevent.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Marius Gripsgard -// SPDX-FileCopyrightText: 2018 Josip Delic -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "callanswerevent.h" - -/* -m.call.answer -{ - "age": 242352, - "content": { - "answer": { - "sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]", - "type": "answer" - }, - "call_id": "12345", - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.answer" -} -*/ - -using namespace Quotient; - -CallAnswerEvent::CallAnswerEvent(const QString& callId, const QString& sdp) - : EventTemplate(callId, { { QStringLiteral("answer"), - QJsonObject { { QStringLiteral("type"), - QStringLiteral("answer") }, - { QStringLiteral("sdp"), sdp } } } }) -{} diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h deleted file mode 100644 index c5ad14df..00000000 --- a/lib/events/callanswerevent.h +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Marius Gripsgard -// SPDX-FileCopyrightText: 2018 Josip Delic -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "roomevent.h" - -namespace Quotient { -class QUOTIENT_API CallAnswerEvent - : public EventTemplate { -public: - QUO_EVENT(CallAnswerEvent, "m.call.answer") - - using EventTemplate::EventTemplate; - - explicit CallAnswerEvent(const QString& callId, const QString& sdp); - - QString sdp() const - { - return contentPart("answer"_ls).value("sdp"_ls).toString(); - } -}; -} // namespace Quotient diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h deleted file mode 100644 index f5d2f815..00000000 --- a/lib/events/callcandidatesevent.h +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Marius Gripsgard -// SPDX-FileCopyrightText: 2018 Josip Delic -// SPDX-FileCopyrightText: 2018 Kitsune Ral -// SPDX-FileCopyrightText: 2020 Carl Schwan -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "roomevent.h" - -namespace Quotient { -class CallCandidatesEvent : public EventTemplate { -public: - QUO_EVENT(CallCandidatesEvent, "m.call.candidates") - - using EventTemplate::EventTemplate; - - explicit CallCandidatesEvent(const QString& callId, - const QJsonArray& candidates) - : EventTemplate(callId, { { QStringLiteral("candidates"), candidates } }) - {} - - QUO_CONTENT_GETTER(QJsonArray, candidates) - QUO_CONTENT_GETTER(QString, callId) - QUO_CONTENT_GETTER(int, version) -}; -} // namespace Quotient diff --git a/lib/events/callevents.cpp b/lib/events/callevents.cpp new file mode 100644 index 00000000..3873614d --- /dev/null +++ b/lib/events/callevents.cpp @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2022 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "callevents.h" + +#include "logging.h" + +using namespace Quotient; + +QJsonObject CallEvent::basicJson(const QString& matrixType, + const QString& callId, int version, + QJsonObject contentJson) +{ + contentJson.insert(QStringLiteral("call_id"), callId); + contentJson.insert(QStringLiteral("version"), version); + return RoomEvent::basicJson(matrixType, contentJson); +} + +CallEvent::CallEvent(const QJsonObject& json) + : RoomEvent(json) +{ + if (callId().isEmpty()) + qCWarning(EVENTS) << id() << "is a call event with an empty call id"; +} + +/* +m.call.invite +{ + "age": 242352, + "content": { + "call_id": "12345", + "lifetime": 60000, + "offer": { + "sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]", + "type": "offer" + }, + "version": 0 + }, + "event_id": "$WLGTSEFSEF:localhost", + "origin_server_ts": 1431961217939, + "room_id": "!Cuyf34gef24t:localhost", + "sender": "@example:localhost", + "type": "m.call.invite" +} +*/ + +CallInviteEvent::CallInviteEvent(const QString& callId, int lifetime, + const QString& sdp) + : EventTemplate( + callId, + { { QStringLiteral("lifetime"), lifetime }, + { QStringLiteral("offer"), + QJsonObject{ { QStringLiteral("type"), QStringLiteral("offer") }, + { QStringLiteral("sdp"), sdp } } } }) +{} + +/* +m.call.answer +{ + "age": 242352, + "content": { + "answer": { + "sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]", + "type": "answer" + }, + "call_id": "12345", + "version": 0 + }, + "event_id": "$WLGTSEFSEF:localhost", + "origin_server_ts": 1431961217939, + "room_id": "!Cuyf34gef24t:localhost", + "sender": "@example:localhost", + "type": "m.call.answer" +} +*/ + +CallAnswerEvent::CallAnswerEvent(const QString& callId, const QString& sdp) + : EventTemplate(callId, { { QStringLiteral("answer"), + QJsonObject { { QStringLiteral("type"), + QStringLiteral("answer") }, + { QStringLiteral("sdp"), sdp } } } }) +{} diff --git a/lib/events/callevents.h b/lib/events/callevents.h new file mode 100644 index 00000000..6d9feae4 --- /dev/null +++ b/lib/events/callevents.h @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2022 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "roomevent.h" + +namespace Quotient { + +class QUOTIENT_API CallEvent : public RoomEvent { +public: + QUO_BASE_EVENT(CallEvent, "m.call.*"_ls, RoomEvent::BaseMetaType) + static bool matches(const QJsonObject&, const QString& mType) + { + return mType.startsWith("m.call."); + } + + explicit CallEvent(const QJsonObject& json); + + QUO_CONTENT_GETTER(QString, callId) + QUO_CONTENT_GETTER(int, version) + +protected: + static QJsonObject basicJson(const QString& matrixType, + const QString& callId, int version, + QJsonObject contentJson = {}); +}; +using CallEventBase + [[deprecated("CallEventBase is CallEvent now")]] = CallEvent; + +template +class EventTemplate : public CallEvent { +public: + using CallEvent::CallEvent; + explicit EventTemplate(const QString& callId, + const QJsonObject& contentJson = {}) + : EventTemplate(basicJson(EventT::TypeId, callId, 0, contentJson)) + {} +}; + +template +class EventTemplate + : public EventTemplate { +public: + using EventTemplate::EventTemplate; + template + explicit EventTemplate(const QString& callId, + ContentParamTs&&... contentParams) + : EventTemplate( + callId, + toJson(ContentT{ std::forward(contentParams)... })) + {} +}; + +class QUOTIENT_API CallInviteEvent + : public EventTemplate { +public: + QUO_EVENT(CallInviteEvent, "m.call.invite") + + using EventTemplate::EventTemplate; + + explicit CallInviteEvent(const QString& callId, int lifetime, + const QString& sdp); + + QUO_CONTENT_GETTER(int, lifetime) + QString sdp() const + { + return contentPart("offer"_ls).value("sdp"_ls).toString(); + } +}; + +DEFINE_SIMPLE_EVENT(CallCandidatesEvent, CallEvent, "m.call.candidates", + QJsonArray, candidates, "candidates") + +class QUOTIENT_API CallAnswerEvent + : public EventTemplate { +public: + QUO_EVENT(CallAnswerEvent, "m.call.answer") + + using EventTemplate::EventTemplate; + + explicit CallAnswerEvent(const QString& callId, const QString& sdp); + + QString sdp() const + { + return contentPart("answer"_ls).value("sdp"_ls).toString(); + } +}; + +class QUOTIENT_API CallHangupEvent + : public EventTemplate { +public: + QUO_EVENT(CallHangupEvent, "m.call.hangup") + using EventTemplate::EventTemplate; +}; + +} // namespace Quotient +Q_DECLARE_METATYPE(Quotient::CallEvent*) +Q_DECLARE_METATYPE(const Quotient::CallEvent*) diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h deleted file mode 100644 index f0b131b9..00000000 --- a/lib/events/callhangupevent.h +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Marius Gripsgard -// SPDX-FileCopyrightText: 2018 Josip Delic -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "roomevent.h" - -namespace Quotient { -class QUOTIENT_API CallHangupEvent - : public EventTemplate { -public: - QUO_EVENT(CallHangupEvent, "m.call.hangup") - using EventTemplate::EventTemplate; -}; -//REGISTER_EVENT_TYPE(CallHangupEvent) -} // namespace Quotient diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp deleted file mode 100644 index 0232275b..00000000 --- a/lib/events/callinviteevent.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Marius Gripsgard -// SPDX-FileCopyrightText: 2018 Josip Delic -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "callinviteevent.h" - -/* -m.call.invite -{ - "age": 242352, - "content": { - "call_id": "12345", - "lifetime": 60000, - "offer": { - "sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]", - "type": "offer" - }, - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.invite" -} -*/ - -using namespace Quotient; - -CallInviteEvent::CallInviteEvent(const QString& callId, int lifetime, - const QString& sdp) - : EventTemplate(callId, - { { QStringLiteral("lifetime"), lifetime }, - { QStringLiteral("offer"), - QJsonObject { { QStringLiteral("type"), QStringLiteral("offer") }, - { QStringLiteral("sdp"), sdp } } } }) -{} diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h deleted file mode 100644 index fc22f7e1..00000000 --- a/lib/events/callinviteevent.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Marius Gripsgard -// SPDX-FileCopyrightText: 2018 Josip Delic -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "roomevent.h" - -namespace Quotient { -class QUOTIENT_API CallInviteEvent - : public EventTemplate { -public: - QUO_EVENT(CallInviteEvent, "m.call.invite") - - using EventTemplate::EventTemplate; - - explicit CallInviteEvent(const QString& callId, int lifetime, - const QString& sdp); - - QUO_CONTENT_GETTER(int, lifetime) - QString sdp() const - { - return contentPart("offer"_ls).value("sdp"_ls).toString(); - } -}; -} // namespace Quotient diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 2843e1dc..ca751081 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -3,6 +3,7 @@ #include "event.h" +#include "callevents.h" #include "logging.h" #include "stateevent.h" @@ -74,7 +75,7 @@ const QJsonObject Event::unsignedJson() const bool Event::isStateEvent() const { return is(); } -bool Event::isCallEvent() const { return is(); } +bool Event::isCallEvent() const { return is(); } void Event::dumpTo(QDebug dbg) const { diff --git a/lib/events/event.h b/lib/events/event.h index ea5a2554..6a7acf28 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -361,7 +361,7 @@ public: // as an exception. For other base events, Event::is<>() and // Quotient::is<>() should be used; don't add is* methods here bool isStateEvent() const; - [[deprecated("Use is() instead")]] bool isCallEvent() const; + [[deprecated("Use is() instead")]] bool isCallEvent() const; protected: QJsonObject& editJson() { return _json; } @@ -386,7 +386,7 @@ using Events = EventsArray; //! don't need to create an event from its content structure, just go and derive //! straight from the respective \p EventBaseT instead of using EventTemplate); //! specialisations may override that and provide useful semantics even without -//! \p ContentT (see EventTemplate, e.g.). +//! \p ContentT (see EventTemplate, e.g.). //! //! The template uses CRTP to pick the event type id from the actual class; //! it will fail to compile if \p EventT doesn't provide TypeId. It also uses @@ -402,8 +402,8 @@ public: !std::is_same_v, "If you see this, you tried to use EventTemplate with the default" " ContentT type, which is void. This default is only used with explicit" - " specialisations (see CallEventBase, e.g.). Otherwise, if you don't" - " intend to use the content part of EventTemplate then you don't need" + " specialisations (see CallEvent, e.g.). Otherwise, if you don't intend" + " to use the content part of EventTemplate then you don't need" " EventTemplate; just use the base event class directly"); using content_type = ContentT; diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index bd06f5c5..8928c81c 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -96,22 +96,6 @@ void RoomEvent::dumpTo(QDebug dbg) const dbg << " (made at " << originTimestamp().toString(Qt::ISODate) << ')'; } -QJsonObject CallEventBase::basicJson(const QString& matrixType, - const QString& callId, int version, - QJsonObject contentJson) -{ - contentJson.insert(QStringLiteral("call_id"), callId); - contentJson.insert(QStringLiteral("version"), version); - return RoomEvent::basicJson(matrixType, contentJson); -} - -CallEventBase::CallEventBase(const QJsonObject& json) - : RoomEvent(json) -{ - if (callId().isEmpty()) - qCWarning(EVENTS) << id() << "is a call event with an empty call id"; -} - #ifdef Quotient_E2EE_ENABLED void RoomEvent::setOriginalEvent(event_ptr_tt&& originalEvent) { diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 830f1d30..47b0b59d 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -79,31 +79,6 @@ using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; using RoomEventsRange = Range; -class QUOTIENT_API CallEventBase : public RoomEvent { -public: - QUO_BASE_EVENT(CallEventBase, "m.call.*"_ls, RoomEvent::BaseMetaType) - - explicit CallEventBase(const QJsonObject& json); - - QUO_CONTENT_GETTER(QString, callId) - QUO_CONTENT_GETTER(int, version) - -protected: - static QJsonObject basicJson(const QString& matrixType, - const QString& callId, int version, - QJsonObject contentJson = {}); -}; - -template -class EventTemplate : public CallEventBase { -public: - using CallEventBase::CallEventBase; - explicit EventTemplate(const QString& callId, - const QJsonObject& contentJson = {}) - : EventTemplate(basicJson(EventT::TypeId, callId, 0, contentJson)) - {} -}; - } // namespace Quotient Q_DECLARE_METATYPE(Quotient::RoomEvent*) Q_DECLARE_METATYPE(const Quotient::RoomEvent*) diff --git a/lib/room.cpp b/lib/room.cpp index a6617cc3..24939b55 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -35,10 +35,7 @@ #include "csapi/rooms.h" #include "csapi/tags.h" -#include "events/callanswerevent.h" -#include "events/callcandidatesevent.h" -#include "events/callhangupevent.h" -#include "events/callinviteevent.h" +#include "events/callevents.h" #include "events/encryptionevent.h" #include "events/reactionevent.h" #include "events/receiptevent.h" @@ -2916,7 +2913,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) if (q->supportsCalls()) for (auto it = from; it != syncEdge(); ++it) - if (const auto* evt = it->viewAs()) + if (const auto* evt = it->viewAs()) emit q->callEvent(q, evt); if (totalInserted > 0) { -- 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(). --- autotests/callcandidateseventtest.cpp | 13 ++++++++----- lib/connection.cpp | 10 ++++++---- lib/events/callevents.h | 4 ++-- lib/events/event.h | 11 +++++++++-- lib/events/roomevent.cpp | 2 +- lib/events/roomevent.h | 8 ++++---- lib/events/stateevent.h | 15 +++++++++------ 7 files changed, 39 insertions(+), 24 deletions(-) (limited to 'lib/events') diff --git a/autotests/callcandidateseventtest.cpp b/autotests/callcandidateseventtest.cpp index b37dd109..257e0ef2 100644 --- a/autotests/callcandidateseventtest.cpp +++ b/autotests/callcandidateseventtest.cpp @@ -40,13 +40,16 @@ void TestCallCandidatesEvent::fromJson() auto object = document.object(); - Quotient::CallCandidatesEvent callCandidatesEvent(object); + using namespace Quotient; + const auto& callCandidatesEvent = loadEvent(object); + QVERIFY(callCandidatesEvent); + QVERIFY(callCandidatesEvent->is()); - QCOMPARE(callCandidatesEvent.version(), 0); - QCOMPARE(callCandidatesEvent.callId(), QStringLiteral("12345")); - QCOMPARE(callCandidatesEvent.candidates().count(), 1); + QCOMPARE(callCandidatesEvent->version(), 0); + QCOMPARE(callCandidatesEvent->callId(), QStringLiteral("12345")); + QCOMPARE(callCandidatesEvent->candidates().count(), 1); - const QJsonObject &candidate = callCandidatesEvent.candidates().at(0).toObject(); + const auto& candidate = callCandidatesEvent->candidates().at(0).toObject(); QCOMPARE(candidate.value("sdpMid").toString(), QStringLiteral("audio")); QCOMPARE(candidate.value("sdpMLineIndex").toInt(), 0); QCOMPARE(candidate.value("candidate").toString(), 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 diff --git a/lib/events/callevents.h b/lib/events/callevents.h index 6d9feae4..752e331d 100644 --- a/lib/events/callevents.h +++ b/lib/events/callevents.h @@ -15,12 +15,12 @@ public: return mType.startsWith("m.call."); } - explicit CallEvent(const QJsonObject& json); - QUO_CONTENT_GETTER(QString, callId) QUO_CONTENT_GETTER(int, version) protected: + explicit CallEvent(const QJsonObject& json); + static QJsonObject basicJson(const QString& matrixType, const QString& callId, int version, QJsonObject contentJson = {}); diff --git a/lib/events/event.h b/lib/events/event.h index 6a7acf28..9d7c61a9 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -203,6 +203,9 @@ private: // === Event creation facilities === //! \brief Create an event of arbitrary type from its arguments +//! +//! This should not be used to load events from JSON - use loadEvent() for that. +//! \sa loadEvent template inline event_ptr_tt makeEvent(ArgTs&&... args) { @@ -266,8 +269,6 @@ public: return BaseMetaType; } - explicit Event(const QJsonObject& json); - Q_DISABLE_COPY(Event) Event(Event&&) noexcept = default; Event& operator=(Event&&) = delete; @@ -364,6 +365,10 @@ public: [[deprecated("Use is() instead")]] bool isCallEvent() const; protected: + friend class EventMetaType; // To access the below constructor + + explicit Event(const QJsonObject& json); + QJsonObject& editJson() { return _json; } virtual void dumpTo(QDebug dbg) const; @@ -427,6 +432,7 @@ public: //! pointing to that BaseMetaType. //! \sa EventMetaType, EventMetaType::SuppressLoadDerived #define QUO_BASE_EVENT(CppType_, ...) \ + friend class EventMetaType; \ static inline EventMetaType BaseMetaType{ \ #CppType_ __VA_OPT__(,) __VA_ARGS__ }; \ const AbstractEventMetaType& metaType() const override \ @@ -452,6 +458,7 @@ public: //! \sa EventMetaType #define QUO_EVENT(CppType_, MatrixType_, ...) \ static inline const auto& TypeId = MatrixType_##_ls; \ + friend class EventMetaType; \ static inline const EventMetaType MetaType{ \ #CppType_, TypeId, BaseMetaType __VA_OPT__(,) __VA_ARGS__ \ }; \ diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 8928c81c..e98cb591 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -12,7 +12,7 @@ RoomEvent::RoomEvent(const QJsonObject& json) : Event(json) { if (const auto redaction = unsignedPart(RedactedCauseKeyL); !redaction.isEmpty()) - _redactedBecause = makeEvent(redaction); + _redactedBecause = loadEvent(redaction); } RoomEvent::~RoomEvent() = default; // Let the smart pointer do its job diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 47b0b59d..203434f6 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -16,10 +16,7 @@ class QUOTIENT_API RoomEvent : public Event { public: QUO_BASE_EVENT(RoomEvent, {}, Event::BaseMetaType) - // RedactionEvent is an incomplete type here so we cannot inline - // constructors using it and also destructors (with 'using', in particular). - explicit RoomEvent(const QJsonObject& json); - ~RoomEvent() override; + ~RoomEvent() override; // Don't inline this - see the private section QString id() const; QDateTime originTimestamp() const; @@ -66,9 +63,12 @@ public: #endif protected: + explicit RoomEvent(const QJsonObject& json); void dumpTo(QDebug dbg) const override; private: + // RedactionEvent is an incomplete type here so we cannot inline + // constructors using it and also destructors (with 'using', in particular). event_ptr_tt _redactedBecause; #ifdef Quotient_E2EE_ENABLED diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 911972f2..ffbce76e 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -24,7 +24,6 @@ public: //! constructors and calls in, e.g., RoomStateView don't include it. static constexpr auto needsStateKey = false; - explicit StateEventBase(const QJsonObject& json); explicit StateEventBase(Type type, const QString& stateKey = {}, const QJsonObject& contentJson = {}); @@ -39,9 +38,11 @@ public: } QString replacedState() const; - void dumpTo(QDebug dbg) const override; - virtual bool repeatsState() const; + +protected: + explicit StateEventBase(const QJsonObject& json); + void dumpTo(QDebug dbg) const override; }; using StateEventPtr = event_ptr_tt; using StateEvents = EventsArray; @@ -129,13 +130,15 @@ private: using base_type = EventTemplate; public: - explicit KeylessStateEventBase(const QJsonObject& fullJson) - : base_type(fullJson) - {} template explicit KeylessStateEventBase(ContentParamTs&&... contentParams) : base_type(QString(), std::forward(contentParams)...) {} + +protected: + explicit KeylessStateEventBase(const QJsonObject& fullJson) + : base_type(fullJson) + {} }; template -- 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 +- lib/connection.h | 2 +- lib/eventitem.h | 4 ++-- lib/events/event.h | 43 ++++++++++++++++++++++++++----------------- quotest/quotest.cpp | 4 ++-- 5 files changed, 32 insertions(+), 23 deletions(-) (limited to 'lib/events') 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( diff --git a/lib/connection.h b/lib/connection.h index 66ed8b68..5afcfc2f 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -189,7 +189,7 @@ public: //! of that type. //! \note Direct chats map cannot be retrieved using this method _yet_; //! use directChats() instead. - template + template const EventT* accountData() const { return eventCast(accountData(EventT::TypeId)); diff --git a/lib/eventitem.h b/lib/eventitem.h index 2e55a724..445f8265 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -46,7 +46,7 @@ public: const RoomEvent* event() const { return rawPtr(evt); } const RoomEvent* get() const { return event(); } - template + template EventT> const EventT* viewAs() const { return eventCast(evt); @@ -67,7 +67,7 @@ public: std::any& userData() { return data; } protected: - template + template EventT> EventT* getAs() { return eventCast(evt); diff --git a/lib/events/event.h b/lib/events/event.h index 9d7c61a9..d0b63085 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -64,7 +64,11 @@ struct QUOTIENT_API EventTypeRegistry { class Event; -template +// TODO: move over to std::derived_from once it's available everywhere +template +concept EventClass = std::is_base_of_v; + +template bool is(const Event& e); //! \brief The base class for event metatypes @@ -206,13 +210,13 @@ private: //! //! This should not be used to load events from JSON - use loadEvent() for that. //! \sa loadEvent -template +template inline event_ptr_tt makeEvent(ArgTs&&... args) { return std::make_unique(std::forward(args)...); } -template +template constexpr const auto& mostSpecificMetaType() { if constexpr (requires { EventT::MetaType; }) @@ -226,7 +230,7 @@ constexpr const auto& mostSpecificMetaType() //! Use this factory template to detect the type from the JSON object //! contents (the detected event type should derive from the template //! parameter type) and create an event object of that type. -template +template inline event_ptr_tt loadEvent(const QJsonObject& fullJson) { return mostSpecificMetaType().loadFrom( @@ -238,7 +242,7 @@ inline event_ptr_tt loadEvent(const QJsonObject& fullJson) //! Use this template to resolve the C++ type from the Matrix type string in //! \p matrixType and create an event of that type by passing all parameters //! to BaseEventT::basicJson(). -template +template inline event_ptr_tt loadEvent(const QString& matrixType, const auto&... otherBasicJsonParams) { @@ -246,7 +250,7 @@ inline event_ptr_tt loadEvent(const QString& matrixType, EventT::basicJson(matrixType, otherBasicJsonParams...), matrixType); } -template +template struct JsonConverter> : JsonObjectUnpacker> { // No dump() to avoid any ambiguity on whether a given export to JSON uses @@ -295,7 +299,7 @@ public: //! the returned value will be different. QString matrixType() const; - template + template bool is() const { return Quotient::is(*this); @@ -377,7 +381,7 @@ private: }; using EventPtr = event_ptr_tt; -template +template using EventsArray = std::vector>; using Events = EventsArray; @@ -400,8 +404,10 @@ using Events = EventsArray; //! your class will likely be clearer and more concise. //! \sa https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern //! \sa DEFINE_SIMPLE_EVENT -template +template class EventTemplate : public BaseEventT { + // Above: can't constrain EventT to be EventClass because it's incomplete + // by CRTP definition. public: static_assert( !std::is_same_v, @@ -522,7 +528,7 @@ public: // === is<>(), eventCast<>() and switchOnType<>() === -template +template inline bool is(const Event& e) { if constexpr (requires { EventT::MetaType; }) { @@ -544,7 +550,7 @@ inline bool is(const Event& e) //! can be either "dumb" (BaseEventT*) or "smart" (`event_ptr_tt<>`). This //! overload doesn't affect the event ownership - if the original pointer owns //! the event it must outlive the downcast pointer to keep it from dangling. -template +template inline auto eventCast(const BasePtrT& eptr) -> decltype(static_cast(&*eptr)) { @@ -567,7 +573,7 @@ inline auto eventCast(const BasePtrT& eptr) //! after calling this overload; if it is a temporary, this normally //! leads to the event getting deleted along with the end of //! the temporary's lifetime. -template +template inline auto eventCast(event_ptr_tt&& eptr) { return eptr && is>(*eptr) @@ -576,12 +582,15 @@ inline auto eventCast(event_ptr_tt&& eptr) } namespace _impl { - template - concept Invocable_With_Downcast = + template + concept Invocable_With_Downcast = requires + { + requires EventClass; std::is_base_of_v>>; + }; } -template +template inline auto switchOnType(const BaseT& event, TailT&& tail) { if constexpr (std::is_invocable_v) { @@ -596,7 +605,7 @@ inline auto switchOnType(const BaseT& event, TailT&& tail) } } -template +template inline auto switchOnType(const BaseT& event, FnT1&& fn1, FnTs&&... fns) { using event_type1 = fn_arg_t; @@ -605,7 +614,7 @@ inline auto switchOnType(const BaseT& event, FnT1&& fn1, FnTs&&... fns) return switchOnType(event, std::forward(fns)...); } -template +template [[deprecated("The new name for visit() is switchOnType()")]] // inline auto visit(const BaseT& event, FnTs&&... fns) { diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 3ac6404a..624888be 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -128,7 +128,7 @@ private: [[nodiscard]] bool checkRedactionOutcome(const QByteArray& thisTest, const QString& evtIdToRedact); - template + template EventT> [[nodiscard]] bool validatePendingEvent(const QString& txnId); [[nodiscard]] bool checkDirectChat() const; void finishTest(const TestToken& token, bool condition, const char* file, @@ -156,7 +156,7 @@ void TestSuite::doTest(const QByteArray& testName) Q_ARG(TestToken, testName)); } -template +template EventT> bool TestSuite::validatePendingEvent(const QString& txnId) { auto it = targetRoom->findPendingEvent(txnId); -- cgit v1.2.3 From 7251d6856993a08dd8ec1d4965a310e4cf8e97d3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 10 Aug 2022 21:54:15 +0200 Subject: StateEventBase -> StateEvent Now that StateEvent name is vacated, the naming for event core classes can be completely unified: Event, RoomEvent, CallEvent, StateEvent. --- lib/eventitem.h | 5 ++--- lib/events/event.cpp | 2 +- lib/events/event.h | 4 ++-- lib/events/roomcreateevent.h | 4 ++-- lib/events/roomtombstoneevent.h | 4 ++-- lib/events/stateevent.cpp | 13 ++++++------- lib/events/stateevent.h | 42 +++++++++++++++++++++-------------------- lib/room.cpp | 15 +++++++-------- lib/room.h | 20 ++++++++++++-------- lib/roomstateview.cpp | 10 +++++----- lib/roomstateview.h | 13 ++++++------- 11 files changed, 67 insertions(+), 65 deletions(-) (limited to 'lib/events') diff --git a/lib/eventitem.h b/lib/eventitem.h index 445f8265..96e45b38 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -95,10 +95,9 @@ private: }; template <> -inline const StateEventBase* EventItemBase::viewAs() const +inline const StateEvent* EventItemBase::viewAs() const { - return evt->isStateEvent() ? weakPtrCast(evt) - : nullptr; + return evt->isStateEvent() ? weakPtrCast(evt) : nullptr; } template <> diff --git a/lib/events/event.cpp b/lib/events/event.cpp index ca751081..da7de919 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -73,7 +73,7 @@ const QJsonObject Event::unsignedJson() const return fullJson()[UnsignedKeyL].toObject(); } -bool Event::isStateEvent() const { return is(); } +bool Event::isStateEvent() const { return is(); } bool Event::isCallEvent() const { return is(); } diff --git a/lib/events/event.h b/lib/events/event.h index d0b63085..c8ef5acb 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -125,7 +125,7 @@ inline bool operator==(const AbstractEventMetaType& lhs, //! //! TL;DR for the loadFrom() story: //! - for base event types, use QUO_BASE_EVENT and, if you have additional -//! validation (e.g., JSON has to contain a certain key - see StateEventBase +//! validation (e.g., JSON has to contain a certain key - see StateEvent //! for a real example), define it in the static EventT::isValid() member //! function accepting QJsonObject and returning bool. //! - for leaf (specific) event types - simply use QUO_EVENT and it will do @@ -153,7 +153,7 @@ public: //! any of its base event types) has a static isValid() predicate and //! the event JSON does not satisfy it, nullptr is immediately returned //! to the upper level or to the loadFrom() caller. This is how existence - //! of `state_key` is checked in any type derived from StateEventBase. + //! of `state_key` is checked in any type derived from StateEvent. //! 3. If step 1b above returned non-nullptr, immediately return it. //! 4. //! a. If EventT::isValid() or EventT::TypeId (either, or both) exist and diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index 2709258f..5968e187 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -7,11 +7,11 @@ #include "quotient_common.h" namespace Quotient { -class QUOTIENT_API RoomCreateEvent : public StateEventBase { +class QUOTIENT_API RoomCreateEvent : public StateEvent { public: QUO_EVENT(RoomCreateEvent, "m.room.create") - using StateEventBase::StateEventBase; + using StateEvent::StateEvent; struct Predecessor { QString roomId; diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h index 95743e32..c85b4dfd 100644 --- a/lib/events/roomtombstoneevent.h +++ b/lib/events/roomtombstoneevent.h @@ -6,11 +6,11 @@ #include "stateevent.h" namespace Quotient { -class QUOTIENT_API RoomTombstoneEvent : public StateEventBase { +class QUOTIENT_API RoomTombstoneEvent : public StateEvent { public: QUO_EVENT(RoomTombstoneEvent, "m.room.tombstone") - using StateEventBase::StateEventBase; + using StateEvent::StateEvent; QString serverMessage() const; QString successorRoomId() const; diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 204044bb..72ecd5ad 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -6,30 +6,29 @@ using namespace Quotient; -StateEventBase::StateEventBase(const QJsonObject& json) +StateEvent::StateEvent(const QJsonObject& json) : RoomEvent(json) { Q_ASSERT_X(json.contains(StateKeyKeyL), __FUNCTION__, "Attempt to create a state event without state key"); } -StateEventBase::StateEventBase(Event::Type type, const QString& stateKey, +StateEvent::StateEvent(Event::Type type, const QString& stateKey, const QJsonObject& contentJson) : RoomEvent(basicJson(type, stateKey, contentJson)) {} -bool StateEventBase::repeatsState() const +bool StateEvent::repeatsState() const { - const auto prevContentJson = unsignedPart(PrevContentKeyL); - return fullJson().value(ContentKeyL) == prevContentJson; + return contentJson() == unsignedPart(PrevContentKeyL); } -QString StateEventBase::replacedState() const +QString StateEvent::replacedState() const { return unsignedPart("replaces_state"_ls); } -void StateEventBase::dumpTo(QDebug dbg) const +void StateEvent::dumpTo(QDebug dbg) const { if (!stateKey().isEmpty()) dbg << '<' << stateKey() << "> "; diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index ffbce76e..992ec2e2 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -7,10 +7,10 @@ namespace Quotient { -class QUOTIENT_API StateEventBase : public RoomEvent { +class QUOTIENT_API StateEvent : public RoomEvent { public: - QUO_BASE_EVENT(StateEventBase, "json.contains('state_key')"_ls, - RoomEvent::BaseMetaType) + QUO_BASE_EVENT(StateEvent, "json.contains('state_key')"_ls, + RoomEvent::BaseMetaType) static bool isValid(const QJsonObject& fullJson) { return fullJson.contains(StateKeyKeyL); @@ -24,8 +24,8 @@ public: //! constructors and calls in, e.g., RoomStateView don't include it. static constexpr auto needsStateKey = false; - explicit StateEventBase(Type type, const QString& stateKey = {}, - const QJsonObject& contentJson = {}); + explicit StateEvent(Type type, const QString& stateKey = {}, + const QJsonObject& contentJson = {}); //! Make a minimal correct Matrix state event JSON static QJsonObject basicJson(const QString& matrixTypeId, @@ -41,18 +41,20 @@ public: virtual bool repeatsState() const; protected: - explicit StateEventBase(const QJsonObject& json); + explicit StateEvent(const QJsonObject& json); void dumpTo(QDebug dbg) const override; }; -using StateEventPtr = event_ptr_tt; -using StateEvents = EventsArray; +using StateEventBase + [[deprecated("StateEventBase is StateEvent now")]] = StateEvent; +using StateEventPtr = event_ptr_tt; +using StateEvents = EventsArray; -[[deprecated("Use StateEventBase::basicJson() instead")]] +[[deprecated("Use StateEvent::basicJson() instead")]] inline QJsonObject basicStateEventJson(const QString& matrixTypeId, const QJsonObject& content, const QString& stateKey = {}) { - return StateEventBase::basicJson(matrixTypeId, stateKey, content); + return StateEvent::basicJson(matrixTypeId, stateKey, content); } /** @@ -64,8 +66,8 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, using StateEventKey = std::pair; template -class EventTemplate - : public StateEventBase { +class EventTemplate + : public StateEvent { public: using content_type = ContentT; @@ -82,14 +84,14 @@ public: }; explicit EventTemplate(const QJsonObject& fullJson) - : StateEventBase(fullJson) + : StateEvent(fullJson) , _content(fromJson(Event::contentJson())) , _prev(unsignedJson()) {} template explicit EventTemplate(const QString& stateKey, ContentParamTs&&... contentParams) - : StateEventBase(EventT::TypeId, stateKey) + : StateEvent(EventT::TypeId, stateKey) , _content { std::forward(contentParams)... } { editJson().insert(ContentKey, toJson(_content)); @@ -113,11 +115,11 @@ private: template class KeyedStateEventBase - : public EventTemplate { + : public EventTemplate { public: static constexpr auto needsStateKey = true; - using EventTemplate::EventTemplate; + using EventTemplate::EventTemplate; }; template @@ -125,9 +127,9 @@ concept Keyed_State_Event = EvT::needsStateKey; template class KeylessStateEventBase - : public EventTemplate { + : public EventTemplate { private: - using base_type = EventTemplate; + using base_type = EventTemplate; public: template @@ -145,5 +147,5 @@ template concept Keyless_State_Event = !EvT::needsStateKey; } // namespace Quotient -Q_DECLARE_METATYPE(Quotient::StateEventBase*) -Q_DECLARE_METATYPE(const Quotient::StateEventBase*) +Q_DECLARE_METATYPE(Quotient::StateEvent*) +Q_DECLARE_METATYPE(const Quotient::StateEvent*) diff --git a/lib/room.cpp b/lib/room.cpp index 24939b55..6bed9b56 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -208,7 +208,7 @@ public: void getPreviousContent(int limit = 10, const QString &filter = {}); - const StateEventBase* getCurrentState(const StateEventKey& evtKey) const + const StateEvent* getCurrentState(const StateEventKey& evtKey) const { const auto* evt = currentState.value(evtKey, nullptr); if (!evt) { @@ -216,9 +216,8 @@ public: // In the absence of a real event, make a stub as-if an event // with empty content has been received. Event classes should be // prepared for empty/invalid/malicious content anyway. - stubbedState.emplace(evtKey, - loadEvent(evtKey.first, - evtKey.second)); + stubbedState.emplace( + evtKey, loadEvent(evtKey.first, evtKey.second)); qCDebug(STATE) << "A new stub event created for key {" << evtKey.first << evtKey.second << "}"; qCDebug(STATE) << "Stubbed state size:" << stubbedState.size(); @@ -1565,8 +1564,8 @@ bool Room::usesEncryption() const .isEmpty(); } -const StateEventBase* Room::getCurrentState(const QString& evtType, - const QString& stateKey) const +const StateEvent* Room::getCurrentState(const QString& evtType, + const QString& stateKey) const { return d->getCurrentState({ evtType, stateKey }); } @@ -2279,7 +2278,7 @@ QString Room::postJson(const QString& matrixType, return d->sendEvent(loadEvent(matrixType, eventContent)); } -SetRoomStateWithKeyJob* Room::setState(const StateEventBase& evt) +SetRoomStateWithKeyJob* Room::setState(const StateEvent& evt) { return setState(evt.matrixType(), evt.stateKey(), evt.contentJson()); } @@ -3095,7 +3094,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) // Change the state const auto* const oldStateEvent = - std::exchange(curStateEvent, static_cast(&e)); + std::exchange(curStateEvent, static_cast(&e)); Q_ASSERT(!oldStateEvent || (oldStateEvent->matrixType() == e.matrixType() && oldStateEvent->stateKey() == e.stateKey())); diff --git a/lib/room.h b/lib/room.h index b454bfbc..5eb66b8b 100644 --- a/lib/room.h +++ b/lib/room.h @@ -757,9 +757,8 @@ public: */ [[deprecated("Use currentState().get() instead; " "make sure to check its result for nullptrs")]] // - const Quotient::StateEventBase* - getCurrentState(const QString& evtType, - const QString& stateKey = {}) const; + const StateEvent* getCurrentState(const QString& evtType, + const QString& stateKey = {}) const; /// Get a state event with the given event type and state key /*! This is a typesafe overload that accepts a C++ event type instead of @@ -782,16 +781,21 @@ public: RoomStateView currentState() const; //! Send a request to update the room state with the given event - SetRoomStateWithKeyJob* setState(const StateEventBase& evt); + SetRoomStateWithKeyJob* setState(const StateEvent& evt); //! \brief Set a state event of the given type with the given arguments //! //! This typesafe overload attempts to send a state event with the type //! \p EvT and the content defined by \p args. Specifically, the function - //! creates a temporary object of type \p EvT passing \p args to - //! the constructor, and sends a request to the homeserver using - //! the Matrix event type defined by \p EvT and the event content produced - //! via EvT::contentJson(). + //! constructs a temporary object of type \p EvT with its content + //! list-initialised from \p args, and sends a request to the homeserver + //! using the Matrix event type defined by \p EvT and the event content + //! produced via EvT::contentJson(). + //! + //! \note This call is not suitable for events that assume non-empty + //! stateKey, such as member events; for those you have to create + //! a temporary event object yourself and use the setState() overload + //! that accepts StateEvent const-ref. template auto setState(ArgTs&&... args) { diff --git a/lib/roomstateview.cpp b/lib/roomstateview.cpp index 94c88eee..be0f7c6c 100644 --- a/lib/roomstateview.cpp +++ b/lib/roomstateview.cpp @@ -5,8 +5,8 @@ using namespace Quotient; -const StateEventBase* RoomStateView::get(const QString& evtType, - const QString& stateKey) const +const StateEvent* RoomStateView::get(const QString& evtType, + const QString& stateKey) const { return value({ evtType, stateKey }); } @@ -23,10 +23,10 @@ QJsonObject RoomStateView::contentJson(const QString& evtType, return queryOr(evtType, stateKey, &Event::contentJson, QJsonObject()); } -const QVector -RoomStateView::eventsOfType(const QString& evtType) const +const QVector RoomStateView::eventsOfType( + const QString& evtType) const { - auto vals = QVector(); + auto vals = QVector(); for (auto it = cbegin(); it != cend(); ++it) if (it.key().first == evtType) vals.append(it.value()); diff --git a/lib/roomstateview.h b/lib/roomstateview.h index 119c24cf..c5261a1e 100644 --- a/lib/roomstateview.h +++ b/lib/roomstateview.h @@ -22,10 +22,10 @@ template >> concept Keyless_State_Fn = !EvT::needsStateKey; class QUOTIENT_API RoomStateView - : private QHash { + : private QHash { Q_GADGET public: - const QHash& events() const + const QHash& events() const { return *this; } @@ -40,8 +40,8 @@ public: //! have to check that it has_value() before using. Alternatively //! you can now use queryCurrentState() to access state safely. //! \sa getCurrentStateContentJson - const StateEventBase* get(const QString& evtType, - const QString& stateKey = {}) const; + const StateEvent* get(const QString& evtType, + const QString& stateKey = {}) const; //! \brief Get a state event with the given event type and state key //! @@ -94,7 +94,7 @@ public: auto content(const QString& stateKey, typename EvT::content_type defaultValue = {}) const { - // StateEvent<>::content is special in that it returns a const-ref, + // EventBase<>::content is special in that it returns a const-ref, // and lift() inside queryOr() can't wrap that in a temporary Omittable. if (const auto evt = get(stateKey)) return evt->content(); @@ -122,8 +122,7 @@ public: //! //! This method returns all known state events that have occured in //! the room of the given type. - const QVector - eventsOfType(const QString& evtType) const; + const QVector eventsOfType(const QString& evtType) const; //! \brief Run a function on a state event with the given type and key //! -- cgit v1.2.3 From bd2736bc9f8b6023ecbc21d0d831856703b853db Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 21 Jun 2022 07:37:01 +0200 Subject: SingleKeyValue: use reference for template parameter I guess it was simply overlooked originally; in any case, currently used compilers deal with the reference just as fine as with the pointer. --- lib/events/event.h | 26 +++++++++++++------------- lib/events/simplestateevents.h | 24 ++++++++++++------------ lib/events/single_key_value.h | 6 +++--- 3 files changed, 28 insertions(+), 28 deletions(-) (limited to 'lib/events') diff --git a/lib/events/event.h b/lib/events/event.h index c8ef5acb..0abef1f0 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -511,19 +511,19 @@ public: /// To retrieve the value the getter uses a JSON key name that corresponds to /// its own (getter's) name but written in snake_case. \p GetterName_ must be /// in camelCase, no quotes (an identifier, not a literal). -#define DEFINE_SIMPLE_EVENT(Name_, Base_, TypeId_, ValueType_, GetterName_, \ - JsonKey_) \ - constexpr auto Name_##ContentKey = JsonKey_##_ls; \ - class QUOTIENT_API Name_ \ - : public EventTemplate< \ - Name_, Base_, \ - EventContent::SingleKeyValue> { \ - public: \ - QUO_EVENT(Name_, TypeId_) \ - using value_type = ValueType_; \ - using EventTemplate::EventTemplate; \ - QUO_CONTENT_GETTER_X(ValueType_, GetterName_, Name_##ContentKey) \ - }; \ +#define DEFINE_SIMPLE_EVENT(Name_, Base_, TypeId_, ValueType_, GetterName_, \ + JsonKey_) \ + constexpr auto Name_##ContentKey = JsonKey_##_ls; \ + class QUOTIENT_API Name_ \ + : public EventTemplate< \ + Name_, Base_, \ + EventContent::SingleKeyValue> { \ + public: \ + QUO_EVENT(Name_, TypeId_) \ + using value_type = ValueType_; \ + using EventTemplate::EventTemplate; \ + QUO_CONTENT_GETTER_X(ValueType_, GetterName_, Name_##ContentKey) \ + }; \ // End of macro // === is<>(), eventCast<>() and switchOnType<>() === diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index d84dc1b1..2a0d3817 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -7,17 +7,17 @@ #include "single_key_value.h" namespace Quotient { -#define DEFINE_SIMPLE_STATE_EVENT(Name_, TypeId_, ValueType_, ContentKey_) \ - constexpr auto Name_##Key = #ContentKey_##_ls; \ - class QUOTIENT_API Name_ \ - : public KeylessStateEventBase< \ - Name_, EventContent::SingleKeyValue> { \ - public: \ - using value_type = ValueType_; \ - QUO_EVENT(Name_, TypeId_) \ - using KeylessStateEventBase::KeylessStateEventBase; \ - auto ContentKey_() const { return content().value; } \ - }; \ +#define DEFINE_SIMPLE_STATE_EVENT(Name_, TypeId_, ValueType_, ContentKey_) \ + constexpr auto Name_##Key = #ContentKey_##_ls; \ + class QUOTIENT_API Name_ \ + : public KeylessStateEventBase< \ + Name_, EventContent::SingleKeyValue> { \ + public: \ + using value_type = ValueType_; \ + QUO_EVENT(Name_, TypeId_) \ + using KeylessStateEventBase::KeylessStateEventBase; \ + auto ContentKey_() const { return content().value; } \ + }; \ // End of macro DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) @@ -29,7 +29,7 @@ constexpr auto RoomAliasesEventKey = "aliases"_ls; class QUOTIENT_API RoomAliasesEvent : public KeyedStateEventBase< RoomAliasesEvent, - EventContent::SingleKeyValue> + EventContent::SingleKeyValue> { public: QUO_EVENT(RoomAliasesEvent, "m.room.aliases") diff --git a/lib/events/single_key_value.h b/lib/events/single_key_value.h index 5edff3b1..ca2bd331 100644 --- a/lib/events/single_key_value.h +++ b/lib/events/single_key_value.h @@ -5,7 +5,7 @@ namespace Quotient { namespace EventContent { - template + template struct SingleKeyValue { // NOLINTBEGIN(google-explicit-constructor): that check should learn // about explicit(false) @@ -20,7 +20,7 @@ namespace EventContent { }; } // namespace EventContent -template +template struct JsonConverter> { using content_type = EventContent::SingleKeyValue; static content_type load(const QJsonValue& jv) @@ -31,6 +31,6 @@ struct JsonConverter> { { return { { JsonKey, toJson(c.value) } }; } - static inline const auto JsonKey = toSnakeCase(*KeyStr); + static inline const auto JsonKey = toSnakeCase(KeyStr); }; } // namespace Quotient -- 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. --- autotests/testkeyverification.cpp | 4 +- lib/connection.cpp | 44 +++++-------- lib/connection.h | 11 +--- lib/events/keyverificationevent.h | 74 ++++++++------------- lib/keyverificationsession.cpp | 133 ++++++++++++++------------------------ lib/keyverificationsession.h | 6 +- 6 files changed, 102 insertions(+), 170 deletions(-) (limited to 'lib/events') diff --git a/autotests/testkeyverification.cpp b/autotests/testkeyverification.cpp index 59a1c934..96aad6c7 100644 --- a/autotests/testkeyverification.cpp +++ b/autotests/testkeyverification.cpp @@ -17,7 +17,7 @@ private Q_SLOTS: CREATE_CONNECTION(a, "alice1", "secret", "AliceDesktop") CREATE_CONNECTION(b, "alice1", "secret", "AlicePhone") - KeyVerificationSession *aSession = nullptr; + QPointer aSession{}; connect(a.get(), &Connection::newKeyVerificationSession, this, [&](KeyVerificationSession* session) { aSession = session; QVERIFY(session->remoteDeviceId() == b->deviceId()); @@ -51,7 +51,7 @@ private Q_SLOTS: }); b->syncLoop(); a->syncLoop(); - QSignalSpy spy(b.get(), &Connection::incomingKeyVerificationDone); + QSignalSpy spy(aSession, &KeyVerificationSession::finished); spy.wait(10000); } }; 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) diff --git a/lib/connection.h b/lib/connection.h index 79c23c9a..75faf370 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -864,15 +864,10 @@ Q_SIGNALS: void devicesListLoaded(); #ifdef Quotient_E2EE_ENABLED - void incomingKeyVerificationReady(const KeyVerificationReadyEvent& event); - void incomingKeyVerificationStart(const KeyVerificationStartEvent& event); - void incomingKeyVerificationAccept(const KeyVerificationAcceptEvent& event); - void incomingKeyVerificationKey(const KeyVerificationKeyEvent& event); - void incomingKeyVerificationMac(const KeyVerificationMacEvent& event); - void incomingKeyVerificationDone(const KeyVerificationDoneEvent& event); - void incomingKeyVerificationCancel(const KeyVerificationCancelEvent& event); - void newKeyVerificationSession(KeyVerificationSession* session); + void keyVerificationStateChanged( + const KeyVerificationSession* session, + Quotient::KeyVerificationSession::State state); void sessionVerified(const QString& userId, const QString& deviceId); #endif diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index 0ffd8b2c..0e939508 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -9,13 +9,24 @@ namespace Quotient { static constexpr auto SasV1Method = "m.sas.v1"_ls; +class QUOTIENT_API KeyVerificationEvent : public Event { +public: + QUO_BASE_EVENT(KeyVerificationEvent, "m.key.*"_ls, Event::BaseMetaType) + + using Event::Event; + + /// An opaque identifier for the verification request. Must + /// be unique with respect to the devices involved. + QUO_CONTENT_GETTER(QString, transactionId) +}; + /// Requests a key verification with another user's devices. /// Typically sent as a to-device event. -class QUOTIENT_API KeyVerificationRequestEvent : public Event { +class QUOTIENT_API KeyVerificationRequestEvent : public KeyVerificationEvent { public: QUO_EVENT(KeyVerificationRequestEvent, "m.key.verification.request") - using Event::Event; + using KeyVerificationEvent::KeyVerificationEvent; KeyVerificationRequestEvent(const QString& transactionId, const QString& fromDevice, const QStringList& methods, @@ -30,10 +41,6 @@ public: /// The device ID which is initiating the request. QUO_CONTENT_GETTER(QString, fromDevice) - /// An opaque identifier for the verification request. Must - /// be unique with respect to the devices involved. - QUO_CONTENT_GETTER(QString, transactionId) - /// The verification methods supported by the sender. QUO_CONTENT_GETTER(QStringList, methods) @@ -44,11 +51,11 @@ public: QUO_CONTENT_GETTER(QDateTime, timestamp) }; -class QUOTIENT_API KeyVerificationReadyEvent : public Event { +class QUOTIENT_API KeyVerificationReadyEvent : public KeyVerificationEvent { public: QUO_EVENT(KeyVerificationReadyEvent, "m.key.verification.ready") - using Event::Event; + using KeyVerificationEvent::KeyVerificationEvent; KeyVerificationReadyEvent(const QString& transactionId, const QString& fromDevice, const QStringList& methods) @@ -61,19 +68,16 @@ public: /// The device ID which is accepting the request. QUO_CONTENT_GETTER(QString, fromDevice) - /// The transaction id of the verification request - QUO_CONTENT_GETTER(QString, transactionId) - /// The verification methods supported by the sender. QUO_CONTENT_GETTER(QStringList, methods) }; /// Begins a key verification process. -class QUOTIENT_API KeyVerificationStartEvent : public Event { +class QUOTIENT_API KeyVerificationStartEvent : public KeyVerificationEvent { public: QUO_EVENT(KeyVerificationStartEvent, "m.key.verification.start") - using Event::Event; + using KeyVerificationEvent::KeyVerificationEvent; KeyVerificationStartEvent(const QString& transactionId, const QString& fromDevice) : KeyVerificationStartEvent( @@ -92,10 +96,6 @@ public: /// The device ID which is initiating the process. QUO_CONTENT_GETTER(QString, fromDevice) - /// An opaque identifier for the verification request. Must - /// be unique with respect to the devices involved. - QUO_CONTENT_GETTER(QString, transactionId) - /// The verification method to use. QUO_CONTENT_GETTER(QString, method) @@ -140,11 +140,11 @@ public: /// Accepts a previously sent m.key.verification.start message. /// Typically sent as a to-device event. -class QUOTIENT_API KeyVerificationAcceptEvent : public Event { +class QUOTIENT_API KeyVerificationAcceptEvent : public KeyVerificationEvent { public: QUO_EVENT(KeyVerificationAcceptEvent, "m.key.verification.accept") - using Event::Event; + using KeyVerificationEvent::KeyVerificationEvent; KeyVerificationAcceptEvent(const QString& transactionId, const QString& commitment) : KeyVerificationAcceptEvent(basicJson( @@ -158,9 +158,6 @@ public: { "commitment"_ls, commitment } })) {} - /// An opaque identifier for the verification process. - QUO_CONTENT_GETTER(QString, transactionId) - /// The verification method to use. Must be 'm.sas.v1'. QUO_CONTENT_GETTER(QString, method) @@ -170,10 +167,7 @@ public: /// The hash method the device is choosing to use, out of the /// options in the m.key.verification.start message. - QString hashData() const - { - return contentPart("hash"_ls); - } + QUO_CONTENT_GETTER_X(QString, hashData, "hash"_ls) /// The message authentication code the device is choosing to use, out /// of the options in the m.key.verification.start message. @@ -188,11 +182,11 @@ public: QUO_CONTENT_GETTER(QString, commitment) }; -class QUOTIENT_API KeyVerificationCancelEvent : public Event { +class QUOTIENT_API KeyVerificationCancelEvent : public KeyVerificationEvent { public: QUO_EVENT(KeyVerificationCancelEvent, "m.key.verification.cancel") - using Event::Event; + using KeyVerificationEvent::KeyVerificationEvent; KeyVerificationCancelEvent(const QString& transactionId, const QString& reason) : KeyVerificationCancelEvent( @@ -203,9 +197,6 @@ public: })) {} - /// An opaque identifier for the verification process. - QUO_CONTENT_GETTER(QString, transactionId) - /// A human readable description of the code. The client should only /// rely on this string if it does not understand the code. QUO_CONTENT_GETTER(QString, reason) @@ -216,30 +207,27 @@ public: /// Sends the ephemeral public key for a device to the partner device. /// Typically sent as a to-device event. -class QUOTIENT_API KeyVerificationKeyEvent : public Event { +class KeyVerificationKeyEvent : public KeyVerificationEvent { public: QUO_EVENT(KeyVerificationKeyEvent, "m.key.verification.key") - using Event::Event; + using KeyVerificationEvent::KeyVerificationEvent; KeyVerificationKeyEvent(const QString& transactionId, const QString& key) : KeyVerificationKeyEvent( basicJson(TypeId, { { "transaction_id"_ls, transactionId }, { "key"_ls, key } })) {} - /// An opaque identifier for the verification process. - QUO_CONTENT_GETTER(QString, transactionId) - /// The device's ephemeral public key, encoded as unpadded base64. QUO_CONTENT_GETTER(QString, key) }; /// Sends the MAC of a device's key to the partner device. -class QUOTIENT_API KeyVerificationMacEvent : public Event { +class QUOTIENT_API KeyVerificationMacEvent : public KeyVerificationEvent { public: QUO_EVENT(KeyVerificationMacEvent, "m.key.verification.mac") - using Event::Event; + using KeyVerificationEvent::KeyVerificationEvent; KeyVerificationMacEvent(const QString& transactionId, const QString& keys, const QJsonObject& mac) : KeyVerificationMacEvent( @@ -248,9 +236,6 @@ public: { "mac"_ls, mac } })) {} - /// An opaque identifier for the verification process. - QUO_CONTENT_GETTER(QString, transactionId) - /// The device's ephemeral public key, encoded as unpadded base64. QUO_CONTENT_GETTER(QString, keys) @@ -260,17 +245,14 @@ public: } }; -class QUOTIENT_API KeyVerificationDoneEvent : public Event { +class QUOTIENT_API KeyVerificationDoneEvent : public KeyVerificationEvent { public: QUO_EVENT(KeyVerificationDoneEvent, "m.key.verification.done") - using Event::Event; + using KeyVerificationEvent::KeyVerificationEvent; explicit KeyVerificationDoneEvent(const QString& transactionId) : KeyVerificationDoneEvent( basicJson(TypeId, { { "transaction_id"_ls, transactionId } })) {} - - /// The same transactionId as before - QUO_CONTENT_GETTER(QString, transactionId) }; } // namespace Quotient diff --git a/lib/keyverificationsession.cpp b/lib/keyverificationsession.cpp index 24fc08e1..cc4428d7 100644 --- a/lib/keyverificationsession.cpp +++ b/lib/keyverificationsession.cpp @@ -68,42 +68,6 @@ KeyVerificationSession::KeyVerificationSession(QString userId, QString deviceId, void KeyVerificationSession::init(milliseconds timeout) { - connect(m_connection, &Connection::incomingKeyVerificationReady, this, [this](const KeyVerificationReadyEvent& event) { - if (event.transactionId() == m_transactionId && event.fromDevice() == m_remoteDeviceId) { - handleReady(event); - } - }); - connect(m_connection, &Connection::incomingKeyVerificationStart, this, [this](const KeyVerificationStartEvent& event) { - if (event.transactionId() == m_transactionId && event.fromDevice() == m_remoteDeviceId) { - handleStart(event); - } - }); - connect(m_connection, &Connection::incomingKeyVerificationAccept, this, [this](const KeyVerificationAcceptEvent& event) { - if (event.transactionId() == m_transactionId) { - handleAccept(event); - } - }); - connect(m_connection, &Connection::incomingKeyVerificationKey, this, [this](const KeyVerificationKeyEvent& event) { - if (event.transactionId() == m_transactionId) { - handleKey(event); - } - }); - connect(m_connection, &Connection::incomingKeyVerificationMac, this, [this](const KeyVerificationMacEvent& event) { - if (event.transactionId() == m_transactionId) { - handleMac(event); - } - }); - connect(m_connection, &Connection::incomingKeyVerificationDone, this, [this](const KeyVerificationDoneEvent& event) { - if (event.transactionId() == m_transactionId) { - handleDone(event); - } - }); - connect(m_connection, &Connection::incomingKeyVerificationCancel, this, [this](const KeyVerificationCancelEvent& event) { - if (event.transactionId() == m_transactionId) { - handleCancel(event); - } - }); - QTimer::singleShot(timeout, this, [this] { cancelVerification(TIMEOUT); }); m_sas = olm_sas(new std::byte[olm_sas_size()]); @@ -118,6 +82,53 @@ KeyVerificationSession::~KeyVerificationSession() delete[] reinterpret_cast(m_sas); } +void KeyVerificationSession::handleEvent(const KeyVerificationEvent& baseEvent) +{ + if (!switchOnType( + baseEvent, + [this](const KeyVerificationCancelEvent& event) { + setError(stringToError(event.code())); + setState(CANCELED); + return true; + }, + [this](const KeyVerificationStartEvent& event) { + if (state() != WAITINGFORREADY && state() != READY) + return false; + handleStart(event); + return true; + }, + [this](const KeyVerificationReadyEvent& event) { + if (state() == WAITINGFORREADY) + handleReady(event); + // ACCEPTED is also fine here because it's possible to receive + // ready and start in the same sync, in which case start might + // be handled before ready. + return state() == WAITINGFORREADY || state() == ACCEPTED; + }, + [this](const KeyVerificationAcceptEvent& event) { + if (state() != WAITINGFORACCEPT) + return false; + m_commitment = event.commitment(); + sendKey(); + setState(WAITINGFORKEY); + return true; + }, + [this](const KeyVerificationKeyEvent& event) { + if (state() != ACCEPTED && state() != WAITINGFORKEY) + return false; + handleKey(event); + return true; + }, + [this](const KeyVerificationMacEvent& event) { + if (state() != WAITINGFORMAC) + return false; + handleMac(event); + return true; + }, + [this](const KeyVerificationDoneEvent&) { return state() == DONE; })) + cancelVerification(UNEXPECTED_MESSAGE); +} + struct EmojiStoreEntry : EmojiEntry { QHash translatedDescriptions; @@ -154,10 +165,6 @@ EmojiEntry emojiForCode(int code, const QString& language) void KeyVerificationSession::handleKey(const KeyVerificationKeyEvent& event) { - if (state() != WAITINGFORKEY && state() != ACCEPTED) { - cancelVerification(UNEXPECTED_MESSAGE); - return; - } auto eventKey = event.key().toLatin1(); olm_sas_set_their_key(m_sas, eventKey.data(), eventKey.size()); @@ -314,34 +321,18 @@ void KeyVerificationSession::sendStartSas() void KeyVerificationSession::handleReady(const KeyVerificationReadyEvent& event) { - if (state() == ACCEPTED) { - // It's possible to receive ready and start in the same sync, in which case start might be handled before ready. - return; - } - if (state() != WAITINGFORREADY) { - cancelVerification(UNEXPECTED_MESSAGE); - return; - } setState(READY); m_remoteSupportedMethods = event.methods(); auto methods = commonSupportedMethods(m_remoteSupportedMethods); - if (methods.isEmpty()) { + if (methods.isEmpty()) cancelVerification(UNKNOWN_METHOD); - return; - } - - if (methods.size() == 1) { - sendStartSas(); - } + else if (methods.size() == 1) + sendStartSas(); // -> WAITINGFORACCEPT } void KeyVerificationSession::handleStart(const KeyVerificationStartEvent& event) { - if (state() != READY && state() != WAITINGFORREADY) { - cancelVerification(UNEXPECTED_MESSAGE); - return; - } if (startSentByUs) { if (m_remoteUserId > m_connection->userId() || (m_remoteUserId == m_connection->userId() && m_remoteDeviceId > m_connection->deviceId())) { return; @@ -362,17 +353,6 @@ void KeyVerificationSession::handleStart(const KeyVerificationStartEvent& event) setState(ACCEPTED); } -void KeyVerificationSession::handleAccept(const KeyVerificationAcceptEvent& event) -{ - if(state() != WAITINGFORACCEPT) { - cancelVerification(UNEXPECTED_MESSAGE); - return; - } - m_commitment = event.commitment(); - sendKey(); - setState(WAITINGFORKEY); -} - void KeyVerificationSession::handleMac(const KeyVerificationMacEvent& event) { QStringList keys = event.mac().keys(); @@ -402,19 +382,6 @@ void KeyVerificationSession::handleMac(const KeyVerificationMacEvent& event) } } -void KeyVerificationSession::handleDone(const KeyVerificationDoneEvent&) -{ - if (state() != DONE) { - cancelVerification(UNEXPECTED_MESSAGE); - } -} - -void KeyVerificationSession::handleCancel(const KeyVerificationCancelEvent& event) -{ - setError(stringToError(event.code())); - setState(CANCELED); -} - QVector KeyVerificationSession::sasEmojis() const { return m_sasEmojis; diff --git a/lib/keyverificationsession.h b/lib/keyverificationsession.h index b2e3b36d..9cac1184 100644 --- a/lib/keyverificationsession.h +++ b/lib/keyverificationsession.h @@ -91,6 +91,8 @@ public: ~KeyVerificationSession() override; Q_DISABLE_COPY_MOVE(KeyVerificationSession) + void handleEvent(const KeyVerificationEvent& baseEvent); + QVector sasEmojis() const; State state() const; @@ -108,7 +110,6 @@ public Q_SLOTS: void cancelVerification(Error error); Q_SIGNALS: - void startReceived(); void keyReceived(); void sasEmojisChanged(); void stateChanged(); @@ -133,11 +134,8 @@ private: void handleReady(const KeyVerificationReadyEvent& event); void handleStart(const KeyVerificationStartEvent& event); - void handleAccept(const KeyVerificationAcceptEvent& event); void handleKey(const KeyVerificationKeyEvent& event); void handleMac(const KeyVerificationMacEvent& event); - void handleDone(const KeyVerificationDoneEvent&); - void handleCancel(const KeyVerificationCancelEvent& event); void init(std::chrono::milliseconds timeout); void setState(State state); void setError(Error error); -- cgit v1.2.3 From f161519740da12c2578d6ecf79a501e10c2debae Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 24 Sep 2022 11:36:44 +0200 Subject: Fix construction of EncryptedEvents The parent constructor requires full json instead of content json now --- lib/events/encryptedevent.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 94b44901..9ef3b22a 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -8,20 +8,28 @@ using namespace Quotient; EncryptedEvent::EncryptedEvent(const QJsonObject& ciphertexts, const QString& senderKey) - : RoomEvent({ { AlgorithmKeyL, OlmV1Curve25519AesSha2AlgoKey }, - { CiphertextKeyL, ciphertexts }, - { SenderKeyKeyL, senderKey } }) + : RoomEvent({ + {"content", QJsonObject{ + { AlgorithmKeyL, OlmV1Curve25519AesSha2AlgoKey }, + { CiphertextKeyL, ciphertexts }, + { SenderKeyKeyL, senderKey } + }}, + {TypeKeyL, "m.room.encrypted"} + }) {} EncryptedEvent::EncryptedEvent(const QByteArray& ciphertext, const QString& senderKey, const QString& deviceId, const QString& sessionId) : RoomEvent({ - { AlgorithmKeyL, MegolmV1AesSha2AlgoKey }, - { CiphertextKeyL, QString(ciphertext) }, - { DeviceIdKeyL, deviceId }, - { SenderKeyKeyL, senderKey }, - { SessionIdKeyL, sessionId }, + {"content", QJsonObject{ + { AlgorithmKeyL, MegolmV1AesSha2AlgoKey }, + { CiphertextKeyL, QString(ciphertext) }, + { DeviceIdKeyL, deviceId }, + { SenderKeyKeyL, senderKey }, + { SessionIdKeyL, sessionId }, + }}, + {TypeKeyL, "m.room.encrypted"} }) {} -- cgit v1.2.3 From 4146f33bda60d04db34fbd5614439b22b5d5837f Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Sat, 24 Sep 2022 14:27:16 +0200 Subject: Use basicJson() --- lib/events/encryptedevent.cpp | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) (limited to 'lib/events') diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 9ef3b22a..49df25c8 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -8,29 +8,19 @@ using namespace Quotient; EncryptedEvent::EncryptedEvent(const QJsonObject& ciphertexts, const QString& senderKey) - : RoomEvent({ - {"content", QJsonObject{ - { AlgorithmKeyL, OlmV1Curve25519AesSha2AlgoKey }, - { CiphertextKeyL, ciphertexts }, - { SenderKeyKeyL, senderKey } - }}, - {TypeKeyL, "m.room.encrypted"} - }) + : RoomEvent(basicJson(TypeId, { { AlgorithmKeyL, OlmV1Curve25519AesSha2AlgoKey }, + { CiphertextKeyL, ciphertexts }, + { SenderKeyKeyL, senderKey } })) {} EncryptedEvent::EncryptedEvent(const QByteArray& ciphertext, const QString& senderKey, const QString& deviceId, const QString& sessionId) - : RoomEvent({ - {"content", QJsonObject{ - { AlgorithmKeyL, MegolmV1AesSha2AlgoKey }, - { CiphertextKeyL, QString(ciphertext) }, - { DeviceIdKeyL, deviceId }, - { SenderKeyKeyL, senderKey }, - { SessionIdKeyL, sessionId }, - }}, - {TypeKeyL, "m.room.encrypted"} - }) + : RoomEvent(basicJson(TypeId, { { AlgorithmKeyL, MegolmV1AesSha2AlgoKey }, + { CiphertextKeyL, QString(ciphertext) }, + { DeviceIdKeyL, deviceId }, + { SenderKeyKeyL, senderKey }, + { SessionIdKeyL, sessionId } })) {} EncryptedEvent::EncryptedEvent(const QJsonObject& obj) -- 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. --- CMakeLists.txt | 2 +- autotests/testolmaccount.cpp | 3 +- autotests/testolmsession.cpp | 8 +++- lib/connection.cpp | 3 +- lib/e2ee/e2ee.h | 36 ++++----------- lib/e2ee/qolmaccount.cpp | 82 +++++++++++++++++---------------- lib/e2ee/qolmaccount.h | 9 ++-- lib/e2ee/qolmerrors.cpp | 25 ---------- lib/e2ee/qolmerrors.h | 28 ------------ lib/e2ee/qolminboundsession.cpp | 84 +++++++++++++++++++--------------- lib/e2ee/qolminboundsession.h | 5 +- lib/e2ee/qolmoutboundsession.cpp | 81 +++++++++++++++++++-------------- lib/e2ee/qolmoutboundsession.h | 7 ++- lib/e2ee/qolmsession.cpp | 98 +++++++++++++++++++++------------------- lib/e2ee/qolmsession.h | 4 +- lib/e2ee/qolmutility.cpp | 22 +++++---- lib/e2ee/qolmutility.h | 4 +- lib/events/encryptedevent.cpp | 1 + lib/events/encryptedevent.h | 7 ++- lib/room.cpp | 1 - lib/util.h | 17 +++++++ 21 files changed, 266 insertions(+), 261 deletions(-) delete mode 100644 lib/e2ee/qolmerrors.cpp delete mode 100644 lib/e2ee/qolmerrors.h (limited to 'lib/events') diff --git a/CMakeLists.txt b/CMakeLists.txt index a56115e3..b021411c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,6 +150,7 @@ list(APPEND lib_SRCS lib/eventitem.h lib/eventitem.cpp lib/accountregistry.h lib/accountregistry.cpp lib/mxcreply.h lib/mxcreply.cpp + lib/e2ee/e2ee.h # because it's used by generated API lib/events/event.h lib/events/event.cpp lib/events/eventloader.h lib/events/roomevent.h lib/events/roomevent.cpp @@ -193,7 +194,6 @@ if (${PROJECT_NAME}_ENABLE_E2EE) lib/e2ee/qolmoutboundsession.h lib/e2ee/qolmoutboundsession.cpp lib/e2ee/qolmutils.h lib/e2ee/qolmutils.cpp lib/e2ee/qolmutility.h lib/e2ee/qolmutility.cpp - lib/e2ee/qolmerrors.h lib/e2ee/qolmerrors.cpp lib/e2ee/qolmsession.h lib/e2ee/qolmsession.cpp lib/e2ee/qolmmessage.h lib/e2ee/qolmmessage.cpp lib/events/keyverificationevent.h diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 280705d0..eb428661 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -25,7 +25,8 @@ void TestOlmAccount::pickleUnpickledTest() auto identityKeys = olmAccount.identityKeys(); auto pickled = olmAccount.pickle(Unencrypted{}).value(); QOlmAccount olmAccount2(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); - olmAccount2.unpickle(pickled, Unencrypted{}); + auto unpickleResult = olmAccount2.unpickle(pickled, Unencrypted{}); + QCOMPARE(unpickleResult, 0); auto identityKeys2 = olmAccount2.identityKeys(); QCOMPARE(identityKeys.curve25519, identityKeys2.curve25519); QCOMPARE(identityKeys.ed25519, identityKeys2.ed25519); diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 182659e7..66a04241 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -12,9 +12,13 @@ std::pair createSessionPair() QByteArray pickledAccountA("eOBXIKivUT6YYowRH031BNv7zNmzqM5B7CpXdyeaPvala5mt7/OeqrG1qVA7vA1SYloFyvJPIy0QNkD3j1HiPl5vtZHN53rtfZ9exXDok03zjmssqn4IJsqcA7Fbo1FZeKafG0NFcWwCPTdmcV7REqxjqGm3I4K8MQFa45AdTGSUu2C12cWeOcbSMlcINiMral+Uyah1sgPmLJ18h1qcnskXUXQvpffZ5DiUw1Iz5zxnwOQF1GVyowPJD7Zdugvj75RQnDxAn6CzyvrY2k2CuedwqDC3fIXM2xdUNWttW4nC2g4InpBhCVvNwhZYxlUb5BUEjmPI2AB3dAL5ry6o9MFncmbN6x5x"); QByteArray pickledAccountB("eModTvoFi9oOIkax4j4nuxw9Tcl/J8mOmUctUWI68Q89HSaaPTqR+tdlKQ85v2GOs5NlZCp7EuycypN9GQ4fFbHUCrS7nspa3GFBWsR8PnM8+wez5PWmfFZLg3drOvT0jbMjpDx0MjGYClHBqcrEpKx9oFaIRGBaX6HXzT4lRaWSJkXxuX92q8iGNrLn96PuAWFNcD+2JXpPcNFntslwLUNgqzpZ04aIFYwL80GmzyOgq3Bz1GO6u3TgCQEAmTIYN2QkO0MQeuSfe7UoMumhlAJ6R8GPcdSSPtmXNk4tdyzzlgpVq1hm7ZLKto+g8/5Aq3PvnvA8wCqno2+Pi1duK1pZFTIlActr"); auto accountA = QOlmAccount("accountA:foo.com", "Device1UserA"); - accountA.unpickle(pickledAccountA, Unencrypted{}); + if (accountA.unpickle(pickledAccountA, Unencrypted{}) != OLM_SUCCESS) + qFatal("Failed to unpickle account A: %s", accountA.lastError()); + auto accountB = QOlmAccount("accountB:foo.com", "Device1UserB"); - accountB.unpickle(pickledAccountB, Unencrypted{}); + if (accountB.unpickle(pickledAccountB, Unencrypted{}) != OLM_SUCCESS) + qFatal("Failed to unpickle account B: %s", accountB.lastError()); + const QByteArray identityKeyA("qIEr3TWcJQt4CP8QoKKJcCaukByIOpgh6erBkhLEa2o"); const QByteArray oneTimeKeyA("WzsbsjD85iB1R32iWxfJdwkgmdz29ClMbJSJziECYwk"); 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 diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 0772b70a..51ceff67 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -6,21 +6,20 @@ #pragma once #include "converters.h" -#include "expected.h" -#include "qolmerrors.h" #include #include #include -#include -namespace Quotient { +#ifdef Quotient_E2EE_ENABLED +# include "expected.h" + +# include +# include +#endif -constexpr auto CiphertextKeyL = "ciphertext"_ls; -constexpr auto SenderKeyKeyL = "sender_key"_ls; -constexpr auto DeviceIdKeyL = "device_id"_ls; -constexpr auto SessionIdKeyL = "session_id"_ls; +namespace Quotient { constexpr auto AlgorithmKeyL = "algorithm"_ls; constexpr auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; @@ -47,6 +46,7 @@ inline bool isSupportedAlgorithm(const QString& algorithm) != SupportedAlgorithms.cend(); } +#ifdef Quotient_E2EE_ENABLED struct Unencrypted {}; struct Encrypted { QByteArray key; @@ -64,7 +64,8 @@ class QOlmOutboundGroupSession; using QOlmOutboundGroupSessionPtr = std::unique_ptr; template -using QOlmExpected = Expected; +using QOlmExpected = Expected; +#endif struct IdentityKeys { @@ -133,23 +134,6 @@ private: using OneTimeKeys = QHash>; -template -class asKeyValueRange -{ -public: - asKeyValueRange(T& data) - : m_data { data } - {} - - auto begin() { return m_data.keyValueBegin(); } - auto end() { return m_data.keyValueEnd(); } - -private: - T &m_data; -}; -template -asKeyValueRange(T&) -> asKeyValueRange; - } // namespace Quotient Q_DECLARE_METATYPE(Quotient::SignedOneTimeKey) diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index ccb191f4..d33db8e3 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -18,8 +18,13 @@ using namespace Quotient; // Convert olm error to enum -QOlmError lastError(OlmAccount *account) { - return fromString(olm_account_last_error(account)); +OlmErrorCode QOlmAccount::lastErrorCode() const { + return olm_account_last_error_code(m_account); +} + +const char* QOlmAccount::lastError() const +{ + return olm_account_last_error(m_account); } QOlmAccount::QOlmAccount(const QString& userId, const QString& deviceId, @@ -40,24 +45,24 @@ void QOlmAccount::createNewAccount() m_account = olm_account(new uint8_t[olm_account_size()]); size_t randomSize = olm_create_account_random_length(m_account); QByteArray randomData = getRandom(randomSize); - const auto error = olm_create_account(m_account, randomData.data(), randomSize); - if (error == olm_error()) { - throw lastError(m_account); + if (olm_create_account(m_account, randomData.data(), randomSize) + == olm_error()) { + throw lastError(); } emit needsSave(); } -void QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) +OlmErrorCode QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) { m_account = olm_account(new uint8_t[olm_account_size()]); const QByteArray key = toKey(mode); - const auto error = olm_unpickle_account(m_account, key.data(), key.length(), pickled.data(), pickled.size()); - if (error == olm_error()) { - qCWarning(E2EE) << "Failed to unpickle olm account"; - //TODO: Do something that is not dying + if (olm_unpickle_account(m_account, key.data(), key.length(), + pickled.data(), pickled.size()) + == olm_error()) { // Probably log the user out since we have no way of getting to the keys - //throw lastError(m_account); + return lastErrorCode(); } + return OLM_SUCCESS; } QOlmExpected QOlmAccount::pickle(const PicklingMode &mode) @@ -65,11 +70,10 @@ QOlmExpected QOlmAccount::pickle(const PicklingMode &mode) const QByteArray key = toKey(mode); const size_t pickleLength = olm_pickle_account_length(m_account); QByteArray pickleBuffer(pickleLength, '0'); - const auto error = olm_pickle_account(m_account, key.data(), - key.length(), pickleBuffer.data(), pickleLength); - if (error == olm_error()) { - return lastError(m_account); - } + if (olm_pickle_account(m_account, key.data(), key.length(), + pickleBuffer.data(), pickleLength) + == olm_error()) + return lastErrorCode(); return pickleBuffer; } @@ -77,9 +81,9 @@ IdentityKeys QOlmAccount::identityKeys() const { const size_t keyLength = olm_account_identity_keys_length(m_account); QByteArray keyBuffer(keyLength, '0'); - const auto error = olm_account_identity_keys(m_account, keyBuffer.data(), keyLength); - if (error == olm_error()) { - throw lastError(m_account); + if (olm_account_identity_keys(m_account, keyBuffer.data(), keyLength) + == olm_error()) { + throw lastError(); } const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object(); return IdentityKeys { @@ -92,11 +96,10 @@ QByteArray QOlmAccount::sign(const QByteArray &message) const { QByteArray signatureBuffer(olm_account_signature_length(m_account), '0'); - const auto error = olm_account_sign(m_account, message.data(), message.length(), - signatureBuffer.data(), signatureBuffer.length()); - - if (error == olm_error()) { - throw lastError(m_account); + if (olm_account_sign(m_account, message.data(), message.length(), + signatureBuffer.data(), signatureBuffer.length()) + == olm_error()) { + throw lastError(); } return signatureBuffer; } @@ -131,15 +134,15 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); QByteArray randomBuffer = getRandom(randomLength); - const auto error = + const auto result = olm_account_generate_one_time_keys(m_account, numberOfKeys, randomBuffer.data(), randomLength); - if (error == olm_error()) { - throw lastError(m_account); + if (result == olm_error()) { + throw lastError(); } emit needsSave(); - return error; + return result; } UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const @@ -147,11 +150,10 @@ UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); QByteArray oneTimeKeysBuffer(static_cast(oneTimeKeyLength), '0'); - const auto error = olm_account_one_time_keys(m_account, - oneTimeKeysBuffer.data(), - oneTimeKeyLength); - if (error == olm_error()) { - throw lastError(m_account); + if (olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), + oneTimeKeyLength) + == olm_error()) { + throw lastError(); } const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); UnsignedOneTimeKeys oneTimeKeys; @@ -171,16 +173,16 @@ OneTimeKeys QOlmAccount::signOneTimeKeys(const UnsignedOneTimeKeys &keys) const return signedOneTimeKeys; } -std::optional QOlmAccount::removeOneTimeKeys( - const QOlmSession& session) +OlmErrorCode QOlmAccount::removeOneTimeKeys(const QOlmSession& session) { - const auto error = olm_remove_one_time_keys(m_account, session.raw()); - - if (error == olm_error()) { - return lastError(m_account); + if (olm_remove_one_time_keys(m_account, session.raw()) == olm_error()) { + qWarning(E2EE).nospace() + << "Failed to remove one-time keys for session " + << session.sessionId() << ": " << lastError(); + return lastErrorCode(); } emit needsSave(); - return std::nullopt; + return OLM_SUCCESS; } OlmAccount* QOlmAccount::data() { return m_account; } diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index f2a31314..5ad98e47 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -36,7 +36,8 @@ public: //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmAccount`. //! This needs to be called before any other action or use createNewAccount() instead. - void unpickle(QByteArray &pickled, const PicklingMode &mode); + [[nodiscard]] OlmErrorCode unpickle(QByteArray& pickled, + const PicklingMode& mode); //! Serialises an OlmAccount to encrypted Base64. QOlmExpected pickle(const PicklingMode &mode); @@ -69,8 +70,7 @@ public: DeviceKeys deviceKeys() const; //! Remove the one time key used to create the supplied session. - [[nodiscard]] std::optional removeOneTimeKeys( - const QOlmSession& session); + [[nodiscard]] OlmErrorCode removeOneTimeKeys(const QOlmSession& session); //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. //! @@ -92,6 +92,9 @@ public: void markKeysAsPublished(); + OlmErrorCode lastErrorCode() const; + const char *lastError() const; + // HACK do not use directly QOlmAccount(OlmAccount *account); OlmAccount *data(); diff --git a/lib/e2ee/qolmerrors.cpp b/lib/e2ee/qolmerrors.cpp deleted file mode 100644 index 5a60b7e6..00000000 --- a/lib/e2ee/qolmerrors.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - - -#include "qolmerrors.h" -#include "util.h" -#include - -Quotient::QOlmError Quotient::fromString(const char* error_raw) { - const QLatin1String error { error_raw }; - if (error_raw == "BAD_ACCOUNT_KEY"_ls) { - return QOlmError::BadAccountKey; - } else if (error_raw == "BAD_MESSAGE_KEY_ID"_ls) { - return QOlmError::BadMessageKeyId; - } else if (error_raw == "INVALID_BASE64"_ls) { - return QOlmError::InvalidBase64; - } else if (error_raw == "NOT_ENOUGH_RANDOM"_ls) { - return QOlmError::NotEnoughRandom; - } else if (error_raw == "OUTPUT_BUFFER_TOO_SMALL"_ls) { - return QOlmError::OutputBufferTooSmall; - } else { - return QOlmError::Unknown; - } -} diff --git a/lib/e2ee/qolmerrors.h b/lib/e2ee/qolmerrors.h deleted file mode 100644 index 20e61c12..00000000 --- a/lib/e2ee/qolmerrors.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Carl Schwan -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#pragma once - -#include "quotient_export.h" - -namespace Quotient { -//! All errors that could be caused by an operation regarding Olm -//! Errors are named exactly like the ones in libolm. -enum QOlmError -{ - BadAccountKey, - BadMessageFormat, - BadMessageKeyId, - BadMessageMac, - BadMessageVersion, - InvalidBase64, - NotEnoughRandom, - OutputBufferTooSmall, - UnknownMessageIndex, - Unknown, -}; - -QUOTIENT_API QOlmError fromString(const char* error_raw); - -} //namespace Quotient diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 17f06205..870070c2 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -3,20 +3,27 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "e2ee/qolminboundsession.h" -#include +#include "logging.h" + #include +#include +#include using namespace Quotient; -QOlmError lastError(OlmInboundGroupSession *session) { - return fromString(olm_inbound_group_session_last_error(session)); +OlmErrorCode QOlmInboundGroupSession::lastErrorCode() const { + return olm_inbound_group_session_last_error_code(m_groupSession); } -QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session) - : m_groupSession(session) +const char* QOlmInboundGroupSession::lastError() const { + return olm_inbound_group_session_last_error(m_groupSession); } +QOlmInboundGroupSession::QOlmInboundGroupSession(OlmInboundGroupSession *session) + : m_groupSession(session) +{} + QOlmInboundGroupSession::~QOlmInboundGroupSession() { olm_clear_inbound_group_session(m_groupSession); @@ -26,11 +33,12 @@ QOlmInboundGroupSession::~QOlmInboundGroupSession() std::unique_ptr QOlmInboundGroupSession::create(const QByteArray &key) { const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); - const auto error = olm_init_inbound_group_session(olmInboundGroupSession, - reinterpret_cast(key.constData()), key.size()); - - if (error == olm_error()) { - throw lastError(olmInboundGroupSession); + if (olm_init_inbound_group_session( + olmInboundGroupSession, + reinterpret_cast(key.constData()), key.size()) + == olm_error()) { + // FIXME: create QOlmInboundGroupSession earlier and use lastError() + throw olm_inbound_group_session_last_error_code(olmInboundGroupSession); } return std::make_unique(olmInboundGroupSession); @@ -41,10 +49,12 @@ std::unique_ptr QOlmInboundGroupSession::import(const Q const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); QByteArray keyBuf = key; - const auto error = olm_import_inbound_group_session(olmInboundGroupSession, - reinterpret_cast(keyBuf.data()), keyBuf.size()); - if (error == olm_error()) { - throw lastError(olmInboundGroupSession); + if (olm_import_inbound_group_session( + olmInboundGroupSession, + reinterpret_cast(keyBuf.data()), keyBuf.size()) + == olm_error()) { + // FIXME: create QOlmInboundGroupSession earlier and use lastError() + throw olm_inbound_group_session_last_error_code(olmInboundGroupSession); } return std::make_unique(olmInboundGroupSession); @@ -62,10 +72,11 @@ QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const { QByteArray pickledBuf(olm_pickle_inbound_group_session_length(m_groupSession), '0'); const QByteArray key = toKey(mode); - const auto error = olm_pickle_inbound_group_session(m_groupSession, key.data(), key.length(), pickledBuf.data(), - pickledBuf.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); + if (olm_pickle_inbound_group_session(m_groupSession, key.data(), + key.length(), pickledBuf.data(), + pickledBuf.length()) + == olm_error()) { + throw lastError(); } return pickledBuf; } @@ -76,10 +87,12 @@ QOlmExpected QOlmInboundGroupSession::unpickle( QByteArray pickledBuf = pickled; const auto groupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); QByteArray key = toKey(mode); - const auto error = olm_unpickle_inbound_group_session(groupSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.size()); - if (error == olm_error()) { - return lastError(groupSession); + if (olm_unpickle_inbound_group_session(groupSession, key.data(), + key.length(), pickledBuf.data(), + pickledBuf.size()) + == olm_error()) { + // FIXME: create QOlmInboundGroupSession earlier and use lastError() + return olm_inbound_group_session_last_error_code(groupSession); } key.clear(); @@ -105,12 +118,9 @@ QOlmExpected> QOlmInboundGroupSession::decrypt( const auto plaintextLen = olm_group_decrypt(m_groupSession, reinterpret_cast(messageBuf.data()), messageBuf.length(), reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), &messageIndex); - - // Error code or plaintext length is returned - const auto decryptError = plaintextLen; - - if (decryptError == olm_error()) { - return lastError(m_groupSession); + if (plaintextLen == olm_error()) { + qWarning(E2EE) << "Failed to decrypt the message:" << lastError(); + return lastErrorCode(); } QByteArray output(plaintextLen, '0'); @@ -123,10 +133,11 @@ QOlmExpected QOlmInboundGroupSession::exportSession(uint32_t message { const auto keyLength = olm_export_inbound_group_session_length(m_groupSession); QByteArray keyBuf(keyLength, '0'); - const auto error = olm_export_inbound_group_session(m_groupSession, reinterpret_cast(keyBuf.data()), keyLength, messageIndex); - - if (error == olm_error()) { - return lastError(m_groupSession); + if (olm_export_inbound_group_session( + m_groupSession, reinterpret_cast(keyBuf.data()), + keyLength, messageIndex) + == olm_error()) { + return lastErrorCode(); } return keyBuf; } @@ -139,10 +150,11 @@ uint32_t QOlmInboundGroupSession::firstKnownIndex() const QByteArray QOlmInboundGroupSession::sessionId() const { QByteArray sessionIdBuf(olm_inbound_group_session_id_length(m_groupSession), '0'); - const auto error = olm_inbound_group_session_id(m_groupSession, reinterpret_cast(sessionIdBuf.data()), - sessionIdBuf.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); + if (olm_inbound_group_session_id( + m_groupSession, reinterpret_cast(sessionIdBuf.data()), + sessionIdBuf.length()) + == olm_error()) { + throw lastError(); } return sessionIdBuf; } diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index 1a9b4415..e8da6355 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -6,7 +6,7 @@ #include "e2ee/e2ee.h" -#include +struct OlmInboundGroupSession; namespace Quotient { @@ -46,6 +46,9 @@ public: QString senderId() const; void setSenderId(const QString& senderId); + OlmErrorCode lastErrorCode() const; + const char* lastError() const; + QOlmInboundGroupSession(OlmInboundGroupSession* session); private: OlmInboundGroupSession* m_groupSession; diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index a2eff2c8..79c16e01 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -3,12 +3,20 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "e2ee/qolmoutboundsession.h" + #include "e2ee/qolmutils.h" +#include + using namespace Quotient; -QOlmError lastError(OlmOutboundGroupSession *session) { - return fromString(olm_outbound_group_session_last_error(session)); +OlmErrorCode QOlmOutboundGroupSession::lastErrorCode() const { + return olm_outbound_group_session_last_error_code(m_groupSession); +} + +const char* QOlmOutboundGroupSession::lastError() const +{ + return olm_outbound_group_session_last_error(m_groupSession); } QOlmOutboundGroupSession::QOlmOutboundGroupSession(OlmOutboundGroupSession *session) @@ -27,11 +35,12 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() const auto randomLength = olm_init_outbound_group_session_random_length(olmOutboundGroupSession); QByteArray randomBuf = getRandom(randomLength); - const auto error = olm_init_outbound_group_session(olmOutboundGroupSession, - reinterpret_cast(randomBuf.data()), randomBuf.length()); - - if (error == olm_error()) { - throw lastError(olmOutboundGroupSession); + if (olm_init_outbound_group_session( + olmOutboundGroupSession, + reinterpret_cast(randomBuf.data()), randomBuf.length()) + == olm_error()) { + // FIXME: create the session object earlier and use lastError() + throw olm_outbound_group_session_last_error_code(olmOutboundGroupSession); } const auto keyMaxLength = olm_outbound_group_session_key_length(olmOutboundGroupSession); @@ -48,12 +57,11 @@ QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mo { QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); QByteArray key = toKey(mode); - const auto error = olm_pickle_outbound_group_session(m_groupSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()); - - if (error == olm_error()) { - return lastError(m_groupSession); - } + if (olm_pickle_outbound_group_session(m_groupSession, key.data(), + key.length(), pickledBuf.data(), + pickledBuf.length()) + == olm_error()) + return lastErrorCode(); key.clear(); @@ -65,10 +73,13 @@ QOlmExpected QOlmOutboundGroupSession::unpickle(con QByteArray pickledBuf = pickled; auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); QByteArray key = toKey(mode); - const auto error = olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()); - if (error == olm_error()) { - return lastError(olmOutboundGroupSession); + if (olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), + key.length(), pickledBuf.data(), + pickledBuf.length()) + == olm_error()) { + // FIXME: create the session object earlier and use lastError() + return olm_outbound_group_session_last_error_code( + olmOutboundGroupSession); } const auto idMaxLength = olm_outbound_group_session_id_length(olmOutboundGroupSession); QByteArray idBuffer(idMaxLength, '0'); @@ -84,12 +95,13 @@ QOlmExpected QOlmOutboundGroupSession::encrypt(const QString &plaint QByteArray plaintextBuf = plaintext.toUtf8(); const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); QByteArray messageBuf(messageMaxLength, '0'); - const auto error = olm_group_encrypt(m_groupSession, reinterpret_cast(plaintextBuf.data()), - plaintextBuf.length(), reinterpret_cast(messageBuf.data()), messageBuf.length()); - - if (error == olm_error()) { - return lastError(m_groupSession); - } + if (olm_group_encrypt(m_groupSession, + reinterpret_cast(plaintextBuf.data()), + plaintextBuf.length(), + reinterpret_cast(messageBuf.data()), + messageBuf.length()) + == olm_error()) + return lastErrorCode(); return messageBuf; } @@ -103,11 +115,12 @@ QByteArray QOlmOutboundGroupSession::sessionId() const { const auto idMaxLength = olm_outbound_group_session_id_length(m_groupSession); QByteArray idBuffer(idMaxLength, '0'); - const auto error = olm_outbound_group_session_id(m_groupSession, reinterpret_cast(idBuffer.data()), - idBuffer.length()); - if (error == olm_error()) { - throw lastError(m_groupSession); - } + if (olm_outbound_group_session_id( + m_groupSession, reinterpret_cast(idBuffer.data()), + idBuffer.length()) + == olm_error()) + throw lastError(); + return idBuffer; } @@ -115,12 +128,12 @@ QOlmExpected QOlmOutboundGroupSession::sessionKey() const { const auto keyMaxLength = olm_outbound_group_session_key_length(m_groupSession); QByteArray keyBuffer(keyMaxLength, '0'); - const auto error = olm_outbound_group_session_key( - m_groupSession, reinterpret_cast(keyBuffer.data()), - keyMaxLength); - if (error == olm_error()) { - return lastError(m_groupSession); - } + if (olm_outbound_group_session_key( + m_groupSession, reinterpret_cast(keyBuffer.data()), + keyMaxLength) + == olm_error()) + return lastErrorCode(); + return keyBuffer; } diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index 9a82d22a..cd26fc67 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -6,8 +6,7 @@ #include "e2ee/e2ee.h" -#include -#include +struct OlmOutboundGroupSession; namespace Quotient { @@ -51,6 +50,10 @@ public: QDateTime creationTime() const; void setCreationTime(const QDateTime& creationTime); + + OlmErrorCode lastErrorCode() const; + const char* lastError() const; + private: OlmOutboundGroupSession *m_groupSession; int m_messageCount = 0; diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 2a98d5d8..771d310d 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -12,8 +12,13 @@ using namespace Quotient; -QOlmError lastError(OlmSession* session) { - return fromString(olm_session_last_error(session)); +OlmErrorCode QOlmSession::lastErrorCode() const { + return olm_session_last_error_code(m_session); +} + +const char* QOlmSession::lastError() const +{ + return olm_session_last_error(m_session); } Quotient::QOlmSession::~QOlmSession() @@ -32,7 +37,8 @@ QOlmExpected QOlmSession::createInbound( const QString& theirIdentityKey) { if (preKeyMessage.type() != QOlmMessage::PreKey) { - qCCritical(E2EE) << "The message is not a pre-key in when creating inbound session" << BadMessageFormat; + qCCritical(E2EE) << "The message is not a pre-key; will try to create " + "the inbound session anyway"; } const auto olmSession = create(); @@ -47,7 +53,8 @@ QOlmExpected QOlmSession::createInbound( } if (error == olm_error()) { - const auto lastErr = lastError(olmSession); + // FIXME: the QOlmSession object should be created earlier + const auto lastErr = olm_session_last_error_code(olmSession); qCWarning(E2EE) << "Error when creating inbound session" << lastErr; return lastErr; } @@ -78,15 +85,17 @@ QOlmExpected QOlmSession::createOutboundSession( QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8(); - const auto error = olm_create_outbound_session(olmOutboundSession, - account->data(), - reinterpret_cast(theirIdentityKeyBuf.data()), theirIdentityKeyBuf.length(), - reinterpret_cast(theirOneTimeKeyBuf.data()), theirOneTimeKeyBuf.length(), - reinterpret_cast(randomBuf.data()), randomBuf.length()); - - if (error == olm_error()) { - const auto lastErr = lastError(olmOutboundSession); - if (lastErr == QOlmError::NotEnoughRandom) { + if (olm_create_outbound_session( + olmOutboundSession, account->data(), + reinterpret_cast(theirIdentityKeyBuf.data()), + theirIdentityKeyBuf.length(), + reinterpret_cast(theirOneTimeKeyBuf.data()), + theirOneTimeKeyBuf.length(), + reinterpret_cast(randomBuf.data()), randomBuf.length()) + == olm_error()) { + // FIXME: the QOlmSession object should be created earlier + const auto lastErr = olm_session_last_error_code(olmOutboundSession); + if (lastErr == OLM_NOT_ENOUGH_RANDOM) { throw lastErr; } return lastErr; @@ -100,16 +109,12 @@ QOlmExpected QOlmSession::pickle(const PicklingMode &mode) const { QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); QByteArray key = toKey(mode); - const auto error = olm_pickle_session(m_session, key.data(), key.length(), - pickledBuf.data(), - pickledBuf.length()); - - if (error == olm_error()) { - return lastError(m_session); - } + if (olm_pickle_session(m_session, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()) + == olm_error()) + return lastErrorCode(); key.clear(); - return pickledBuf; } @@ -119,10 +124,11 @@ QOlmExpected QOlmSession::unpickle(const QByteArray& pickled, QByteArray pickledBuf = pickled; auto *olmSession = create(); QByteArray key = toKey(mode); - const auto error = olm_unpickle_session(olmSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()); - if (error == olm_error()) { - return lastError(olmSession); + if (olm_unpickle_session(olmSession, key.data(), key.length(), + pickledBuf.data(), pickledBuf.length()) + == olm_error()) { + // FIXME: the QOlmSession object should be created earlier + return olm_session_last_error_code(olmSession); } key.clear(); @@ -137,13 +143,14 @@ QOlmMessage QOlmSession::encrypt(const QString &plaintext) const auto messageType = encryptMessageType(); const auto randomLen = olm_encrypt_random_length(m_session); QByteArray randomBuf = getRandom(randomLen); - const auto error = olm_encrypt(m_session, - reinterpret_cast(plaintextBuf.data()), plaintextBuf.length(), - reinterpret_cast(randomBuf.data()), randomBuf.length(), - reinterpret_cast(messageBuf.data()), messageBuf.length()); - - if (error == olm_error()) { - throw lastError(m_session); + if (olm_encrypt(m_session, reinterpret_cast(plaintextBuf.data()), + plaintextBuf.length(), + reinterpret_cast(randomBuf.data()), + randomBuf.length(), + reinterpret_cast(messageBuf.data()), + messageBuf.length()) + == olm_error()) { + throw lastError(); } return QOlmMessage(messageBuf, messageType); @@ -163,9 +170,8 @@ QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const const auto plaintextMaxLen = olm_decrypt_max_plaintext_length(m_session, messageTypeValue, reinterpret_cast(messageBuf.data()), messageBuf.length()); - if (plaintextMaxLen == olm_error()) { - return lastError(m_session); + return lastError(); } QByteArray plaintextBuf(plaintextMaxLen, '0'); @@ -175,10 +181,9 @@ QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const const auto plaintextResultLen = olm_decrypt(m_session, messageTypeValue, reinterpret_cast(messageBuf2.data()), messageBuf2.length(), reinterpret_cast(plaintextBuf.data()), plaintextMaxLen); - if (plaintextResultLen == olm_error()) { - const auto lastErr = lastError(m_session); - if (lastErr == QOlmError::OutputBufferTooSmall) { + const auto lastErr = lastErrorCode(); + if (lastErr == OLM_OUTPUT_BUFFER_TOO_SMALL) { throw lastErr; } return lastErr; @@ -193,7 +198,7 @@ QOlmMessage::Type QOlmSession::encryptMessageType() { const auto messageTypeResult = olm_encrypt_message_type(m_session); if (messageTypeResult == olm_error()) { - throw lastError(m_session); + throw lastError(); } if (messageTypeResult == OLM_MESSAGE_TYPE_PRE_KEY) { return QOlmMessage::PreKey; @@ -205,10 +210,10 @@ QByteArray QOlmSession::sessionId() const { const auto idMaxLength = olm_session_id_length(m_session); QByteArray idBuffer(idMaxLength, '0'); - const auto error = olm_session_id(m_session, reinterpret_cast(idBuffer.data()), - idBuffer.length()); - if (error == olm_error()) { - throw lastError(m_session); + if (olm_session_id(m_session, reinterpret_cast(idBuffer.data()), + idBuffer.length()) + == olm_error()) { + throw lastError(); } return idBuffer; } @@ -225,11 +230,10 @@ bool QOlmSession::matchesInboundSession(const QOlmMessage& preKeyMessage) const const auto maybeMatches = olm_matches_inbound_session(m_session, oneTimeKeyBuf.data(), oneTimeKeyBuf.length()); - - if (maybeMatches == olm_error()) { - return lastError(m_session); - } - return maybeMatches == 1; + if (maybeMatches == olm_error()) + qWarning(E2EE) << "Error matching an inbound session:" + << olm_session_last_error(m_session); + return maybeMatches == 1; // Any errors are treated as non-match } bool QOlmSession::matchesInboundSessionFrom( diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index 021092c7..cc988a03 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -6,7 +6,6 @@ #include "e2ee/e2ee.h" #include "e2ee/qolmmessage.h" -#include "e2ee/qolmerrors.h" #include "e2ee/qolmaccount.h" struct OlmSession; @@ -71,6 +70,9 @@ public: return *lhs < *rhs; } + OlmErrorCode lastErrorCode() const; + const char* lastError() const; + OlmSession* raw() const { return m_session; } QOlmSession(OlmSession* session); diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index 84559085..15c875c0 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -8,9 +8,13 @@ using namespace Quotient; -// Convert olm error to enum -QOlmError lastError(OlmUtility *utility) { - return fromString(olm_utility_last_error(utility)); +OlmErrorCode QOlmUtility::lastErrorCode() const { + return olm_utility_last_error_code(m_utility); +} + +const char* QOlmUtility::lastError() const +{ + return olm_utility_last_error(m_utility); } QOlmUtility::QOlmUtility() @@ -48,15 +52,15 @@ QOlmExpected QOlmUtility::ed25519Verify(const QByteArray& key, std::copy(signature.begin(), signature.end(), signatureBuf.begin()); const auto ret = olm_ed25519_verify(m_utility, key.data(), key.size(), - message.data(), message.size(), (void *)signatureBuf.data(), signatureBuf.size()); - + message.data(), message.size(), + (void*)signatureBuf.data(), + signatureBuf.size()); if (ret == olm_error()) { - auto error = lastError(m_utility); - if (error == QOlmError::BadMessageMac) { + auto error = lastErrorCode(); + if (error == OLM_BAD_MESSAGE_MAC) return false; - } return error; } - return !ret; // ret == 0 means success + return ret == 0; } diff --git a/lib/e2ee/qolmutility.h b/lib/e2ee/qolmutility.h index 5f6bcdc5..89277385 100644 --- a/lib/e2ee/qolmutility.h +++ b/lib/e2ee/qolmutility.h @@ -32,8 +32,10 @@ public: QOlmExpected ed25519Verify(const QByteArray &key, const QByteArray &message, const QByteArray &signature); + OlmErrorCode lastErrorCode() const; + const char* lastError() const; + private: OlmUtility *m_utility; - }; } diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 94b44901..114addb5 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" +#include "e2ee/e2ee.h" #include "logging.h" using namespace Quotient; diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 02d4c7aa..e24e5745 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -3,10 +3,15 @@ #pragma once -#include "e2ee/e2ee.h" #include "roomevent.h" namespace Quotient { + +constexpr auto CiphertextKeyL = "ciphertext"_ls; +constexpr auto SenderKeyKeyL = "sender_key"_ls; +constexpr auto DeviceIdKeyL = "device_id"_ls; +constexpr auto SessionIdKeyL = "session_id"_ls; + /* * While the specification states: * diff --git a/lib/room.cpp b/lib/room.cpp index 95dd0ab7..382d0243 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -65,7 +65,6 @@ #ifdef Quotient_E2EE_ENABLED #include "e2ee/e2ee.h" #include "e2ee/qolmaccount.h" -#include "e2ee/qolmerrors.h" #include "e2ee/qolminboundsession.h" #include "e2ee/qolmutility.h" #include "database.h" diff --git a/lib/util.h b/lib/util.h index 9efda5d1..ab219488 100644 --- a/lib/util.h +++ b/lib/util.h @@ -111,6 +111,23 @@ private: iterator to; }; +template +class asKeyValueRange +{ +public: + asKeyValueRange(T& data) + : m_data { data } + {} + + auto begin() { return m_data.keyValueBegin(); } + auto end() { return m_data.keyValueEnd(); } + +private: + T &m_data; +}; +template +asKeyValueRange(T&) -> asKeyValueRange; + /** A replica of std::find_first_of that returns a pair of iterators * * Convenient for cases when you need to know which particular "first of" -- 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'. --- autotests/testgroupsession.cpp | 9 ++-- autotests/testolmaccount.cpp | 3 +- autotests/testolmsession.cpp | 10 +++-- autotests/testolmutility.cpp | 15 +++---- lib/connection.cpp | 8 ++-- lib/e2ee/e2ee.h | 2 +- lib/e2ee/qolmaccount.cpp | 54 +++++++++++----------- lib/e2ee/qolmaccount.h | 9 ++-- lib/e2ee/qolminboundsession.cpp | 57 +++++++++++------------- lib/e2ee/qolminboundsession.h | 4 +- lib/e2ee/qolmmessage.cpp | 6 --- lib/e2ee/qolmmessage.h | 12 +++-- lib/e2ee/qolmoutboundsession.cpp | 44 +++++++++--------- lib/e2ee/qolmoutboundsession.h | 4 +- lib/e2ee/qolmsession.cpp | 96 +++++++++++++++------------------------- lib/e2ee/qolmsession.h | 15 +++---- lib/e2ee/qolmutility.cpp | 24 +++++----- lib/e2ee/qolmutils.h | 1 + lib/events/filesourceinfo.cpp | 35 ++++++--------- lib/keyverificationsession.cpp | 6 +-- 20 files changed, 186 insertions(+), 228 deletions(-) (limited to 'lib/events') diff --git a/autotests/testgroupsession.cpp b/autotests/testgroupsession.cpp index 3c329a8a..18ace73b 100644 --- a/autotests/testgroupsession.cpp +++ b/autotests/testgroupsession.cpp @@ -17,7 +17,9 @@ void TestGroupSession::groupSessionPicklingValid() QCOMPARE(0, ogs->sessionMessageIndex()); auto ogsPickled = ogs->pickle(Unencrypted {}).value(); - auto ogs2 = QOlmOutboundGroupSession::unpickle(ogsPickled, Unencrypted {}).value(); + auto ogs2 = + QOlmOutboundGroupSession::unpickle(std::move(ogsPickled), Unencrypted{}) + .value(); QCOMPARE(ogsId, ogs2->sessionId()); auto igs = QOlmInboundGroupSession::create(ogs->sessionKey().value()); @@ -29,7 +31,8 @@ void TestGroupSession::groupSessionPicklingValid() QCOMPARE(0, igs->firstKnownIndex()); auto igsPickled = igs->pickle(Unencrypted {}); - igs = QOlmInboundGroupSession::unpickle(igsPickled, Unencrypted {}).value(); + igs = QOlmInboundGroupSession::unpickle(std::move(igsPickled), Unencrypted{}) + .value(); QCOMPARE(igsId, igs->sessionId()); } @@ -39,7 +42,7 @@ void TestGroupSession::groupSessionCryptoValid() auto igs = QOlmInboundGroupSession::create(ogs->sessionKey().value()); QCOMPARE(ogs->sessionId(), igs->sessionId()); - const auto plainText = QStringLiteral("Hello world!"); + const auto plainText = "Hello world!"; const auto ciphertext = ogs->encrypt(plainText).value(); // ciphertext valid base64? QVERIFY(QByteArray::fromBase64(ciphertext).size() > 0); diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index eb428661..0e1eab84 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -25,7 +25,8 @@ void TestOlmAccount::pickleUnpickledTest() auto identityKeys = olmAccount.identityKeys(); auto pickled = olmAccount.pickle(Unencrypted{}).value(); QOlmAccount olmAccount2(QStringLiteral("@foo:bar.com"), QStringLiteral("QuotientTestDevice")); - auto unpickleResult = olmAccount2.unpickle(pickled, Unencrypted{}); + auto unpickleResult = olmAccount2.unpickle(std::move(pickled), + Unencrypted{}); QCOMPARE(unpickleResult, 0); auto identityKeys2 = olmAccount2.identityKeys(); QCOMPARE(identityKeys.curve25519, identityKeys2.curve25519); diff --git a/autotests/testolmsession.cpp b/autotests/testolmsession.cpp index 66a04241..18b0d5f2 100644 --- a/autotests/testolmsession.cpp +++ b/autotests/testolmsession.cpp @@ -11,12 +11,14 @@ std::pair createSessionPair() { QByteArray pickledAccountA("eOBXIKivUT6YYowRH031BNv7zNmzqM5B7CpXdyeaPvala5mt7/OeqrG1qVA7vA1SYloFyvJPIy0QNkD3j1HiPl5vtZHN53rtfZ9exXDok03zjmssqn4IJsqcA7Fbo1FZeKafG0NFcWwCPTdmcV7REqxjqGm3I4K8MQFa45AdTGSUu2C12cWeOcbSMlcINiMral+Uyah1sgPmLJ18h1qcnskXUXQvpffZ5DiUw1Iz5zxnwOQF1GVyowPJD7Zdugvj75RQnDxAn6CzyvrY2k2CuedwqDC3fIXM2xdUNWttW4nC2g4InpBhCVvNwhZYxlUb5BUEjmPI2AB3dAL5ry6o9MFncmbN6x5x"); QByteArray pickledAccountB("eModTvoFi9oOIkax4j4nuxw9Tcl/J8mOmUctUWI68Q89HSaaPTqR+tdlKQ85v2GOs5NlZCp7EuycypN9GQ4fFbHUCrS7nspa3GFBWsR8PnM8+wez5PWmfFZLg3drOvT0jbMjpDx0MjGYClHBqcrEpKx9oFaIRGBaX6HXzT4lRaWSJkXxuX92q8iGNrLn96PuAWFNcD+2JXpPcNFntslwLUNgqzpZ04aIFYwL80GmzyOgq3Bz1GO6u3TgCQEAmTIYN2QkO0MQeuSfe7UoMumhlAJ6R8GPcdSSPtmXNk4tdyzzlgpVq1hm7ZLKto+g8/5Aq3PvnvA8wCqno2+Pi1duK1pZFTIlActr"); - auto accountA = QOlmAccount("accountA:foo.com", "Device1UserA"); - if (accountA.unpickle(pickledAccountA, Unencrypted{}) != OLM_SUCCESS) + auto accountA = QOlmAccount(u"accountA:foo.com", u"Device1UserA"); + if (accountA.unpickle(std::move(pickledAccountA), Unencrypted{}) + != OLM_SUCCESS) qFatal("Failed to unpickle account A: %s", accountA.lastError()); - auto accountB = QOlmAccount("accountB:foo.com", "Device1UserB"); - if (accountB.unpickle(pickledAccountB, Unencrypted{}) != OLM_SUCCESS) + auto accountB = QOlmAccount(u"accountB:foo.com", u"Device1UserB"); + if (accountB.unpickle(std::move(pickledAccountB), Unencrypted{}) + != OLM_SUCCESS) qFatal("Failed to unpickle account B: %s", accountB.lastError()); diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index 5b67c805..64ceb3e7 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -49,7 +49,7 @@ void TestOlmUtility::canonicalJSON() void TestOlmUtility::verifySignedOneTimeKey() { - QOlmAccount aliceOlm { "@alice:matrix.org", "aliceDevice" }; + QOlmAccount aliceOlm { u"@alice:matrix.org", u"aliceDevice" }; aliceOlm.createNewAccount(); aliceOlm.generateOneTimeKeys(1); auto keys = aliceOlm.oneTimeKeys(); @@ -64,16 +64,13 @@ void TestOlmUtility::verifySignedOneTimeKey() auto utility = olm_utility(utilityBuf); - QByteArray signatureBuf1(sig.length(), '0'); + QByteArray signatureBuf1(sig.length(), '\0'); std::copy(sig.begin(), sig.end(), signatureBuf1.begin()); - auto res = olm_ed25519_verify(utility, - aliceOlm.identityKeys().ed25519.data(), - aliceOlm.identityKeys().ed25519.size(), - msg.data(), - msg.size(), - (void *)sig.data(), - sig.size()); + auto res = + olm_ed25519_verify(utility, aliceOlm.identityKeys().ed25519.data(), + aliceOlm.identityKeys().ed25519.size(), msg.data(), + msg.size(), sig.data(), sig.size()); QCOMPARE(std::string(olm_utility_last_error(utility)), "SUCCESS"); QCOMPARE(res, 0); 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()); diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 51ceff67..5999c0be 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -98,7 +98,7 @@ public: {} //! Unpadded Base64-encoded 32-byte Curve25519 public key - QString key() const { return payload["key"_ls].toString(); } + QByteArray key() const { return payload["key"_ls].toString().toLatin1(); } //! \brief Signatures of the key object //! diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index d33db8e3..556a8274 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -27,11 +27,11 @@ const char* QOlmAccount::lastError() const return olm_account_last_error(m_account); } -QOlmAccount::QOlmAccount(const QString& userId, const QString& deviceId, +QOlmAccount::QOlmAccount(QStringView userId, QStringView deviceId, QObject* parent) : QObject(parent) - , m_userId(userId) - , m_deviceId(deviceId) + , m_userId(userId.toString()) + , m_deviceId(deviceId.toString()) {} QOlmAccount::~QOlmAccount() @@ -43,20 +43,20 @@ QOlmAccount::~QOlmAccount() void QOlmAccount::createNewAccount() { m_account = olm_account(new uint8_t[olm_account_size()]); - size_t randomSize = olm_create_account_random_length(m_account); - QByteArray randomData = getRandom(randomSize); - if (olm_create_account(m_account, randomData.data(), randomSize) + const auto randomLength = olm_create_account_random_length(m_account); + QByteArray randomData = getRandom(randomLength); + if (olm_create_account(m_account, randomData.data(), randomLength) == olm_error()) { throw lastError(); } emit needsSave(); } -OlmErrorCode QOlmAccount::unpickle(QByteArray &pickled, const PicklingMode &mode) +OlmErrorCode QOlmAccount::unpickle(QByteArray&& pickled, const PicklingMode &mode) { m_account = olm_account(new uint8_t[olm_account_size()]); - const QByteArray key = toKey(mode); - if (olm_unpickle_account(m_account, key.data(), key.length(), + if (const auto key = toKey(mode); + olm_unpickle_account(m_account, key.data(), key.length(), pickled.data(), pickled.size()) == olm_error()) { // Probably log the user out since we have no way of getting to the keys @@ -69,7 +69,7 @@ QOlmExpected QOlmAccount::pickle(const PicklingMode &mode) { const QByteArray key = toKey(mode); const size_t pickleLength = olm_pickle_account_length(m_account); - QByteArray pickleBuffer(pickleLength, '0'); + QByteArray pickleBuffer(pickleLength, '\0'); if (olm_pickle_account(m_account, key.data(), key.length(), pickleBuffer.data(), pickleLength) == olm_error()) @@ -80,12 +80,12 @@ QOlmExpected QOlmAccount::pickle(const PicklingMode &mode) IdentityKeys QOlmAccount::identityKeys() const { const size_t keyLength = olm_account_identity_keys_length(m_account); - QByteArray keyBuffer(keyLength, '0'); + QByteArray keyBuffer(keyLength, '\0'); if (olm_account_identity_keys(m_account, keyBuffer.data(), keyLength) == olm_error()) { throw lastError(); } - const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object(); + const auto key = QJsonDocument::fromJson(keyBuffer).object(); return IdentityKeys { key.value(QStringLiteral("curve25519")).toString().toUtf8(), key.value(QStringLiteral("ed25519")).toString().toUtf8() @@ -94,7 +94,7 @@ IdentityKeys QOlmAccount::identityKeys() const QByteArray QOlmAccount::sign(const QByteArray &message) const { - QByteArray signatureBuffer(olm_account_signature_length(m_account), '0'); + QByteArray signatureBuffer(olm_account_signature_length(m_account), '\0'); if (olm_account_sign(m_account, message.data(), message.length(), signatureBuffer.data(), signatureBuffer.length()) @@ -112,15 +112,15 @@ QByteArray QOlmAccount::sign(const QJsonObject &message) const QByteArray QOlmAccount::signIdentityKeys() const { const auto keys = identityKeys(); - return sign(QJsonObject { - { "algorithms", QJsonArray { "m.olm.v1.curve25519-aes-sha2", - "m.megolm.v1.aes-sha2" } }, + return sign(QJsonObject{ + { "algorithms", QJsonArray{ "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" } }, { "user_id", m_userId }, { "device_id", m_deviceId }, - { "keys", QJsonObject { { QStringLiteral("curve25519:") + m_deviceId, - QString::fromUtf8(keys.curve25519) }, - { QStringLiteral("ed25519:") + m_deviceId, - QString::fromUtf8(keys.ed25519) } } } }); + { "keys", QJsonObject{ { QStringLiteral("curve25519:") + m_deviceId, + QString::fromUtf8(keys.curve25519) }, + { QStringLiteral("ed25519:") + m_deviceId, + QString::fromUtf8(keys.ed25519) } } } }); } size_t QOlmAccount::maxNumberOfOneTimeKeys() const @@ -130,7 +130,7 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) { - const size_t randomLength = + const auto randomLength = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); QByteArray randomBuffer = getRandom(randomLength); @@ -147,8 +147,8 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const { - const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); - QByteArray oneTimeKeysBuffer(static_cast(oneTimeKeyLength), '0'); + const auto oneTimeKeyLength = olm_account_one_time_keys_length(m_account); + QByteArray oneTimeKeysBuffer(static_cast(oneTimeKeyLength), '\0'); if (olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength) @@ -193,13 +193,13 @@ DeviceKeys QOlmAccount::deviceKeys() const SupportedAlgorithms.cend()); const auto idKeys = identityKeys(); - return DeviceKeys { + return DeviceKeys{ .userId = m_userId, .deviceId = m_deviceId, .algorithms = Algorithms, - .keys { { "curve25519:" + m_deviceId, idKeys.curve25519 }, - { "ed25519:" + m_deviceId, idKeys.ed25519 } }, - .signatures { + .keys{ { "curve25519:" + m_deviceId, idKeys.curve25519 }, + { "ed25519:" + m_deviceId, idKeys.ed25519 } }, + .signatures{ { m_userId, { { "ed25519:" + m_deviceId, signIdentityKeys() } } } } }; } diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index 5ad98e47..0fb9803f 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -24,7 +24,8 @@ class QUOTIENT_API QOlmAccount : public QObject { Q_OBJECT public: - QOlmAccount(const QString &userId, const QString &deviceId, QObject *parent = nullptr); + QOlmAccount(QStringView userId, QStringView deviceId, + QObject* parent = nullptr); ~QOlmAccount() override; //! Creates a new instance of OlmAccount. During the instantiation @@ -36,7 +37,7 @@ public: //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmAccount`. //! This needs to be called before any other action or use createNewAccount() instead. - [[nodiscard]] OlmErrorCode unpickle(QByteArray& pickled, + [[nodiscard]] OlmErrorCode unpickle(QByteArray&& pickled, const PicklingMode& mode); //! Serialises an OlmAccount to encrypted Base64. @@ -74,7 +75,7 @@ public: //! Creates an inbound session for sending/receiving messages from a received 'prekey' message. //! - //! \param message An Olm pre-key message that was encrypted for this account. + //! \param preKeyMessage An Olm pre-key message that was encrypted for this account. QOlmExpected createInboundSession( const QOlmMessage& preKeyMessage); @@ -93,7 +94,7 @@ public: void markKeysAsPublished(); OlmErrorCode lastErrorCode() const; - const char *lastError() const; + const char* lastError() const; // HACK do not use directly QOlmAccount(OlmAccount *account); diff --git a/lib/e2ee/qolminboundsession.cpp b/lib/e2ee/qolminboundsession.cpp index 870070c2..a05ddf62 100644 --- a/lib/e2ee/qolminboundsession.cpp +++ b/lib/e2ee/qolminboundsession.cpp @@ -2,8 +2,9 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "e2ee/qolminboundsession.h" -#include "logging.h" +#include "qolminboundsession.h" +#include "qolmutils.h" +#include "../logging.h" #include #include @@ -37,7 +38,7 @@ std::unique_ptr QOlmInboundGroupSession::create(const Q olmInboundGroupSession, reinterpret_cast(key.constData()), key.size()) == olm_error()) { - // FIXME: create QOlmInboundGroupSession earlier and use lastError() + // FIXME: create QOlmInboundGroupSession earlier and use lastErrorCode() throw olm_inbound_group_session_last_error_code(olmInboundGroupSession); } @@ -47,11 +48,10 @@ std::unique_ptr QOlmInboundGroupSession::create(const Q std::unique_ptr QOlmInboundGroupSession::import(const QByteArray &key) { const auto olmInboundGroupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); - QByteArray keyBuf = key; if (olm_import_inbound_group_session( olmInboundGroupSession, - reinterpret_cast(keyBuf.data()), keyBuf.size()) + reinterpret_cast(key.data()), key.size()) == olm_error()) { // FIXME: create QOlmInboundGroupSession earlier and use lastError() throw olm_inbound_group_session_last_error_code(olmInboundGroupSession); @@ -60,19 +60,12 @@ std::unique_ptr QOlmInboundGroupSession::import(const Q return std::make_unique(olmInboundGroupSession); } -QByteArray toKey(const PicklingMode &mode) -{ - if (std::holds_alternative(mode)) { - return ""; - } - return std::get(mode).key; -} - -QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const +QByteArray QOlmInboundGroupSession::pickle(const PicklingMode& mode) const { - QByteArray pickledBuf(olm_pickle_inbound_group_session_length(m_groupSession), '0'); - const QByteArray key = toKey(mode); - if (olm_pickle_inbound_group_session(m_groupSession, key.data(), + QByteArray pickledBuf( + olm_pickle_inbound_group_session_length(m_groupSession), '\0'); + if (const auto key = toKey(mode); + olm_pickle_inbound_group_session(m_groupSession, key.data(), key.length(), pickledBuf.data(), pickledBuf.length()) == olm_error()) { @@ -82,14 +75,13 @@ QByteArray QOlmInboundGroupSession::pickle(const PicklingMode &mode) const } QOlmExpected QOlmInboundGroupSession::unpickle( - const QByteArray& pickled, const PicklingMode& mode) + QByteArray&& pickled, const PicklingMode& mode) { - QByteArray pickledBuf = pickled; const auto groupSession = olm_inbound_group_session(new uint8_t[olm_inbound_group_session_size()]); - QByteArray key = toKey(mode); + auto key = toKey(mode); if (olm_unpickle_inbound_group_session(groupSession, key.data(), - key.length(), pickledBuf.data(), - pickledBuf.size()) + key.length(), pickled.data(), + pickled.size()) == olm_error()) { // FIXME: create QOlmInboundGroupSession earlier and use lastError() return olm_inbound_group_session_last_error_code(groupSession); @@ -107,13 +99,16 @@ QOlmExpected> QOlmInboundGroupSession::decrypt( // We need to clone the message because // olm_decrypt_max_plaintext_length destroys the input buffer - QByteArray messageBuf(message.length(), '0'); + QByteArray messageBuf(message.length(), '\0'); std::copy(message.begin(), message.end(), messageBuf.begin()); - QByteArray plaintextBuf(olm_group_decrypt_max_plaintext_length(m_groupSession, - reinterpret_cast(messageBuf.data()), messageBuf.length()), '0'); + QByteArray plaintextBuf(olm_group_decrypt_max_plaintext_length( + m_groupSession, + reinterpret_cast(messageBuf.data()), + messageBuf.length()), + '\0'); - messageBuf = QByteArray(message.length(), '0'); + messageBuf = QByteArray(message.length(), '\0'); std::copy(message.begin(), message.end(), messageBuf.begin()); const auto plaintextLen = olm_group_decrypt(m_groupSession, reinterpret_cast(messageBuf.data()), @@ -123,16 +118,17 @@ QOlmExpected> QOlmInboundGroupSession::decrypt( return lastErrorCode(); } - QByteArray output(plaintextLen, '0'); + QByteArray output(plaintextLen, '\0'); std::memcpy(output.data(), plaintextBuf.data(), plaintextLen); return std::make_pair(output, messageIndex); } -QOlmExpected QOlmInboundGroupSession::exportSession(uint32_t messageIndex) +QOlmExpected QOlmInboundGroupSession::exportSession( + uint32_t messageIndex) { const auto keyLength = olm_export_inbound_group_session_length(m_groupSession); - QByteArray keyBuf(keyLength, '0'); + QByteArray keyBuf(keyLength, '\0'); if (olm_export_inbound_group_session( m_groupSession, reinterpret_cast(keyBuf.data()), keyLength, messageIndex) @@ -149,7 +145,8 @@ uint32_t QOlmInboundGroupSession::firstKnownIndex() const QByteArray QOlmInboundGroupSession::sessionId() const { - QByteArray sessionIdBuf(olm_inbound_group_session_id_length(m_groupSession), '0'); + QByteArray sessionIdBuf(olm_inbound_group_session_id_length(m_groupSession), + '\0'); if (olm_inbound_group_session_id( m_groupSession, reinterpret_cast(sessionIdBuf.data()), sessionIdBuf.length()) diff --git a/lib/e2ee/qolminboundsession.h b/lib/e2ee/qolminboundsession.h index e8da6355..0b89349a 100644 --- a/lib/e2ee/qolminboundsession.h +++ b/lib/e2ee/qolminboundsession.h @@ -21,11 +21,11 @@ public: //! Import an inbound group session, from a previous export. static std::unique_ptr import(const QByteArray& key); //! Serialises an `OlmInboundGroupSession` to encrypted Base64. - QByteArray pickle(const PicklingMode &mode) const; + QByteArray pickle(const PicklingMode& mode) const; //! Deserialises from encrypted Base64 that was previously obtained by pickling //! an `OlmInboundGroupSession`. static QOlmExpected unpickle( - const QByteArray& pickled, const PicklingMode& mode); + QByteArray&& pickled, const PicklingMode& mode); //! Decrypts ciphertext received for this group session. QOlmExpected > decrypt(const QByteArray& message); //! Export the base64-encoded ratchet key for this session, at the given index, diff --git a/lib/e2ee/qolmmessage.cpp b/lib/e2ee/qolmmessage.cpp index f9b4a5c2..b9cb8bd2 100644 --- a/lib/e2ee/qolmmessage.cpp +++ b/lib/e2ee/qolmmessage.cpp @@ -15,12 +15,6 @@ QOlmMessage::QOlmMessage(QByteArray ciphertext, QOlmMessage::Type type) Q_ASSERT_X(!isEmpty(), "olm message", "Ciphertext is empty"); } -QOlmMessage::QOlmMessage(const QOlmMessage &message) - : QByteArray(message) - , m_messageType(message.type()) -{ -} - QOlmMessage::Type QOlmMessage::type() const { return m_messageType; diff --git a/lib/e2ee/qolmmessage.h b/lib/e2ee/qolmmessage.h index b4285a93..ea73b3e3 100644 --- a/lib/e2ee/qolmmessage.h +++ b/lib/e2ee/qolmmessage.h @@ -6,8 +6,9 @@ #include "quotient_export.h" -#include -#include +#include +#include +#include namespace Quotient { @@ -22,15 +23,12 @@ class QUOTIENT_API QOlmMessage : public QByteArray { Q_GADGET public: enum Type { - PreKey = 0, - General, + PreKey = OLM_MESSAGE_TYPE_PRE_KEY, + General = OLM_MESSAGE_TYPE_MESSAGE, }; Q_ENUM(Type) - QOlmMessage() = default; explicit QOlmMessage(QByteArray ciphertext, Type type = General); - explicit QOlmMessage(const QOlmMessage &message); - ~QOlmMessage() = default; static QOlmMessage fromCiphertext(const QByteArray &ciphertext); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 79c16e01..22107a21 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -2,9 +2,10 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "e2ee/qolmoutboundsession.h" +#include "qolmoutboundsession.h" -#include "e2ee/qolmutils.h" +#include "logging.h" +#include "qolmutils.h" #include @@ -43,8 +44,9 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() throw olm_outbound_group_session_last_error_code(olmOutboundGroupSession); } + // FIXME: is it used anywhere? const auto keyMaxLength = olm_outbound_group_session_key_length(olmOutboundGroupSession); - QByteArray keyBuffer(keyMaxLength, '0'); + QByteArray keyBuffer(keyMaxLength, '\0'); olm_outbound_group_session_key(olmOutboundGroupSession, reinterpret_cast(keyBuffer.data()), keyMaxLength); @@ -55,8 +57,9 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mode) const { - QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); - QByteArray key = toKey(mode); + QByteArray pickledBuf( + olm_pickle_outbound_group_session_length(m_groupSession), '\0'); + auto key = toKey(mode); if (olm_pickle_outbound_group_session(m_groupSession, key.data(), key.length(), pickledBuf.data(), pickledBuf.length()) @@ -64,40 +67,41 @@ QOlmExpected QOlmOutboundGroupSession::pickle(const PicklingMode &mo return lastErrorCode(); key.clear(); - return pickledBuf; } -QOlmExpected QOlmOutboundGroupSession::unpickle(const QByteArray &pickled, const PicklingMode &mode) +QOlmExpected QOlmOutboundGroupSession::unpickle( + QByteArray&& pickled, const PicklingMode& mode) { - QByteArray pickledBuf = pickled; auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); - QByteArray key = toKey(mode); + auto key = toKey(mode); if (olm_unpickle_outbound_group_session(olmOutboundGroupSession, key.data(), - key.length(), pickledBuf.data(), - pickledBuf.length()) + key.length(), pickled.data(), + pickled.length()) == olm_error()) { // FIXME: create the session object earlier and use lastError() return olm_outbound_group_session_last_error_code( olmOutboundGroupSession); } const auto idMaxLength = olm_outbound_group_session_id_length(olmOutboundGroupSession); - QByteArray idBuffer(idMaxLength, '0'); + QByteArray idBuffer(idMaxLength, '\0'); olm_outbound_group_session_id(olmOutboundGroupSession, reinterpret_cast(idBuffer.data()), idBuffer.length()); + // FIXME: idBuffer doesn't seem to be used, is it needed here? key.clear(); return std::make_unique(olmOutboundGroupSession); } -QOlmExpected QOlmOutboundGroupSession::encrypt(const QString &plaintext) const +QOlmExpected QOlmOutboundGroupSession::encrypt( + const QByteArray& plaintext) const { - QByteArray plaintextBuf = plaintext.toUtf8(); - const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); - QByteArray messageBuf(messageMaxLength, '0'); + const auto messageMaxLength = + olm_group_encrypt_message_length(m_groupSession, plaintext.length()); + QByteArray messageBuf(messageMaxLength, '\0'); if (olm_group_encrypt(m_groupSession, - reinterpret_cast(plaintextBuf.data()), - plaintextBuf.length(), + reinterpret_cast(plaintext.data()), + plaintext.length(), reinterpret_cast(messageBuf.data()), messageBuf.length()) == olm_error()) @@ -114,7 +118,7 @@ uint32_t QOlmOutboundGroupSession::sessionMessageIndex() const QByteArray QOlmOutboundGroupSession::sessionId() const { const auto idMaxLength = olm_outbound_group_session_id_length(m_groupSession); - QByteArray idBuffer(idMaxLength, '0'); + QByteArray idBuffer(idMaxLength, '\0'); if (olm_outbound_group_session_id( m_groupSession, reinterpret_cast(idBuffer.data()), idBuffer.length()) @@ -127,7 +131,7 @@ QByteArray QOlmOutboundGroupSession::sessionId() const QOlmExpected QOlmOutboundGroupSession::sessionKey() const { const auto keyMaxLength = olm_outbound_group_session_key_length(m_groupSession); - QByteArray keyBuffer(keyMaxLength, '0'); + QByteArray keyBuffer(keyMaxLength, '\0'); if (olm_outbound_group_session_key( m_groupSession, reinterpret_cast(keyBuffer.data()), keyMaxLength) diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index cd26fc67..e5e73ddf 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -24,10 +24,10 @@ public: //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. static QOlmExpected unpickle( - const QByteArray& pickled, const PicklingMode& mode); + QByteArray&& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. - QOlmExpected encrypt(const QString& plaintext) const; + QOlmExpected encrypt(const QByteArray& plaintext) const; //! Get the current message index for this session. //! diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index 771d310d..e252c37f 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -76,22 +76,17 @@ QOlmExpected QOlmSession::createInboundSessionFrom( } QOlmExpected QOlmSession::createOutboundSession( - QOlmAccount* account, const QString& theirIdentityKey, - const QString& theirOneTimeKey) + QOlmAccount* account, const QByteArray& theirIdentityKey, + const QByteArray& theirOneTimeKey) { - auto *olmOutboundSession = create(); - const auto randomLen = olm_create_outbound_session_random_length(olmOutboundSession); - QByteArray randomBuf = getRandom(randomLen); + auto* olmOutboundSession = create(); + auto randomBuf = getRandom( + olm_create_outbound_session_random_length(olmOutboundSession)); - QByteArray theirIdentityKeyBuf = theirIdentityKey.toUtf8(); - QByteArray theirOneTimeKeyBuf = theirOneTimeKey.toUtf8(); if (olm_create_outbound_session( - olmOutboundSession, account->data(), - reinterpret_cast(theirIdentityKeyBuf.data()), - theirIdentityKeyBuf.length(), - reinterpret_cast(theirOneTimeKeyBuf.data()), - theirOneTimeKeyBuf.length(), - reinterpret_cast(randomBuf.data()), randomBuf.length()) + olmOutboundSession, account->data(), theirIdentityKey.data(), + theirIdentityKey.length(), theirOneTimeKey.data(), + theirOneTimeKey.length(), randomBuf.data(), randomBuf.length()) == olm_error()) { // FIXME: the QOlmSession object should be created earlier const auto lastErr = olm_session_last_error_code(olmOutboundSession); @@ -107,7 +102,7 @@ QOlmExpected QOlmSession::createOutboundSession( QOlmExpected QOlmSession::pickle(const PicklingMode &mode) const { - QByteArray pickledBuf(olm_pickle_session_length(m_session), '0'); + QByteArray pickledBuf(olm_pickle_session_length(m_session), '\0'); QByteArray key = toKey(mode); if (olm_pickle_session(m_session, key.data(), key.length(), pickledBuf.data(), pickledBuf.length()) @@ -118,14 +113,13 @@ QOlmExpected QOlmSession::pickle(const PicklingMode &mode) const return pickledBuf; } -QOlmExpected QOlmSession::unpickle(const QByteArray& pickled, +QOlmExpected QOlmSession::unpickle(QByteArray&& pickled, const PicklingMode& mode) { - QByteArray pickledBuf = pickled; auto *olmSession = create(); - QByteArray key = toKey(mode); + auto key = toKey(mode); if (olm_unpickle_session(olmSession, key.data(), key.length(), - pickledBuf.data(), pickledBuf.length()) + pickled.data(), pickled.length()) == olm_error()) { // FIXME: the QOlmSession object should be created earlier return olm_session_last_error_code(olmSession); @@ -135,52 +129,48 @@ QOlmExpected QOlmSession::unpickle(const QByteArray& pickled, return std::make_unique(olmSession); } -QOlmMessage QOlmSession::encrypt(const QString &plaintext) +QOlmMessage QOlmSession::encrypt(const QByteArray& plaintext) { - QByteArray plaintextBuf = plaintext.toUtf8(); - const auto messageMaxLen = olm_encrypt_message_length(m_session, plaintextBuf.length()); - QByteArray messageBuf(messageMaxLen, '0'); - const auto messageType = encryptMessageType(); - const auto randomLen = olm_encrypt_random_length(m_session); - QByteArray randomBuf = getRandom(randomLen); - if (olm_encrypt(m_session, reinterpret_cast(plaintextBuf.data()), - plaintextBuf.length(), - reinterpret_cast(randomBuf.data()), - randomBuf.length(), - reinterpret_cast(messageBuf.data()), + const auto messageMaxLength = + olm_encrypt_message_length(m_session, plaintext.length()); + QByteArray messageBuf(messageMaxLength, '0'); + // NB: The type has to be calculated before calling olm_encrypt() + const auto messageType = olm_encrypt_message_type(m_session); + auto randomBuf = getRandom(olm_encrypt_random_length(m_session)); + if (olm_encrypt(m_session, plaintext.data(), plaintext.length(), + randomBuf.data(), randomBuf.length(), messageBuf.data(), messageBuf.length()) == olm_error()) { throw lastError(); } - return QOlmMessage(messageBuf, messageType); + randomBuf.clear(); + return QOlmMessage(messageBuf, QOlmMessage::Type(messageType)); } QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const { - const auto messageType = message.type(); const auto ciphertext = message.toCiphertext(); - const auto messageTypeValue = messageType == QOlmMessage::Type::General - ? OLM_MESSAGE_TYPE_MESSAGE : OLM_MESSAGE_TYPE_PRE_KEY; + const auto messageTypeValue = message.type(); // We need to clone the message because // olm_decrypt_max_plaintext_length destroys the input buffer - QByteArray messageBuf(ciphertext.length(), '0'); + QByteArray messageBuf(ciphertext.length(), '\0'); std::copy(message.begin(), message.end(), messageBuf.begin()); - const auto plaintextMaxLen = olm_decrypt_max_plaintext_length(m_session, messageTypeValue, - reinterpret_cast(messageBuf.data()), messageBuf.length()); + const auto plaintextMaxLen = olm_decrypt_max_plaintext_length( + m_session, messageTypeValue, messageBuf.data(), messageBuf.length()); if (plaintextMaxLen == olm_error()) { return lastError(); } - QByteArray plaintextBuf(plaintextMaxLen, '0'); - QByteArray messageBuf2(ciphertext.length(), '0'); + QByteArray plaintextBuf(plaintextMaxLen, '\0'); + QByteArray messageBuf2(ciphertext.length(), '\0'); std::copy(message.begin(), message.end(), messageBuf2.begin()); - const auto plaintextResultLen = olm_decrypt(m_session, messageTypeValue, - reinterpret_cast(messageBuf2.data()), messageBuf2.length(), - reinterpret_cast(plaintextBuf.data()), plaintextMaxLen); + const auto plaintextResultLen = + olm_decrypt(m_session, messageTypeValue, messageBuf2.data(), + messageBuf2.length(), plaintextBuf.data(), plaintextMaxLen); if (plaintextResultLen == olm_error()) { const auto lastErr = lastErrorCode(); if (lastErr == OLM_OUTPUT_BUFFER_TOO_SMALL) { @@ -188,31 +178,15 @@ QOlmExpected QOlmSession::decrypt(const QOlmMessage &message) const } return lastErr; } - QByteArray output(plaintextResultLen, '0'); - std::memcpy(output.data(), plaintextBuf.data(), plaintextResultLen); - plaintextBuf.clear(); - return output; -} - -QOlmMessage::Type QOlmSession::encryptMessageType() -{ - const auto messageTypeResult = olm_encrypt_message_type(m_session); - if (messageTypeResult == olm_error()) { - throw lastError(); - } - if (messageTypeResult == OLM_MESSAGE_TYPE_PRE_KEY) { - return QOlmMessage::PreKey; - } - return QOlmMessage::General; + plaintextBuf.truncate(plaintextResultLen); + return plaintextBuf; } QByteArray QOlmSession::sessionId() const { const auto idMaxLength = olm_session_id_length(m_session); QByteArray idBuffer(idMaxLength, '0'); - if (olm_session_id(m_session, reinterpret_cast(idBuffer.data()), - idBuffer.length()) - == olm_error()) { + if (olm_session_id(m_session, idBuffer.data(), idMaxLength) == olm_error()) { throw lastError(); } return idBuffer; diff --git a/lib/e2ee/qolmsession.h b/lib/e2ee/qolmsession.h index cc988a03..174e98ef 100644 --- a/lib/e2ee/qolmsession.h +++ b/lib/e2ee/qolmsession.h @@ -26,18 +26,18 @@ public: const QOlmMessage& preKeyMessage); static QOlmExpected createOutboundSession( - QOlmAccount* account, const QString& theirIdentityKey, - const QString& theirOneTimeKey); + QOlmAccount* account, const QByteArray& theirIdentityKey, + const QByteArray& theirOneTimeKey); //! Serialises an `QOlmSession` to encrypted Base64. QOlmExpected pickle(const PicklingMode &mode) const; - //! Deserialises from encrypted Base64 that was previously obtained by pickling a `QOlmSession`. - static QOlmExpected unpickle( - const QByteArray& pickled, const PicklingMode& mode); + //! Deserialises from encrypted Base64 previously made with pickle() + static QOlmExpected unpickle(QByteArray&& pickled, + const PicklingMode& mode); //! Encrypts a plaintext message using the session. - QOlmMessage encrypt(const QString &plaintext); + QOlmMessage encrypt(const QByteArray& plaintext); //! Decrypts a message using this session. Decoding is lossy, meaning if //! the decrypted plaintext contains invalid UTF-8 symbols, they will @@ -47,9 +47,6 @@ public: //! Get a base64-encoded identifier for this session. QByteArray sessionId() const; - //! The type of the next message that will be returned from encryption. - QOlmMessage::Type encryptMessageType(); - //! Checker for any received messages for this session. bool hasReceivedMessage() const; diff --git a/lib/e2ee/qolmutility.cpp b/lib/e2ee/qolmutility.cpp index 15c875c0..08c2b699 100644 --- a/lib/e2ee/qolmutility.cpp +++ b/lib/e2ee/qolmutility.cpp @@ -32,7 +32,7 @@ QOlmUtility::~QOlmUtility() QString QOlmUtility::sha256Bytes(const QByteArray &inputBuf) const { const auto outputLen = olm_sha256_length(m_utility); - QByteArray outputBuf(outputLen, '0'); + QByteArray outputBuf(outputLen, '\0'); olm_sha256(m_utility, inputBuf.data(), inputBuf.length(), outputBuf.data(), outputBuf.length()); @@ -48,19 +48,17 @@ QOlmExpected QOlmUtility::ed25519Verify(const QByteArray& key, const QByteArray& message, const QByteArray& signature) { - QByteArray signatureBuf(signature.length(), '0'); + QByteArray signatureBuf(signature.length(), '\0'); std::copy(signature.begin(), signature.end(), signatureBuf.begin()); - const auto ret = olm_ed25519_verify(m_utility, key.data(), key.size(), - message.data(), message.size(), - (void*)signatureBuf.data(), - signatureBuf.size()); - if (ret == olm_error()) { - auto error = lastErrorCode(); - if (error == OLM_BAD_MESSAGE_MAC) - return false; - return error; - } + if (olm_ed25519_verify(m_utility, key.data(), key.size(), message.data(), + message.size(), signatureBuf.data(), + signatureBuf.size()) + == 0) + return true; - return ret == 0; + auto error = lastErrorCode(); + if (error == OLM_BAD_MESSAGE_MAC) + return false; + return error; } diff --git a/lib/e2ee/qolmutils.h b/lib/e2ee/qolmutils.h index f218e628..7a8511c3 100644 --- a/lib/e2ee/qolmutils.h +++ b/lib/e2ee/qolmutils.h @@ -9,6 +9,7 @@ #include "e2ee/e2ee.h" namespace Quotient { + // Convert PicklingMode to key QUOTIENT_API QByteArray toKey(const PicklingMode &mode); QUOTIENT_API QByteArray getRandom(size_t bufferSize); diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp index e8b6794b..6abe6a08 100644 --- a/lib/events/filesourceinfo.cpp +++ b/lib/events/filesourceinfo.cpp @@ -59,19 +59,15 @@ std::pair Quotient::encryptFile( const QByteArray& plainText) { #ifdef Quotient_E2EE_ENABLED - QByteArray k = getRandom(32); - auto kBase64 = k.toBase64(); - QByteArray iv = getRandom(16); - JWK key = { "oct"_ls, - { "encrypt"_ls, "decrypt"_ls }, - "A256CTR"_ls, - QString(k.toBase64()) - .replace(u'/', u'_') - .replace(u'+', u'-') - .left(kBase64.indexOf('=')), - true }; - - int length; + auto k = getRandom(32); + auto kBase64 = k.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals); + auto iv = getRandom(16); + JWK key = { + "oct"_ls, { "encrypt"_ls, "decrypt"_ls }, "A256CTR"_ls, kBase64, true + }; + + int length = -1; auto* ctx = EVP_CIPHER_CTX_new(); EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, reinterpret_cast(k.data()), @@ -89,14 +85,11 @@ std::pair Quotient::encryptFile( EVP_CIPHER_CTX_free(ctx); auto hash = QCryptographicHash::hash(cipherText, QCryptographicHash::Sha256) - .toBase64(); - auto ivBase64 = iv.toBase64(); - EncryptedFileMetadata efm = { {}, - key, - ivBase64.left(ivBase64.indexOf('=')), - { { QStringLiteral("sha256"), - hash.left(hash.indexOf('=')) } }, - "v2"_ls }; + .toBase64(QByteArray::OmitTrailingEquals); + auto ivBase64 = iv.toBase64(QByteArray::OmitTrailingEquals); + EncryptedFileMetadata efm = { + {}, key, ivBase64, { { QStringLiteral("sha256"), hash } }, "v2"_ls + }; return { efm, cipherText }; #else return {}; diff --git a/lib/keyverificationsession.cpp b/lib/keyverificationsession.cpp index 3f76eac1..0f24c743 100644 --- a/lib/keyverificationsession.cpp +++ b/lib/keyverificationsession.cpp @@ -71,9 +71,9 @@ void KeyVerificationSession::init(milliseconds timeout) QTimer::singleShot(timeout, this, [this] { cancelVerification(TIMEOUT); }); m_sas = olm_sas(new std::byte[olm_sas_size()]); - auto randomSize = olm_create_sas_random_length(m_sas); - auto random = getRandom(randomSize); - olm_create_sas(m_sas, random.data(), randomSize); + const auto randomLength = olm_create_sas_random_length(m_sas); + auto random = getRandom(randomLength); + olm_create_sas(m_sas, random.data(), randomLength); } KeyVerificationSession::~KeyVerificationSession() -- 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 +- lib/e2ee/qolmaccount.cpp | 9 +++------ lib/e2ee/qolmoutboundsession.cpp | 9 +++------ lib/e2ee/qolmsession.cpp | 12 +++++------- lib/e2ee/qolmutils.cpp | 7 +++---- lib/e2ee/qolmutils.h | 23 ++++++++++++++++++++++- lib/events/filesourceinfo.cpp | 8 +++----- lib/keyverificationsession.cpp | 3 +-- 8 files changed, 41 insertions(+), 32 deletions(-) (limited to 'lib/events') 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")); diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 556a8274..b56272ef 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -44,8 +44,7 @@ void QOlmAccount::createNewAccount() { m_account = olm_account(new uint8_t[olm_account_size()]); const auto randomLength = olm_create_account_random_length(m_account); - QByteArray randomData = getRandom(randomLength); - if (olm_create_account(m_account, randomData.data(), randomLength) + if (olm_create_account(m_account, RandomBuffer(randomLength), randomLength) == olm_error()) { throw lastError(); } @@ -133,10 +132,8 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const auto randomLength = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); - QByteArray randomBuffer = getRandom(randomLength); - const auto result = - olm_account_generate_one_time_keys(m_account, numberOfKeys, - randomBuffer.data(), randomLength); + const auto result = olm_account_generate_one_time_keys( + m_account, numberOfKeys, RandomBuffer(randomLength), randomLength); if (result == olm_error()) { throw lastError(); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 22107a21..3d176274 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -34,11 +34,10 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() { auto *olmOutboundGroupSession = olm_outbound_group_session(new uint8_t[olm_outbound_group_session_size()]); const auto randomLength = olm_init_outbound_group_session_random_length(olmOutboundGroupSession); - QByteArray randomBuf = getRandom(randomLength); - if (olm_init_outbound_group_session( - olmOutboundGroupSession, - reinterpret_cast(randomBuf.data()), randomBuf.length()) + if (olm_init_outbound_group_session(olmOutboundGroupSession, + RandomBuffer(randomLength).bytes(), + randomLength) == olm_error()) { // FIXME: create the session object earlier and use lastError() throw olm_outbound_group_session_last_error_code(olmOutboundGroupSession); @@ -50,8 +49,6 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() olm_outbound_group_session_key(olmOutboundGroupSession, reinterpret_cast(keyBuffer.data()), keyMaxLength); - randomBuf.clear(); - return std::make_unique(olmOutboundGroupSession); } diff --git a/lib/e2ee/qolmsession.cpp b/lib/e2ee/qolmsession.cpp index e252c37f..7c102a96 100644 --- a/lib/e2ee/qolmsession.cpp +++ b/lib/e2ee/qolmsession.cpp @@ -80,13 +80,13 @@ QOlmExpected QOlmSession::createOutboundSession( const QByteArray& theirOneTimeKey) { auto* olmOutboundSession = create(); - auto randomBuf = getRandom( - olm_create_outbound_session_random_length(olmOutboundSession)); + const auto randomLength = + olm_create_outbound_session_random_length(olmOutboundSession); if (olm_create_outbound_session( olmOutboundSession, account->data(), theirIdentityKey.data(), theirIdentityKey.length(), theirOneTimeKey.data(), - theirOneTimeKey.length(), randomBuf.data(), randomBuf.length()) + theirOneTimeKey.length(), RandomBuffer(randomLength), randomLength) == olm_error()) { // FIXME: the QOlmSession object should be created earlier const auto lastErr = olm_session_last_error_code(olmOutboundSession); @@ -96,7 +96,6 @@ QOlmExpected QOlmSession::createOutboundSession( return lastErr; } - randomBuf.clear(); return std::make_unique(olmOutboundSession); } @@ -136,15 +135,14 @@ QOlmMessage QOlmSession::encrypt(const QByteArray& plaintext) QByteArray messageBuf(messageMaxLength, '0'); // NB: The type has to be calculated before calling olm_encrypt() const auto messageType = olm_encrypt_message_type(m_session); - auto randomBuf = getRandom(olm_encrypt_random_length(m_session)); + const auto randomLength = olm_encrypt_random_length(m_session); if (olm_encrypt(m_session, plaintext.data(), plaintext.length(), - randomBuf.data(), randomBuf.length(), messageBuf.data(), + RandomBuffer(randomLength), randomLength, messageBuf.data(), messageBuf.length()) == olm_error()) { throw lastError(); } - randomBuf.clear(); return QOlmMessage(messageBuf, QOlmMessage::Type(messageType)); } diff --git a/lib/e2ee/qolmutils.cpp b/lib/e2ee/qolmutils.cpp index 6f7937e8..c6e51bcd 100644 --- a/lib/e2ee/qolmutils.cpp +++ b/lib/e2ee/qolmutils.cpp @@ -15,9 +15,8 @@ QByteArray Quotient::toKey(const Quotient::PicklingMode &mode) return std::get(mode).key; } -QByteArray Quotient::getRandom(size_t bufferSize) +RandomBuffer::RandomBuffer(size_t size) + : QByteArray(static_cast(size), '\0') { - QByteArray buffer(bufferSize, '0'); - QRandomGenerator::system()->generate(buffer.begin(), buffer.end()); - return buffer; + QRandomGenerator::system()->generate(begin(), end()); } diff --git a/lib/e2ee/qolmutils.h b/lib/e2ee/qolmutils.h index 7a8511c3..da9d2d18 100644 --- a/lib/e2ee/qolmutils.h +++ b/lib/e2ee/qolmutils.h @@ -12,5 +12,26 @@ namespace Quotient { // Convert PicklingMode to key QUOTIENT_API QByteArray toKey(const PicklingMode &mode); -QUOTIENT_API QByteArray getRandom(size_t bufferSize); + +class QUOTIENT_API RandomBuffer : public QByteArray { +public: + explicit RandomBuffer(size_t size); + ~RandomBuffer() { clear(); } + + // NOLINTNEXTLINE(google-explicit-constructor) + QUO_IMPLICIT operator void*() { return data(); } + char* chars() { return data(); } + uint8_t* bytes() { return reinterpret_cast(data()); } + + Q_DISABLE_COPY(RandomBuffer) + RandomBuffer(RandomBuffer&&) = default; + void operator=(RandomBuffer&&) = delete; +}; + +[[deprecated("Create RandomBuffer directly")]] inline auto getRandom( + size_t bufferSize) +{ + return RandomBuffer(bufferSize); } + +} // namespace Quotient diff --git a/lib/events/filesourceinfo.cpp b/lib/events/filesourceinfo.cpp index 6abe6a08..a60d86d2 100644 --- a/lib/events/filesourceinfo.cpp +++ b/lib/events/filesourceinfo.cpp @@ -59,19 +59,17 @@ std::pair Quotient::encryptFile( const QByteArray& plainText) { #ifdef Quotient_E2EE_ENABLED - auto k = getRandom(32); + auto k = RandomBuffer(32); auto kBase64 = k.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - auto iv = getRandom(16); + auto iv = RandomBuffer(16); JWK key = { "oct"_ls, { "encrypt"_ls, "decrypt"_ls }, "A256CTR"_ls, kBase64, true }; int length = -1; auto* ctx = EVP_CIPHER_CTX_new(); - EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, - reinterpret_cast(k.data()), - reinterpret_cast(iv.data())); + EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, k.bytes(), iv.bytes()); const auto blockSize = EVP_CIPHER_CTX_block_size(ctx); QByteArray cipherText(plainText.size() + blockSize - 1, '\0'); EVP_EncryptUpdate(ctx, reinterpret_cast(cipherText.data()), diff --git a/lib/keyverificationsession.cpp b/lib/keyverificationsession.cpp index 0f24c743..171596c0 100644 --- a/lib/keyverificationsession.cpp +++ b/lib/keyverificationsession.cpp @@ -72,8 +72,7 @@ void KeyVerificationSession::init(milliseconds timeout) m_sas = olm_sas(new std::byte[olm_sas_size()]); const auto randomLength = olm_create_sas_random_length(m_sas); - auto random = getRandom(randomLength); - olm_create_sas(m_sas, random.data(), randomLength); + olm_create_sas(m_sas, RandomBuffer(randomLength), randomLength); } KeyVerificationSession::~KeyVerificationSession() -- cgit v1.2.3 From 6824a0ab5614c5497b220eb9e18e190cd57d5710 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Thu, 6 Oct 2022 12:27:42 +0200 Subject: Properly export KeyVerificationKeyEvent --- lib/events/keyverificationevent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/events') diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h index 0e939508..80aebcf3 100644 --- a/lib/events/keyverificationevent.h +++ b/lib/events/keyverificationevent.h @@ -207,7 +207,7 @@ public: /// Sends the ephemeral public key for a device to the partner device. /// Typically sent as a to-device event. -class KeyVerificationKeyEvent : public KeyVerificationEvent { +class QUOTIENT_API KeyVerificationKeyEvent : public KeyVerificationEvent { public: QUO_EVENT(KeyVerificationKeyEvent, "m.key.verification.key") -- cgit v1.2.3