Add XEP-0353: Jingle Message Initiation manager
This commit is contained in:
parent
2fde987d39
commit
a4dcd90685
|
@ -489,6 +489,14 @@ SPDX-License-Identifier: CC0-1.0
|
|||
<xmpp:since>1.0</xmpp:since>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource='https://xmpp.org/extensions/xep-0353.html'/>
|
||||
<xmpp:status>complete</xmpp:status>
|
||||
<xmpp:version>0.6</xmpp:version>
|
||||
<xmpp:since>1.6</xmpp:since>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource='https://xmpp.org/extensions/xep-0357.html'/>
|
||||
|
|
|
@ -108,6 +108,7 @@ set(INSTALL_HEADER_FILES
|
|||
client/QXmppHttpUploadManager.h
|
||||
client/QXmppInvokable.h
|
||||
client/QXmppIqHandling.h
|
||||
client/QXmppJingleMessageInitiationManager.h
|
||||
client/QXmppMamManager.h
|
||||
client/QXmppMessageHandler.h
|
||||
client/QXmppMessageReceiptManager.h
|
||||
|
@ -243,6 +244,7 @@ set(SOURCE_FILES
|
|||
client/QXmppInternalClientExtension.cpp
|
||||
client/QXmppInvokable.cpp
|
||||
client/QXmppIqHandling.cpp
|
||||
client/QXmppJingleMessageInitiationManager.cpp
|
||||
client/QXmppMamManager.cpp
|
||||
client/QXmppMessageReceiptManager.cpp
|
||||
client/QXmppMucManager.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)
|
||||
///
|
|
@ -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
|
|
@ -42,6 +42,7 @@ add_simple_test(qxmpphttpuploadiq)
|
|||
add_simple_test(qxmppiceconnection)
|
||||
add_simple_test(qxmppiq)
|
||||
add_simple_test(qxmppjingleiq)
|
||||
add_simple_test(qxmppjinglemessageinitiationmanager)
|
||||
add_simple_test(qxmppmammanager)
|
||||
add_simple_test(qxmppmixinvitation)
|
||||
add_simple_test(qxmppmixitems)
|
||||
|
|
|
@ -0,0 +1,924 @@
|
|||
// SPDX-FileCopyrightText: 2023 Tibor Csötönyi <work@taibsu.de>
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "QXmppClient.h"
|
||||
#include "QXmppConstants_p.h"
|
||||
#include "QXmppJingleMessageInitiationManager.h"
|
||||
#include "QXmppMessage.h"
|
||||
#include "QXmppUtils.h"
|
||||
|
||||
#include "IntegrationTesting.h"
|
||||
#include "util.h"
|
||||
#include <QTest>
|
||||
|
||||
using Jmi = QXmppJingleMessageInitiation;
|
||||
using JmiType = QXmppJingleMessageInitiationElement::Type;
|
||||
using Result = QXmppJingleMessageInitiation::Result;
|
||||
|
||||
class tst_QXmppJingleMessageInitiationManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Q_SLOT void initTestCase();
|
||||
|
||||
Q_SLOT void testClear();
|
||||
Q_SLOT void testClearAll();
|
||||
|
||||
Q_SLOT void testRing();
|
||||
Q_SLOT void testProceed();
|
||||
Q_SLOT void testReject();
|
||||
Q_SLOT void testRetract();
|
||||
Q_SLOT void testFinish();
|
||||
|
||||
Q_SLOT void testPropose();
|
||||
Q_SLOT void testSendMessage();
|
||||
Q_SLOT void testHandleNonExistingSessionLowerId();
|
||||
Q_SLOT void testHandleNonExistingSessionHigherId();
|
||||
Q_SLOT void testHandleExistingSession();
|
||||
Q_SLOT void testHandleTieBreak();
|
||||
Q_SLOT void testHandleProposeJmiElement();
|
||||
Q_SLOT void testHandleExistingJmi();
|
||||
Q_SLOT void testHandleJmiElement();
|
||||
Q_SLOT void testHandleMessage_data();
|
||||
Q_SLOT void testHandleMessage();
|
||||
Q_SLOT void testHandleMessageRinging();
|
||||
Q_SLOT void testHandleMessageProceeded();
|
||||
Q_SLOT void testHandleMessageClosedRejected();
|
||||
Q_SLOT void testHandleMessageClosedRetracted();
|
||||
Q_SLOT void testHandleMessageClosedFinished();
|
||||
|
||||
QXmppClient m_client;
|
||||
QXmppLogger m_logger;
|
||||
QXmppJingleMessageInitiationManager m_manager;
|
||||
};
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::initTestCase()
|
||||
{
|
||||
m_client.addExtension(&m_manager);
|
||||
|
||||
m_logger.setLoggingType(QXmppLogger::SignalLogging);
|
||||
m_client.setLogger(&m_logger);
|
||||
|
||||
m_client.connectToServer(IntegrationTests::clientConfiguration());
|
||||
|
||||
qRegisterMetaType<QXmppJingleMessageInitiation::Result>();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testClear()
|
||||
{
|
||||
QCOMPARE(m_manager.jmis().size(), 0);
|
||||
auto jmi1 { m_manager.addJmi("test1") };
|
||||
auto jmi2 { m_manager.addJmi("test2") };
|
||||
QCOMPARE(m_manager.jmis().size(), 2);
|
||||
|
||||
m_manager.clear(jmi1);
|
||||
m_manager.clear(jmi2);
|
||||
QCOMPARE(m_manager.jmis().size(), 0);
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testClearAll()
|
||||
{
|
||||
QCOMPARE(m_manager.jmis().size(), 0);
|
||||
m_manager.addJmi("test1");
|
||||
m_manager.addJmi("test2");
|
||||
m_manager.addJmi("test3");
|
||||
m_manager.addJmi("test4");
|
||||
m_manager.addJmi("test5");
|
||||
QCOMPARE(m_manager.jmis().size(), 5);
|
||||
|
||||
m_manager.clearAll();
|
||||
QCOMPARE(m_manager.jmis().size(), 0);
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testRing()
|
||||
{
|
||||
auto jmi { m_manager.addJmi("julietRing@capulet.example") };
|
||||
jmi->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
connect(&m_logger, &QXmppLogger::message, this, [jmicallPartnerJid = jmi->callPartnerJid()](QXmppLogger::MessageType type, const QString &text) {
|
||||
if (type == QXmppLogger::SentMessage) {
|
||||
QXmppMessage message;
|
||||
parsePacket(message, text.toUtf8());
|
||||
|
||||
if (message.to() == jmicallPartnerJid) {
|
||||
QVERIFY(message.jingleMessageInitiationElement());
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->type(), JmiType::Ringing);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto future = jmi->ring();
|
||||
|
||||
while (!future.isFinished()) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
QVERIFY(future.isFinished());
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testProceed()
|
||||
{
|
||||
auto jmi { m_manager.addJmi("julietProceed@capulet.example") };
|
||||
jmi->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
connect(&m_logger, &QXmppLogger::message, this, [jmiCallPartnerJid = jmi->callPartnerJid()](QXmppLogger::MessageType type, const QString &text) {
|
||||
if (type == QXmppLogger::SentMessage) {
|
||||
QXmppMessage message;
|
||||
parsePacket(message, text.toUtf8());
|
||||
|
||||
if (message.to() == jmiCallPartnerJid) {
|
||||
QVERIFY(message.jingleMessageInitiationElement());
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->type(), JmiType::Proceed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto future = jmi->proceed();
|
||||
|
||||
while (!future.isFinished()) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
QVERIFY(future.isFinished());
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testReject()
|
||||
{
|
||||
auto jmi { m_manager.addJmi("julietReject@capulet.example") };
|
||||
jmi->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
QXmppJingleReason reason;
|
||||
reason.setType(QXmppJingleReason::Decline);
|
||||
reason.setText("Declined");
|
||||
reason.setNamespaceUri(ns_jingle);
|
||||
|
||||
connect(&m_logger, &QXmppLogger::message, this, [jmiCallPartnerJid = jmi->callPartnerJid()](QXmppLogger::MessageType type, const QString &text) {
|
||||
if (type == QXmppLogger::SentMessage) {
|
||||
QXmppMessage message;
|
||||
parsePacket(message, text.toUtf8());
|
||||
|
||||
if (message.to() == jmiCallPartnerJid) {
|
||||
QVERIFY(message.jingleMessageInitiationElement());
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->type(), JmiType::Reject);
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->reason()->type(), QXmppJingleReason::Decline);
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->reason()->text(), "Declined");
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->reason()->namespaceUri(), ns_jingle);
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->containsTieBreak(), true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto future = jmi->reject(reason, true);
|
||||
|
||||
while (!future.isFinished()) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
QVERIFY(future.isFinished());
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testRetract()
|
||||
{
|
||||
auto jmi { m_manager.addJmi("julietRetract@capulet.example") };
|
||||
jmi->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
QXmppJingleReason reason;
|
||||
reason.setType(QXmppJingleReason::Gone);
|
||||
reason.setText("Gone");
|
||||
reason.setNamespaceUri(ns_jingle);
|
||||
|
||||
connect(&m_logger, &QXmppLogger::message, this, [jmicallPartnerJid = jmi->callPartnerJid()](QXmppLogger::MessageType type, const QString &text) {
|
||||
if (type == QXmppLogger::SentMessage) {
|
||||
QXmppMessage message;
|
||||
parsePacket(message, text.toUtf8());
|
||||
|
||||
if (message.to() == jmicallPartnerJid) {
|
||||
QVERIFY(message.jingleMessageInitiationElement());
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->type(), JmiType::Retract);
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->reason()->type(), QXmppJingleReason::Gone);
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->reason()->text(), "Gone");
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->reason()->namespaceUri(), ns_jingle);
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->containsTieBreak(), true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto future = jmi->retract(reason, true);
|
||||
|
||||
while (!future.isFinished()) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
QVERIFY(future.isFinished());
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testFinish()
|
||||
{
|
||||
auto jmi { m_manager.addJmi("julietFinish@capulet.example") };
|
||||
jmi->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
QXmppJingleReason reason;
|
||||
reason.setType(QXmppJingleReason::Success);
|
||||
reason.setText("Finished");
|
||||
reason.setNamespaceUri(ns_jingle);
|
||||
|
||||
connect(&m_logger, &QXmppLogger::message, this, [jmicallPartnerJid = jmi->callPartnerJid()](QXmppLogger::MessageType type, const QString &text) {
|
||||
if (type == QXmppLogger::SentMessage) {
|
||||
QXmppMessage message;
|
||||
parsePacket(message, text.toUtf8());
|
||||
|
||||
if (message.to() == jmicallPartnerJid) {
|
||||
QVERIFY(message.jingleMessageInitiationElement());
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->type(), JmiType::Finish);
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->reason()->type(), QXmppJingleReason::Success);
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->reason()->text(), "Finished");
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->reason()->namespaceUri(), ns_jingle);
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->migratedTo(), "fecbea35-08d3-404f-9ec7-2b57c566fa74");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto future = jmi->finish(reason, "fecbea35-08d3-404f-9ec7-2b57c566fa74");
|
||||
|
||||
while (!future.isFinished()) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
QVERIFY(future.isFinished());
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testPropose()
|
||||
{
|
||||
QString jid { "julietPropose@capulet.example" };
|
||||
|
||||
QXmppJingleDescription description;
|
||||
description.setMedia(QStringLiteral("audio"));
|
||||
description.setSsrc(123);
|
||||
description.setType(ns_jingle_rtp);
|
||||
|
||||
connect(&m_logger, &QXmppLogger::message, this, [&, jid, description](QXmppLogger::MessageType type, const QString &text) {
|
||||
if (type == QXmppLogger::SentMessage) {
|
||||
QXmppMessage message;
|
||||
parsePacket(message, text.toUtf8());
|
||||
|
||||
if (message.to() == jid) {
|
||||
const auto &jmiElement { message.jingleMessageInitiationElement() };
|
||||
QVERIFY(jmiElement);
|
||||
|
||||
QCOMPARE(jmiElement->type(), JmiType::Propose);
|
||||
QVERIFY(!jmiElement->id().isEmpty());
|
||||
QVERIFY(jmiElement->description());
|
||||
QCOMPARE(jmiElement->description()->media(), description.media());
|
||||
QCOMPARE(jmiElement->description()->ssrc(), description.ssrc());
|
||||
QCOMPARE(jmiElement->description()->type(), description.type());
|
||||
|
||||
SKIP_IF_INTEGRATION_TESTS_DISABLED()
|
||||
|
||||
// verify that the JMI ID has been changed and the JMI was processed
|
||||
QCOMPARE(m_manager.jmis().size(), 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto future = m_manager.propose(jid, description);
|
||||
|
||||
while (!future.isFinished()) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
QVERIFY(future.isFinished());
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testSendMessage()
|
||||
{
|
||||
QString jid { "julietSendMessage@capulet.example" };
|
||||
|
||||
QXmppJingleMessageInitiationElement jmiElement;
|
||||
jmiElement.setType(JmiType::Propose);
|
||||
jmiElement.setId(QStringLiteral("fecbea35-08d3-404f-9ec7-2b57c566fa74"));
|
||||
|
||||
connect(&m_logger, &QXmppLogger::message, this, [jid, jmiElement](QXmppLogger::MessageType type, const QString &text) {
|
||||
if (type == QXmppLogger::SentMessage) {
|
||||
QXmppMessage message;
|
||||
parsePacket(message, text.toUtf8());
|
||||
|
||||
if (message.to() == jid) {
|
||||
QVERIFY(message.hasHint(QXmppMessage::Store));
|
||||
QVERIFY(message.jingleMessageInitiationElement());
|
||||
QCOMPARE(message.jingleMessageInitiationElement()->type(), jmiElement.type());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto future = m_manager.sendMessage(jmiElement, jid);
|
||||
|
||||
while (!future.isFinished()) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
QVERIFY(future.isFinished());
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleNonExistingSessionLowerId()
|
||||
{
|
||||
// --- request with lower id sends propose to request with higher id ---
|
||||
|
||||
QByteArray xmlProposeLowId {
|
||||
"<message from='romeoNonExistingSession@montague.example/low' to='juliet@capulet.example' type='chat'>"
|
||||
"<propose xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'>"
|
||||
"<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'/>"
|
||||
"</propose>"
|
||||
"<store xmlns=\"urn:xmpp:hints\"/>"
|
||||
"</message>"
|
||||
};
|
||||
|
||||
auto jmiWithHigherId { m_manager.addJmi("romeoNonExistingSession@montague.example") };
|
||||
jmiWithHigherId->setId("fecbea35-08d3-404f-9ec7-2b57c566fa74");
|
||||
|
||||
QXmppJingleReason reason;
|
||||
reason.setType(QXmppJingleReason::Expired);
|
||||
reason.setText("Tie-Break");
|
||||
reason.setNamespaceUri(ns_jingle);
|
||||
|
||||
// make sure that request with higher ID is being retracted
|
||||
connect(&m_logger, &QXmppLogger::message, this, [jmiWithHigherId, reason](QXmppLogger::MessageType type, const QString &text) {
|
||||
if (type == QXmppLogger::SentMessage) {
|
||||
QXmppMessage message;
|
||||
parsePacket(message, text.toUtf8());
|
||||
|
||||
if (message.to() == jmiWithHigherId->callPartnerJid()) {
|
||||
const auto &jmiElement { message.jingleMessageInitiationElement() };
|
||||
QVERIFY(jmiElement);
|
||||
|
||||
QCOMPARE(jmiElement->type(), JmiType::Retract);
|
||||
QCOMPARE(jmiElement->id(), "fecbea35-08d3-404f-9ec7-2b57c566fa74");
|
||||
QVERIFY(jmiElement->reason());
|
||||
QCOMPARE(jmiElement->reason()->type(), reason.type());
|
||||
QCOMPARE(jmiElement->reason()->text(), reason.text());
|
||||
QCOMPARE(jmiElement->reason()->namespaceUri(), reason.namespaceUri());
|
||||
|
||||
SKIP_IF_INTEGRATION_TESTS_DISABLED()
|
||||
|
||||
// verify that the JMI ID has been changed and the JMI was processed
|
||||
QCOMPARE(jmiWithHigherId->id(), "ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
QVERIFY(jmiWithHigherId->isProceeded());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
QXmppMessage message;
|
||||
message.parse(xmlToDom(xmlProposeLowId));
|
||||
|
||||
QVERIFY(m_manager.handleMessage(message));
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleNonExistingSessionHigherId()
|
||||
{
|
||||
// --- request with higher id sends propose to request with lower id ---
|
||||
QByteArray xmlProposeHighId {
|
||||
"<message from='julietNonExistingSession@capulet.example/high' to='romeo@montague.example' type='chat'>"
|
||||
"<propose xmlns='urn:xmpp:jingle-message:0' id='fecbea35-08d3-404f-9ec7-2b57c566fa74'>"
|
||||
"<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'/>"
|
||||
"</propose>"
|
||||
"<store xmlns=\"urn:xmpp:hints\"/>"
|
||||
"</message>"
|
||||
};
|
||||
|
||||
QXmppJingleReason reason;
|
||||
reason.setType(QXmppJingleReason::Expired);
|
||||
reason.setText("Tie-Break");
|
||||
reason.setNamespaceUri(ns_jingle);
|
||||
|
||||
auto jmiWithLowerId { m_manager.addJmi("julietNonExistingSession@capulet.example") };
|
||||
jmiWithLowerId->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
// make sure that request with lower id rejects request with higher id
|
||||
connect(&m_logger, &QXmppLogger::message, this, [jid = jmiWithLowerId->callPartnerJid(), reason](QXmppLogger::MessageType type, const QString &text) {
|
||||
if (type == QXmppLogger::SentMessage) {
|
||||
QXmppMessage message;
|
||||
parsePacket(message, text.toUtf8());
|
||||
|
||||
if (message.to() == jid) {
|
||||
const auto &jmiElement { message.jingleMessageInitiationElement() };
|
||||
QVERIFY(jmiElement);
|
||||
|
||||
QCOMPARE(jmiElement->type(), JmiType::Reject);
|
||||
QCOMPARE(jmiElement->id(), "fecbea35-08d3-404f-9ec7-2b57c566fa74");
|
||||
QVERIFY(jmiElement->reason());
|
||||
QCOMPARE(jmiElement->reason()->type(), reason.type());
|
||||
QCOMPARE(jmiElement->reason()->text(), reason.text());
|
||||
QCOMPARE(jmiElement->reason()->namespaceUri(), reason.namespaceUri());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
QXmppMessage message;
|
||||
message.parse(xmlToDom(xmlProposeHighId));
|
||||
|
||||
QVERIFY(m_manager.handleMessage(message));
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleExistingSession()
|
||||
{
|
||||
QXmppMessage message;
|
||||
|
||||
QByteArray xmlPropose {
|
||||
"<message from='julietExistingSession@capulet.example/tablet' to='romeo@montague.example' type='chat'>"
|
||||
"<propose xmlns='urn:xmpp:jingle-message:0' id='989a46a6-f202-4910-a7c3-83c6ba3f3947'>"
|
||||
"<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'/>"
|
||||
"</propose>"
|
||||
"<store xmlns=\"urn:xmpp:hints\"/>"
|
||||
"</message>"
|
||||
};
|
||||
|
||||
auto jmi { m_manager.addJmi("julietExistingSession@capulet.example") };
|
||||
jmi->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
jmi->setIsProceeded(true);
|
||||
|
||||
QXmppJingleReason reason;
|
||||
reason.setType(QXmppJingleReason::Expired);
|
||||
reason.setText("Session migrated");
|
||||
reason.setNamespaceUri(ns_jingle);
|
||||
|
||||
connect(&m_logger, &QXmppLogger::message, this, [jmi, reason](QXmppLogger::MessageType type, const QString &text) {
|
||||
if (type == QXmppLogger::SentMessage) {
|
||||
QXmppMessage message;
|
||||
parsePacket(message, text.toUtf8());
|
||||
|
||||
if (message.to() == jmi->callPartnerJid()) {
|
||||
const auto &jmiElement { message.jingleMessageInitiationElement() };
|
||||
QVERIFY(jmiElement);
|
||||
|
||||
QCOMPARE(jmiElement->type(), JmiType::Finish);
|
||||
QCOMPARE(jmiElement->id(), jmi->id());
|
||||
QCOMPARE(jmiElement->migratedTo(), "989a46a6-f202-4910-a7c3-83c6ba3f3947");
|
||||
QVERIFY(jmiElement->reason());
|
||||
QCOMPARE(jmiElement->reason()->type(), reason.type());
|
||||
QCOMPARE(jmiElement->reason()->text(), reason.text());
|
||||
QCOMPARE(jmiElement->reason()->namespaceUri(), reason.namespaceUri());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
message.parse(xmlToDom(xmlPropose));
|
||||
|
||||
QVERIFY(m_manager.handleMessage(message));
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleTieBreak()
|
||||
{
|
||||
QString callPartnerJid { "romeoHandleTieBreakExistingSession@montague.example/orchard" };
|
||||
QString jmiId { "ca3cf894-5325-482f-a412-a6e9f832298d" };
|
||||
auto jmi { m_manager.addJmi(QXmppUtils::jidToBareJid(callPartnerJid)) };
|
||||
jmi->setId(jmiId);
|
||||
|
||||
QXmppJingleMessageInitiationElement jmiElement;
|
||||
QString newJmiId("989a46a6-f202-4910-a7c3-83c6ba3f3947");
|
||||
jmiElement.setId(newJmiId);
|
||||
|
||||
// Cannot use macro SKIP_IF_INTEGRATION_TESTS_DISABLED() here since
|
||||
// this would also skip the manager cleanup.
|
||||
if (IntegrationTests::enabled()) {
|
||||
// --- ensure handleExistingSession ---
|
||||
jmi->setIsProceeded(true);
|
||||
QSignalSpy closedSpy(jmi.get(), &QXmppJingleMessageInitiation::closed);
|
||||
QVERIFY(m_manager.handleTieBreak(jmi, jmiElement, QXmppUtils::jidToResource(callPartnerJid)));
|
||||
QCOMPARE(closedSpy.count(), 1);
|
||||
|
||||
// --- ensure handleNonExistingSession ---
|
||||
jmi->setIsProceeded(false);
|
||||
QSignalSpy proceededSpy(jmi.get(), &QXmppJingleMessageInitiation::proceeded);
|
||||
QVERIFY(m_manager.handleTieBreak(jmi, jmiElement, QXmppUtils::jidToResource(callPartnerJid)));
|
||||
QCOMPARE(proceededSpy.count(), 1);
|
||||
}
|
||||
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleProposeJmiElement()
|
||||
{
|
||||
QXmppJingleMessageInitiationElement jmiElement;
|
||||
|
||||
QXmppJingleDescription description;
|
||||
description.setMedia("audio");
|
||||
description.setSsrc(321);
|
||||
description.setType("abcd");
|
||||
|
||||
jmiElement.setId("ca3cf123-5325-482f-a412-a6e9f832298d");
|
||||
jmiElement.setDescription(description);
|
||||
|
||||
QString callPartnerJid { "juliet@capulet.example" };
|
||||
|
||||
// --- Tie break ---
|
||||
|
||||
auto jmi { m_manager.addJmi(callPartnerJid) };
|
||||
jmi->setId("989a4123-f202-4910-a7c3-83c6ba3f3947");
|
||||
|
||||
QVERIFY(m_manager.handleProposeJmiElement(jmiElement, callPartnerJid));
|
||||
QCOMPARE(m_manager.jmis().size(), 1);
|
||||
m_manager.clearAll();
|
||||
|
||||
// --- usual JMI proposal ---
|
||||
|
||||
connect(&m_manager, &QXmppJingleMessageInitiationManager::proposed, this, [&, jmiElement](const std::shared_ptr<Jmi> &, const QString &jmiElementId, const std::optional<QXmppJingleDescription> &description) {
|
||||
if (jmiElement.id() == jmiElementId) {
|
||||
QCOMPARE(m_manager.jmis().size(), 1);
|
||||
QVERIFY(description.has_value());
|
||||
QCOMPARE(description->media(), jmiElement.description()->media());
|
||||
QCOMPARE(description->ssrc(), jmiElement.description()->ssrc());
|
||||
QCOMPARE(description->type(), jmiElement.description()->type());
|
||||
}
|
||||
});
|
||||
|
||||
callPartnerJid = "romeoHandleProposeJmiElement@montague.example";
|
||||
|
||||
QVERIFY(m_manager.handleProposeJmiElement(jmiElement, callPartnerJid));
|
||||
QCOMPARE(m_manager.jmis().size(), 1);
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleExistingJmi()
|
||||
{
|
||||
QString callPartnerJid { "juliet@capulet.example" };
|
||||
QString jmiId { "989a46a6-f202-4910-a7c3-83c6ba3f3947" };
|
||||
|
||||
auto jmi { m_manager.addJmi(callPartnerJid) };
|
||||
jmi->setId(jmiId);
|
||||
|
||||
QXmppJingleMessageInitiationElement jmiElement;
|
||||
jmiElement.setId(jmiId);
|
||||
|
||||
// --- ringing ---
|
||||
|
||||
QSignalSpy ringingSpy(jmi.get(), &QXmppJingleMessageInitiation::ringing);
|
||||
|
||||
jmiElement.setType(JmiType::Ringing);
|
||||
|
||||
QVERIFY(m_manager.handleExistingJmi(jmi, jmiElement, callPartnerJid));
|
||||
QCOMPARE(ringingSpy.count(), 1);
|
||||
m_manager.clearAll();
|
||||
|
||||
// --- proceeded ---
|
||||
|
||||
jmi = m_manager.addJmi(callPartnerJid);
|
||||
jmi->setId(jmiId);
|
||||
|
||||
jmiElement.setType(JmiType::Proceed);
|
||||
connect(jmi.get(), &QXmppJingleMessageInitiation::proceeded, this, [jmiElement](const QString &jmiElementId) {
|
||||
if (jmiElementId == jmiElement.id()) {
|
||||
QVERIFY(true);
|
||||
}
|
||||
});
|
||||
|
||||
QVERIFY(m_manager.handleExistingJmi(jmi, jmiElement, callPartnerJid));
|
||||
m_manager.clearAll();
|
||||
|
||||
// --- closed: rejected ---
|
||||
|
||||
jmi = m_manager.addJmi(callPartnerJid);
|
||||
jmi->setId(jmiId);
|
||||
|
||||
QXmppJingleReason reason;
|
||||
reason.setType(QXmppJingleReason::Expired);
|
||||
reason.setText("Rejected because expired.");
|
||||
reason.setNamespaceUri(ns_jingle);
|
||||
|
||||
jmiElement.setType(JmiType::Reject);
|
||||
jmiElement.setReason(reason);
|
||||
|
||||
connect(jmi.get(), &QXmppJingleMessageInitiation::closed, this, [jmiElement](const Result &result) {
|
||||
using ResultType = QXmppJingleMessageInitiation::Rejected;
|
||||
|
||||
QVERIFY(std::holds_alternative<ResultType>(result));
|
||||
const ResultType &rejectedJmiElement { std::get<ResultType>(result) };
|
||||
|
||||
QVERIFY(rejectedJmiElement.reason);
|
||||
QCOMPARE(rejectedJmiElement.reason->type(), jmiElement.reason()->type());
|
||||
QCOMPARE(rejectedJmiElement.reason->text(), jmiElement.reason()->text());
|
||||
QCOMPARE(rejectedJmiElement.reason->namespaceUri(), jmiElement.reason()->namespaceUri());
|
||||
QCOMPARE(rejectedJmiElement.containsTieBreak, jmiElement.containsTieBreak());
|
||||
});
|
||||
|
||||
QVERIFY(m_manager.handleExistingJmi(jmi, jmiElement, callPartnerJid));
|
||||
m_manager.clearAll();
|
||||
|
||||
// --- closed: retracted ---
|
||||
|
||||
jmi = m_manager.addJmi(callPartnerJid);
|
||||
jmi->setId(jmiId);
|
||||
|
||||
reason.setType(QXmppJingleReason::ConnectivityError);
|
||||
reason.setText("Retracted due to connectivity error.");
|
||||
reason.setNamespaceUri(ns_jingle);
|
||||
|
||||
jmiElement.setType(JmiType::Retract);
|
||||
jmiElement.setReason(reason);
|
||||
|
||||
connect(jmi.get(), &QXmppJingleMessageInitiation::closed, this, [jmiElement](const Result &result) {
|
||||
using ResultType = QXmppJingleMessageInitiation::Retracted;
|
||||
|
||||
QVERIFY(std::holds_alternative<ResultType>(result));
|
||||
const ResultType &rejectedJmiElement { std::get<ResultType>(result) };
|
||||
|
||||
QVERIFY(rejectedJmiElement.reason);
|
||||
QCOMPARE(rejectedJmiElement.reason->type(), jmiElement.reason()->type());
|
||||
QCOMPARE(rejectedJmiElement.reason->text(), jmiElement.reason()->text());
|
||||
QCOMPARE(rejectedJmiElement.reason->namespaceUri(), jmiElement.reason()->namespaceUri());
|
||||
QCOMPARE(rejectedJmiElement.containsTieBreak, jmiElement.containsTieBreak());
|
||||
});
|
||||
|
||||
QVERIFY(m_manager.handleExistingJmi(jmi, jmiElement, callPartnerJid));
|
||||
m_manager.clearAll();
|
||||
|
||||
// --- closed: finished ---
|
||||
|
||||
jmi = m_manager.addJmi(callPartnerJid);
|
||||
jmi->setId(jmiId);
|
||||
|
||||
reason.setType(QXmppJingleReason::Success);
|
||||
reason.setText("Finished.");
|
||||
reason.setNamespaceUri(ns_jingle);
|
||||
|
||||
jmiElement.setType(JmiType::Finish);
|
||||
jmiElement.setReason(reason);
|
||||
jmiElement.setMigratedTo("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
connect(jmi.get(), &QXmppJingleMessageInitiation::closed, this, [jmiElement](const Result &result) {
|
||||
using ResultType = QXmppJingleMessageInitiation::Finished;
|
||||
|
||||
QVERIFY(std::holds_alternative<ResultType>(result));
|
||||
const ResultType &rejectedJmiElement { std::get<ResultType>(result) };
|
||||
|
||||
QVERIFY(rejectedJmiElement.reason);
|
||||
QCOMPARE(rejectedJmiElement.reason->type(), jmiElement.reason()->type());
|
||||
QCOMPARE(rejectedJmiElement.reason->text(), jmiElement.reason()->text());
|
||||
QCOMPARE(rejectedJmiElement.reason->namespaceUri(), jmiElement.reason()->namespaceUri());
|
||||
QCOMPARE(rejectedJmiElement.migratedTo, jmiElement.migratedTo());
|
||||
});
|
||||
|
||||
QVERIFY(m_manager.handleExistingJmi(jmi, jmiElement, callPartnerJid));
|
||||
m_manager.clearAll();
|
||||
|
||||
// --- none ---
|
||||
|
||||
jmi = m_manager.addJmi(callPartnerJid);
|
||||
jmi->setId(jmiId);
|
||||
|
||||
jmiElement.setType(JmiType::None);
|
||||
|
||||
QCOMPARE(m_manager.handleExistingJmi(jmi, jmiElement, callPartnerJid), false);
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleJmiElement()
|
||||
{
|
||||
QString callPartnerJid { "romeoHandleJmiElement@montague.example/orchard" };
|
||||
QString jmiId { "ca3cf894-5325-482f-a412-a6e9f832298d" };
|
||||
|
||||
// case 1: no JMI found in JMIs vector and jmiElement is not a propose element
|
||||
QXmppJingleMessageInitiationElement jmiElement;
|
||||
jmiElement.setType(JmiType::None);
|
||||
|
||||
QCOMPARE(m_manager.handleJmiElement(std::move(jmiElement), {}), false);
|
||||
|
||||
// case 2: no JMI found in JMIs vector and jmiElement is a propose element
|
||||
jmiElement = {};
|
||||
jmiElement.setType(JmiType::Propose);
|
||||
jmiElement.setId(jmiId);
|
||||
|
||||
QSignalSpy proposedSpy(&m_manager, &QXmppJingleMessageInitiationManager::proposed);
|
||||
QVERIFY(m_manager.handleJmiElement(std::move(jmiElement), callPartnerJid));
|
||||
QCOMPARE(proposedSpy.count(), 1);
|
||||
m_manager.clearAll();
|
||||
|
||||
// case 3: JMI found in JMIs vector, existing session
|
||||
jmiElement = {};
|
||||
jmiElement.setType(JmiType::Ringing);
|
||||
jmiElement.setId(jmiId);
|
||||
auto jmi { m_manager.addJmi(QXmppUtils::jidToBareJid(callPartnerJid)) };
|
||||
jmi->setId(jmiId);
|
||||
|
||||
QSignalSpy ringingSpy(jmi.get(), &QXmppJingleMessageInitiation::ringing);
|
||||
QVERIFY(m_manager.handleJmiElement(std::move(jmiElement), callPartnerJid));
|
||||
QCOMPARE(ringingSpy.count(), 1);
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleMessage_data()
|
||||
{
|
||||
QTest::addColumn<QByteArray>("xml");
|
||||
QTest::addColumn<bool>("isValid");
|
||||
|
||||
QTest::newRow("xmlValid")
|
||||
<< QByteArray(
|
||||
"<message to='julietHandleMessageValid@capulet.example' from='romeoHandleMessageValid@montague.example/orchard' type='chat'>"
|
||||
"<store xmlns=\"urn:xmpp:hints\"/>"
|
||||
"<propose xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'>"
|
||||
"<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'/>"
|
||||
"</propose>"
|
||||
"</message>")
|
||||
<< true;
|
||||
|
||||
QTest::newRow("xmlInvalidTypeNotChat")
|
||||
<< QByteArray(
|
||||
"<message to='julietHandleMessageNoChat@capulet.example' from='romeoHandleMessageNoChat@montague.example/orchard' type='normal'>"
|
||||
"<store xmlns=\"urn:xmpp:hints\"/>"
|
||||
"<propose xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'>"
|
||||
"<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'/>"
|
||||
"</propose>"
|
||||
"</message>")
|
||||
<< false;
|
||||
|
||||
QTest::newRow("xmlInvalidNoStore")
|
||||
<< QByteArray(
|
||||
"<message to='julietHandleMessageNoStore@capulet.example' from='romeoHandleMessageNoStore@montague.example/orchard' type='chat'>"
|
||||
"<propose xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'>"
|
||||
"<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'/>"
|
||||
"</propose>"
|
||||
"</message>")
|
||||
<< false;
|
||||
|
||||
QTest::newRow("xmlInvalidNoJmiElement")
|
||||
<< QByteArray("<message to='julietHandleMessageNoJmi@capulet.example' from='romeoHandleMessageNoJmi@montague.example/orchard' type='chat'/>")
|
||||
<< false;
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleMessage()
|
||||
{
|
||||
QFETCH(QByteArray, xml);
|
||||
QFETCH(bool, isValid);
|
||||
|
||||
QXmppMessage message;
|
||||
|
||||
parsePacket(message, xml);
|
||||
QCOMPARE(m_manager.handleMessage(message), isValid);
|
||||
serializePacket(message, xml);
|
||||
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleMessageRinging()
|
||||
{
|
||||
QXmppMessage message;
|
||||
QByteArray xmlRinging {
|
||||
"<message from='juliet@capulet.example/phone' to='romeo@montague.example' type='chat'>"
|
||||
"<ringing xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'/>"
|
||||
"<store xmlns=\"urn:xmpp:hints\"/>"
|
||||
"</message>"
|
||||
};
|
||||
|
||||
auto jmi { m_manager.addJmi("juliet@capulet.example") };
|
||||
jmi->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
QSignalSpy ringingSpy(jmi.get(), &QXmppJingleMessageInitiation::ringing);
|
||||
|
||||
message.parse(xmlToDom(xmlRinging));
|
||||
|
||||
QVERIFY(m_manager.handleMessage(message));
|
||||
QCOMPARE(ringingSpy.count(), 1);
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleMessageProceeded()
|
||||
{
|
||||
QXmppMessage message;
|
||||
QByteArray xmlProceed {
|
||||
"<message from='juliet@capulet.example/phone' to='romeo@montague.example' type='chat'>"
|
||||
"<proceed xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'/>"
|
||||
"<store xmlns=\"urn:xmpp:hints\"/>"
|
||||
"</message>"
|
||||
};
|
||||
|
||||
auto jmi { m_manager.addJmi("juliet@capulet.example") };
|
||||
jmi->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
QSignalSpy proceededSpy(jmi.get(), &QXmppJingleMessageInitiation::proceeded);
|
||||
|
||||
message.parse(xmlToDom(xmlProceed));
|
||||
|
||||
QVERIFY(m_manager.handleMessage(message));
|
||||
QCOMPARE(proceededSpy.count(), 1);
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleMessageClosedRejected()
|
||||
{
|
||||
QXmppMessage message;
|
||||
QByteArray xmlReject {
|
||||
"<message from='juliet@capulet.example/phone' to='romeo@montague.example' type='chat'>"
|
||||
"<reject xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'>"
|
||||
"<reason xmlns=\"urn:xmpp:jingle:1\">"
|
||||
"<busy/>"
|
||||
"<text>Busy</text>"
|
||||
"</reason>"
|
||||
"</reject>"
|
||||
"<store xmlns=\"urn:xmpp:hints\"/>"
|
||||
"</message>"
|
||||
};
|
||||
|
||||
auto jmi { m_manager.addJmi("juliet@capulet.example") };
|
||||
jmi->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
connect(jmi.get(), &QXmppJingleMessageInitiation::closed, this, [](const Result &result) {
|
||||
using ResultType = QXmppJingleMessageInitiation::Rejected;
|
||||
|
||||
QVERIFY(std::holds_alternative<ResultType>(result));
|
||||
const ResultType &rejectedJmiElement { std::get<ResultType>(result) };
|
||||
|
||||
QCOMPARE(rejectedJmiElement.reason->type(), QXmppJingleReason::Busy);
|
||||
QCOMPARE(rejectedJmiElement.reason->text(), "Busy");
|
||||
QCOMPARE(rejectedJmiElement.reason->namespaceUri(), ns_jingle);
|
||||
});
|
||||
|
||||
message.parse(xmlToDom(xmlReject));
|
||||
|
||||
QVERIFY(m_manager.handleMessage(message));
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleMessageClosedRetracted()
|
||||
{
|
||||
QXmppMessage message;
|
||||
QByteArray xmlRetract {
|
||||
"<message from='romeo@montague.example/orchard' to='juliet@capulet.example' type='chat'>"
|
||||
"<retract xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'>"
|
||||
"<reason xmlns=\"urn:xmpp:jingle:1\">"
|
||||
"<cancel/>"
|
||||
"<text>Retracted</text>"
|
||||
"</reason>"
|
||||
"</retract>"
|
||||
"<store xmlns=\"urn:xmpp:hints\"/>"
|
||||
"</message>"
|
||||
};
|
||||
|
||||
auto jmi { m_manager.addJmi("romeo@montague.example") };
|
||||
jmi->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
connect(jmi.get(), &QXmppJingleMessageInitiation::closed, this, [](const Result &result) {
|
||||
using ResultType = QXmppJingleMessageInitiation::Retracted;
|
||||
|
||||
QVERIFY(std::holds_alternative<ResultType>(result));
|
||||
const ResultType &retractedJmiElement { std::get<ResultType>(result) };
|
||||
|
||||
QCOMPARE(retractedJmiElement.reason->type(), QXmppJingleReason::Cancel);
|
||||
QCOMPARE(retractedJmiElement.reason->text(), "Retracted");
|
||||
QCOMPARE(retractedJmiElement.reason->namespaceUri(), ns_jingle);
|
||||
});
|
||||
|
||||
message.parse(xmlToDom(xmlRetract));
|
||||
|
||||
QVERIFY(m_manager.handleMessage(message));
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
void tst_QXmppJingleMessageInitiationManager::testHandleMessageClosedFinished()
|
||||
{
|
||||
QXmppMessage message;
|
||||
QByteArray xmlFinish {
|
||||
"<message from='romeo@montague.example/orchard' to='juliet@capulet.example' type='chat'>"
|
||||
"<finish xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'>"
|
||||
"<reason xmlns=\"urn:xmpp:jingle:1\">"
|
||||
"<success/>"
|
||||
"<text>Success</text>"
|
||||
"</reason>"
|
||||
"<migrated to='989a46a6-f202-4910-a7c3-83c6ba3f3947'/>"
|
||||
"</finish>"
|
||||
"<store xmlns=\"urn:xmpp:hints\"/>"
|
||||
"</message>"
|
||||
};
|
||||
|
||||
auto jmi { m_manager.addJmi("romeo@montague.example") };
|
||||
jmi->setId("ca3cf894-5325-482f-a412-a6e9f832298d");
|
||||
|
||||
connect(jmi.get(), &QXmppJingleMessageInitiation::closed, this, [](const Result &result) {
|
||||
using ResultType = QXmppJingleMessageInitiation::Finished;
|
||||
|
||||
QVERIFY(std::holds_alternative<ResultType>(result));
|
||||
const ResultType &finishedJmiElement { std::get<ResultType>(result) };
|
||||
|
||||
QCOMPARE(finishedJmiElement.reason->type(), QXmppJingleReason::Success);
|
||||
QCOMPARE(finishedJmiElement.reason->text(), "Success");
|
||||
QCOMPARE(finishedJmiElement.reason->namespaceUri(), ns_jingle);
|
||||
QCOMPARE(finishedJmiElement.migratedTo, "989a46a6-f202-4910-a7c3-83c6ba3f3947");
|
||||
});
|
||||
|
||||
message.parse(xmlToDom(xmlFinish));
|
||||
|
||||
QVERIFY(m_manager.handleMessage(message));
|
||||
m_manager.clearAll();
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QXmppJingleMessageInitiationManager)
|
||||
#include "tst_qxmppjinglemessageinitiationmanager.moc"
|
Loading…
Reference in New Issue