aboutsummaryrefslogtreecommitdiff
path: root/tests/qxmppomemomanager/tst_qxmppomemomanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/qxmppomemomanager/tst_qxmppomemomanager.cpp')
-rw-r--r--tests/qxmppomemomanager/tst_qxmppomemomanager.cpp513
1 files changed, 513 insertions, 0 deletions
diff --git a/tests/qxmppomemomanager/tst_qxmppomemomanager.cpp b/tests/qxmppomemomanager/tst_qxmppomemomanager.cpp
new file mode 100644
index 00000000..f0cf2447
--- /dev/null
+++ b/tests/qxmppomemomanager/tst_qxmppomemomanager.cpp
@@ -0,0 +1,513 @@
+// SPDX-FileCopyrightText: 2022 Melvin Keskin <melvo@olomono.de>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppAtmManager.h"
+#include "QXmppAtmTrustMemoryStorage.h"
+#include "QXmppBitsOfBinaryContentId.h"
+#include "QXmppBitsOfBinaryIq.h"
+#include "QXmppCarbonManager.h"
+#include "QXmppClient.h"
+#include "QXmppDiscoveryManager.h"
+#include "QXmppE2eeMetadata.h"
+#include "QXmppMessage.h"
+#include "QXmppOmemoElement_p.h"
+#include "QXmppOmemoEnvelope_p.h"
+#include "QXmppOmemoManager.h"
+#include "QXmppOmemoManager_p.h"
+#include "QXmppOmemoMemoryStorage.h"
+#include "QXmppPubSubIq.h"
+#include "QXmppPubSubItem.h"
+#include "QXmppPubSubManager.h"
+
+#include "IntegrationTesting.h"
+#include "util.h"
+#include <QObject>
+
+using namespace QXmpp;
+using namespace QXmpp::Private;
+
+struct OmemoUser
+{
+ QXmppClient client;
+ QXmppLogger logger;
+ QXmppOmemoManager *manager;
+ QXmppCarbonManager *carbonManager;
+ QXmppDiscoveryManager *discoveryManager;
+ QXmppPubSubManager *pubSubManager;
+ std::unique_ptr<QXmppOmemoMemoryStorage> omemoStorage;
+ std::unique_ptr<QXmppAtmTrustStorage> trustStorage;
+ QXmppAtmManager *trustManager;
+};
+
+class OmemoIqHandler : public QXmppClientExtension
+{
+public:
+ OmemoIqHandler(const QXmppBitsOfBinaryIq &requestIq, const QXmppBitsOfBinaryIq &responseIq)
+ {
+ m_requestIq = requestIq;
+ m_responseIq = responseIq;
+ }
+
+ bool handleStanza(const QDomElement &stanza, const std::optional<QXmppE2eeMetadata> &e2eeMetadata) override
+ {
+ if (stanza.tagName() == "iq" && QXmppBitsOfBinaryIq::isBitsOfBinaryIq(stanza)) {
+ QXmppBitsOfBinaryIq iq;
+ iq.parse(stanza);
+
+ if (iq.cid().toContentId() != m_requestIq.cid().toContentId()) {
+ return false;
+ }
+
+ m_responseIq.setId(iq.id());
+ client()->reply(std::move(m_responseIq), e2eeMetadata);
+ return true;
+ }
+
+ return false;
+ };
+
+private:
+ QXmppBitsOfBinaryIq m_requestIq;
+ QXmppBitsOfBinaryIq m_responseIq;
+};
+
+class tst_QXmppOmemoManager : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void testSecurityPolicies();
+ void testTrustLevels();
+ void initOmemoUser(OmemoUser &omemoUser);
+ void testInit();
+ void testSetUp();
+ void testLoad();
+ void testSendMessage();
+ void testSendIq();
+ void finish(OmemoUser &omemoUser);
+
+private:
+ OmemoUser m_alice1;
+ OmemoUser m_alice2;
+};
+
+void tst_QXmppOmemoManager::initTestCase()
+{
+ initOmemoUser(m_alice1);
+ initOmemoUser(m_alice2);
+}
+
+void tst_QXmppOmemoManager::testSecurityPolicies()
+{
+ auto futureSecurityPolicy = m_alice1.manager->securityPolicy();
+ QVERIFY(futureSecurityPolicy.isFinished());
+ auto resultSecurityPolicy = futureSecurityPolicy.result();
+ QCOMPARE(resultSecurityPolicy, NoSecurityPolicy);
+
+ m_alice1.manager->setSecurityPolicy(Toakafa);
+
+ futureSecurityPolicy = m_alice1.manager->securityPolicy();
+ QVERIFY(futureSecurityPolicy.isFinished());
+ resultSecurityPolicy = futureSecurityPolicy.result();
+ QCOMPARE(resultSecurityPolicy, Toakafa);
+}
+
+void tst_QXmppOmemoManager::testTrustLevels()
+{
+ auto futureTrustLevel = m_alice1.manager->trustLevel(QStringLiteral("alice@example.org"),
+ QByteArray::fromBase64(QByteArrayLiteral("AZ/cF4OrUOILKO1gQBf62pQevOhBJ2NyHnXLwM4FDZU=")));
+ QVERIFY(futureTrustLevel.isFinished());
+ auto resultTrustLevel = futureTrustLevel.result();
+ QCOMPARE(resultTrustLevel, TrustLevel::Undecided);
+
+ m_alice1.manager->setTrustLevel(
+ { { QStringLiteral("alice@example.org"),
+ QByteArray::fromBase64(QByteArrayLiteral("AZ/cF4OrUOILKO1gQBf62pQevOhBJ2NyHnXLwM4FDZU=")) },
+ { QStringLiteral("bob@example.com"),
+ QByteArray::fromBase64(QByteArrayLiteral("9E51lG3vVmUn8CM7/AIcmIlLP2HPl6Ao0/VSf4VT/oA=")) } },
+ TrustLevel::Authenticated);
+
+ futureTrustLevel = m_alice1.manager->trustLevel(QStringLiteral("alice@example.org"),
+ QByteArray::fromBase64(QByteArrayLiteral("AZ/cF4OrUOILKO1gQBf62pQevOhBJ2NyHnXLwM4FDZU=")));
+ QVERIFY(futureTrustLevel.isFinished());
+ resultTrustLevel = futureTrustLevel.result();
+ QCOMPARE(resultTrustLevel, TrustLevel::Authenticated);
+}
+
+void tst_QXmppOmemoManager::initOmemoUser(OmemoUser &omemoUser)
+{
+ omemoUser.discoveryManager = new QXmppDiscoveryManager;
+ omemoUser.client.addExtension(omemoUser.discoveryManager);
+
+ omemoUser.pubSubManager = new QXmppPubSubManager;
+ omemoUser.client.addExtension(omemoUser.pubSubManager);
+
+ omemoUser.trustStorage = std::make_unique<QXmppAtmTrustMemoryStorage>();
+ omemoUser.trustManager = new QXmppAtmManager(omemoUser.trustStorage.get());
+ omemoUser.client.addExtension(omemoUser.trustManager);
+
+ omemoUser.omemoStorage = std::make_unique<QXmppOmemoMemoryStorage>();
+ omemoUser.manager = new QXmppOmemoManager(omemoUser.omemoStorage.get());
+ omemoUser.client.addExtension(omemoUser.manager);
+
+ omemoUser.carbonManager = new QXmppCarbonManager;
+ omemoUser.client.addExtension(omemoUser.carbonManager);
+
+ connect(omemoUser.carbonManager, &QXmppCarbonManager::messageSent, omemoUser.manager, &QXmppOmemoManager::handleMessage);
+ connect(omemoUser.carbonManager, &QXmppCarbonManager::messageReceived, omemoUser.manager, &QXmppOmemoManager::handleMessage);
+
+ omemoUser.logger.setLoggingType(QXmppLogger::SignalLogging);
+ omemoUser.client.setLogger(&omemoUser.logger);
+}
+
+void tst_QXmppOmemoManager::testInit()
+{
+ auto omemoStorage = std::make_unique<QXmppOmemoMemoryStorage>();
+ auto manager = std::make_unique<QXmppOmemoManager>(omemoStorage.get());
+ QVERIFY(manager->d->initGlobalContext());
+ QVERIFY(manager->d->initLocking());
+ QVERIFY(manager->d->initCryptoProvider());
+ // TODO: Test initStores()
+}
+
+void tst_QXmppOmemoManager::testSetUp()
+{
+ SKIP_IF_INTEGRATION_TESTS_DISABLED();
+
+ auto isManagerSetUp = false;
+ const QObject context;
+
+ connect(&m_alice1.client, &QXmppClient::connected, &context, [=, &isManagerSetUp]() {
+ auto future = m_alice1.manager->setUp();
+ await(future, this, [=, &isManagerSetUp](bool isSetUp) {
+ if (isSetUp) {
+ isManagerSetUp = true;
+ }
+ });
+ });
+
+ connect(&m_alice1.logger, &QXmppLogger::message, &context, [=](QXmppLogger::MessageType type, const QString &text) {
+ if (type == QXmppLogger::SentMessage) {
+ qDebug() << "SENT: " << text;
+ } else {
+ qDebug() << "RECEIVED: " << text;
+ }
+ });
+
+ m_alice1.client.connectToServer(IntegrationTests::clientConfiguration());
+
+ QTRY_VERIFY(isManagerSetUp);
+ finish(m_alice1);
+}
+
+void tst_QXmppOmemoManager::testLoad()
+{
+ auto future = m_alice1.manager->load();
+ while (!future.isFinished()) {
+ QCoreApplication::processEvents();
+ }
+ auto result = future.result();
+ QVERIFY(!result);
+
+ const QXmppOmemoStorage::OwnDevice ownDevice = { 1,
+ QStringLiteral("notebook"),
+ QByteArray::fromBase64(QByteArrayLiteral("OU5HM3loYnFjZVVaYmpSbHdab0FPTDhJVHRzUFVUcFMK")),
+ QByteArray::fromBase64(QByteArrayLiteral("TkhodEZ6cnFDeGtENWRuT1ZZdUsyaGIwQkRPdHFRSE8K")),
+ 2,
+ 3 };
+ m_alice1.omemoStorage->setOwnDevice(ownDevice);
+ m_alice1.omemoStorage->addSignedPreKeyPair(2,
+ { QDateTime::currentDateTimeUtc(),
+ QByteArray::fromBase64(QByteArrayLiteral("VEZBOTZFRjNQSVRzVE1OcnIzYmV2ZFFuM0R3WmduUWwK")) });
+ m_alice1.omemoStorage->addPreKeyPairs({ { 3,
+ QByteArray::fromBase64(QByteArrayLiteral("RmVmQ0RTTzB0Z2R2T0ZjckQ4N29PN01VTGFFMVZjUmIK")) } });
+
+ future = m_alice1.manager->load();
+ while (!future.isFinished()) {
+ QCoreApplication::processEvents();
+ }
+ result = future.result();
+ QVERIFY(result);
+
+ const auto storedOwnDevice = m_alice1.manager->ownDevice();
+ // QCOMPARE(storedOwnDevice.keyId(), m_alice1.manager->d->createKeyId(ownDevice.publicIdentityKey));
+ QCOMPARE(storedOwnDevice.label(), ownDevice.label);
+
+ m_alice1.omemoStorage->resetAll();
+}
+
+void tst_QXmppOmemoManager::testSendMessage()
+{
+ SKIP_IF_INTEGRATION_TESTS_DISABLED()
+
+ QSignalSpy disconnectedAlice1Spy(&m_alice1.client, &QXmppClient::disconnected);
+
+ auto isFirstMessageSentByAlice1 = false;
+ auto isFirstMessageDecryptedByAlice2 = false;
+ auto isEmptyOmemoMessageReceivedByAlice1 = false;
+ auto isSecondMessageSentByAlice1 = false;
+ auto isSecondMessageDecryptedByAlice2 = false;
+
+ const auto config1 = IntegrationTests::clientConfiguration();
+ auto config2 = config1;
+ config2.setResource(config2.resource() % QStringLiteral("2"));
+
+ const QObject context;
+ QString recipient = "bob@" % config1.domain();
+
+ QXmppMessage message1;
+ message1.setTo(recipient);
+ message1.setBody("Hello Bob!");
+
+ QXmppMessage message2;
+ message2.setTo(recipient);
+ message2.setBody("Hello Bob again!");
+
+ connect(&m_alice1.client, &QXmppClient::connected, &context, [=]() {
+ auto future = m_alice1.manager->setUp();
+ await(future, this, [=](bool isSetUp) {
+ if (isSetUp) {
+ m_alice1.carbonManager->setCarbonsEnabled(true);
+
+ auto future = m_alice1.manager->setSecurityPolicy(Toakafa);
+ await(future, this, [=]() {
+ auto future = m_alice2.manager->setSecurityPolicy(Toakafa);
+ await(future, this, [=]() {
+ m_alice2.client.connectToServer(config2);
+ });
+ });
+ }
+ });
+ });
+
+ connect(&m_alice2.client, &QXmppClient::connected, &context, [=]() {
+ auto future = m_alice2.manager->setUp();
+ await(future, this, [=](bool isSetUp) {
+ if (isSetUp) {
+ m_alice2.carbonManager->setCarbonsEnabled(true);
+ }
+ });
+ });
+
+ connect(&m_alice2.logger, &QXmppLogger::message, &context, [=](QXmppLogger::MessageType type, const QString &text) {
+ if (type == QXmppLogger::SentMessage) {
+ qDebug() << "Alice 2 - SENT: " << text;
+ } else {
+ qDebug() << "Alice 2 - RECEIVED: " << text;
+ }
+ });
+
+ connect(&m_alice2.client, &QXmppClient::messageReceived, &context, [=, &isFirstMessageDecryptedByAlice2, &isSecondMessageDecryptedByAlice2](const QXmppMessage &receivedMessage) {
+ // Process only encrypted stanzas.
+ if (receivedMessage.e2eeMetadata()) {
+ qDebug() << "Decrypted message:" << receivedMessage.body();
+ if (receivedMessage.body() == message1.body()) {
+ isFirstMessageDecryptedByAlice2 = true;
+ } else if (receivedMessage.body() == message2.body()) {
+ isSecondMessageDecryptedByAlice2 = true;
+ }
+ }
+ });
+
+ connect(&m_alice1.logger, &QXmppLogger::message, &context, [=, &isEmptyOmemoMessageReceivedByAlice1, &isSecondMessageSentByAlice1](QXmppLogger::MessageType type, const QString &text) mutable {
+ if (type == QXmppLogger::SentMessage) {
+ qDebug() << "Alice - SENT: " << text;
+ } else if (type == QXmppLogger::ReceivedMessage) {
+ qDebug() << "Alice - RECEIVED: " << text;
+
+ // Check if Alice 1 received an empty OMEMO message from Alice 2.
+ // If that is the case, send a second message to Alice 2.
+ // The empty OMEMO message is not emitted via QXmppClient::messageReceived().
+ // Thus, it must be parsed manually here.
+ const auto content = text.toUtf8();
+ if (content.startsWith(QByteArrayLiteral("<message "))) {
+ QXmppMessage message;
+ parsePacket(message, text.toUtf8());
+
+ if (const auto optionalOmemoElement = message.omemoElement(); optionalOmemoElement && optionalOmemoElement.value().payload().isEmpty()) {
+ isEmptyOmemoMessageReceivedByAlice1 = true;
+
+ auto future = m_alice1.client.send(std::move(message2), QXmppSendStanzaParams());
+ await(future, this, [=, &isSecondMessageSentByAlice1](QXmpp::SendResult result) {
+ if (std::get_if<QXmpp::SendSuccess>(&result)) {
+ isSecondMessageSentByAlice1 = true;
+ }
+ });
+ }
+ }
+ }
+ });
+
+ // Wait for receiving the device of Alice 2 in order to send a message to Bob and a message
+ // carbon to Alice 2.
+ connect(m_alice1.manager, &QXmppOmemoManager::deviceAdded, &context, [=, &isFirstMessageSentByAlice1](const QString &jid, uint32_t) mutable {
+ if (jid == m_alice2.client.configuration().jidBare()) {
+ if (!isFirstMessageSentByAlice1) {
+ auto future = m_alice1.client.send(std::move(message1), QXmppSendStanzaParams());
+ await(future, this, [=, &isFirstMessageSentByAlice1](QXmpp::SendResult result) {
+ if (std::get_if<QXmpp::SendSuccess>(&result)) {
+ isFirstMessageSentByAlice1 = true;
+ }
+ });
+ }
+ }
+ });
+
+ m_alice1.client.connectToServer(config1);
+
+ QTRY_VERIFY_WITH_TIMEOUT(isFirstMessageSentByAlice1, 10000);
+ QTRY_VERIFY_WITH_TIMEOUT(isFirstMessageDecryptedByAlice2, 10000);
+ QTRY_VERIFY_WITH_TIMEOUT(isEmptyOmemoMessageReceivedByAlice1, 10000);
+ QTRY_VERIFY_WITH_TIMEOUT(isSecondMessageSentByAlice1, 10000);
+ QTRY_VERIFY_WITH_TIMEOUT(isSecondMessageDecryptedByAlice2, 10000);
+
+ m_alice1.client.disconnectFromServer();
+ QVERIFY2(disconnectedAlice1Spy.wait(), "Could not disconnect from server!");
+ finish(m_alice2);
+}
+
+void tst_QXmppOmemoManager::testSendIq()
+{
+ SKIP_IF_INTEGRATION_TESTS_DISABLED()
+
+ QSignalSpy disconnectedAlice1Spy(&m_alice1.client, &QXmppClient::disconnected);
+
+ auto isFirstRequestSent = false;
+ auto isErrorResponseReceived = false;
+ auto isSecondRequestSent = false;
+ auto isResultResponseReceived = false;
+
+ const auto config1 = IntegrationTests::clientConfiguration();
+ auto config2 = config1;
+ config2.setResource(config2.resource() % QStringLiteral("2"));
+
+ const QObject context;
+
+ QXmppBitsOfBinaryIq requestIq;
+ requestIq.setTo(config2.jid());
+ requestIq.setCid(QXmppBitsOfBinaryContentId::fromContentId(QStringLiteral("sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org")));
+
+ QXmppBitsOfBinaryIq responseIq;
+ responseIq.setType(QXmppIq::Result);
+ responseIq.setTo(config1.jid());
+ responseIq.setData(QByteArray::fromBase64(QByteArrayLiteral(
+ "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAAclBMVEUAAADYZArfaA9GIAoBAAGN"
+ "QA3MXgniaAiEOgZMIATDXRXZZhHUZBHIXhDrbQ6sUQ7OYA2TRAubRwqMQQq7VQlKHgMAAAK5WRfJ"
+ "YBOORBFoMBCwUQ/ycA6FPgvbZQpeKglNJQmrTQeOPgQyFwR6MwACAABRPE/oAAAAW0lEQVQI1xXI"
+ "Rw6EMBTAUP8kJKENnaF37n9FQPLCekAgzklhgCwfrlNHEXhrvCsxaU/SwLGAFuIWZFpBERtKm9Xf"
+ "JqH+vVWh4POqgHrsAtht095b+geYRSl57QHSPgP3+CwvAAAAAABJRU5ErkJggg==")));
+
+ OmemoIqHandler iqHandler(requestIq, responseIq);
+
+ connect(&m_alice1.client, &QXmppClient::connected, &context, [=]() {
+ auto future = m_alice1.manager->setUp();
+ await(future, this, [=](bool isSetUp) {
+ if (isSetUp) {
+ auto future = m_alice1.manager->setSecurityPolicy(Toakafa);
+ await(future, this, [=]() {
+ auto future = m_alice2.manager->setSecurityPolicy(Toakafa);
+ await(future, this, [=]() {
+ m_alice2.client.connectToServer(config2);
+ });
+ });
+ }
+ });
+ });
+
+ connect(&m_alice2.client, &QXmppClient::connected, &context, [=]() {
+ m_alice2.manager->setUp();
+ });
+
+ connect(&m_alice1.logger, &QXmppLogger::message, &context, [=](QXmppLogger::MessageType type, const QString &text) {
+ if (type == QXmppLogger::SentMessage) {
+ qDebug() << "Alice - SENT: " << text;
+ } else if (type == QXmppLogger::ReceivedMessage) {
+ qDebug() << "Alice - RECEIVED: " << text;
+ }
+ });
+
+ connect(&m_alice2.logger, &QXmppLogger::message, &context, [=](QXmppLogger::MessageType type, const QString &text) {
+ if (type == QXmppLogger::SentMessage) {
+ qDebug() << "Alice 2 - SENT: " << text;
+ } else {
+ qDebug() << "Alice 2 - RECEIVED: " << text;
+ }
+ });
+
+ // Wait for receiving the device of Alice 2 in order to send a request to it.
+ connect(m_alice1.manager, &QXmppOmemoManager::deviceAdded, &context, [=, &isFirstRequestSent, &isErrorResponseReceived, &isSecondRequestSent, &isResultResponseReceived, &iqHandler](const QString &jid, uint32_t) {
+ if (jid != m_alice2.client.configuration().jidBare()) {
+ return;
+ }
+ if (!isFirstRequestSent && !isSecondRequestSent) {
+ auto requestIqCopy = requestIq;
+ auto future = m_alice1.client.sendSensitiveIq(std::move(requestIqCopy));
+ await(future, this, [=, &isFirstRequestSent, &isErrorResponseReceived, &isSecondRequestSent, &isResultResponseReceived, &iqHandler](QXmppClient::IqResult result) {
+ if (const auto response = std::get_if<QDomElement>(&result)) {
+ isFirstRequestSent = true;
+
+ QXmppIq iq;
+ iq.parse(*response);
+
+ QCOMPARE(iq.type(), QXmppIq::Error);
+ const auto error = iq.error();
+ QCOMPARE(error.type(), QXmppStanza::Error::Cancel);
+ QCOMPARE(error.condition(), QXmppStanza::Error::FeatureNotImplemented);
+ isErrorResponseReceived = true;
+
+ m_alice2.client.addExtension(&iqHandler);
+
+ auto requestIqCopy = requestIq;
+ auto future = m_alice1.client.sendSensitiveIq(std::move(requestIqCopy));
+ await(future, this, [=, &isSecondRequestSent, &isResultResponseReceived](QXmppClient::IqResult result) {
+ if (const auto response = std::get_if<QDomElement>(&result)) {
+ isSecondRequestSent = true;
+
+ if (QXmppBitsOfBinaryIq::isBitsOfBinaryIq(*response)) {
+ QXmppBitsOfBinaryIq iq;
+ iq.parse(*response);
+ QCOMPARE(iq.data(), responseIq.data());
+ isResultResponseReceived = true;
+ }
+ }
+ });
+ }
+ });
+ }
+ });
+
+ m_alice1.client.connectToServer(config1);
+
+ QTRY_VERIFY_WITH_TIMEOUT(isFirstRequestSent, 20'000);
+ QTRY_VERIFY(isErrorResponseReceived);
+ QTRY_VERIFY(isSecondRequestSent);
+ QTRY_VERIFY(isResultResponseReceived);
+
+ m_alice1.client.disconnectFromServer();
+ QVERIFY2(disconnectedAlice1Spy.wait(), "Could not disconnect from server!");
+ finish(m_alice2);
+}
+
+void tst_QXmppOmemoManager::finish(OmemoUser &omemoUser)
+{
+ QSignalSpy disconnectedSpy(&omemoUser.client, &QXmppClient::disconnected);
+
+ bool isManagerReset = false;
+
+ auto future = omemoUser.manager->resetAll();
+ await(future, this, [=, &isManagerReset, &omemoUser](bool isReset) {
+ if (isReset) {
+ isManagerReset = true;
+ }
+
+ omemoUser.client.disconnectFromServer();
+ });
+
+ QVERIFY2(disconnectedSpy.wait(), "Could not disconnect from server!");
+ QTRY_VERIFY(isManagerReset);
+}
+
+QTEST_MAIN(tst_QXmppOmemoManager)
+#include "tst_qxmppomemomanager.moc"