aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLinus Jahn <lnj@kaidan.im>2023-03-17 17:24:52 +0100
committerLinus Jahn <lnj@kaidan.im>2023-03-17 17:24:52 +0100
commit55362b2e36f91282ccfbdd2bd5a9bba1d50c2002 (patch)
treea0d6193add779507821defb2ebe9e9f8d8405628 /src
parentd679ad1c49eeb28be2ac3a75bd7fd1a9be24d483 (diff)
parent1cf0a4aff856a1f3cab0f9750ee6b361691350a7 (diff)
Merge branch '1.5'
Diffstat (limited to 'src')
-rw-r--r--src/base/QXmppMessage.cpp2
-rw-r--r--src/base/QXmppStream.cpp18
-rw-r--r--src/base/QXmppStream.h2
-rw-r--r--src/client/QXmppCarbonManagerV2.cpp5
-rw-r--r--src/client/QXmppIqHandling.h10
-rw-r--r--src/client/QXmppMamManager.cpp230
-rw-r--r--src/client/QXmppOutgoingClient.cpp8
-rw-r--r--src/omemo/QXmppOmemoManager.cpp36
-rw-r--r--src/omemo/QXmppOmemoManager_p.cpp61
-rw-r--r--src/omemo/QXmppOmemoManager_p.h1
10 files changed, 247 insertions, 126 deletions
diff --git a/src/base/QXmppMessage.cpp b/src/base/QXmppMessage.cpp
index c0923d22..9527f5ed 100644
--- a/src/base/QXmppMessage.cpp
+++ b/src/base/QXmppMessage.cpp
@@ -1659,7 +1659,7 @@ void QXmppMessage::serializeExtensions(QXmlStreamWriter *writer, QXmpp::SceMode
writer->writeStartElement(QStringLiteral("encryption"));
writer->writeDefaultNamespace(ns_eme);
writer->writeAttribute(QStringLiteral("namespace"), d->encryptionMethod);
- helperToXmlAddAttribute(writer, QStringLiteral("name"), d->encryptionName);
+ helperToXmlAddAttribute(writer, QStringLiteral("name"), encryptionName());
writer->writeEndElement();
}
diff --git a/src/base/QXmppStream.cpp b/src/base/QXmppStream.cpp
index 0ea87cbd..7c49c762 100644
--- a/src/base/QXmppStream.cpp
+++ b/src/base/QXmppStream.cpp
@@ -197,7 +197,7 @@ QXmppTask<QXmpp::SendResult> QXmppStream::send(QXmppPacket &&packet, bool &writt
///
/// \since QXmpp 1.5
///
-QXmppTask<QXmppStream::IqResult> QXmppStream::sendIq(QXmppIq &&iq)
+QXmppTask<QXmppStream::IqResult> QXmppStream::sendIq(QXmppIq &&iq, const QString &to)
{
using namespace QXmpp;
@@ -212,7 +212,7 @@ QXmppTask<QXmppStream::IqResult> QXmppStream::sendIq(QXmppIq &&iq)
iq.setId(QXmppUtils::generateStanzaUuid());
}
- return sendIq(QXmppPacket(iq), iq.id(), iq.to());
+ return sendIq(QXmppPacket(iq), iq.id(), to);
}
///
@@ -466,10 +466,18 @@ bool QXmppStream::handleIqResponse(const QDomElement &stanza)
return false;
}
- if (auto itr = d->runningIqs.find(stanza.attribute(QStringLiteral("id")));
+ const auto id = stanza.attribute(QStringLiteral("id"));
+ if (auto itr = d->runningIqs.find(id);
itr != d->runningIqs.end()) {
- if (stanza.attribute("from") != itr.value().jid) {
- warning(QStringLiteral("Received IQ response to one of our requests from wrong sender. Ignoring."));
+ const auto expectedFrom = itr.value().jid;
+ // Check that the sender of the response matches the recipient of the request.
+ // Stanzas coming from the server on behalf of the user's account must have no "from"
+ // attribute or have it set to the user's bare JID.
+ // If 'from' is empty, the IQ has been sent by the server. In this case we don't need to
+ // do the check as we trust the server anyways.
+ if (const auto from = stanza.attribute("from"); !from.isEmpty() && from != expectedFrom) {
+ warning(QStringLiteral("Ignored received IQ response to request '%1' because of wrong sender '%2' instead of expected sender '%3'")
+ .arg(id, from, expectedFrom));
return false;
}
diff --git a/src/base/QXmppStream.h b/src/base/QXmppStream.h
index 55dc7967..a7584366 100644
--- a/src/base/QXmppStream.h
+++ b/src/base/QXmppStream.h
@@ -47,7 +47,7 @@ public:
QXmppTask<QXmpp::SendResult> send(QXmppPacket &&);
using IqResult = std::variant<QDomElement, QXmppError>;
- QXmppTask<IqResult> sendIq(QXmppIq &&);
+ QXmppTask<IqResult> sendIq(QXmppIq &&, const QString &to);
QXmppTask<IqResult> sendIq(QXmppPacket &&, const QString &id, const QString &to);
void cancelOngoingIqs();
bool hasIqId(const QString &id) const;
diff --git a/src/client/QXmppCarbonManagerV2.cpp b/src/client/QXmppCarbonManagerV2.cpp
index 7959cab3..97831731 100644
--- a/src/client/QXmppCarbonManagerV2.cpp
+++ b/src/client/QXmppCarbonManagerV2.cpp
@@ -18,10 +18,9 @@ using namespace QXmpp::Private;
class CarbonEnableIq : public QXmppIq
{
public:
- CarbonEnableIq(const QString &jid)
+ CarbonEnableIq()
: QXmppIq()
{
- setTo(jid);
setType(QXmppIq::Set);
}
@@ -165,7 +164,7 @@ void QXmppCarbonManagerV2::enableCarbons()
return;
}
- client()->sendIq(CarbonEnableIq(client()->configuration().jidBare())).then(this, [this](QXmppClient::IqResult domResult) {
+ client()->sendIq(CarbonEnableIq()).then(this, [this](QXmppClient::IqResult domResult) {
if (auto err = parseIq(std::move(domResult))) {
warning("Could not enable message carbons: " % err->description);
} else {
diff --git a/src/client/QXmppIqHandling.h b/src/client/QXmppIqHandling.h
index 7535bc30..63eba306 100644
--- a/src/client/QXmppIqHandling.h
+++ b/src/client/QXmppIqHandling.h
@@ -95,12 +95,10 @@ namespace Private {
iq.parse(element);
iq.setE2eeMetadata(e2eeMetadata);
- processHandleIqResult(
- client,
- iq.id(),
- iq.from(),
- e2eeMetadata,
- invokeIqHandler(std::forward<Handler>(handler), std::move(iq)));
+ auto id = iq.id(), from = iq.from();
+
+ processHandleIqResult(client, id, from, e2eeMetadata,
+ invokeIqHandler(std::forward<Handler>(handler), std::move(iq)));
return true;
}
return false;
diff --git a/src/client/QXmppMamManager.cpp b/src/client/QXmppMamManager.cpp
index c39a3411..fe0735c7 100644
--- a/src/client/QXmppMamManager.cpp
+++ b/src/client/QXmppMamManager.cpp
@@ -9,29 +9,98 @@
#include "QXmppConstants_p.h"
#include "QXmppDataForm.h"
#include "QXmppE2eeExtension.h"
-#include "QXmppFutureUtils_p.h"
#include "QXmppMamIq.h"
#include "QXmppMessage.h"
+#include "QXmppPromise.h"
#include "QXmppUtils.h"
#include <unordered_map>
#include <QDomElement>
+using namespace QXmpp;
using namespace QXmpp::Private;
+template<typename T, typename Converter>
+auto transform(const T &input, Converter convert)
+{
+ using Output = std::decay_t<decltype(convert(*input.begin()))>;
+ QVector<Output> output;
+ output.reserve(input.size());
+ std::transform(input.begin(), input.end(), std::back_inserter(output), std::move(convert));
+ return output;
+}
+
+template<typename T>
+auto sum(const T &c)
+{
+ return std::accumulate(c.begin(), c.end(), 0);
+}
+
+struct MamMessage
+{
+ QDomElement element;
+ std::optional<QDateTime> delay;
+};
+
+enum EncryptedType { Unencrypted,
+ Encrypted };
+
+QXmppMessage parseMamMessage(const MamMessage &mamMessage, EncryptedType encrypted)
+{
+ QXmppMessage m;
+ m.parse(mamMessage.element, encrypted == Encrypted ? ScePublic : SceAll);
+ if (mamMessage.delay) {
+ m.setStamp(*mamMessage.delay);
+ }
+ return m;
+}
+
+std::optional<std::tuple<MamMessage, QString>> parseMamMessageResult(const QDomElement &messageEl)
+{
+ auto resultElement = messageEl.firstChildElement("result");
+ if (resultElement.isNull() || resultElement.namespaceURI() != ns_mam) {
+ return {};
+ }
+
+ auto forwardedElement = resultElement.firstChildElement("forwarded");
+ if (forwardedElement.isNull() || forwardedElement.namespaceURI() != ns_forwarding) {
+ return {};
+ }
+
+ auto queryId = resultElement.attribute("queryid");
+
+ auto messageElement = forwardedElement.firstChildElement("message");
+ if (messageElement.isNull()) {
+ return {};
+ }
+
+ auto parseDelay = [](const auto &forwardedEl) -> std::optional<QDateTime> {
+ auto delayEl = forwardedEl.firstChildElement("delay");
+ if (!delayEl.isNull() && delayEl.namespaceURI() == ns_delayed_delivery) {
+ return QXmppUtils::datetimeFromString(delayEl.attribute("stamp"));
+ }
+ return {};
+ };
+
+ return { { MamMessage { messageElement, parseDelay(forwardedElement) }, queryId } };
+}
+
struct RetrieveRequestState
{
QXmppPromise<QXmppMamManager::RetrieveResult> promise;
QXmppMamResultIq iq;
- QVector<QXmppMessage> messages;
+ QVector<MamMessage> messages;
+ QVector<QXmppMessage> processedMessages;
+ uint runningDecryptionJobs = 0;
void finish()
{
+ Q_ASSERT(messages.count() == processedMessages.count());
promise.finish(
QXmppMamManager::RetrievedMessages {
std::move(iq),
- std::move(messages) });
+ std::move(processedMessages) });
}
};
@@ -87,28 +156,8 @@ QStringList QXmppMamManager::discoveryFeatures() const
bool QXmppMamManager::handleStanza(const QDomElement &element)
{
if (element.tagName() == "message") {
- QDomElement resultElement = element.firstChildElement("result");
- if (!resultElement.isNull() && resultElement.namespaceURI() == ns_mam) {
- QDomElement forwardedElement = resultElement.firstChildElement("forwarded");
- QString queryId = resultElement.attribute("queryid");
-
- if (forwardedElement.isNull() || forwardedElement.namespaceURI() != ns_forwarding) {
- return false;
- }
-
- auto messageElement = forwardedElement.firstChildElement("message");
- auto delayElement = forwardedElement.firstChildElement("delay");
-
- if (messageElement.isNull()) {
- return false;
- }
-
- QXmppMessage message;
- message.parse(messageElement);
- if (!delayElement.isNull() && delayElement.namespaceURI() == ns_delayed_delivery) {
- const QString stamp = delayElement.attribute("stamp");
- message.setStamp(QXmppUtils::datetimeFromString(stamp));
- }
+ if (auto result = parseMamMessageResult(element)) {
+ auto &[message, queryId] = *result;
auto itr = d->ongoingRequests.find(queryId.toStdString());
if (itr != d->ongoingRequests.end()) {
@@ -116,7 +165,7 @@ bool QXmppMamManager::handleStanza(const QDomElement &element)
itr->second.messages.append(std::move(message));
} else {
// signal-based API
- Q_EMIT archivedMessageReceived(queryId, message);
+ Q_EMIT archivedMessageReceived(queryId, parseMamMessage(message, Unencrypted));
}
return true;
}
@@ -240,74 +289,97 @@ QString QXmppMamManager::retrieveArchivedMessages(const QString &to,
QXmppTask<QXmppMamManager::RetrieveResult> QXmppMamManager::retrieveMessages(const QString &to, const QString &node, const QString &jid, const QDateTime &start, const QDateTime &end, const QXmppResultSetQuery &resultSetQuery)
{
auto queryIq = buildRequest(to, node, jid, start, end, resultSetQuery);
+ auto queryId = queryIq.queryId();
+
+ auto [itr, inserted] = d->ongoingRequests.insert({ queryIq.queryId().toStdString(), RetrieveRequestState() });
+ Q_ASSERT(inserted);
- auto [itr, _] = d->ongoingRequests.insert({ queryIq.queryId().toStdString(), RetrieveRequestState() });
+ // create task here; promise could finish immediately after client()->sendIq()
+ auto task = itr->second.promise.task();
// retrieve messages
- client()->sendIq(std::move(queryIq)).then(this, [this, queryId = queryIq.queryId()](QXmppClient::IqResult result) {
+ client()->sendIq(std::move(queryIq)).then(this, [this, queryId](QXmppClient::IqResult result) {
auto itr = d->ongoingRequests.find(queryId.toStdString());
if (itr == d->ongoingRequests.end()) {
return;
}
+ auto &state = itr->second;
- if (std::holds_alternative<QDomElement>(result)) {
- auto &iq = itr->second.iq;
- iq.parse(std::get<QDomElement>(result));
+ // handle IQ sending errors
+ if (std::holds_alternative<QXmppError>(result)) {
+ state.promise.finish(std::get<QXmppError>(result));
+ d->ongoingRequests.erase(itr);
+ return;
+ }
- if (iq.type() == QXmppIq::Error) {
- itr->second.promise.finish(QXmppError { iq.error().text(), iq.error() });
- d->ongoingRequests.erase(itr);
- return;
- }
+ // parse IQ
+ auto &iq = state.iq;
+ iq.parse(std::get<QDomElement>(result));
- // decrypt encrypted messages
- if (auto *e2eeExt = client()->encryptionExtension()) {
- auto &messages = itr->second.messages;
- auto running = std::make_shared<uint>(0);
- // handle case when no message is encrypted
- auto hasEncryptedMessages = false;
+ // handle MAM error result IQ
+ if (iq.type() == QXmppIq::Error) {
+ state.promise.finish(QXmppError { iq.error().text(), iq.error() });
+ d->ongoingRequests.erase(itr);
+ return;
+ }
- for (auto i = 0; i < messages.size(); i++) {
- if (!e2eeExt->isEncrypted(messages.at(i))) {
- continue;
- }
- hasEncryptedMessages = true;
-
- auto message = messages.at(i);
- (*running)++;
- e2eeExt->decryptMessage(std::move(message)).then(this, [this, i, running, queryId](auto result) {
- (*running)--;
- auto itr = d->ongoingRequests.find(queryId.toStdString());
- if (itr == d->ongoingRequests.end()) {
- return;
- }
-
- if (std::holds_alternative<QXmppMessage>(result)) {
- itr->second.messages[i] = std::get<QXmppMessage>(std::move(result));
- } else {
- warning(QStringLiteral("Error decrypting message."));
- }
- if (*running == 0) {
- itr->second.finish();
- d->ongoingRequests.erase(itr);
- }
- });
+ // decrypt encrypted messages
+ if (auto *e2eeExt = client()->encryptionExtension()) {
+ // initialize processed messages (we need random access because
+ // decryptMessage() may finish in random order)
+ state.processedMessages.resize(state.messages.size());
+
+ // check for encrypted messages (once)
+ auto messagesEncrypted = transform(state.messages, [&](const auto &m) {
+ return e2eeExt->isEncrypted(m.element);
+ });
+ auto encryptedCount = sum(messagesEncrypted);
+
+ // We can't do this on the fly (with ++ and --) in the for loop
+ // because some decryptMessage() jobs could finish instantly
+ state.runningDecryptionJobs = encryptedCount;
+
+ for (auto i = 0; i < state.messages.size(); i++) {
+ if (!messagesEncrypted[i]) {
+ continue;
}
- if (!hasEncryptedMessages) {
- // finish here, no decryptMessage callback will do it
- itr->second.finish();
- d->ongoingRequests.erase(itr);
- }
- } else {
- itr->second.finish();
- d->ongoingRequests.erase(itr);
+ e2eeExt->decryptMessage(parseMamMessage(state.messages.at(i), Encrypted)).then(this, [this, i, queryId](auto result) {
+ auto itr = d->ongoingRequests.find(queryId.toStdString());
+ Q_ASSERT(itr != d->ongoingRequests.end());
+
+ auto &state = itr->second;
+
+ // store decrypted message, fallback to encrypted message
+ if (std::holds_alternative<QXmppMessage>(result)) {
+ state.processedMessages[i] = std::get<QXmppMessage>(std::move(result));
+ } else {
+ warning(QStringLiteral("Error decrypting message."));
+ state.processedMessages[i] = parseMamMessage(state.messages[i], Unencrypted);
+ }
+
+ // finish promise if this was the last job
+ state.runningDecryptionJobs--;
+ if (state.runningDecryptionJobs == 0) {
+ state.finish();
+ d->ongoingRequests.erase(itr);
+ }
+ });
+ }
+
+ // finishing the promise is done after decryptMessage()
+ if (encryptedCount > 0) {
+ return;
}
- } else {
- itr->second.promise.finish(std::get<QXmppError>(result));
- d->ongoingRequests.erase(itr);
}
+
+ // for the case without decryption, finish here
+ state.processedMessages = transform(state.messages, [](const auto &m) {
+ return parseMamMessage(m, Unencrypted);
+ });
+ state.finish();
+ d->ongoingRequests.erase(itr);
});
- return itr->second.promise.task();
+ return task;
}
diff --git a/src/client/QXmppOutgoingClient.cpp b/src/client/QXmppOutgoingClient.cpp
index 046bdcf2..19895938 100644
--- a/src/client/QXmppOutgoingClient.cpp
+++ b/src/client/QXmppOutgoingClient.cpp
@@ -324,11 +324,9 @@ bool QXmppOutgoingClient::isStreamResumed() const
///
QXmppTask<QXmppStream::IqResult> QXmppOutgoingClient::sendIq(QXmppIq &&iq)
{
- // always set a to address (the QXmppStream needs this for matching)
- if (iq.to().isEmpty()) {
- iq.setTo(d->config.domain());
- }
- return QXmppStream::sendIq(std::move(iq));
+ // If 'to' is empty the user's bare JID is meant implicitly (see RFC6120, section 10.3.3.).
+ auto to = iq.to();
+ return QXmppStream::sendIq(std::move(iq), to.isEmpty() ? d->config.jidBare() : to);
}
void QXmppOutgoingClient::_q_socketDisconnected()
diff --git a/src/omemo/QXmppOmemoManager.cpp b/src/omemo/QXmppOmemoManager.cpp
index 0aab152d..a3ad12bb 100644
--- a/src/omemo/QXmppOmemoManager.cpp
+++ b/src/omemo/QXmppOmemoManager.cpp
@@ -340,7 +340,7 @@ QXmppOmemoManager::~QXmppOmemoManager() = default;
///
/// This should be called after starting the client and before the login.
/// It must only be called after \c setUp() has been called once for the user
-/// during one of the past login session.
+/// during one of the past login sessions.
/// It does not need to be called if setUp() has been called during the current
/// login session.
///
@@ -1267,29 +1267,33 @@ bool Manager::handlePubSubEvent(const QDomElement &element, const QString &pubSu
switch (event.eventType()) {
// Items have been published.
case QXmppPubSubEventBase::Items: {
- const auto items = event.items();
-
// Only process items if the event notification contains one.
- // That is necessary because PubSub allows publishing without
- // items leading to notification-only events.
- if (!items.isEmpty()) {
- const auto &deviceListItem = items.constFirst();
- if (deviceListItem.id() == QXmppPubSubManager::standardItemIdToString(QXmppPubSubManager::Current)) {
- d->updateDevices(pubSubService, event.items().constFirst());
+ // That is necessary because PubSub allows publishing without items leading to
+ // notification-only events.
+ if (const auto &items = event.items(); !items.isEmpty()) {
+ // Since the usage of the item ID \c QXmppPubSubManager::Current is only RECOMMENDED
+ // by \xep{0060, Publish-Subscribe} (PubSub) but not obligatory, an appropriate
+ // contact device list is determined.
+ // In case of the own device list node, it is sctrictly processed as a recommended
+ // singleton item and changed to fit that if needed.
+ const auto isOwnDeviceListNode = d->ownBareJid() == pubSubService;
+ if (isOwnDeviceListNode) {
+ const auto &deviceListItem = items.constFirst();
+ if (deviceListItem.id() == QXmppPubSubManager::standardItemIdToString(QXmppPubSubManager::Current)) {
+ d->updateDevices(pubSubService, event.items().constFirst());
+ } else {
+ d->handleIrregularDeviceListChanges(pubSubService);
+ }
} else {
- d->handleIrregularDeviceListChanges(pubSubService);
+ d->updateContactDevices(pubSubService, items);
}
}
break;
}
- // Items have been retracted.
+ // Specific items are deleted.
case QXmppPubSubEventBase::Retract: {
- // Specific items are deleted.
- const auto &retractedItem = event.retractIds().constFirst();
- if (retractedItem == QXmppPubSubManager::standardItemIdToString(QXmppPubSubManager::Current)) {
- d->handleIrregularDeviceListChanges(pubSubService);
- }
+ d->handleIrregularDeviceListChanges(pubSubService);
}
// All items are deleted.
case QXmppPubSubEventBase::Purge:
diff --git a/src/omemo/QXmppOmemoManager_p.cpp b/src/omemo/QXmppOmemoManager_p.cpp
index 1c86d80b..bcfd8303 100644
--- a/src/omemo/QXmppOmemoManager_p.cpp
+++ b/src/omemo/QXmppOmemoManager_p.cpp
@@ -920,7 +920,7 @@ bool ManagerPrivate::updatePreKeyPairs(uint32_t count)
deviceBundle.addPublicPreKey(preKeyId, serializedPublicPreKey);
}
- this->preKeyPairs.insert(serializedPreKeyPairs);
+ preKeyPairs.insert(serializedPreKeyPairs);
omemoStorage->addPreKeyPairs(serializedPreKeyPairs);
ownDevice.latestPreKeyId = latestPreKeyId - 1 + count;
@@ -2648,6 +2648,36 @@ void ManagerPrivate::updateOwnDevicesLocally(bool isDeviceListNodeExistent, Func
}
//
+// Updates all locally stored devices of a contact.
+//
+// \param deviceOwnerJid bare JID of the devices' owner
+// \param deviceListItems PEP items that may contain a device list
+//
+// \returns a found device list item
+//
+std::optional<QXmppOmemoDeviceListItem> QXmppOmemoManagerPrivate::updateContactDevices(const QString &deviceOwnerJid, const QVector<QXmppOmemoDeviceListItem> &deviceListItems)
+{
+ if (deviceListItems.size() > 1) {
+ const auto itr = std::find_if(deviceListItems.cbegin(), deviceListItems.cend(), [=](const QXmppOmemoDeviceListItem &item) {
+ return item.id() == QXmppPubSubManager::Current;
+ });
+
+ if (itr != deviceListItems.cend()) {
+ updateDevices(deviceOwnerJid, *itr);
+ return *itr;
+ } else {
+ warning("Device list for JID '" % deviceOwnerJid % "' could not be updated because the node contains more than one item but none with the singleton node's specific ID '" % QXmppPubSubManager::standardItemIdToString(QXmppPubSubManager::Current) % "'");
+ handleIrregularDeviceListChanges(deviceOwnerJid);
+ return {};
+ }
+ }
+
+ const auto &item = deviceListItems.constFirst();
+ updateDevices(deviceOwnerJid, item);
+ return item;
+}
+
+//
// Updates all locally stored devices by a passed device list item.
//
// \param deviceOwnerJid bare JID of the devices' owner
@@ -2783,7 +2813,7 @@ void ManagerPrivate::updateDevices(const QString &deviceOwnerJid, const QXmppOme
// Publish an own correct device list if the PEP service's one is incorrect
// and the devices are already set up locally.
if (isOwnDeviceListIncorrect) {
- if (!this->devices.isEmpty()) {
+ if (!devices.isEmpty()) {
publishDeviceListItem(true, [=](bool isPublished) {
if (!isPublished) {
warning("Own device list item could not be published in order to correct the PEP service's one");
@@ -2795,7 +2825,7 @@ void ManagerPrivate::updateDevices(const QString &deviceOwnerJid, const QXmppOme
//
// Corrects the own device list on the PEP service by the locally stored
-// devices or set a contact device to be removed locally in the future.
+// devices or sets a contact device to be removed locally in the future.
//
// \param deviceOwnerJid bare JID of the devices' owner
//
@@ -2810,7 +2840,7 @@ void ManagerPrivate::handleIrregularDeviceListChanges(const QString &deviceOwner
auto future = pubSubManager->deleteOwnPepNode(ns_omemo_2_devices);
future.then(q, [=](QXmppPubSubManager::Result result) {
if (const auto error = std::get_if<QXmppError>(&result)) {
- warning("Node '" % QString(ns_omemo_2_devices) % "' of JID '" % deviceOwnerJid %
+ warning("Node '" % QString(ns_omemo_2_devices) % "' of JID '" % deviceOwnerJid %
"' could not be deleted in order to recover from an inconsistent node: " %
errorToString(*error));
} else {
@@ -2845,7 +2875,7 @@ void ManagerPrivate::handleIrregularDeviceListChanges(const QString &deviceOwner
}
});
} else {
- auto &ownerDevices = this->devices[deviceOwnerJid];
+ auto &ownerDevices = devices[deviceOwnerJid];
// Set a timestamp for locally stored contact devices being removed
// later if their device list item is removed, if their device list node
@@ -3055,16 +3085,27 @@ QXmppTask<bool> ManagerPrivate::changeDeviceLabel(const QString &deviceLabel)
//
QXmppTask<QXmppPubSubManager::ItemResult<QXmppOmemoDeviceListItem>> ManagerPrivate::requestDeviceList(const QString &jid)
{
- auto future = pubSubManager->requestItem<QXmppOmemoDeviceListItem>(jid, ns_omemo_2_devices, QXmppPubSubManager::Current);
- future.then(q, [this, jid](QXmppPubSubManager::ItemResult<QXmppOmemoDeviceListItem> result) mutable {
+ QXmppPromise<QXmppPubSubManager::ItemResult<QXmppOmemoDeviceListItem>> interface;
+
+ // Since the usage of the item ID \c QXmppPubSubManager::Current is only RECOMMENDED by
+ // \xep{0060, Publish-Subscribe} (PubSub) but not obligatory, all items are requested even if
+ // the node should contain only one item.
+ auto future = pubSubManager->requestItems<QXmppOmemoDeviceListItem>(jid, ns_omemo_2_devices);
+ future.then(q, [this, interface, jid](QXmppPubSubManager::ItemsResult<QXmppOmemoDeviceListItem> result) mutable {
if (const auto error = std::get_if<QXmppError>(&result)) {
warning("Device list for JID '" % jid % "' could not be retrieved: " % errorToString(*error));
+ interface.finish(*error);
+ } else if (const auto &items = std::get<QXmppPubSubManager::Items<QXmppOmemoDeviceListItem>>(result).items; items.isEmpty()) {
+ const auto errorMessage = "Device list for JID '" % jid % "' could not be retrieved because the node does not contain any item";
+ warning(errorMessage);
+ interface.finish(QXmppError { errorMessage });
+ } else if (const auto item = updateContactDevices(jid, items); item) {
+ interface.finish(*item);
} else {
- const auto &item = std::get<QXmppOmemoDeviceListItem>(result);
- updateDevices(jid, item);
+ interface.finish(QXmppError { "Device list for JID '" % jid % "' could not be retrieved because the node does not contain an appropriate item" });
}
});
- return future;
+ return interface.task();
}
//
diff --git a/src/omemo/QXmppOmemoManager_p.h b/src/omemo/QXmppOmemoManager_p.h
index 96f10f94..0792bdf2 100644
--- a/src/omemo/QXmppOmemoManager_p.h
+++ b/src/omemo/QXmppOmemoManager_p.h
@@ -290,6 +290,7 @@ public:
QXmppOmemoDeviceListItem deviceListItem(bool addOwnDevice = true);
template<typename Function>
void updateOwnDevicesLocally(bool isDeviceListNodeExistent, Function continuation);
+ std::optional<QXmppOmemoDeviceListItem> updateContactDevices(const QString &deviceOwnerJid, const QVector<QXmppOmemoDeviceListItem> &deviceListItems);
void updateDevices(const QString &deviceOwnerJid, const QXmppOmemoDeviceListItem &deviceListItem);
void handleIrregularDeviceListChanges(const QString &deviceOwnerJid);
template<typename Function>