aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/QXmppJingleMessageInitiationManager.cpp584
-rw-r--r--src/client/QXmppJingleMessageInitiationManager.h126
2 files changed, 710 insertions, 0 deletions
diff --git a/src/client/QXmppJingleMessageInitiationManager.cpp b/src/client/QXmppJingleMessageInitiationManager.cpp
new file mode 100644
index 00000000..d7a4cea1
--- /dev/null
+++ b/src/client/QXmppJingleMessageInitiationManager.cpp
@@ -0,0 +1,584 @@
+// SPDX-FileCopyrightText: 2023 Tibor Csötönyi <work@taibsu.de>
+// SPDX-FileCopyrightText: 2023 Melvin Keskin <melvo@olomono.de>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppJingleMessageInitiationManager.h"
+
+#include "QXmppClient.h"
+#include "QXmppConstants_p.h"
+#include "QXmppMessage.h"
+#include "QXmppPromise.h"
+#include "QXmppUtils.h"
+
+#include <QStringBuilder>
+#include <QUuid>
+
+using namespace QXmpp;
+using Jmi = QXmppJingleMessageInitiation;
+using JmiManager = QXmppJingleMessageInitiationManager;
+using JmiElement = QXmppJingleMessageInitiationElement;
+using JmiType = JmiElement::Type;
+
+class QXmppJingleMessageInitiationPrivate
+{
+public:
+ QXmppJingleMessageInitiationPrivate(JmiManager *manager)
+ : manager(manager)
+ {
+ }
+
+ QXmppTask<SendResult> request(JmiElement &&jmiElement);
+
+ QXmppJingleMessageInitiationManager *manager;
+ QString id;
+ QString callPartnerJid;
+ bool isProceeded { false };
+};
+
+///
+/// \brief Creates a Jingle Message Initiation request based on given type.
+/// \param type The request type (proceed, accept, reject, retract, finish).
+///
+QXmppTask<SendResult> QXmppJingleMessageInitiationPrivate::request(JmiElement &&jmiElement)
+{
+ jmiElement.setId(id);
+ return manager->sendMessage(jmiElement, callPartnerJid);
+}
+
+///
+/// \class QXmppJingleMessageInitiation
+///
+/// \brief The QXmppJingleMessageInitiation class holds information about the JMI element in the
+/// current context.
+///
+/// \since QXmpp 1.6
+///
+
+///
+/// \brief Constructs a Jingle Message Initiation object.
+///
+QXmppJingleMessageInitiation::QXmppJingleMessageInitiation(QXmppJingleMessageInitiationManager *manager)
+ : d(new QXmppJingleMessageInitiationPrivate(manager))
+{
+}
+
+QXmppJingleMessageInitiation::~QXmppJingleMessageInitiation() = default;
+
+///
+/// Creates a JMI element of type "ringing" and sends a request containing the element.
+///
+QXmppTask<SendResult> QXmppJingleMessageInitiation::ring()
+{
+ QXmppJingleMessageInitiationElement jmiElement;
+ jmiElement.setType(JmiType::Ringing);
+
+ return d->request(std::move(jmiElement));
+}
+
+///
+/// Creates a JMI element of type "proceed" and sends a request containing the element.
+///
+QXmppTask<SendResult> QXmppJingleMessageInitiation::proceed()
+{
+ QXmppJingleMessageInitiationElement jmiElement;
+ jmiElement.setType(JmiType::Proceed);
+
+ return d->request(std::move(jmiElement));
+}
+
+///
+/// Creates a JMI element of type "reject" and sends a request containing the element.
+/// The default reason tag/type will be "busy" with text "Busy".
+///
+/// \param reason Reason object for reject element
+/// \param containsTieBreak Whether the reject element contains a tie-break tag or not
+///
+QXmppTask<SendResult> QXmppJingleMessageInitiation::reject(std::optional<QXmppJingleReason> reason, bool containsTieBreak)
+{
+ JmiElement jmiElement;
+ jmiElement.setType(JmiType::Reject);
+
+ if (!reason) {
+ reason = QXmppJingleReason();
+ reason->setType(QXmppJingleReason::Busy);
+ reason->setText(QStringLiteral("Busy"));
+ }
+
+ jmiElement.setReason(reason);
+ jmiElement.setContainsTieBreak(containsTieBreak);
+
+ return d->request(std::move(jmiElement));
+}
+
+///
+/// Creates a JMI element of type "retract" and sends a request containing the element.
+/// The default reason tag/type will be "cancel" with text "Retracted".
+///
+/// \param reason Reason object for retract element
+/// \param containsTieBreak Whether the retract element contains a tie-break tag or not
+///
+QXmppTask<SendResult> QXmppJingleMessageInitiation::retract(std::optional<QXmppJingleReason> reason, bool containsTieBreak)
+{
+ JmiElement jmiElement;
+ jmiElement.setType(JmiType::Retract);
+
+ if (!reason) {
+ reason = QXmppJingleReason();
+ reason->setType(QXmppJingleReason::Cancel);
+ reason->setText(QStringLiteral("Retracted"));
+ }
+
+ jmiElement.setReason(reason);
+ jmiElement.setContainsTieBreak(containsTieBreak);
+
+ return d->request(std::move(jmiElement));
+}
+
+///
+/// Creates a JMI element of type "finish" and sends a request containing the element.
+/// The default reason type/tag will be "success" with text "Success".
+///
+/// \param reason Reason object for finish element
+/// \param migratedTo JMI id the session has been migrated to
+///
+QXmppTask<SendResult> QXmppJingleMessageInitiation::finish(std::optional<QXmppJingleReason> reason, const QString &migratedTo)
+{
+ JmiElement jmiElement;
+ jmiElement.setType(JmiType::Finish);
+
+ if (!reason) {
+ reason = QXmppJingleReason();
+ reason->setType(QXmppJingleReason::Success);
+ reason->setText(QStringLiteral("Success"));
+ }
+
+ jmiElement.setReason(reason);
+ jmiElement.setMigratedTo(migratedTo);
+
+ return d->request(std::move(jmiElement));
+}
+
+///
+/// Returns the JMI ID.
+///
+QString QXmppJingleMessageInitiation::id() const
+{
+ return d->id;
+}
+
+///
+/// Sets the JMI ID.
+///
+void QXmppJingleMessageInitiation::setId(const QString &id)
+{
+ d->id = id;
+}
+
+///
+/// Sets the call partner's bare JID.
+///
+/// Normally, the JMI ID would be sufficient in order to differentiate the JMIs.
+/// However, attackers pretending to be the call partner can be mitigated by caching the call
+/// partner's JID.
+///
+/// \param callPartnerJid bare JID of the call partner
+///
+void QXmppJingleMessageInitiation::setCallPartnerJid(const QString &callPartnerJid)
+{
+ d->callPartnerJid = callPartnerJid;
+}
+
+///
+/// Returns the call partner's bare JID.
+///
+/// \return the call partner's bare JID.
+///
+QString QXmppJingleMessageInitiation::callPartnerJid() const
+{
+ return d->callPartnerJid;
+}
+
+///
+/// Returns the "isProceeded" flag, e.g., if the Jingle Message Initiation has already been
+/// proceeded.
+///
+bool QXmppJingleMessageInitiation::isProceeded() const
+{
+ return d->isProceeded;
+}
+
+///
+/// Sets the "isProceeded" flag, e.g., if the Jingle Message Initiation has already been
+/// proceeded.
+///
+void QXmppJingleMessageInitiation::setIsProceeded(bool isProceeded)
+{
+ d->isProceeded = isProceeded;
+}
+
+///
+/// \fn QXmppJingleMessageInitiation::ringing()
+///
+/// Emitted when a propose request was accepted and the device starts ringing.
+///
+
+///
+/// \fn QXmppJingleMessageInitiation::proceeded(const QString &, const QString &)
+///
+/// Emitted when a propose request was successfully processed and accepted.
+///
+/// \param id belonging JMI id
+/// \param callPartnerResource resource of the call partner about to be called
+///
+
+///
+/// \fn QXmppJingleMessageInitiation::closed(const Result &)
+///
+/// Emitted when a call was ended either through rejection, retraction, finish or an error.
+///
+/// \param result close reason
+///
+
+class QXmppJingleMessageInitiationManagerPrivate
+{
+public:
+ QVector<std::shared_ptr<Jmi>> jmis;
+};
+
+///
+/// \typedef QXmppJingleMessageInitiationManager::ProposeResult
+///
+/// Contains JMI object or an error if sending the propose message failed.
+///
+
+///
+/// \class QXmppJingleMessageInitiationManager
+///
+/// \brief The QXmppJingleMessageInitiationManager class makes it possible to retrieve
+/// Jingle Message Initiation elements as defined by \xep{0353, Jingle Message Initiation}.
+///
+/// \since QXmpp 1.6
+///
+QXmppJingleMessageInitiationManager::QXmppJingleMessageInitiationManager()
+ : d(std::make_unique<QXmppJingleMessageInitiationManagerPrivate>())
+{
+}
+
+QXmppJingleMessageInitiationManager::~QXmppJingleMessageInitiationManager() = default;
+
+/// \cond
+QStringList QXmppJingleMessageInitiationManager::discoveryFeatures() const
+{
+ return { ns_jingle_message_initiation };
+}
+/// \endcond
+
+///
+/// Creates a proposal JMI element and passes it as a message.
+///
+QXmppTask<QXmppJingleMessageInitiationManager::ProposeResult> QXmppJingleMessageInitiationManager::propose(const QString &callPartnerJid, const QXmppJingleDescription &description)
+{
+ QXmppPromise<ProposeResult> promise;
+
+ QXmppJingleMessageInitiationElement jmiElement;
+ jmiElement.setType(JmiType::Propose);
+ jmiElement.setId(QXmppUtils::generateStanzaUuid());
+ jmiElement.setDescription(description);
+
+ sendMessage(jmiElement, callPartnerJid).then(this, [this, promise, callPartnerJid](SendResult result) mutable {
+ if (auto error = std::get_if<QXmppError>(&result)) {
+ warning(u"Error sending Jingle Message Initiation proposal: " % error->description);
+ promise.finish(*error);
+ } else {
+ promise.finish(addJmi(callPartnerJid));
+ }
+ });
+
+ return promise.task();
+}
+
+/// \cond
+bool QXmppJingleMessageInitiationManager::handleMessage(const QXmppMessage &message)
+{
+ // JMI messages must be of type "chat" and contain a <store/> hint.
+ if (message.type() != QXmppMessage::Chat || !message.hasHint(QXmppMessage::Store)) {
+ return false;
+ }
+
+ // Only continue if the message contains a JMI element.
+ if (auto jmiElement = message.jingleMessageInitiationElement()) {
+ return handleJmiElement(std::move(*jmiElement), message.from());
+ }
+
+ return false;
+}
+
+void QXmppJingleMessageInitiationManager::setClient(QXmppClient *client)
+{
+ QXmppClientExtension::setClient(client);
+}
+/// \endcond
+
+///
+/// Lets the client send a message to user with given callPartnerJid containing the JMI element.
+///
+/// \param jmiElement the JMI element to be passed
+/// \param callPartnerJid bare JID of the call partner
+///
+QXmppTask<SendResult> QXmppJingleMessageInitiationManager::sendMessage(const QXmppJingleMessageInitiationElement &jmiElement, const QString &callPartnerJid)
+{
+ QXmppMessage message;
+ message.setTo(callPartnerJid);
+ message.addHint(QXmppMessage::Store);
+ message.setJingleMessageInitiationElement(jmiElement);
+
+ return client()->send(std::move(message));
+}
+
+///
+/// Removes a JMI object from the JMIs vector.
+///
+/// \param jmi object to be removed
+///
+void QXmppJingleMessageInitiationManager::clear(const std::shared_ptr<QXmppJingleMessageInitiation> &jmi)
+{
+ d->jmis.erase(
+ std::remove_if(
+ d->jmis.begin(),
+ d->jmis.end(),
+ [&jmi](const auto &storedJmi) {
+ return jmi->id() == storedJmi->id() && jmi->callPartnerJid() == storedJmi->callPartnerJid();
+ }),
+ d->jmis.end());
+}
+
+///
+/// Removes all JMI objects from the JMI vector.
+///
+void QXmppJingleMessageInitiationManager::clearAll()
+{
+ d->jmis.clear();
+}
+
+bool QXmppJingleMessageInitiationManager::handleJmiElement(QXmppJingleMessageInitiationElement &&jmiElement, const QString &senderJid)
+{
+ auto jmiElementId = jmiElement.id();
+ auto callPartnerJid = QXmppUtils::jidToBareJid(senderJid);
+
+ // Check if there's already a JMI object with jmiElementId and callPartnerJid in JMIs vector.
+ // That means that a JMI has already been created with given (J)IDs.
+ auto itr = std::find_if(d->jmis.begin(), d->jmis.end(), [&jmiElementId, &callPartnerJid](const auto &jmi) {
+ return jmi->id() == jmiElementId && jmi->callPartnerJid() == callPartnerJid;
+ });
+
+ if (itr != d->jmis.end()) {
+ return handleExistingJmi(*itr, jmiElement, QXmppUtils::jidToResource(senderJid));
+ }
+
+ if (jmiElement.type() == JmiType::Propose) {
+ return handleProposeJmiElement(jmiElement, callPartnerJid);
+ }
+
+ return false;
+}
+
+///
+/// Handles a JMI object which already exists in the JMIs vector.
+///
+/// \param existingJmi JMI object to be handled
+/// \param jmiElement JMI element to be processed with the JMI object
+/// \param callPartnerResource resource of the call partner (i.e., phone, tablet etc.)
+/// \return success (true) or failure
+///
+bool QXmppJingleMessageInitiationManager::handleExistingJmi(const std::shared_ptr<Jmi> &existingJmi, const QXmppJingleMessageInitiationElement &jmiElement, const QString &callPartnerResource)
+{
+ switch (jmiElement.type()) {
+ case JmiType::Ringing:
+ Q_EMIT existingJmi->ringing();
+ return true;
+ case JmiType::Proceed:
+ Q_EMIT existingJmi->proceeded(jmiElement.id(), callPartnerResource);
+ existingJmi->setIsProceeded(true);
+ return true;
+ case JmiType::Reject:
+ Q_EMIT existingJmi->closed(
+ Jmi::Rejected { jmiElement.reason(), jmiElement.containsTieBreak() });
+ return true;
+ case JmiType::Retract:
+ Q_EMIT existingJmi->closed(
+ Jmi::Retracted { jmiElement.reason(), jmiElement.containsTieBreak() });
+ return true;
+ case JmiType::Finish:
+ existingJmi->finish(jmiElement.reason(), jmiElement.migratedTo());
+ Q_EMIT existingJmi->closed(
+ Jmi::Finished { jmiElement.reason(), jmiElement.migratedTo() });
+ return true;
+ default:
+ return false;
+ }
+}
+
+///
+/// Handles a propose JMI element.
+///
+/// \param jmiElement to be handled
+/// \param callPartnerJid bare JID of the call partner
+/// \return success (true) or failure
+///
+bool QXmppJingleMessageInitiationManager::handleProposeJmiElement(const QXmppJingleMessageInitiationElement &jmiElement, const QString &callPartnerJid)
+{
+ // Check if there's already a JMI object with provided callPartnerJid in JMIs vector.
+ // That means that a propose has already been sent.
+ auto itr = std::find_if(
+ d->jmis.cbegin(),
+ d->jmis.cend(),
+ [&callPartnerJid](const auto &jmi) {
+ return jmi->callPartnerJid() == callPartnerJid;
+ });
+
+ // Tie break case or usual JMI proposal?
+ if (itr != d->jmis.end()) {
+ return handleTieBreak(*itr, jmiElement, callPartnerJid);
+ }
+
+ Q_EMIT proposed(addJmi(callPartnerJid), jmiElement.id(), jmiElement.description());
+ return true;
+}
+
+///
+/// Handles a tie break case as defined in https://xmpp.org/extensions/xep-0353.html#tie-breaking.
+/// \param existingJmi existing JMI object to be handled
+/// \param jmiElement JMI element to be processed with existing JMI object
+/// \param callPartnerResource resource of the call partner (i.e., phone, tablet etc.)
+/// \return success (true) or failure
+///
+bool QXmppJingleMessageInitiationManager::handleTieBreak(const std::shared_ptr<Jmi> &existingJmi, const QXmppJingleMessageInitiationElement &jmiElement, const QString &callPartnerResource)
+{
+ // Tie break -> session is set to be expired
+ QXmppJingleReason reason;
+ reason.setType(QXmppJingleReason::Expired);
+
+ // Existing (proceeded) or non-existing session?
+ if (existingJmi->isProceeded()) {
+ return handleExistingSession(existingJmi, jmiElement.id());
+ }
+
+ // Tie break in propose state (no existing session) - two parties try calling each other
+ // at the same time, the proposal with the lower ID overrules the other one.
+ return handleNonExistingSession(existingJmi, jmiElement.id(), callPartnerResource);
+}
+
+///
+/// Device switch: session already exists and will be migrated to new device with id jmiElementId.
+///
+/// \param existingJmi Current JMI object
+/// \param jmiElementId New (counterpart's) session JMI element ID
+///
+bool QXmppJingleMessageInitiationManager::handleExistingSession(const std::shared_ptr<Jmi> &existingJmi, const QString &jmiElementId)
+{
+ // Old session will be finished with reason "Expired".
+ QXmppJingleReason reason;
+ reason.setType(QXmppJingleReason::Expired);
+ reason.setText(QStringLiteral("Session migrated"));
+
+ // Tell the old session to be finished.
+ Q_EMIT existingJmi->closed(Jmi::Finished { reason, jmiElementId });
+
+ existingJmi->finish(reason, jmiElementId).then(this, [this, existingJmi, jmiElementId](SendResult result) {
+ if (auto *error = std::get_if<QXmppError>(&result)) {
+ Q_EMIT existingJmi->closed(*error);
+ } else {
+ // Then, proceed (accept) the new proposal and set the JMI ID
+ // to the ID of the received JMI element.
+ existingJmi->setId(jmiElementId);
+ existingJmi->proceed().then(this, [existingJmi](SendResult result) {
+ if (auto *error = std::get_if<QXmppError>(&result)) {
+ Q_EMIT existingJmi->closed(*error);
+ } else {
+ // The session is now closed as it is finished.
+ existingJmi->setIsProceeded(true);
+ }
+ });
+ }
+ });
+
+ return true;
+}
+
+///
+/// \brief Tie break in propose state (no existing session) - two parties try calling each other
+/// at the same time, the proposal with the lower ID overrules the other one.
+///
+/// \param existingJmi Current JMI object
+/// \param jmiElementId Counterpart's JMI element ID
+///
+bool QXmppJingleMessageInitiationManager::handleNonExistingSession(const std::shared_ptr<Jmi> &existingJmi, const QString &jmiElementId, const QString &callPartnerResource)
+{
+ QXmppJingleReason reason;
+ reason.setType(QXmppJingleReason::Expired);
+ reason.setText(QStringLiteral("Tie-Break"));
+
+ if (QUuid::fromString(existingJmi->id()) < QUuid::fromString(jmiElementId)) {
+ // Jingle message initiator with lower ID rejects the other proposal.
+ existingJmi->setId(jmiElementId);
+ existingJmi->reject(std::move(reason), true).then(this, [existingJmi](auto result) {
+ if (auto *error = std::get_if<QXmppError>(&result)) {
+ Q_EMIT existingJmi->closed(*error);
+ }
+ });
+ } else {
+ // Jingle message initiator with higher ID retracts its proposal.
+ existingJmi->retract(std::move(reason), true).then(this, [this, existingJmi, jmiElementId, callPartnerResource](auto result) {
+ if (auto error = std::get_if<QXmppError>(&result)) {
+ Q_EMIT existingJmi->closed(*error);
+ } else {
+ // Afterwards, JMI ID is changed to lower ID.
+ existingJmi->setId(jmiElementId);
+
+ // Finally, the call is being accepted.
+ existingJmi->proceed().then(this, [existingJmi, jmiElementId, callPartnerResource](SendResult result) {
+ if (auto *error = std::get_if<QXmppError>(&result)) {
+ Q_EMIT existingJmi->closed(*error);
+ } else {
+ existingJmi->setIsProceeded(true);
+ Q_EMIT existingJmi->proceeded(jmiElementId, callPartnerResource);
+ }
+ });
+ }
+ });
+ }
+
+ return true;
+}
+
+///
+/// Adds a JMI object to the JMIs vector and sets the bare JID of the call partner in the JMI object.
+/// \param callPartnerJid bare JID of the call partner
+/// \return The newly created JMI
+///
+std::shared_ptr<QXmppJingleMessageInitiation> QXmppJingleMessageInitiationManager::addJmi(const QString &callPartnerJid)
+{
+ auto jmi { std::make_shared<QXmppJingleMessageInitiation>(this) };
+ jmi->setCallPartnerJid(callPartnerJid);
+ d->jmis.append(jmi);
+ return jmi;
+}
+
+///
+/// Returns the JMIs vector.
+///
+const QVector<std::shared_ptr<QXmppJingleMessageInitiation>> &QXmppJingleMessageInitiationManager::jmis() const
+{
+ return d->jmis;
+}
+
+///
+/// \fn QXmppJingleMessageInitiationManager::proposed(const std::shared_ptr<QXmppJingleMessageInitiation> &, const QString &, const QXmppJingleDescription &)
+///
+/// Emitted when a call has been proposed.
+///
+/// \param jmi Jingle Message Initiation object of proposed session
+/// \param id JMI element id
+/// \param description JMI element's description containing media type (i.e., audio, video)
+///
diff --git a/src/client/QXmppJingleMessageInitiationManager.h b/src/client/QXmppJingleMessageInitiationManager.h
new file mode 100644
index 00000000..cd5bb494
--- /dev/null
+++ b/src/client/QXmppJingleMessageInitiationManager.h
@@ -0,0 +1,126 @@
+// SPDX-FileCopyrightText: 2023 Tibor Csötönyi <work@taibsu.de>
+// SPDX-FileCopyrightText: 2023 Melvin Keskin <melvo@olomono.de>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifndef QXMPPJINGLEMESSAGEINITIATIONMANAGER_H
+#define QXMPPJINGLEMESSAGEINITIATIONMANAGER_H
+
+#include "QXmppClientExtension.h"
+#include "QXmppError.h"
+#include "QXmppJingleIq.h"
+#include "QXmppMessageHandler.h"
+#include "QXmppSendResult.h"
+#include "QXmppTask.h"
+
+class QXmppJingleMessageInitiationManager;
+class QXmppJingleMessageInitiationPrivate;
+class QXmppJingleMessageInitiationManagerPrivate;
+
+class QXMPP_EXPORT QXmppJingleMessageInitiation : public QObject
+{
+ Q_OBJECT
+public:
+ struct Rejected
+ {
+ std::optional<QXmppJingleReason> reason;
+ bool containsTieBreak;
+ };
+
+ struct Retracted
+ {
+ std::optional<QXmppJingleReason> reason;
+ bool containsTieBreak;
+ };
+
+ struct Finished
+ {
+ std::optional<QXmppJingleReason> reason;
+ QString migratedTo;
+ };
+
+ /// Variant of Rejected, Retracted, Finished or Error result types
+ using Result = std::variant<Rejected, Retracted, Finished, QXmppError>;
+
+ QXmppJingleMessageInitiation(QXmppJingleMessageInitiationManager *manager);
+ ~QXmppJingleMessageInitiation();
+
+ QXmppTask<QXmpp::SendResult> ring();
+ QXmppTask<QXmpp::SendResult> proceed();
+ QXmppTask<QXmpp::SendResult> reject(std::optional<QXmppJingleReason> reason, bool containsTieBreak = false);
+ QXmppTask<QXmpp::SendResult> retract(std::optional<QXmppJingleReason> reason, bool containsTieBreak = false);
+ QXmppTask<QXmpp::SendResult> finish(std::optional<QXmppJingleReason> reason, const QString &migratedTo = {});
+
+ Q_SIGNAL void ringing();
+ Q_SIGNAL void proceeded(const QString &id, const QString &callPartnerResource);
+ Q_SIGNAL void closed(const Result &result);
+
+private:
+ QString id() const;
+ void setId(const QString &id);
+ void setCallPartnerJid(const QString &callPartnerJid);
+ QString callPartnerJid() const;
+ bool isProceeded() const;
+ void setIsProceeded(bool isProceeded);
+
+ std::unique_ptr<QXmppJingleMessageInitiationPrivate> d;
+
+ friend class QXmppJingleMessageInitiationManager;
+ friend class tst_QXmppJingleMessageInitiationManager;
+};
+
+class QXMPP_EXPORT QXmppJingleMessageInitiationManager : public QXmppClientExtension, public QXmppMessageHandler
+{
+ Q_OBJECT
+public:
+ using ProposeResult = std::variant<std::shared_ptr<QXmppJingleMessageInitiation>, QXmppError>;
+
+ QXmppJingleMessageInitiationManager();
+ ~QXmppJingleMessageInitiationManager();
+
+ /// \cond
+ QStringList discoveryFeatures() const override;
+ /// \endcond
+
+ QXmppTask<ProposeResult> propose(
+ const QString &callPartnerJid,
+ const QXmppJingleDescription &description);
+
+ Q_SIGNAL void proposed(
+ const std::shared_ptr<QXmppJingleMessageInitiation> &jmi,
+ const QString &id,
+ const std::optional<QXmppJingleDescription> &description);
+
+protected:
+ /// \cond
+ bool handleMessage(const QXmppMessage &) override;
+ void setClient(QXmppClient *client) override;
+ /// \endcond
+
+private:
+ QXmppTask<QXmpp::SendResult> sendMessage(
+ const QXmppJingleMessageInitiationElement &jmiElement,
+ const QString &callPartnerJid);
+
+ void clear(const std::shared_ptr<QXmppJingleMessageInitiation> &jmi);
+ void clearAll();
+
+ bool handleJmiElement(QXmppJingleMessageInitiationElement &&jmiElement, const QString &senderJid);
+ bool handleExistingJmi(const std::shared_ptr<QXmppJingleMessageInitiation> &existingJmi, const QXmppJingleMessageInitiationElement &jmiElement, const QString &callPartnerResource);
+ bool handleProposeJmiElement(const QXmppJingleMessageInitiationElement &jmiElement, const QString &callPartnerJid);
+ bool handleTieBreak(const std::shared_ptr<QXmppJingleMessageInitiation> &existingJmi, const QXmppJingleMessageInitiationElement &jmiElement, const QString &callPartnerResource);
+ bool handleExistingSession(const std::shared_ptr<QXmppJingleMessageInitiation> &existingJmi, const QString &jmiElementId);
+ bool handleNonExistingSession(const std::shared_ptr<QXmppJingleMessageInitiation> &existingJmi, const QString &jmiElementId, const QString &callPartnerResource);
+ std::shared_ptr<QXmppJingleMessageInitiation> addJmi(const QString &callPartnerJid);
+ const QVector<std::shared_ptr<QXmppJingleMessageInitiation>> &jmis() const;
+
+private:
+ std::unique_ptr<QXmppJingleMessageInitiationManagerPrivate> d;
+
+ friend class QXmppJingleMessageInitiationPrivate;
+ friend class tst_QXmppJingleMessageInitiationManager;
+};
+
+Q_DECLARE_METATYPE(QXmppJingleMessageInitiation::Result)
+
+#endif // QXMPPJINGLEMESSAGEINITIATIONMANAGER_H