diff options
| author | Linus Jahn <lnj@kaidan.im> | 2023-05-15 00:00:35 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-05-15 00:00:35 +0200 |
| commit | 6fe82239fc55b16953f965ea4e20e5fbfe806dd5 (patch) | |
| tree | 8c640ff269f527c7685d07a82517ba040d4d8e7f | |
| parent | fbb96a37f1c118c14fd158173e0d691022183ee3 (diff) | |
| parent | 85006abce021819de6af389d04e88756fac0745a (diff) | |
XEP-0353: Jingle Message Initiation
| -rw-r--r-- | doc/doap.xml | 8 | ||||
| -rw-r--r-- | src/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | src/base/QXmppConstants.cpp | 2 | ||||
| -rw-r--r-- | src/base/QXmppConstants_p.h | 2 | ||||
| -rw-r--r-- | src/base/QXmppJingleData.cpp (renamed from src/base/QXmppJingleIq.cpp) | 607 | ||||
| -rw-r--r-- | src/base/QXmppJingleData.h | 662 | ||||
| -rw-r--r-- | src/base/QXmppJingleIq.h | 574 | ||||
| -rw-r--r-- | src/base/QXmppMessage.cpp | 35 | ||||
| -rw-r--r-- | src/base/QXmppMessage.h | 6 | ||||
| -rw-r--r-- | src/client/QXmppJingleMessageInitiationManager.cpp | 584 | ||||
| -rw-r--r-- | src/client/QXmppJingleMessageInitiationManager.h | 126 | ||||
| -rw-r--r-- | tests/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | tests/qxmppjingledata/tst_qxmppjingledata.cpp (renamed from tests/qxmppjingleiq/tst_qxmppjingleiq.cpp) | 530 | ||||
| -rw-r--r-- | tests/qxmppjinglemessageinitiationmanager/tst_qxmppjinglemessageinitiationmanager.cpp | 903 | ||||
| -rw-r--r-- | tests/qxmppmessage/tst_qxmppmessage.cpp | 24 |
15 files changed, 3281 insertions, 790 deletions
diff --git a/doc/doap.xml b/doc/doap.xml index e32eff05..0bbb1cc9 100644 --- a/doc/doap.xml +++ b/doc/doap.xml @@ -491,6 +491,14 @@ SPDX-License-Identifier: CC0-1.0 </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'/> <xmpp:status>complete</xmpp:status> <xmpp:version>0.4</xmpp:version> diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 929554c4..7e94efdc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,6 +36,7 @@ set(INSTALL_HEADER_FILES base/QXmppIbbIq.h base/QXmppIq.h base/QXmppJingleIq.h + base/QXmppJingleData.h base/QXmppLogger.h base/QXmppMamIq.h base/QXmppMessage.h @@ -108,6 +109,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 @@ -172,7 +174,7 @@ set(SOURCE_FILES base/QXmppHttpUploadIq.cpp base/QXmppIbbIq.cpp base/QXmppIq.cpp - base/QXmppJingleIq.cpp + base/QXmppJingleData.cpp base/QXmppLogger.cpp base/QXmppMamIq.cpp base/QXmppMessage.cpp @@ -243,6 +245,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 diff --git a/src/base/QXmppConstants.cpp b/src/base/QXmppConstants.cpp index be5b62c7..0ddd3db2 100644 --- a/src/base/QXmppConstants.cpp +++ b/src/base/QXmppConstants.cpp @@ -160,6 +160,8 @@ const char *ns_chat_markers = "urn:xmpp:chat-markers:0"; const char *ns_message_processing_hints = "urn:xmpp:hints"; // XEP-0352: Client State Indication const char *ns_csi = "urn:xmpp:csi:0"; +// XEP-0353: Jingle Message Initiation +const char *ns_jingle_message_initiation = "urn:xmpp:jingle-message:0"; // XEP-0357: Push Notifications const char *ns_push = "urn:xmpp:push:0"; // XEP-0359: Unique and Stable Stanza IDs diff --git a/src/base/QXmppConstants_p.h b/src/base/QXmppConstants_p.h index b90346b6..c859de96 100644 --- a/src/base/QXmppConstants_p.h +++ b/src/base/QXmppConstants_p.h @@ -172,6 +172,8 @@ extern const char *ns_chat_markers; extern const char *ns_message_processing_hints; // XEP-0352: Client State Indication extern const char *ns_csi; +// XEP-0353: Jingle Message Initiation +extern const char *ns_jingle_message_initiation; // XEP-0357: Push Notifications extern const char *ns_push; // XEP-0359: Unique and Stable Stanza IDs diff --git a/src/base/QXmppJingleIq.cpp b/src/base/QXmppJingleData.cpp index a89f87a6..ceb31b32 100644 --- a/src/base/QXmppJingleIq.cpp +++ b/src/base/QXmppJingleData.cpp @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "QXmppJingleIq.h" +#include "QXmppJingleData.h" #include "QXmppConstants_p.h" #include "QXmppUtils.h" @@ -224,9 +224,7 @@ public: QString name; QString senders; - QString descriptionMedia; - quint32 descriptionSsrc; - QString descriptionType; + QXmppJingleDescription description; bool isRtpMultiplexingSupported = false; QString transportType; @@ -237,7 +235,6 @@ public: QString transportFingerprintHash; QString transportFingerprintSetup; - QList<QXmppJinglePayloadType> payloadTypes; QList<QXmppJingleCandidate> transportCandidates; // XEP-0167: Jingle RTP Sessions @@ -253,8 +250,8 @@ public: }; QXmppJingleIqContentPrivate::QXmppJingleIqContentPrivate() - : descriptionSsrc(0) { + description.setSsrc(0); } /// @@ -362,36 +359,74 @@ void QXmppJingleIq::Content::setSenders(const QString &senders) d->senders = senders; } +/// +/// Returns the description as specified by +/// \xep{0167, Jingle RTP Sessions} and RFC 3550. +/// +/// \since QXmpp 0.9 +/// +QXmppJingleDescription QXmppJingleIq::Content::description() const +{ + return d->description; +} + +void QXmppJingleIq::Content::setDescription(const QXmppJingleDescription &description) +{ + d->description = description; +} + +/// \deprecated This method is deprecated since QXmpp 1.6. Use +/// \c QXmppJingleIq::Conent::description().media() instead. QString QXmppJingleIq::Content::descriptionMedia() const { - return d->descriptionMedia; + return d->description.media(); } +/// \deprecated This method is deprecated since QXmpp 1.6. Use +/// \c QXmppJingleIq::Conent::description().setMedia() instead. void QXmppJingleIq::Content::setDescriptionMedia(const QString &media) { - d->descriptionMedia = media; + d->description.setMedia(media); } -/// /// Returns the description's 32-bit synchronization source for the media stream as specified by /// \xep{0167, Jingle RTP Sessions} and RFC 3550. /// /// \since QXmpp 0.9 +/// \deprecated This method is deprecated since QXmpp 1.6. Use +/// \c QXmppJingleIq::Content::description().setSsrc() instead. /// quint32 QXmppJingleIq::Content::descriptionSsrc() const { - return d->descriptionSsrc; + return d->description.ssrc(); } -/// -/// Sets the description's 32-bit synchronization source for the media stream as specified by -/// \xep{0167, Jingle RTP Sessions} and RFC 3550. -/// -/// \since QXmpp 0.9 -/// +/// \deprecated This method is deprecated since QXmpp 1.6. Use +/// \c QXmppJingleIq::Conent::description().setSsrc() instead. void QXmppJingleIq::Content::setDescriptionSsrc(quint32 ssrc) { - d->descriptionSsrc = ssrc; + d->description.setSsrc(ssrc); +} + +/// \deprecated This method is deprecated since QXmpp 1.6. Use +/// \c QXmppJingleIq::Conent::description().addPayloadType() instead. +void QXmppJingleIq::Content::addPayloadType(const QXmppJinglePayloadType &payload) +{ + d->description.addPayloadType(payload); +} + +/// \deprecated This method is deprecated since QXmpp 1.6. Use +/// \c QXmppJingleIq::Conent::description().payloadTypes() instead. +QList<QXmppJinglePayloadType> QXmppJingleIq::Content::payloadTypes() const +{ + return d->description.payloadTypes(); +} + +/// \deprecated This method is deprecated since QXmpp 1.6. Use +/// \c QXmppJingleIq::Conent::description().setPayloadTypes() instead. +void QXmppJingleIq::Content::setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes) +{ + d->description.setPayloadTypes(payloadTypes); } /// @@ -446,23 +481,6 @@ void QXmppJingleIq::Content::setRtpEncryption(const std::optional<QXmppJingleRtp d->rtpEncryption = rtpEncryption; } -void QXmppJingleIq::Content::addPayloadType(const QXmppJinglePayloadType &payload) -{ - d->descriptionType = ns_jingle_rtp; - d->payloadTypes << payload; -} - -QList<QXmppJinglePayloadType> QXmppJingleIq::Content::payloadTypes() const -{ - return d->payloadTypes; -} - -void QXmppJingleIq::Content::setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes) -{ - d->descriptionType = payloadTypes.isEmpty() ? QString() : ns_jingle_rtp; - d->payloadTypes = payloadTypes; -} - void QXmppJingleIq::Content::addTransportCandidate(const QXmppJingleCandidate &candidate) { d->transportType = ns_jingle_ice_udp; @@ -687,9 +705,9 @@ void QXmppJingleIq::Content::parse(const QDomElement &element) // description QDomElement descriptionElement = element.firstChildElement(QStringLiteral("description")); - d->descriptionType = descriptionElement.namespaceURI(); - d->descriptionMedia = descriptionElement.attribute(QStringLiteral("media")); - d->descriptionSsrc = descriptionElement.attribute(QStringLiteral("ssrc")).toULong(); + d->description.setType(descriptionElement.namespaceURI()); + d->description.setMedia(descriptionElement.attribute(QStringLiteral("media"))); + d->description.setSsrc(descriptionElement.attribute(QStringLiteral("ssrc")).toULong()); d->isRtpMultiplexingSupported = !descriptionElement.firstChildElement(QStringLiteral("rtcp-mux")).isNull(); for (auto childElement = descriptionElement.firstChildElement(); @@ -710,7 +728,7 @@ void QXmppJingleIq::Content::parse(const QDomElement &element) while (!child.isNull()) { QXmppJinglePayloadType payload; payload.parse(child); - d->payloadTypes << payload; + d->description.addPayloadType(payload); child = child.nextSiblingElement(QStringLiteral("payload-type")); } @@ -749,13 +767,13 @@ void QXmppJingleIq::Content::toXml(QXmlStreamWriter *writer) const helperToXmlAddAttribute(writer, QStringLiteral("senders"), d->senders); // description - if (!d->descriptionType.isEmpty() || !d->payloadTypes.isEmpty()) { + if (!d->description.type().isEmpty() || !d->description.payloadTypes().isEmpty()) { writer->writeStartElement(QStringLiteral("description")); - writer->writeDefaultNamespace(d->descriptionType); - helperToXmlAddAttribute(writer, QStringLiteral("media"), d->descriptionMedia); + writer->writeDefaultNamespace(d->description.type()); + helperToXmlAddAttribute(writer, QStringLiteral("media"), d->description.media()); - if (d->descriptionSsrc) { - writer->writeAttribute(QStringLiteral("ssrc"), QString::number(d->descriptionSsrc)); + if (d->description.ssrc()) { + writer->writeAttribute(QStringLiteral("ssrc"), QString::number(d->description.ssrc())); } if (d->isRtpMultiplexingSupported) { @@ -769,7 +787,7 @@ void QXmppJingleIq::Content::toXml(QXmlStreamWriter *writer) const jingleRtpFeedbackNegotiationElementsToXml(writer, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); jingleRtpHeaderExtensionsNegotiationElementsToXml(writer, d->rtpHeaderExtensionProperties, d->isRtpHeaderExtensionMixingAllowed); - for (const auto &payload : d->payloadTypes) { + for (const auto &payload : d->description.payloadTypes()) { payload.toXml(writer); } @@ -888,7 +906,7 @@ bool QXmppJingleIq::Content::parseSdp(const QString &sdp) qWarning() << "Could not parse ssrc" << line; return false; } - d->descriptionSsrc = bits[0].toULong(); + d->description.setSsrc(bits[0].toULong()); } } else if (line.startsWith(QStringLiteral("m="))) { // FIXME: what do we do with the profile (bits[2]) ? @@ -897,7 +915,7 @@ bool QXmppJingleIq::Content::parseSdp(const QString &sdp) qWarning() << "Could not parse media" << line; return false; } - d->descriptionMedia = bits[0]; + d->description.setMedia(bits[0]); // parse payload types for (int i = 3; i < bits.size(); ++i) { @@ -912,7 +930,8 @@ bool QXmppJingleIq::Content::parseSdp(const QString &sdp) } } } - setPayloadTypes(payloads); + + d->description.setPayloadTypes(payloads); return true; } @@ -945,7 +964,7 @@ QString QXmppJingleIq::Content::toSdp() const // media QString payloads; QStringList attrs; - for (const QXmppJinglePayloadType &payload : d->payloadTypes) { + for (const QXmppJinglePayloadType &payload : d->description.payloadTypes()) { payloads += " " + QString::number(payload.id()); QString rtpmap = QString::number(payload.id()) + " " + payload.name() + "/" + QString::number(payload.clockrate()); if (payload.channels() > 1) { @@ -970,7 +989,7 @@ QString QXmppJingleIq::Content::toSdp() const attrs << QStringLiteral("a=fmtp:") + QByteArray::number(payload.id()) + QStringLiteral(" ") + paramList.join("; "); } } - sdp << QStringLiteral("m=%1 %2 RTP/AVP%3").arg(d->descriptionMedia, QString::number(localRtpPort), payloads); + sdp << QStringLiteral("m=%1 %2 RTP/AVP%3").arg(d->description.media(), QString::number(localRtpPort), payloads); sdp << QStringLiteral("c=%1").arg(addressToSdp(localRtpAddress)); sdp += attrs; @@ -996,45 +1015,60 @@ QString QXmppJingleIq::Content::toSdp() const /// \endcond +class QXmppJingleIqReasonPrivate : public QSharedData +{ +public: + QXmppJingleIqReasonPrivate(); + + QString m_text; + QXmppJingleReason::Type m_type; + QXmppJingleReason::RtpErrorCondition m_rtpErrorCondition; +}; + +QXmppJingleIqReasonPrivate::QXmppJingleIqReasonPrivate() + : m_type(QXmppJingleReason::Type::None), + m_rtpErrorCondition(QXmppJingleReason::RtpErrorCondition::NoErrorCondition) +{ +} + /// -/// \enum QXmppJingleIq::Reason::RtpErrorCondition -/// -/// Condition of an RTP-specific error +/// \class QXmppJingleReason /// -/// \since QXmpp 1.5 +/// The QXmppJingleReason class represents the "reason" element of a +/// QXmppJingle element. /// -QXmppJingleIq::Reason::Reason() - : m_type(None) +QXmppJingleReason::QXmppJingleReason() + : d(new QXmppJingleIqReasonPrivate()) { } /// Returns the reason's textual description. -QString QXmppJingleIq::Reason::text() const +QString QXmppJingleReason::text() const { - return m_text; + return d->m_text; } /// Sets the reason's textual description. -void QXmppJingleIq::Reason::setText(const QString &text) +void QXmppJingleReason::setText(const QString &text) { - m_text = text; + d->m_text = text; } /// Gets the reason's type. -QXmppJingleIq::Reason::Type QXmppJingleIq::Reason::type() const +QXmppJingleReason::Type QXmppJingleReason::type() const { - return m_type; + return d->m_type; } /// Sets the reason's type. -void QXmppJingleIq::Reason::setType(QXmppJingleIq::Reason::Type type) +void QXmppJingleReason::setType(QXmppJingleReason::Type type) { - m_type = type; + d->m_type = type; } /// @@ -1044,9 +1078,9 @@ void QXmppJingleIq::Reason::setType(QXmppJingleIq::Reason::Type type) /// /// \since QXmpp 1.5 /// -QXmppJingleIq::Reason::RtpErrorCondition QXmppJingleIq::Reason::rtpErrorCondition() const +QXmppJingleReason::RtpErrorCondition QXmppJingleReason::rtpErrorCondition() const { - return m_rtpErrorCondition; + return d->m_rtpErrorCondition; } /// @@ -1056,18 +1090,18 @@ QXmppJingleIq::Reason::RtpErrorCondition QXmppJingleIq::Reason::rtpErrorConditio /// /// \since QXmpp 1.5 /// -void QXmppJingleIq::Reason::setRtpErrorCondition(RtpErrorCondition rtpErrorCondition) +void QXmppJingleReason::setRtpErrorCondition(RtpErrorCondition rtpErrorCondition) { - m_rtpErrorCondition = rtpErrorCondition; + d->m_rtpErrorCondition = rtpErrorCondition; } /// \cond -void QXmppJingleIq::Reason::parse(const QDomElement &element) +void QXmppJingleReason::parse(const QDomElement &element) { - m_text = element.firstChildElement(QStringLiteral("text")).text(); + d->m_text = element.firstChildElement(QStringLiteral("text")).text(); for (int i = AlternativeSession; i <= UnsupportedTransports; i++) { if (!element.firstChildElement(jingle_reasons[i]).isNull()) { - m_type = static_cast<Type>(i); + d->m_type = static_cast<Type>(i); break; } } @@ -1078,27 +1112,29 @@ void QXmppJingleIq::Reason::parse(const QDomElement &element) if (child.namespaceURI() == ns_jingle_rtp_errors) { if (const auto index = JINGLE_RTP_ERROR_CONDITIONS.indexOf(child.tagName()); index != -1) { - m_rtpErrorCondition = RtpErrorCondition(index); + d->m_rtpErrorCondition = RtpErrorCondition(index); } break; } } } -void QXmppJingleIq::Reason::toXml(QXmlStreamWriter *writer) const +void QXmppJingleReason::toXml(QXmlStreamWriter *writer) const { - if (m_type < AlternativeSession || m_type > UnsupportedTransports) { + if (d->m_type < AlternativeSession || d->m_type > UnsupportedTransports) { return; } writer->writeStartElement(QStringLiteral("reason")); - if (!m_text.isEmpty()) { - helperToXmlAddTextElement(writer, QStringLiteral("text"), m_text); + writer->writeDefaultNamespace(ns_jingle); + + if (!d->m_text.isEmpty()) { + helperToXmlAddTextElement(writer, QStringLiteral("text"), d->m_text); } - writer->writeEmptyElement(jingle_reasons[m_type]); + writer->writeEmptyElement(jingle_reasons[d->m_type]); - if (m_rtpErrorCondition != NoErrorCondition) { - writer->writeStartElement(JINGLE_RTP_ERROR_CONDITIONS.at(m_rtpErrorCondition)); + if (d->m_rtpErrorCondition != NoErrorCondition) { + writer->writeStartElement(JINGLE_RTP_ERROR_CONDITIONS.at(d->m_rtpErrorCondition)); writer->writeDefaultNamespace(ns_jingle_rtp_errors); writer->writeEndElement(); } @@ -1120,7 +1156,7 @@ public: QString mujiGroupChatJid; QList<QXmppJingleIq::Content> contents; - QXmppJingleIq::Reason reason; + QXmppJingleReason reason; std::optional<QXmppJingleIq::RtpSessionState> rtpSessionState; }; @@ -1213,14 +1249,14 @@ void QXmppJingleIq::setInitiator(const QString &initiator) /// Returns a reference to the IQ's reason element. -QXmppJingleIq::Reason &QXmppJingleIq::reason() +QXmppJingleReason &QXmppJingleIq::reason() { return d->reason; } /// Returns a const reference to the IQ's reason element. -const QXmppJingleIq::Reason &QXmppJingleIq::reason() const +const QXmppJingleReason &QXmppJingleIq::reason() const { return d->reason; } @@ -1378,6 +1414,7 @@ void QXmppJingleIq::parseElementFromChild(const QDomElement &element) addContent(content); contentElement = contentElement.nextSiblingElement(QStringLiteral("content")); } + QDomElement reasonElement = jingleElement.firstChildElement(QStringLiteral("reason")); d->reason.parse(reasonElement); @@ -2056,6 +2093,142 @@ bool QXmppJinglePayloadType::operator==(const QXmppJinglePayloadType &other) con } } +class QXmppJingleDescriptionPrivate : public QSharedData +{ +public: + QXmppJingleDescriptionPrivate() = default; + + QString media; + quint32 ssrc; + QString type; + QList<QXmppJinglePayloadType> payloadTypes; +}; + +/// +/// \class QXmppJingleDescription +/// +/// \brief The QXmppJingleDescription class represents descriptions for Jingle elements including +/// media type, streaming source, namespace and payload types. +/// +/// \since QXmpp 1.6 +/// + +QXmppJingleDescription::QXmppJingleDescription() + : d(new QXmppJingleDescriptionPrivate()) +{ +} + +QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleDescription) + +/// +/// Returns the media type. +/// +QString QXmppJingleDescription::media() const +{ + return d->media; +} + +/// +/// Sets the media type. +/// +void QXmppJingleDescription::setMedia(const QString &media) +{ + d->media = media; +} + +/// +/// Returns the streaming source. +/// +quint32 QXmppJingleDescription::ssrc() const +{ + return d->ssrc; +} + +/// +/// Sets the streaming source. +/// +void QXmppJingleDescription::setSsrc(quint32 ssrc) +{ + d->ssrc = ssrc; +} + +/// +/// Returns the description namespace. +/// +QString QXmppJingleDescription::type() const +{ + return d->type; +} + +/// +/// Sets the description namespace. +/// +void QXmppJingleDescription::setType(const QString &type) +{ + d->type = type; +} + +/// +/// Adds a payload type to the list of payload types. +/// +void QXmppJingleDescription::addPayloadType(const QXmppJinglePayloadType &payload) +{ + d->type = ns_jingle_rtp; + d->payloadTypes.append(payload); +} + +/// +/// Returns a list of payload types. +/// +const QList<QXmppJinglePayloadType> &QXmppJingleDescription::payloadTypes() const +{ + return d->payloadTypes; +} + +/// +/// Sets the list of payload types. +/// +void QXmppJingleDescription::setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes) +{ + d->type = payloadTypes.isEmpty() ? QString() : ns_jingle_rtp; + d->payloadTypes = payloadTypes; +} + +/// \cond +void QXmppJingleDescription::parse(const QDomElement &element) +{ + d->type = element.namespaceURI(); + d->media = element.attribute(QStringLiteral("media")); + d->ssrc = element.attribute(QStringLiteral("ssrc")).toULong(); + + QDomElement child { element.firstChildElement(QStringLiteral("payload-type")) }; + while (!child.isNull()) { + QXmppJinglePayloadType payload; + payload.parse(child); + d->payloadTypes.append(payload); + child = child.nextSiblingElement(QStringLiteral("payload-type")); + } +} + +void QXmppJingleDescription::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("description")); + writer->writeDefaultNamespace(d->type); + + helperToXmlAddAttribute(writer, QStringLiteral("media"), d->media); + + if (d->ssrc) { + writer->writeAttribute(QStringLiteral("ssrc"), QString::number(d->ssrc)); + } + + for (const auto &payloadType : d->payloadTypes) { + payloadType.toXml(writer); + } + + writer->writeEndElement(); +} +/// \endcond + class QXmppSdpParameterPrivate : public QSharedData { public: @@ -2779,3 +2952,275 @@ bool QXmppJingleRtpHeaderExtensionProperty::isJingleRtpHeaderExtensionProperty(c return element.tagName() == QStringLiteral("rtp-hdrext") && element.namespaceURI() == ns_jingle_rtp_header_extensions_negotiation; } + +class QXmppJingleMessageInitiationElementPrivate : public QSharedData +{ +public: + QXmppJingleMessageInitiationElementPrivate() = default; + + QXmppJingleMessageInitiationElement::Type type { QXmppJingleMessageInitiationElement::Type::None }; + QString id; + + std::optional<QXmppJingleDescription> description; + std::optional<QXmppJingleReason> reason; + QString migratedTo; + + bool containsTieBreak; +}; + +QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleReason) + +/// +/// \enum QXmppJingleMessageInitiationElement::Type +/// +/// Possible types of Jingle Message Initiation elements +/// + +/// +/// \class QXmppJingleMessageInitiationElement +/// +/// \brief The QXmppJingleMessageInitiationElement class represents a Jingle Message Initiation +/// element as specified by \xep{0353}: Jingle Message Initiation. +/// +/// \ingroup Stanzas +/// +/// \since QXmpp 1.6 +/// + +/// +/// \brief Constructs a Jingle Message Initiation element. +/// \param type The JMI element type +/// +QXmppJingleMessageInitiationElement::QXmppJingleMessageInitiationElement() + : d(new QXmppJingleMessageInitiationElementPrivate()) +{ +} + +/// +/// Returns the Jingle Message Initiation element type +/// +QXmppJingleMessageInitiationElement::Type QXmppJingleMessageInitiationElement::type() const +{ + return d->type; +} + +/// +/// Sets the Jingle Message Initiation element type. +/// +void QXmppJingleMessageInitiationElement::setType(Type type) +{ + d->type = type; +} + +/// +/// Returns the Jingle Message Initiation element id. +/// +QString QXmppJingleMessageInitiationElement::id() const +{ + return d->id; +} + +/// +/// Sets the Jingle Message Initiation element id. +/// +void QXmppJingleMessageInitiationElement::setId(const QString &id) +{ + d->id = id; +} + +/// +/// Returns the Jingle Message Initiation element description. +/// +std::optional<QXmppJingleDescription> QXmppJingleMessageInitiationElement::description() const +{ + return d->description; +} + +/// +/// Sets the Jingle Message Initiation element description. +/// +void QXmppJingleMessageInitiationElement::setDescription(std::optional<QXmppJingleDescription> description) +{ + d->description = description; +} + +/// +/// Returns the Jingle Message Initiation element reason. +/// +std::optional<QXmppJingleReason> QXmppJingleMessageInitiationElement::reason() const +{ + return d->reason; +} + +/// +/// Sets the Jingle Message Initiation element reason. +/// +void QXmppJingleMessageInitiationElement::setReason(std::optional<QXmppJingleReason> reason) +{ + d->reason = reason; +} + +/// +/// Returns true if the Jingle Message Initiation element contains a <tie-break/> tag. +/// +bool QXmppJingleMessageInitiationElement::containsTieBreak() const +{ + return d->containsTieBreak; +} + +/// +/// Sets if the Jingle Message Initiation element contains a <tie-break/> tag. +/// +void QXmppJingleMessageInitiationElement::setContainsTieBreak(bool containsTieBreak) +{ + d->containsTieBreak = containsTieBreak; +} + +/// +/// Returns the Jingle Message Initiation element ID migrated to if the Jingle is being migrated +/// to a different device. +/// +QString QXmppJingleMessageInitiationElement::migratedTo() const +{ + return d->migratedTo; +} + +/// +/// Sets the Jingle Message Initiation element ID migrated to if the Jingle is being migrated +/// to a different device. +/// +void QXmppJingleMessageInitiationElement::setMigratedTo(const QString &migratedTo) +{ + d->migratedTo = migratedTo; +} + +/// \cond +void QXmppJingleMessageInitiationElement::parse(const QDomElement &element) +{ + std::optional<Type> type { stringToJmiElementType(element.nodeName()) }; + + if (!type.has_value()) { + return; + } + + d->type = type.value(); + d->id = element.attribute(QStringLiteral("id")); + + // Type::Proceed and Type::Ringing don't need any parsing aside of the id. + switch (d->type) { + case Type::Propose: { + if (const auto &descriptionElement = element.firstChildElement("description"); !descriptionElement.isNull()) { + d->description = QXmppJingleDescription(); + d->description->parse(descriptionElement); + } + + break; + } + case Type::Reject: + case Type::Retract: + d->containsTieBreak = !element.firstChildElement("tie-break").isNull(); + + if (const auto &reasonElement = element.firstChildElement("reason"); !reasonElement.isNull()) { + d->reason = QXmppJingleReason(); + d->reason->parse(reasonElement); + } + + break; + case Type::Finish: + if (auto reasonElement = element.firstChildElement("reason"); !reasonElement.isNull()) { + d->reason = QXmppJingleReason(); + d->reason->parse(reasonElement); + } + + if (auto migratedToElement = element.firstChildElement("migrated"); !migratedToElement.isNull()) { + d->migratedTo = migratedToElement.attribute("to"); + } + + break; + default: + break; + } +} + +void QXmppJingleMessageInitiationElement::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(jmiElementTypeToString(d->type)); + writer->writeDefaultNamespace(ns_jingle_message_initiation); + + helperToXmlAddAttribute(writer, QStringLiteral("id"), d->id); + + if (d->description) { + d->description->toXml(writer); + } + + if (d->reason) { + d->reason->toXml(writer); + } + + if (d->containsTieBreak) { + writer->writeEmptyElement(QStringLiteral("tie-break")); + } + + if (!d->migratedTo.isEmpty()) { + writer->writeEmptyElement(QStringLiteral("migrated")); + helperToXmlAddAttribute(writer, QStringLiteral("to"), d->migratedTo); + } + + writer->writeEndElement(); +} +/// \endcond + +QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleMessageInitiationElement) + +/// +/// Returns true if passed QDomElement is a Jingle Message Initiation element +/// +bool QXmppJingleMessageInitiationElement::isJingleMessageInitiationElement(const QDomElement &element) +{ + return stringToJmiElementType(element.tagName()).has_value() && element.hasAttribute(QStringLiteral("id")) && element.namespaceURI() == ns_jingle_message_initiation; +} + +/// +/// Takes a Jingle Message Initiation element type and parses it to a string. +/// +QString QXmppJingleMessageInitiationElement::jmiElementTypeToString(Type type) +{ + switch (type) { + case Type::Propose: + return "propose"; + case Type::Ringing: + return "ringing"; + case Type::Proceed: + return "proceed"; + case Type::Reject: + return "reject"; + case Type::Retract: + return "retract"; + case Type::Finish: + return "finish"; + default: + return {}; + } +} + +/// +/// Takes a string and parses it to a Jingle Message Initiation element type. +/// +std::optional<QXmppJingleMessageInitiationElement::Type> QXmppJingleMessageInitiationElement::stringToJmiElementType(const QString &typeStr) +{ + if (typeStr == "propose") { + return Type::Propose; + } else if (typeStr == "ringing") { + return Type::Ringing; + } else if (typeStr == "proceed") { + return Type::Proceed; + } else if (typeStr == "reject") { + return Type::Reject; + } else if (typeStr == "retract") { + return Type::Retract; + } else if (typeStr == "finish") { + return Type::Finish; + } + + return std::nullopt; +} diff --git a/src/base/QXmppJingleData.h b/src/base/QXmppJingleData.h new file mode 100644 index 00000000..e36577be --- /dev/null +++ b/src/base/QXmppJingleData.h @@ -0,0 +1,662 @@ +// SPDX-FileCopyrightText: 2010 Jeremy Lainé <jeremy.laine@m4x.org> +// SPDX-FileCopyrightText: 2022 Melvin Keskin <melvo@olomono.de> +// SPDX-FileCopyrightText: 2023 Tibor Csötönyi <work@taibsu.de> +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPJINGLEIQ_H +#define QXMPPJINGLEIQ_H + +#include "QXmppIq.h" + +#include <variant> + +#include <QHostAddress> + +class QXmppJingleCandidatePrivate; +class QXmppJingleDescriptionPrivate; +class QXmppJingleIqContentPrivate; +class QXmppJingleIqReasonPrivate; +class QXmppJingleIqPrivate; +class QXmppJinglePayloadTypePrivate; +class QXmppJingleRtpCryptoElementPrivate; +class QXmppJingleRtpEncryptionPrivate; +class QXmppJingleRtpFeedbackPropertyPrivate; +class QXmppJingleRtpHeaderExtensionPropertyPrivate; +class QXmppSdpParameterPrivate; +class QXmppJingleMessageInitiationElementPrivate; + +class QXMPP_EXPORT QXmppSdpParameter +{ +public: + QXmppSdpParameter(); + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppSdpParameter) + + QString name() const; + void setName(const QString &name); + + QString value() const; + void setValue(const QString &value); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + static bool isSdpParameter(const QDomElement &element); + +private: + QSharedDataPointer<QXmppSdpParameterPrivate> d; +}; + +class QXMPP_EXPORT QXmppJingleRtpCryptoElement +{ +public: + QXmppJingleRtpCryptoElement(); + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpCryptoElement) + + uint32_t tag() const; + void setTag(uint32_t tag); + + QString cryptoSuite() const; + void setCryptoSuite(const QString &cryptoSuite); + + QString keyParams() const; + void setKeyParams(const QString &keyParams); + + QString sessionParams() const; + void setSessionParams(const QString &sessionParams); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + static bool isJingleRtpCryptoElement(const QDomElement &element); + +private: + QSharedDataPointer<QXmppJingleRtpCryptoElementPrivate> d; +}; + +class QXMPP_EXPORT QXmppJingleRtpEncryption +{ +public: + QXmppJingleRtpEncryption(); + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpEncryption) + + bool isRequired() const; + void setRequired(bool isRequired); + + QVector<QXmppJingleRtpCryptoElement> cryptoElements() const; + void setCryptoElements(const QVector<QXmppJingleRtpCryptoElement> &cryptoElements); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + static bool isJingleRtpEncryption(const QDomElement &element); + +private: + QSharedDataPointer<QXmppJingleRtpEncryptionPrivate> d; +}; + +class QXMPP_EXPORT QXmppJingleRtpFeedbackProperty +{ +public: + QXmppJingleRtpFeedbackProperty(); + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpFeedbackProperty) + + QString type() const; + void setType(const QString &type); + + QString subtype() const; + void setSubtype(const QString &subtype); + + QVector<QXmppSdpParameter> parameters() const; + void setParameters(const QVector<QXmppSdpParameter> ¶meters); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + static bool isJingleRtpFeedbackProperty(const QDomElement &element); + +private: + QSharedDataPointer<QXmppJingleRtpFeedbackPropertyPrivate> d; +}; + +class QXMPP_EXPORT QXmppJingleRtpFeedbackInterval +{ +public: + QXmppJingleRtpFeedbackInterval(); + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpFeedbackInterval) + + uint64_t value() const; + void setValue(uint64_t value); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + static bool isJingleRtpFeedbackInterval(const QDomElement &element); + +private: + uint64_t m_value; +}; + +class QXMPP_EXPORT QXmppJingleRtpHeaderExtensionProperty +{ +public: + enum Senders { + /// The initiator and the sender are allowed. + Both, + /// Only the initiator is allowed. + Initiator, + /// Only the responder is allowed. + Responder + }; + + QXmppJingleRtpHeaderExtensionProperty(); + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpHeaderExtensionProperty) + + uint32_t id() const; + void setId(uint32_t id); + + QString uri() const; + void setUri(const QString &uri); + + Senders senders() const; + void setSenders(Senders senders); + + QVector<QXmppSdpParameter> parameters() const; + void setParameters(const QVector<QXmppSdpParameter> ¶meters); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + static bool isJingleRtpHeaderExtensionProperty(const QDomElement &element); + +private: + QSharedDataPointer<QXmppJingleRtpHeaderExtensionPropertyPrivate> d; +}; + +/// +/// \brief The QXmppJinglePayloadType class represents a payload type +/// as specified by \xep{0167}: Jingle RTP Sessions and RFC 5245. +/// +class QXMPP_EXPORT QXmppJinglePayloadType +{ +public: + QXmppJinglePayloadType(); + QXmppJinglePayloadType(const QXmppJinglePayloadType &other); + ~QXmppJinglePayloadType(); + + unsigned char channels() const; + void setChannels(unsigned char channels); + + unsigned int clockrate() const; + void setClockrate(unsigned int clockrate); + + unsigned char id() const; + void setId(unsigned char id); + + unsigned int maxptime() const; + void setMaxptime(unsigned int maxptime); + + QString name() const; + void setName(const QString &name); + + QMap<QString, QString> parameters() const; + void setParameters(const QMap<QString, QString> ¶meters); + + unsigned int ptime() const; + void setPtime(unsigned int ptime); + + QVector<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties() const; + void setRtpFeedbackProperties(const QVector<QXmppJingleRtpFeedbackProperty> &rtpFeedbackProperties); + + QVector<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals() const; + void setRtpFeedbackIntervals(const QVector<QXmppJingleRtpFeedbackInterval> &rtpFeedbackIntervals); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + QXmppJinglePayloadType &operator=(const QXmppJinglePayloadType &other); + bool operator==(const QXmppJinglePayloadType &other) const; + +private: + QSharedDataPointer<QXmppJinglePayloadTypePrivate> d; +}; + +class QXMPP_EXPORT QXmppJingleDescription +{ +public: + QXmppJingleDescription(); + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleDescription) + + QString media() const; + void setMedia(const QString &media); + + quint32 ssrc() const; + void setSsrc(quint32 ssrc); + + QString type() const; + void setType(const QString &type); + + void addPayloadType(const QXmppJinglePayloadType &payload); + const QList<QXmppJinglePayloadType> &payloadTypes() const; + void setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QSharedDataPointer<QXmppJingleDescriptionPrivate> d; +}; + +/// +/// \brief The QXmppJingleCandidate class represents a transport candidate +/// as specified by \xep{0176}: Jingle ICE-UDP Transport Method. +/// +class QXMPP_EXPORT QXmppJingleCandidate +{ +public: + /// This enum is used to describe a candidate's type. + enum Type { + HostType, ///< Host candidate, a local address/port. + PeerReflexiveType, ///< Peer-reflexive candidate, + ///< the address/port as seen from the peer. + ServerReflexiveType, ///< Server-reflexive candidate, + ///< the address/port as seen by the STUN server + RelayedType ///< Relayed candidate, a candidate from + ///< a TURN relay. + }; + + QXmppJingleCandidate(); + QXmppJingleCandidate(const QXmppJingleCandidate &other); + QXmppJingleCandidate(QXmppJingleCandidate &&); + ~QXmppJingleCandidate(); + + QXmppJingleCandidate &operator=(const QXmppJingleCandidate &other); + QXmppJingleCandidate &operator=(QXmppJingleCandidate &&); + + int component() const; + void setComponent(int component); + + QString foundation() const; + void setFoundation(const QString &foundation); + + int generation() const; + void setGeneration(int generation); + + QHostAddress host() const; + void setHost(const QHostAddress &host); + + QString id() const; + void setId(const QString &id); + + int network() const; + void setNetwork(int network); + + quint16 port() const; + void setPort(quint16 port); + + int priority() const; + void setPriority(int priority); + + QString protocol() const; + void setProtocol(const QString &protocol); + + QXmppJingleCandidate::Type type() const; + void setType(QXmppJingleCandidate::Type); + + bool isNull() const; + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + + static QXmppJingleCandidate::Type typeFromString(const QString &typeStr, bool *ok = nullptr); + static QString typeToString(QXmppJingleCandidate::Type type); + /// \endcond + +private: + QSharedDataPointer<QXmppJingleCandidatePrivate> d; +}; + +class QXMPP_EXPORT QXmppJingleReason +{ +public: + /// This enum is used to describe a reason's type. + enum Type { + None, + AlternativeSession, + Busy, + Cancel, + ConnectivityError, + Decline, + Expired, + FailedApplication, + FailedTransport, + GeneralError, + Gone, + IncompatibleParameters, + MediaError, + SecurityError, + Success, + Timeout, + UnsupportedApplications, + UnsupportedTransports + }; + + /// Condition of an RTP-specific error + /// \since QXmpp 1.5 + enum RtpErrorCondition { + /// There is no error condition. + NoErrorCondition, + /// The encryption offer is rejected. + InvalidCrypto, + /// Encryption is required but not offered. + CryptoRequired + }; + + QXmppJingleReason(); + + QString text() const; + void setText(const QString &text); + + Type type() const; + void setType(Type type); + + RtpErrorCondition rtpErrorCondition() const; + void setRtpErrorCondition(RtpErrorCondition rtpErrorCondition); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + + /// \endcond + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleReason) + +private: + QSharedDataPointer<QXmppJingleIqReasonPrivate> d; +}; + +/// +/// \brief The QXmppJingleIq class represents an IQ used for initiating media +/// sessions as specified by \xep{0166}: Jingle. +/// +/// \ingroup Stanzas +/// +class QXMPP_EXPORT QXmppJingleIq : public QXmppIq +{ +public: + /// This enum is used to describe a Jingle action. + enum Action { + ContentAccept, + ContentAdd, + ContentModify, + ContentReject, + ContentRemove, + DescriptionInfo, + SecurityInfo, + SessionAccept, + SessionInfo, + SessionInitiate, + SessionTerminate, + TransportAccept, + TransportInfo, + TransportReject, + TransportReplace + }; + + enum Creator { + /// The initiator generated the content type. + Initiator, + /// The responder generated the content type. + Responder + }; + + struct RtpSessionStateActive + { + }; + + struct RtpSessionStateHold + { + }; + + struct RtpSessionStateUnhold + { + }; + + struct RtpSessionStateMuting + { + /// True when temporarily not sending media to the other party but continuing to accept + /// media from it, false for ending mute state + bool isMute = true; + /// Creator of the corresponding session + Creator creator; + /// Session to be muted (e.g., only audio or video) + QString name; + }; + + struct RtpSessionStateRinging + { + }; + + using RtpSessionState = std::variant<RtpSessionStateActive, RtpSessionStateHold, RtpSessionStateUnhold, RtpSessionStateMuting, RtpSessionStateRinging>; + + /// Alias to QXmppJingleReason for compatibility. + using Reason = QXmppJingleReason; + + /// \internal + /// + /// The QXmppJingleIq::Content class represents the "content" element of a + /// QXmppJingleIq. + /// + class QXMPP_EXPORT Content + { + public: + Content(); + Content(const QXmppJingleIq::Content &other); + Content(QXmppJingleIq::Content &&); + ~Content(); + + Content &operator=(const Content &other); + Content &operator=(Content &&); + + QString creator() const; + void setCreator(const QString &creator); + + QString name() const; + void setName(const QString &name); + + QString senders() const; + void setSenders(const QString &senders); + + // XEP-0167: Jingle RTP Sessions + QXmppJingleDescription description() const; + void setDescription(const QXmppJingleDescription &description); + +#if QXMPP_DEPRECATED_SINCE(1, 6) + QString descriptionMedia() const; + void setDescriptionMedia(const QString &media); + + quint32 descriptionSsrc() const; + void setDescriptionSsrc(quint32 ssrc); + + void addPayloadType(const QXmppJinglePayloadType &payload); + QList<QXmppJinglePayloadType> payloadTypes() const; + void setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes); +#endif + + bool isRtpMultiplexingSupported() const; + void setRtpMultiplexingSupported(bool isRtpMultiplexingSupported); + + std::optional<QXmppJingleRtpEncryption> rtpEncryption() const; + void setRtpEncryption(const std::optional<QXmppJingleRtpEncryption> &rtpEncryption); + + void addTransportCandidate(const QXmppJingleCandidate &candidate); + QList<QXmppJingleCandidate> transportCandidates() const; + void setTransportCandidates(const QList<QXmppJingleCandidate> &candidates); + + QString transportUser() const; + void setTransportUser(const QString &user); + + QString transportPassword() const; + void setTransportPassword(const QString &password); + + QVector<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties() const; + void setRtpFeedbackProperties(const QVector<QXmppJingleRtpFeedbackProperty> &rtpFeedbackProperties); + + QVector<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals() const; + void setRtpFeedbackIntervals(const QVector<QXmppJingleRtpFeedbackInterval> &rtpFeedbackIntervals); + + QVector<QXmppJingleRtpHeaderExtensionProperty> rtpHeaderExtensionProperties() const; + void setRtpHeaderExtensionProperties(const QVector<QXmppJingleRtpHeaderExtensionProperty> &rtpHeaderExtensionProperties); + + bool isRtpHeaderExtensionMixingAllowed() const; + void setRtpHeaderExtensionMixingAllowed(bool isRtpHeaderExtensionMixingAllowed); + + // XEP-0320: Use of DTLS-SRTP in Jingle Sessions + QByteArray transportFingerprint() const; + void setTransportFingerprint(const QByteArray &fingerprint); + + QString transportFingerprintHash() const; + void setTransportFingerprintHash(const QString &hash); + + QString transportFingerprintSetup() const; + void setTransportFingerprintSetup(const QString &setup); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + + bool parseSdp(const QString &sdp); + QString toSdp() const; + /// \endcond + + private: + QSharedDataPointer<QXmppJingleIqContentPrivate> d; + }; + + QXmppJingleIq(); + QXmppJingleIq(const QXmppJingleIq &other); + QXmppJingleIq(QXmppJingleIq &&); + ~QXmppJingleIq() override; + + QXmppJingleIq &operator=(const QXmppJingleIq &other); + QXmppJingleIq &operator=(QXmppJingleIq &&); + + Action action() const; + void setAction(Action action); + + void addContent(const Content &content); + QList<Content> contents() const; + void setContents(const QList<Content> &contents); + + QString initiator() const; + void setInitiator(const QString &initiator); + + QXmppJingleReason &reason(); + const QXmppJingleReason &reason() const; + + QString responder() const; + void setResponder(const QString &responder); + +#if QXMPP_DEPRECATED_SINCE(1, 5) + QT_DEPRECATED_X("Use QXmpp::rtpSessionState() instead") + bool ringing() const; + QT_DEPRECATED_X("Use QXmpp::setRtpSessionState() instead") + void setRinging(bool ringing); +#endif + + QString sid() const; + void setSid(const QString &sid); + + QString mujiGroupChatJid() const; + void setMujiGroupChatJid(const QString &mujiGroupChatJid); + + std::optional<RtpSessionState> rtpSessionState() const; + void setRtpSessionState(const std::optional<RtpSessionState> &rtpSessionState); + + /// \cond + static bool isJingleIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element) override; + void toXmlElementFromChild(QXmlStreamWriter *writer) const override; + /// \endcond + +private: + QSharedDataPointer<QXmppJingleIqPrivate> d; +}; + +class QXMPP_EXPORT QXmppJingleMessageInitiationElement +{ +public: + enum class Type { + None, + Propose, + Ringing, + Proceed, + Reject, + Retract, + Finish + }; + + QXmppJingleMessageInitiationElement(); + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleMessageInitiationElement) + + Type type() const; + void setType(Type type); + + QString id() const; + void setId(const QString &id); + + std::optional<QXmppJingleDescription> description() const; + void setDescription(std::optional<QXmppJingleDescription> description); + + std::optional<QXmppJingleReason> reason() const; + void setReason(std::optional<QXmppJingleReason> reason); + + bool containsTieBreak() const; + void setContainsTieBreak(bool containsTieBreak); + + QString migratedTo() const; + void setMigratedTo(const QString &migratedTo); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + static bool isJingleMessageInitiationElement(const QDomElement &); + static QString jmiElementTypeToString(Type type); + static std::optional<Type> stringToJmiElementType(const QString &typeStr); + +private: + QSharedDataPointer<QXmppJingleMessageInitiationElementPrivate> d; +}; + +Q_DECLARE_METATYPE(QXmppJingleReason::RtpErrorCondition) + +#endif diff --git a/src/base/QXmppJingleIq.h b/src/base/QXmppJingleIq.h index f5da136c..358f7d0b 100644 --- a/src/base/QXmppJingleIq.h +++ b/src/base/QXmppJingleIq.h @@ -1,577 +1,5 @@ // SPDX-FileCopyrightText: 2010 Jeremy Lainé <jeremy.laine@m4x.org> -// SPDX-FileCopyrightText: 2022 Melvin Keskin <melvo@olomono.de> // // SPDX-License-Identifier: LGPL-2.1-or-later -#ifndef QXMPPJINGLEIQ_H -#define QXMPPJINGLEIQ_H - -#include "QXmppIq.h" - -#include <variant> - -#include <QHostAddress> - -class QXmppJingleCandidatePrivate; -class QXmppJingleIqContentPrivate; -class QXmppJingleIqPrivate; -class QXmppJinglePayloadTypePrivate; -class QXmppJingleRtpCryptoElementPrivate; -class QXmppJingleRtpEncryptionPrivate; -class QXmppJingleRtpFeedbackPropertyPrivate; -class QXmppJingleRtpHeaderExtensionPropertyPrivate; -class QXmppSdpParameterPrivate; - -class QXMPP_EXPORT QXmppSdpParameter -{ -public: - QXmppSdpParameter(); - - QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppSdpParameter) - - QString name() const; - void setName(const QString &name); - - QString value() const; - void setValue(const QString &value); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - - static bool isSdpParameter(const QDomElement &element); - -private: - QSharedDataPointer<QXmppSdpParameterPrivate> d; -}; - -class QXMPP_EXPORT QXmppJingleRtpCryptoElement -{ -public: - QXmppJingleRtpCryptoElement(); - - QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpCryptoElement) - - uint32_t tag() const; - void setTag(uint32_t tag); - - QString cryptoSuite() const; - void setCryptoSuite(const QString &cryptoSuite); - - QString keyParams() const; - void setKeyParams(const QString &keyParams); - - QString sessionParams() const; - void setSessionParams(const QString &sessionParams); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - - static bool isJingleRtpCryptoElement(const QDomElement &element); - -private: - QSharedDataPointer<QXmppJingleRtpCryptoElementPrivate> d; -}; - -class QXMPP_EXPORT QXmppJingleRtpEncryption -{ -public: - QXmppJingleRtpEncryption(); - - QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpEncryption) - - bool isRequired() const; - void setRequired(bool isRequired); - - QVector<QXmppJingleRtpCryptoElement> cryptoElements() const; - void setCryptoElements(const QVector<QXmppJingleRtpCryptoElement> &cryptoElements); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - - static bool isJingleRtpEncryption(const QDomElement &element); - -private: - QSharedDataPointer<QXmppJingleRtpEncryptionPrivate> d; -}; - -class QXMPP_EXPORT QXmppJingleRtpFeedbackProperty -{ -public: - QXmppJingleRtpFeedbackProperty(); - - QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpFeedbackProperty) - - QString type() const; - void setType(const QString &type); - - QString subtype() const; - void setSubtype(const QString &subtype); - - QVector<QXmppSdpParameter> parameters() const; - void setParameters(const QVector<QXmppSdpParameter> ¶meters); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - - static bool isJingleRtpFeedbackProperty(const QDomElement &element); - -private: - QSharedDataPointer<QXmppJingleRtpFeedbackPropertyPrivate> d; -}; - -class QXMPP_EXPORT QXmppJingleRtpFeedbackInterval -{ -public: - QXmppJingleRtpFeedbackInterval(); - - QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpFeedbackInterval) - - uint64_t value() const; - void setValue(uint64_t value); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - - static bool isJingleRtpFeedbackInterval(const QDomElement &element); - -private: - uint64_t m_value; -}; - -class QXMPP_EXPORT QXmppJingleRtpHeaderExtensionProperty -{ -public: - enum Senders { - /// The initiator and the sender are allowed. - Both, - /// Only the initiator is allowed. - Initiator, - /// Only the responder is allowed. - Responder - }; - - QXmppJingleRtpHeaderExtensionProperty(); - - QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpHeaderExtensionProperty) - - uint32_t id() const; - void setId(uint32_t id); - - QString uri() const; - void setUri(const QString &uri); - - Senders senders() const; - void setSenders(Senders senders); - - QVector<QXmppSdpParameter> parameters() const; - void setParameters(const QVector<QXmppSdpParameter> ¶meters); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - - static bool isJingleRtpHeaderExtensionProperty(const QDomElement &element); - -private: - QSharedDataPointer<QXmppJingleRtpHeaderExtensionPropertyPrivate> d; -}; - -/// -/// \brief The QXmppJinglePayloadType class represents a payload type -/// as specified by \xep{0167}: Jingle RTP Sessions and RFC 5245. -/// -class QXMPP_EXPORT QXmppJinglePayloadType -{ -public: - QXmppJinglePayloadType(); - QXmppJinglePayloadType(const QXmppJinglePayloadType &other); - ~QXmppJinglePayloadType(); - - unsigned char channels() const; - void setChannels(unsigned char channels); - - unsigned int clockrate() const; - void setClockrate(unsigned int clockrate); - - unsigned char id() const; - void setId(unsigned char id); - - unsigned int maxptime() const; - void setMaxptime(unsigned int maxptime); - - QString name() const; - void setName(const QString &name); - - QMap<QString, QString> parameters() const; - void setParameters(const QMap<QString, QString> ¶meters); - - unsigned int ptime() const; - void setPtime(unsigned int ptime); - - QVector<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties() const; - void setRtpFeedbackProperties(const QVector<QXmppJingleRtpFeedbackProperty> &rtpFeedbackProperties); - - QVector<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals() const; - void setRtpFeedbackIntervals(const QVector<QXmppJingleRtpFeedbackInterval> &rtpFeedbackIntervals); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - - QXmppJinglePayloadType &operator=(const QXmppJinglePayloadType &other); - bool operator==(const QXmppJinglePayloadType &other) const; - -private: - QSharedDataPointer<QXmppJinglePayloadTypePrivate> d; -}; - -/// -/// \brief The QXmppJingleCandidate class represents a transport candidate -/// as specified by \xep{0176}: Jingle ICE-UDP Transport Method. -/// -class QXMPP_EXPORT QXmppJingleCandidate -{ -public: - /// This enum is used to describe a candidate's type. - enum Type { - HostType, ///< Host candidate, a local address/port. - PeerReflexiveType, ///< Peer-reflexive candidate, - ///< the address/port as seen from the peer. - ServerReflexiveType, ///< Server-reflexive candidate, - ///< the address/port as seen by the STUN server - RelayedType ///< Relayed candidate, a candidate from - ///< a TURN relay. - }; - - QXmppJingleCandidate(); - QXmppJingleCandidate(const QXmppJingleCandidate &other); - QXmppJingleCandidate(QXmppJingleCandidate &&); - ~QXmppJingleCandidate(); - - QXmppJingleCandidate &operator=(const QXmppJingleCandidate &other); - QXmppJingleCandidate &operator=(QXmppJingleCandidate &&); - - int component() const; - void setComponent(int component); - - QString foundation() const; - void setFoundation(const QString &foundation); - - int generation() const; - void setGeneration(int generation); - - QHostAddress host() const; - void setHost(const QHostAddress &host); - - QString id() const; - void setId(const QString &id); - - int network() const; - void setNetwork(int network); - - quint16 port() const; - void setPort(quint16 port); - - int priority() const; - void setPriority(int priority); - - QString protocol() const; - void setProtocol(const QString &protocol); - - QXmppJingleCandidate::Type type() const; - void setType(QXmppJingleCandidate::Type); - - bool isNull() const; - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - - static QXmppJingleCandidate::Type typeFromString(const QString &typeStr, bool *ok = nullptr); - static QString typeToString(QXmppJingleCandidate::Type type); - /// \endcond - -private: - QSharedDataPointer<QXmppJingleCandidatePrivate> d; -}; - -/// -/// \brief The QXmppJingleIq class represents an IQ used for initiating media -/// sessions as specified by \xep{0166}: Jingle. -/// -/// \ingroup Stanzas -/// -class QXMPP_EXPORT QXmppJingleIq : public QXmppIq -{ -public: - /// This enum is used to describe a Jingle action. - enum Action { - ContentAccept, - ContentAdd, - ContentModify, - ContentReject, - ContentRemove, - DescriptionInfo, - SecurityInfo, - SessionAccept, - SessionInfo, - SessionInitiate, - SessionTerminate, - TransportAccept, - TransportInfo, - TransportReject, - TransportReplace - }; - - enum Creator { - /// The initiator generated the content type. - Initiator, - /// The responder generated the content type. - Responder - }; - - struct RtpSessionStateActive - { - }; - - struct RtpSessionStateHold - { - }; - - struct RtpSessionStateUnhold - { - }; - - struct RtpSessionStateMuting - { - /// True when temporarily not sending media to the other party but continuing to accept - /// media from it, false for ending mute state - bool isMute = true; - /// Creator of the corresponding session - Creator creator; - /// Session to be muted (e.g., only audio or video) - QString name; - }; - - struct RtpSessionStateRinging - { - }; - - using RtpSessionState = std::variant<RtpSessionStateActive, RtpSessionStateHold, RtpSessionStateUnhold, RtpSessionStateMuting, RtpSessionStateRinging>; - - /// \internal - /// - /// The QXmppJingleIq::Content class represents the "content" element of a - /// QXmppJingleIq. - /// - class QXMPP_EXPORT Content - { - public: - Content(); - Content(const QXmppJingleIq::Content &other); - Content(QXmppJingleIq::Content &&); - ~Content(); - - Content &operator=(const Content &other); - Content &operator=(Content &&); - - QString creator() const; - void setCreator(const QString &creator); - - QString name() const; - void setName(const QString &name); - - QString senders() const; - void setSenders(const QString &senders); - - // XEP-0167: Jingle RTP Sessions - QString descriptionMedia() const; - void setDescriptionMedia(const QString &media); - - quint32 descriptionSsrc() const; - void setDescriptionSsrc(quint32 ssrc); - - bool isRtpMultiplexingSupported() const; - void setRtpMultiplexingSupported(bool isRtpMultiplexingSupported); - - std::optional<QXmppJingleRtpEncryption> rtpEncryption() const; - void setRtpEncryption(const std::optional<QXmppJingleRtpEncryption> &rtpEncryption); - - void addPayloadType(const QXmppJinglePayloadType &payload); - QList<QXmppJinglePayloadType> payloadTypes() const; - void setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes); - - void addTransportCandidate(const QXmppJingleCandidate &candidate); - QList<QXmppJingleCandidate> transportCandidates() const; - void setTransportCandidates(const QList<QXmppJingleCandidate> &candidates); - - QString transportUser() const; - void setTransportUser(const QString &user); - - QString transportPassword() const; - void setTransportPassword(const QString &password); - - QVector<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties() const; - void setRtpFeedbackProperties(const QVector<QXmppJingleRtpFeedbackProperty> &rtpFeedbackProperties); - - QVector<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals() const; - void setRtpFeedbackIntervals(const QVector<QXmppJingleRtpFeedbackInterval> &rtpFeedbackIntervals); - - QVector<QXmppJingleRtpHeaderExtensionProperty> rtpHeaderExtensionProperties() const; - void setRtpHeaderExtensionProperties(const QVector<QXmppJingleRtpHeaderExtensionProperty> &rtpHeaderExtensionProperties); - - bool isRtpHeaderExtensionMixingAllowed() const; - void setRtpHeaderExtensionMixingAllowed(bool isRtpHeaderExtensionMixingAllowed); - - // XEP-0320: Use of DTLS-SRTP in Jingle Sessions - QByteArray transportFingerprint() const; - void setTransportFingerprint(const QByteArray &fingerprint); - - QString transportFingerprintHash() const; - void setTransportFingerprintHash(const QString &hash); - - QString transportFingerprintSetup() const; - void setTransportFingerprintSetup(const QString &setup); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - - bool parseSdp(const QString &sdp); - QString toSdp() const; - /// \endcond - - private: - QSharedDataPointer<QXmppJingleIqContentPrivate> d; - }; - - /// \internal - /// - /// The QXmppJingleIq::Reason class represents the "reason" element of a - /// QXmppJingleIq. - /// - class QXMPP_EXPORT Reason - { - public: - /// This enum is used to describe a reason's type. - enum Type { - None, - AlternativeSession, - Busy, - Cancel, - ConnectivityError, - Decline, - Expired, - FailedApplication, - FailedTransport, - GeneralError, - Gone, - IncompatibleParameters, - MediaError, - SecurityError, - Success, - Timeout, - UnsupportedApplications, - UnsupportedTransports - }; - - enum RtpErrorCondition { - /// There is no error condition. - NoErrorCondition, - /// The encryption offer is rejected. - InvalidCrypto, - /// Encryption is required but not offered. - CryptoRequired - }; - - Reason(); - - QString text() const; - void setText(const QString &text); - - Type type() const; - void setType(Type type); - - RtpErrorCondition rtpErrorCondition() const; - void setRtpErrorCondition(RtpErrorCondition rtpErrorCondition); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - - private: - QString m_text; - Type m_type; - RtpErrorCondition m_rtpErrorCondition = NoErrorCondition; - }; - - QXmppJingleIq(); - QXmppJingleIq(const QXmppJingleIq &other); - QXmppJingleIq(QXmppJingleIq &&); - ~QXmppJingleIq() override; - - QXmppJingleIq &operator=(const QXmppJingleIq &other); - QXmppJingleIq &operator=(QXmppJingleIq &&); - - Action action() const; - void setAction(Action action); - - void addContent(const Content &content); - QList<Content> contents() const; - void setContents(const QList<Content> &contents); - - QString initiator() const; - void setInitiator(const QString &initiator); - - Reason &reason(); - const Reason &reason() const; - - QString responder() const; - void setResponder(const QString &responder); - -#if QXMPP_DEPRECATED_SINCE(1, 5) - QT_DEPRECATED_X("Use QXmpp::rtpSessionState() instead") - bool ringing() const; - QT_DEPRECATED_X("Use QXmpp::setRtpSessionState() instead") - void setRinging(bool ringing); -#endif - - QString sid() const; - void setSid(const QString &sid); - - QString mujiGroupChatJid() const; - void setMujiGroupChatJid(const QString &mujiGroupChatJid); - - std::optional<RtpSessionState> rtpSessionState() const; - void setRtpSessionState(const std::optional<RtpSessionState> &rtpSessionState); - - /// \cond - static bool isJingleIq(const QDomElement &element); - /// \endcond - -protected: - /// \cond - void parseElementFromChild(const QDomElement &element) override; - void toXmlElementFromChild(QXmlStreamWriter *writer) const override; - /// \endcond - -private: - QSharedDataPointer<QXmppJingleIqPrivate> d; -}; - -Q_DECLARE_METATYPE(QXmppJingleIq::Reason::RtpErrorCondition) - -#endif +#include "QXmppJingleData.h" diff --git a/src/base/QXmppMessage.cpp b/src/base/QXmppMessage.cpp index 9527f5ed..be6cd83e 100644 --- a/src/base/QXmppMessage.cpp +++ b/src/base/QXmppMessage.cpp @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2010 Jeremy Lainé <jeremy.laine@m4x.org> // SPDX-FileCopyrightText: 2018 Linus Jahn <lnj@kaidan.im> // SPDX-FileCopyrightText: 2021 Melvin Keskin <melvo@olomono.de> +// SPDX-FileCopyrightText: 2023 Tibor Csötönyi <work@taibsu.de> // // SPDX-License-Identifier: LGPL-2.1-or-later @@ -11,6 +12,7 @@ #include "QXmppConstants_p.h" #include "QXmppFileShare.h" #include "QXmppGlobal_p.h" +#include "QXmppJingleData.h" #include "QXmppMessageReaction.h" #include "QXmppMixInvitation.h" #ifdef BUILD_OMEMO @@ -123,6 +125,9 @@ public: // XEP-0334: Message Processing Hints quint8 hints; + // XEP-0353: Jingle Message Initiation + std::optional<QXmppJingleMessageInitiationElement> jingleMessageInitiationElement; + // XEP-0359: Unique and Stable Stanza IDs QString stanzaId; QString stanzaIdBy; @@ -872,6 +877,24 @@ void QXmppMessage::removeAllHints() } /// +/// Returns a Jingle Message Initiation element as defined in \xep{0353}: Jingle Message +/// Initiation. +/// +std::optional<QXmppJingleMessageInitiationElement> QXmppMessage::jingleMessageInitiationElement() const +{ + return d->jingleMessageInitiationElement; +} + +/// +/// Sets a Jingle Message Initiation element as defined in \xep{0353}: Jingle Message +/// Initiation. +/// +void QXmppMessage::setJingleMessageInitiationElement(const std::optional<QXmppJingleMessageInitiationElement> &jingleMessageInitiationElement) +{ + d->jingleMessageInitiationElement = jingleMessageInitiationElement; +} + +/// /// Returns the stanza ID of the message according to \xep{0359}: Unique and /// Stable Stanza IDs. /// @@ -1393,6 +1416,13 @@ bool QXmppMessage::parseExtension(const QDomElement &element, QXmpp::SceMode sce } return true; } + // XEP-0353: Jingle Message Initiation + if (QXmppJingleMessageInitiationElement::isJingleMessageInitiationElement(element)) { + QXmppJingleMessageInitiationElement jingleMessageInitiationElement; + jingleMessageInitiationElement.parse(element); + d->jingleMessageInitiationElement = jingleMessageInitiationElement; + return true; + } // XEP-0359: Unique and Stable Stanza IDs if (checkElement(element, QStringLiteral("stanza-id"), ns_sid)) { d->stanzaId = element.attribute(QStringLiteral("id")); @@ -1811,6 +1841,11 @@ void QXmppMessage::serializeExtensions(QXmlStreamWriter *writer, QXmpp::SceMode writer->writeEndElement(); } + // XEP-0353: Jingle Message Initiation + if (d->jingleMessageInitiationElement) { + d->jingleMessageInitiationElement->toXml(writer); + } + // XEP-0367: Message Attaching if (!d->attachId.isEmpty()) { writer->writeStartElement(QStringLiteral("attach-to")); diff --git a/src/base/QXmppMessage.h b/src/base/QXmppMessage.h index be9e9b96..cf6df18d 100644 --- a/src/base/QXmppMessage.h +++ b/src/base/QXmppMessage.h @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2010 Jeremy Lainé <jeremy.laine@m4x.org> // SPDX-FileCopyrightText: 2018 Linus Jahn <lnj@kaidan.im> // SPDX-FileCopyrightText: 2020 Melvin Keskin <melvo@olomono.de> +// SPDX-FileCopyrightText: 2023 Tibor Csötönyi <work@taibsu.de> // // SPDX-License-Identifier: LGPL-2.1-or-later @@ -18,6 +19,7 @@ class QXmppMessagePrivate; class QXmppBitsOfBinaryDataList; +class QXmppJingleMessageInitiationElement; class QXmppMessageReaction; class QXmppMixInvitation; #ifdef BUILD_OMEMO @@ -205,6 +207,10 @@ public: void removeHint(const Hint hint); void removeAllHints(); + // XEP-0353: Jingle Message Initiation + std::optional<QXmppJingleMessageInitiationElement> jingleMessageInitiationElement() const; + void setJingleMessageInitiationElement(const std::optional<QXmppJingleMessageInitiationElement> &jingleMessageInitiationElement); + // XEP-0359: Unique and Stable Stanza IDs QString stanzaId() const; void setStanzaId(const QString &id); 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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ce2ca155..4e0b23c6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,7 +41,8 @@ add_simple_test(qxmppexternalservicediscoverymanager TestClient.h) add_simple_test(qxmpphttpuploadiq) add_simple_test(qxmppiceconnection) add_simple_test(qxmppiq) -add_simple_test(qxmppjingleiq) +add_simple_test(qxmppjingledata) +add_simple_test(qxmppjinglemessageinitiationmanager) add_simple_test(qxmppmammanager) add_simple_test(qxmppmixinvitation) add_simple_test(qxmppmixitems) diff --git a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp b/tests/qxmppjingledata/tst_qxmppjingledata.cpp index b27d7454..9322f242 100644 --- a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp +++ b/tests/qxmppjingledata/tst_qxmppjingledata.cpp @@ -1,14 +1,15 @@ // SPDX-FileCopyrightText: 2012 Jeremy Lainé <jeremy.laine@m4x.org> // SPDX-FileCopyrightText: 2022 Melvin Keskin <melvo@olomono.de> +// SPDX-FileCopyrightText: 2023 Tibor Csötönyi <work@taibsu.de> // // SPDX-License-Identifier: LGPL-2.1-or-later -#include "QXmppJingleIq.h" +#include "QXmppJingleData.h" #include "util.h" #include <QObject> -class tst_QXmppJingleIq : public QObject +class tst_QXmppJingleData : public QObject { Q_OBJECT @@ -55,9 +56,13 @@ private: Q_SLOT void testPayloadTypeRtpFeedbackNegotiation(); Q_SLOT void testRtpErrorCondition_data(); Q_SLOT void testRtpErrorCondition(); + + Q_SLOT void testIsJingleMessageInitiationElement_data(); + Q_SLOT void testIsJingleMessageInitiationElement(); + Q_SLOT void testJingleMessageInitiationElement(); }; -void tst_QXmppJingleIq::testIsSdpParameter_data() +void tst_QXmppJingleData::testIsSdpParameter_data() { QTest::addColumn<QByteArray>("xml"); QTest::addColumn<bool>("isValid"); @@ -70,7 +75,7 @@ void tst_QXmppJingleIq::testIsSdpParameter_data() << false; } -void tst_QXmppJingleIq::testIsSdpParameter() +void tst_QXmppJingleData::testIsSdpParameter() { QFETCH(QByteArray, xml); QFETCH(bool, isValid); @@ -78,7 +83,7 @@ void tst_QXmppJingleIq::testIsSdpParameter() QCOMPARE(QXmppSdpParameter::isSdpParameter(xmlToDom(xml)), isValid); } -void tst_QXmppJingleIq::testSdpParameter() +void tst_QXmppJingleData::testSdpParameter() { const QByteArray xml("<parameter name=\"test-name\" value=\"test-value\"/>"); @@ -99,7 +104,7 @@ void tst_QXmppJingleIq::testSdpParameter() serializePacket(parameter2, xml); } -void tst_QXmppJingleIq::testSdpParameterWithoutValue() +void tst_QXmppJingleData::testSdpParameterWithoutValue() { const QByteArray xml("<parameter name=\"test-name\"/>"); @@ -117,7 +122,7 @@ void tst_QXmppJingleIq::testSdpParameterWithoutValue() serializePacket(parameter2, xml); } -void tst_QXmppJingleIq::testIsRtpCryptoElement_data() +void tst_QXmppJingleData::testIsRtpCryptoElement_data() { QTest::addColumn<QByteArray>("xml"); QTest::addColumn<bool>("isValid"); @@ -130,7 +135,7 @@ void tst_QXmppJingleIq::testIsRtpCryptoElement_data() << false; } -void tst_QXmppJingleIq::testIsRtpCryptoElement() +void tst_QXmppJingleData::testIsRtpCryptoElement() { QFETCH(QByteArray, xml); QFETCH(bool, isValid); @@ -138,7 +143,7 @@ void tst_QXmppJingleIq::testIsRtpCryptoElement() QCOMPARE(QXmppJingleRtpCryptoElement::isJingleRtpCryptoElement(xmlToDom(xml)), isValid); } -void tst_QXmppJingleIq::testRtpCryptoElement_data() +void tst_QXmppJingleData::testRtpCryptoElement_data() { QTest::addColumn<QByteArray>("xml"); QTest::addColumn<bool>("hasSessionParams"); @@ -158,7 +163,7 @@ void tst_QXmppJingleIq::testRtpCryptoElement_data() << true; } -void tst_QXmppJingleIq::testRtpCryptoElement() +void tst_QXmppJingleData::testRtpCryptoElement() { QFETCH(QByteArray, xml); QFETCH(bool, hasSessionParams); @@ -202,7 +207,7 @@ void tst_QXmppJingleIq::testRtpCryptoElement() serializePacket(rtpCryptoElement2, xml); } -void tst_QXmppJingleIq::testIsRtpEncryption_data() +void tst_QXmppJingleData::testIsRtpEncryption_data() { QTest::addColumn<QByteArray>("xml"); QTest::addColumn<bool>("isValid"); @@ -218,7 +223,7 @@ void tst_QXmppJingleIq::testIsRtpEncryption_data() << false; } -void tst_QXmppJingleIq::testIsRtpEncryption() +void tst_QXmppJingleData::testIsRtpEncryption() { QFETCH(QByteArray, xml); QFETCH(bool, isValid); @@ -226,7 +231,7 @@ void tst_QXmppJingleIq::testIsRtpEncryption() QCOMPARE(QXmppJingleRtpEncryption::isJingleRtpEncryption(xmlToDom(xml)), isValid); } -void tst_QXmppJingleIq::testRtpEncryption_data() +void tst_QXmppJingleData::testRtpEncryption_data() { QTest::addColumn<QByteArray>("xml"); QTest::addColumn<bool>("isRequired"); @@ -265,7 +270,7 @@ void tst_QXmppJingleIq::testRtpEncryption_data() << 2; } -void tst_QXmppJingleIq::testRtpEncryption() +void tst_QXmppJingleData::testRtpEncryption() { QFETCH(QByteArray, xml); QFETCH(bool, isRequired); @@ -310,7 +315,7 @@ void tst_QXmppJingleIq::testRtpEncryption() serializePacket(rtpEncryption2, xml); } -void tst_QXmppJingleIq::testIsRtpFeedbackProperty_data() +void tst_QXmppJingleData::testIsRtpFeedbackProperty_data() { QTest::addColumn<QByteArray>("xml"); QTest::addColumn<bool>("isValid"); @@ -326,7 +331,7 @@ void tst_QXmppJingleIq::testIsRtpFeedbackProperty_data() << false; } -void tst_QXmppJingleIq::testIsRtpFeedbackProperty() +void tst_QXmppJingleData::testIsRtpFeedbackProperty() { QFETCH(QByteArray, xml); QFETCH(bool, isValid); @@ -334,7 +339,7 @@ void tst_QXmppJingleIq::testIsRtpFeedbackProperty() QCOMPARE(QXmppJingleRtpFeedbackProperty::isJingleRtpFeedbackProperty(xmlToDom(xml)), isValid); } -void tst_QXmppJingleIq::testRtpFeedbackProperty() +void tst_QXmppJingleData::testRtpFeedbackProperty() { const QByteArray xml("<rtcp-fb xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" type=\"nack\" subtype=\"sli\"/>"); @@ -358,7 +363,7 @@ void tst_QXmppJingleIq::testRtpFeedbackProperty() serializePacket(property2, xml); } -void tst_QXmppJingleIq::testRtpFeedbackPropertyWithParameters() +void tst_QXmppJingleData::testRtpFeedbackPropertyWithParameters() { const QByteArray xml( "<rtcp-fb xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" type=\"test-type\">" @@ -395,7 +400,7 @@ void tst_QXmppJingleIq::testRtpFeedbackPropertyWithParameters() serializePacket(property2, xml); } -void tst_QXmppJingleIq::testIsRtpFeedbackInterval_data() +void tst_QXmppJingleData::testIsRtpFeedbackInterval_data() { QTest::addColumn<QByteArray>("xml"); QTest::addColumn<bool>("isValid"); @@ -411,7 +416,7 @@ void tst_QXmppJingleIq::testIsRtpFeedbackInterval_data() << false; } -void tst_QXmppJingleIq::testIsRtpFeedbackInterval() +void tst_QXmppJingleData::testIsRtpFeedbackInterval() { QFETCH(QByteArray, xml); QFETCH(bool, isValid); @@ -419,7 +424,7 @@ void tst_QXmppJingleIq::testIsRtpFeedbackInterval() QCOMPARE(QXmppJingleRtpFeedbackInterval::isJingleRtpFeedbackInterval(xmlToDom(xml)), isValid); } -void tst_QXmppJingleIq::testRtpFeedbackInterval() +void tst_QXmppJingleData::testRtpFeedbackInterval() { const QByteArray xml("<rtcp-fb-trr-int xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" value=\"100\"/>"); @@ -438,7 +443,7 @@ void tst_QXmppJingleIq::testRtpFeedbackInterval() serializePacket(interval2, xml); } -void tst_QXmppJingleIq::testIsRtpHeaderExtensionProperty_data() +void tst_QXmppJingleData::testIsRtpHeaderExtensionProperty_data() { QTest::addColumn<QByteArray>("xml"); QTest::addColumn<bool>("isValid"); @@ -454,7 +459,7 @@ void tst_QXmppJingleIq::testIsRtpHeaderExtensionProperty_data() << false; } -void tst_QXmppJingleIq::testIsRtpHeaderExtensionProperty() +void tst_QXmppJingleData::testIsRtpHeaderExtensionProperty() { QFETCH(QByteArray, xml); QFETCH(bool, isValid); @@ -462,7 +467,7 @@ void tst_QXmppJingleIq::testIsRtpHeaderExtensionProperty() QCOMPARE(QXmppJingleRtpHeaderExtensionProperty::isJingleRtpHeaderExtensionProperty(xmlToDom(xml)), isValid); } -void tst_QXmppJingleIq::testRtpHeaderExtensionProperty() +void tst_QXmppJingleData::testRtpHeaderExtensionProperty() { const QByteArray xml("<rtp-hdrext xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\" id=\"1\" uri=\"urn:ietf:params:rtp-hdrext:toffset\"/>"); @@ -490,7 +495,7 @@ void tst_QXmppJingleIq::testRtpHeaderExtensionProperty() serializePacket(property2, xml); } -void tst_QXmppJingleIq::testRtpHeaderExtensionPropertyWithSenders() +void tst_QXmppJingleData::testRtpHeaderExtensionPropertyWithSenders() { const QByteArray xml("<rtp-hdrext xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\" id=\"1\" uri=\"urn:ietf:params:rtp-hdrext:toffset\" senders=\"initiator\"/>"); @@ -511,7 +516,7 @@ void tst_QXmppJingleIq::testRtpHeaderExtensionPropertyWithSenders() serializePacket(property2, xml); } -void tst_QXmppJingleIq::testRtpHeaderExtensionPropertyWithParameters() +void tst_QXmppJingleData::testRtpHeaderExtensionPropertyWithParameters() { const QByteArray xml( "<rtp-hdrext xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\" id=\"1\" uri=\"urn:ietf:params:rtp-hdrext:toffset\">" @@ -547,7 +552,7 @@ void tst_QXmppJingleIq::testRtpHeaderExtensionPropertyWithParameters() serializePacket(property2, xml); } -void tst_QXmppJingleIq::testCandidate() +void tst_QXmppJingleData::testCandidate() { const QByteArray xml( "<candidate component=\"1\"" @@ -575,7 +580,7 @@ void tst_QXmppJingleIq::testCandidate() serializePacket(candidate, xml); }; -void tst_QXmppJingleIq::testContent() +void tst_QXmppJingleData::testContent() { const QByteArray xml( "<content creator=\"initiator\" name=\"voice\">" @@ -613,11 +618,11 @@ void tst_QXmppJingleIq::testContent() QXmppJingleIq::Content content1; QVERIFY(content1.creator().isEmpty()); QVERIFY(content1.name().isEmpty()); - QVERIFY(content1.descriptionMedia().isEmpty()); - QCOMPARE(content1.descriptionSsrc(), quint32(0)); + QVERIFY(content1.description().media().isEmpty()); + QCOMPARE(content1.description().ssrc(), quint32(0)); QVERIFY(!content1.isRtpMultiplexingSupported()); QVERIFY(!content1.rtpEncryption()); - QCOMPARE(content1.payloadTypes().size(), 0); + QCOMPARE(content1.description().payloadTypes().size(), 0); QVERIFY(content1.transportUser().isEmpty()); QVERIFY(content1.transportPassword().isEmpty()); QCOMPARE(content1.transportCandidates().size(), 0); @@ -625,13 +630,13 @@ void tst_QXmppJingleIq::testContent() QCOMPARE(content1.creator(), QStringLiteral("initiator")); QCOMPARE(content1.name(), QStringLiteral("voice")); - QCOMPARE(content1.descriptionMedia(), QStringLiteral("audio")); - QCOMPARE(content1.descriptionSsrc(), quint32(0)); + QCOMPARE(content1.description().media(), QStringLiteral("audio")); + QCOMPARE(content1.description().ssrc(), quint32(0)); QVERIFY(content1.isRtpMultiplexingSupported()); QVERIFY(content1.rtpEncryption()); - QCOMPARE(content1.payloadTypes().size(), 2); - QCOMPARE(content1.payloadTypes().at(0).id(), quint8(96)); - QCOMPARE(content1.payloadTypes().at(1).id(), quint8(97)); + QCOMPARE(content1.description().payloadTypes().size(), 2); + QCOMPARE(content1.description().payloadTypes().at(0).id(), quint8(96)); + QCOMPARE(content1.description().payloadTypes().at(1).id(), quint8(97)); QCOMPARE(content1.transportUser(), QStringLiteral("8hhy")); QCOMPARE(content1.transportPassword(), QStringLiteral("asd88fgpdd777uzjYhagZg")); QCOMPARE(content1.transportCandidates().size(), 2); @@ -642,8 +647,9 @@ void tst_QXmppJingleIq::testContent() QXmppJingleIq::Content content2; content2.setCreator(QStringLiteral("initiator")); content2.setName(QStringLiteral("voice")); - content2.setDescriptionMedia(QStringLiteral("audio")); - content2.setDescriptionSsrc(quint32(0)); + QXmppJingleDescription content2desc; + content2desc.setMedia(QStringLiteral("audio")); + content2desc.setSsrc(quint32(0)); content2.setRtpMultiplexingSupported(true); QXmppJingleRtpCryptoElement rtpCryptoElement; rtpCryptoElement.setTag(1); @@ -654,10 +660,11 @@ void tst_QXmppJingleIq::testContent() content2.setRtpEncryption(rtpEncryption); QXmppJinglePayloadType payloadType1; payloadType1.setId(quint8(96)); - content2.setPayloadTypes({ payloadType1 }); + content2desc.setPayloadTypes({ payloadType1 }); QXmppJinglePayloadType payloadType2; payloadType2.setId(quint8(97)); - content2.addPayloadType(payloadType2); + content2desc.addPayloadType(payloadType2); + content2.setDescription(content2desc); content2.setTransportUser(QStringLiteral("8hhy")); content2.setTransportPassword(QStringLiteral("asd88fgpdd777uzjYhagZg")); QXmppJingleCandidate transportCandidate1; @@ -669,13 +676,13 @@ void tst_QXmppJingleIq::testContent() QCOMPARE(content2.creator(), QStringLiteral("initiator")); QCOMPARE(content2.name(), QStringLiteral("voice")); - QCOMPARE(content2.descriptionMedia(), QStringLiteral("audio")); - QCOMPARE(content2.descriptionSsrc(), quint32(0)); + QCOMPARE(content2.description().media(), QStringLiteral("audio")); + QCOMPARE(content2.description().ssrc(), quint32(0)); QVERIFY(content2.isRtpMultiplexingSupported()); QVERIFY(content2.rtpEncryption()); - QCOMPARE(content2.payloadTypes().size(), 2); - QCOMPARE(content2.payloadTypes().at(0).id(), quint8(96)); - QCOMPARE(content2.payloadTypes().at(1).id(), quint8(97)); + QCOMPARE(content2.description().payloadTypes().size(), 2); + QCOMPARE(content2.description().payloadTypes().at(0).id(), quint8(96)); + QCOMPARE(content2.description().payloadTypes().at(1).id(), quint8(97)); QCOMPARE(content2.transportUser(), QStringLiteral("8hhy")); QCOMPARE(content2.transportPassword(), QStringLiteral("asd88fgpdd777uzjYhagZg")); QCOMPARE(content2.transportCandidates().size(), 2); @@ -684,7 +691,7 @@ void tst_QXmppJingleIq::testContent() serializePacket(content2, xml); } -void tst_QXmppJingleIq::testContentFingerprint() +void tst_QXmppJingleData::testContentFingerprint() { const QByteArray xml( "<content creator=\"initiator\" name=\"voice\">" @@ -715,10 +722,10 @@ void tst_QXmppJingleIq::testContentFingerprint() QCOMPARE(content.creator(), QLatin1String("initiator")); QCOMPARE(content.name(), QLatin1String("voice")); - QCOMPARE(content.descriptionMedia(), QLatin1String("audio")); - QCOMPARE(content.descriptionSsrc(), quint32(0)); - QCOMPARE(content.payloadTypes().size(), 1); - QCOMPARE(content.payloadTypes()[0].id(), quint8(0)); + QCOMPARE(content.description().media(), QLatin1String("audio")); + QCOMPARE(content.description().ssrc(), quint32(0)); + QCOMPARE(content.description().payloadTypes().size(), 1); + QCOMPARE(content.description().payloadTypes()[0].id(), quint8(0)); QCOMPARE(content.transportCandidates().size(), 1); QCOMPARE(content.transportCandidates()[0].component(), 1); QCOMPARE(content.transportCandidates()[0].foundation(), QLatin1String("1")); @@ -736,7 +743,7 @@ void tst_QXmppJingleIq::testContentFingerprint() serializePacket(content, xml); } -void tst_QXmppJingleIq::testContentSdp() +void tst_QXmppJingleData::testContentSdp() { const QString sdp( "m=audio 8998 RTP/AVP 96 97 18 0 103 98\r\n" @@ -755,15 +762,15 @@ void tst_QXmppJingleIq::testContentSdp() QXmppJingleIq::Content content; QVERIFY(content.parseSdp(sdp)); - QCOMPARE(content.descriptionMedia(), QLatin1String("audio")); - QCOMPARE(content.descriptionSsrc(), quint32(0)); - QCOMPARE(content.payloadTypes().size(), 6); - QCOMPARE(content.payloadTypes()[0].id(), quint8(96)); - QCOMPARE(content.payloadTypes()[1].id(), quint8(97)); - QCOMPARE(content.payloadTypes()[2].id(), quint8(18)); - QCOMPARE(content.payloadTypes()[3].id(), quint8(0)); - QCOMPARE(content.payloadTypes()[4].id(), quint8(103)); - QCOMPARE(content.payloadTypes()[5].id(), quint8(98)); + QCOMPARE(content.description().media(), QLatin1String("audio")); + QCOMPARE(content.description().ssrc(), quint32(0)); + QCOMPARE(content.description().payloadTypes().size(), 6); + QCOMPARE(content.description().payloadTypes()[0].id(), quint8(96)); + QCOMPARE(content.description().payloadTypes()[1].id(), quint8(97)); + QCOMPARE(content.description().payloadTypes()[2].id(), quint8(18)); + QCOMPARE(content.description().payloadTypes()[3].id(), quint8(0)); + QCOMPARE(content.description().payloadTypes()[4].id(), quint8(103)); + QCOMPARE(content.description().payloadTypes()[5].id(), quint8(98)); QCOMPARE(content.transportCandidates().size(), 2); QCOMPARE(content.transportCandidates()[0].component(), 1); QCOMPARE(content.transportCandidates()[0].foundation(), QLatin1String("1")); @@ -785,7 +792,7 @@ void tst_QXmppJingleIq::testContentSdp() QCOMPARE(content.toSdp(), sdp); } -void tst_QXmppJingleIq::testContentSdpReflexive() +void tst_QXmppJingleData::testContentSdpReflexive() { const QString sdp( "m=audio 45664 RTP/AVP 96 97 18 0 103 98\r\n" @@ -804,15 +811,15 @@ void tst_QXmppJingleIq::testContentSdpReflexive() QXmppJingleIq::Content content; QVERIFY(content.parseSdp(sdp)); - QCOMPARE(content.descriptionMedia(), QLatin1String("audio")); - QCOMPARE(content.descriptionSsrc(), quint32(0)); - QCOMPARE(content.payloadTypes().size(), 6); - QCOMPARE(content.payloadTypes()[0].id(), quint8(96)); - QCOMPARE(content.payloadTypes()[1].id(), quint8(97)); - QCOMPARE(content.payloadTypes()[2].id(), quint8(18)); - QCOMPARE(content.payloadTypes()[3].id(), quint8(0)); - QCOMPARE(content.payloadTypes()[4].id(), quint8(103)); - QCOMPARE(content.payloadTypes()[5].id(), quint8(98)); + QCOMPARE(content.description().media(), QLatin1String("audio")); + QCOMPARE(content.description().ssrc(), quint32(0)); + QCOMPARE(content.description().payloadTypes().size(), 6); + QCOMPARE(content.description().payloadTypes()[0].id(), quint8(96)); + QCOMPARE(content.description().payloadTypes()[1].id(), quint8(97)); + QCOMPARE(content.description().payloadTypes()[2].id(), quint8(18)); + QCOMPARE(content.description().payloadTypes()[3].id(), quint8(0)); + QCOMPARE(content.description().payloadTypes()[4].id(), quint8(103)); + QCOMPARE(content.description().payloadTypes()[5].id(), quint8(98)); QCOMPARE(content.transportCandidates().size(), 2); QCOMPARE(content.transportCandidates()[0].component(), 1); QCOMPARE(content.transportCandidates()[0].foundation(), QLatin1String("1")); @@ -834,7 +841,7 @@ void tst_QXmppJingleIq::testContentSdpReflexive() QCOMPARE(content.toSdp(), sdp); } -void tst_QXmppJingleIq::testContentSdpFingerprint() +void tst_QXmppJingleData::testContentSdpFingerprint() { const QString sdp( "m=audio 8998 RTP/AVP 96 100\r\n" @@ -850,14 +857,14 @@ void tst_QXmppJingleIq::testContentSdpFingerprint() QXmppJingleIq::Content content; QVERIFY(content.parseSdp(sdp)); - QCOMPARE(content.descriptionMedia(), QLatin1String("audio")); - QCOMPARE(content.descriptionSsrc(), quint32(0)); - QCOMPARE(content.payloadTypes().size(), 2); - QCOMPARE(content.payloadTypes()[0].id(), quint8(96)); - QCOMPARE(content.payloadTypes()[0].parameters().value("vbr"), QLatin1String("on")); - QCOMPARE(content.payloadTypes()[0].parameters().value("cng"), QLatin1String("on")); - QCOMPARE(content.payloadTypes()[1].id(), quint8(100)); - QCOMPARE(content.payloadTypes()[1].parameters().value("events"), QLatin1String("0-15,66,70")); + QCOMPARE(content.description().media(), QLatin1String("audio")); + QCOMPARE(content.description().ssrc(), quint32(0)); + QCOMPARE(content.description().payloadTypes().size(), 2); + QCOMPARE(content.description().payloadTypes()[0].id(), quint8(96)); + QCOMPARE(content.description().payloadTypes()[0].parameters().value("vbr"), QLatin1String("on")); + QCOMPARE(content.description().payloadTypes()[0].parameters().value("cng"), QLatin1String("on")); + QCOMPARE(content.description().payloadTypes()[1].id(), quint8(100)); + QCOMPARE(content.description().payloadTypes()[1].parameters().value("events"), QLatin1String("0-15,66,70")); QCOMPARE(content.transportCandidates().size(), 1); QCOMPARE(content.transportCandidates()[0].component(), 1); QCOMPARE(content.transportCandidates()[0].foundation(), QLatin1String("1")); @@ -873,7 +880,7 @@ void tst_QXmppJingleIq::testContentSdpFingerprint() QCOMPARE(content.toSdp(), sdp); } -void tst_QXmppJingleIq::testContentSdpParameters() +void tst_QXmppJingleData::testContentSdpParameters() { const QString sdp( "m=audio 8998 RTP/AVP 96 100\r\n" @@ -887,14 +894,14 @@ void tst_QXmppJingleIq::testContentSdpParameters() QXmppJingleIq::Content content; QVERIFY(content.parseSdp(sdp)); - QCOMPARE(content.descriptionMedia(), QLatin1String("audio")); - QCOMPARE(content.descriptionSsrc(), quint32(0)); - QCOMPARE(content.payloadTypes().size(), 2); - QCOMPARE(content.payloadTypes()[0].id(), quint8(96)); - QCOMPARE(content.payloadTypes()[0].parameters().value("vbr"), QLatin1String("on")); - QCOMPARE(content.payloadTypes()[0].parameters().value("cng"), QLatin1String("on")); - QCOMPARE(content.payloadTypes()[1].id(), quint8(100)); - QCOMPARE(content.payloadTypes()[1].parameters().value("events"), QLatin1String("0-15,66,70")); + QCOMPARE(content.description().media(), QLatin1String("audio")); + QCOMPARE(content.description().ssrc(), quint32(0)); + QCOMPARE(content.description().payloadTypes().size(), 2); + QCOMPARE(content.description().payloadTypes()[0].id(), quint8(96)); + QCOMPARE(content.description().payloadTypes()[0].parameters().value("vbr"), QLatin1String("on")); + QCOMPARE(content.description().payloadTypes()[0].parameters().value("cng"), QLatin1String("on")); + QCOMPARE(content.description().payloadTypes()[1].id(), quint8(100)); + QCOMPARE(content.description().payloadTypes()[1].parameters().value("events"), QLatin1String("0-15,66,70")); QCOMPARE(content.transportCandidates().size(), 1); QCOMPARE(content.transportCandidates()[0].component(), 1); QCOMPARE(content.transportCandidates()[0].foundation(), QLatin1String("1")); @@ -907,7 +914,7 @@ void tst_QXmppJingleIq::testContentSdpParameters() QCOMPARE(content.toSdp(), sdp); } -void tst_QXmppJingleIq::testContentRtpFeedbackNegotiation() +void tst_QXmppJingleData::testContentRtpFeedbackNegotiation() { const QByteArray xml( "<content creator=\"initiator\" name=\"voice\">" @@ -958,7 +965,9 @@ void tst_QXmppJingleIq::testContentRtpFeedbackNegotiation() QXmppJingleIq::Content content2; content2.setCreator(QStringLiteral("initiator")); content2.setName(QStringLiteral("voice")); - content2.addPayloadType(payloadType); + QXmppJingleDescription content2desc; + content2desc.addPayloadType(payloadType); + content2.setDescription(content2desc); content2.setRtpFeedbackProperties({ rtpFeedbackProperty1, rtpFeedbackProperty2 }); content2.setRtpFeedbackIntervals({ rtpFeedbackInterval1, rtpFeedbackInterval2 }); @@ -975,7 +984,7 @@ void tst_QXmppJingleIq::testContentRtpFeedbackNegotiation() serializePacket(content2, xml); } -void tst_QXmppJingleIq::testContentRtpHeaderExtensionsNegotiation() +void tst_QXmppJingleData::testContentRtpHeaderExtensionsNegotiation() { const QByteArray xml( "<content creator=\"initiator\" name=\"voice\">" @@ -1016,7 +1025,9 @@ void tst_QXmppJingleIq::testContentRtpHeaderExtensionsNegotiation() QXmppJingleIq::Content content2; content2.setCreator(QStringLiteral("initiator")); content2.setName(QStringLiteral("voice")); - content2.addPayloadType(payloadType); + QXmppJingleDescription content2desc; + content2desc.addPayloadType(payloadType); + content2.setDescription(content2desc); content2.setRtpHeaderExtensionProperties({ rtpHeaderExtensionProperty1, rtpHeaderExtensionProperty2 }); content2.setRtpHeaderExtensionMixingAllowed(true); @@ -1030,7 +1041,7 @@ void tst_QXmppJingleIq::testContentRtpHeaderExtensionsNegotiation() serializePacket(content2, xml); } -void tst_QXmppJingleIq::testSession() +void tst_QXmppJingleData::testSession() { const QByteArray xml( "<iq" @@ -1060,11 +1071,11 @@ void tst_QXmppJingleIq::testSession() QCOMPARE(session.contents()[0].creator(), QLatin1String("initiator")); QCOMPARE(session.contents()[0].name(), QLatin1String("this-is-a-stub")); QCOMPARE(session.reason().text(), QString()); - QCOMPARE(session.reason().type(), QXmppJingleIq::Reason::None); + QCOMPARE(session.reason().type(), QXmppJingleReason::None); serializePacket(session, xml); } -void tst_QXmppJingleIq::testTerminate() +void tst_QXmppJingleData::testTerminate() { const QByteArray xml( "<iq" @@ -1075,7 +1086,7 @@ void tst_QXmppJingleIq::testTerminate() "<jingle xmlns=\"urn:xmpp:jingle:1\"" " action=\"session-terminate\"" " sid=\"a73sjjvkla37jfea\">" - "<reason>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" "<success/>" "</reason>" "</jingle>" @@ -1087,11 +1098,11 @@ void tst_QXmppJingleIq::testTerminate() QCOMPARE(session.initiator(), QString()); QCOMPARE(session.sid(), QLatin1String("a73sjjvkla37jfea")); QCOMPARE(session.reason().text(), QString()); - QCOMPARE(session.reason().type(), QXmppJingleIq::Reason::Success); + QCOMPARE(session.reason().type(), QXmppJingleReason::Success); serializePacket(session, xml); } -void tst_QXmppJingleIq::testRtpSessionState_data() +void tst_QXmppJingleData::testRtpSessionState_data() { QTest::addColumn<QByteArray>("xml"); QTest::addColumn<QString>("state"); @@ -1140,7 +1151,7 @@ void tst_QXmppJingleIq::testRtpSessionState_data() << QStringLiteral("ringing"); } -void tst_QXmppJingleIq::testRtpSessionState() +void tst_QXmppJingleData::testRtpSessionState() { QFETCH(QByteArray, xml); QFETCH(QString, state); @@ -1228,7 +1239,7 @@ void tst_QXmppJingleIq::testRtpSessionState() serializePacket(iq2, xml); } -void tst_QXmppJingleIq::testAudioPayloadType() +void tst_QXmppJingleData::testAudioPayloadType() { const QByteArray xml(R"(<payload-type id="103" name="L16" channels="2" clockrate="16000"/>)"); QXmppJinglePayloadType payload; @@ -1240,7 +1251,7 @@ void tst_QXmppJingleIq::testAudioPayloadType() serializePacket(payload, xml); } -void tst_QXmppJingleIq::testVideoPayloadType() +void tst_QXmppJingleData::testVideoPayloadType() { const QByteArray xml( "<payload-type id=\"98\" name=\"theora\" clockrate=\"90000\">" @@ -1258,7 +1269,7 @@ void tst_QXmppJingleIq::testVideoPayloadType() serializePacket(payload, xml); } -void tst_QXmppJingleIq::testPayloadTypeRtpFeedbackNegotiation() +void tst_QXmppJingleData::testPayloadTypeRtpFeedbackNegotiation() { const QByteArray xml( "<payload-type id=\"96\">" @@ -1317,61 +1328,61 @@ void tst_QXmppJingleIq::testPayloadTypeRtpFeedbackNegotiation() serializePacket(payload2, xml); } -void tst_QXmppJingleIq::testRtpErrorCondition_data() +void tst_QXmppJingleData::testRtpErrorCondition_data() { QTest::addColumn<QByteArray>("xml"); - QTest::addColumn<QXmppJingleIq::Reason::RtpErrorCondition>("condition"); + QTest::addColumn<QXmppJingleReason::RtpErrorCondition>("condition"); QTest::newRow("NoErrorCondition") << QByteArrayLiteral("<iq type=\"set\">" "<jingle xmlns=\"urn:xmpp:jingle:1\" action=\"session-terminate\">" - "<reason>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" "<security-error/>" "</reason>" "</jingle>" "</iq>") - << QXmppJingleIq::Reason::NoErrorCondition; + << QXmppJingleReason::NoErrorCondition; QTest::newRow("InvalidCrypto") << QByteArrayLiteral("<iq type=\"set\">" "<jingle xmlns=\"urn:xmpp:jingle:1\" action=\"session-terminate\">" - "<reason>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" "<security-error/>" "<invalid-crypto xmlns=\"urn:xmpp:jingle:apps:rtp:errors:1\"/>" "</reason>" "</jingle>" "</iq>") - << QXmppJingleIq::Reason::InvalidCrypto; + << QXmppJingleReason::InvalidCrypto; QTest::newRow("CryptoRequired") << QByteArrayLiteral("<iq type=\"set\">" "<jingle xmlns=\"urn:xmpp:jingle:1\" action=\"session-terminate\">" - "<reason>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" "<security-error/>" "<crypto-required xmlns=\"urn:xmpp:jingle:apps:rtp:errors:1\"/>" "</reason>" "</jingle>" "</iq>") - << QXmppJingleIq::Reason::CryptoRequired; + << QXmppJingleReason::CryptoRequired; } -void tst_QXmppJingleIq::testRtpErrorCondition() +void tst_QXmppJingleData::testRtpErrorCondition() { QFETCH(QByteArray, xml); - QFETCH(QXmppJingleIq::Reason::RtpErrorCondition, condition); + QFETCH(QXmppJingleReason::RtpErrorCondition, condition); QXmppJingleIq iq1; - QCOMPARE(iq1.reason().rtpErrorCondition(), QXmppJingleIq::Reason::NoErrorCondition); + QCOMPARE(iq1.reason().rtpErrorCondition(), QXmppJingleReason::NoErrorCondition); parsePacket(iq1, xml); const auto rtpErrorCondition1 = iq1.reason().rtpErrorCondition(); switch (condition) { - case QXmppJingleIq::Reason::NoErrorCondition: - QVERIFY(rtpErrorCondition1 == QXmppJingleIq::Reason::NoErrorCondition); + case QXmppJingleReason::NoErrorCondition: + QVERIFY(rtpErrorCondition1 == QXmppJingleReason::NoErrorCondition); break; - case QXmppJingleIq::Reason::InvalidCrypto: - QVERIFY(rtpErrorCondition1 == QXmppJingleIq::Reason::InvalidCrypto); + case QXmppJingleReason::InvalidCrypto: + QVERIFY(rtpErrorCondition1 == QXmppJingleReason::InvalidCrypto); break; - case QXmppJingleIq::Reason::CryptoRequired: - QVERIFY(rtpErrorCondition1 == QXmppJingleIq::Reason::CryptoRequired); + case QXmppJingleReason::CryptoRequired: + QVERIFY(rtpErrorCondition1 == QXmppJingleReason::CryptoRequired); break; } @@ -1383,34 +1394,285 @@ void tst_QXmppJingleIq::testRtpErrorCondition() iq2.setAction(QXmppJingleIq::SessionTerminate); switch (condition) { - case QXmppJingleIq::Reason::NoErrorCondition: - iq2.reason().setRtpErrorCondition(QXmppJingleIq::Reason::NoErrorCondition); + case QXmppJingleReason::NoErrorCondition: + iq2.reason().setRtpErrorCondition(QXmppJingleReason::NoErrorCondition); break; - case QXmppJingleIq::Reason::InvalidCrypto: - iq2.reason().setRtpErrorCondition(QXmppJingleIq::Reason::InvalidCrypto); + case QXmppJingleReason::InvalidCrypto: + iq2.reason().setRtpErrorCondition(QXmppJingleReason::InvalidCrypto); break; - case QXmppJingleIq::Reason::CryptoRequired: - iq2.reason().setRtpErrorCondition(QXmppJingleIq::Reason::CryptoRequired); + case QXmppJingleReason::CryptoRequired: + iq2.reason().setRtpErrorCondition(QXmppJingleReason::CryptoRequired); break; } - iq2.reason().setType(QXmppJingleIq::Reason::SecurityError); + iq2.reason().setType(QXmppJingleReason::SecurityError); const auto rtpErrorCondition2 = iq2.reason().rtpErrorCondition(); switch (condition) { - case QXmppJingleIq::Reason::NoErrorCondition: - QVERIFY(rtpErrorCondition2 == QXmppJingleIq::Reason::NoErrorCondition); + case QXmppJingleReason::NoErrorCondition: + QVERIFY(rtpErrorCondition2 == QXmppJingleReason::NoErrorCondition); break; - case QXmppJingleIq::Reason::InvalidCrypto: - QVERIFY(rtpErrorCondition2 == QXmppJingleIq::Reason::InvalidCrypto); + case QXmppJingleReason::InvalidCrypto: + QVERIFY(rtpErrorCondition2 == QXmppJingleReason::InvalidCrypto); break; - case QXmppJingleIq::Reason::CryptoRequired: - QVERIFY(rtpErrorCondition2 == QXmppJingleIq::Reason::CryptoRequired); + case QXmppJingleReason::CryptoRequired: + QVERIFY(rtpErrorCondition2 == QXmppJingleReason::CryptoRequired); break; } serializePacket(iq2, xml); } -QTEST_MAIN(tst_QXmppJingleIq) -#include "tst_qxmppjingleiq.moc" +void tst_QXmppJingleData::testIsJingleMessageInitiationElement_data() +{ + QTest::addColumn<QByteArray>("xml"); + QTest::addColumn<bool>("isValid"); + + // --- Propose --- + + QTest::newRow("validPropose") + << QByteArrayLiteral( + "<propose xmlns='urn:xmpp:jingle-message:0' id='a73sjjvkla37jfea'>" + "<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'/>" + "</propose>") + << true; + QTest::newRow("invalidProposeIdMissing") + << QByteArrayLiteral( + "<propose xmlns='urn:xmpp:jingle-message:0'>" + "<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'/>" + "</propose>") + << false; + QTest::newRow("invalidProposeNamespaceMissing") + << QByteArrayLiteral( + "<propose id='a73sjjvkla37jfea'>" + "<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'/>" + "</propose>") + << false; + + // --- Ringing --- + + QTest::newRow("validRinging") + << QByteArrayLiteral("<ringing xmlns='urn:xmpp:jingle-message:0' id='a73sjjvkla37jfea'/>") + << true; + QTest::newRow("invalidRingingIdMissing") + << QByteArrayLiteral("<ringing xmlns='urn:xmpp:jingle-message:0'/>") + << false; + QTest::newRow("invalidRingingNamespaceMissing") + << QByteArrayLiteral("<ringing id='a73sjjvkla37jfea'/>") + << false; + + // --- Proceed --- + + QTest::newRow("validProceed") + << QByteArrayLiteral("<proceed xmlns='urn:xmpp:jingle-message:0' id='a73sjjvkla37jfea'/>") + << true; + QTest::newRow("invalidProceedIdMissing") + << QByteArrayLiteral("<proceed xmlns='urn:xmpp:jingle-message:0'/>") + << false; + QTest::newRow("invalidProceedNamespaceMissing") + << QByteArrayLiteral("<proceed id='a73sjjvkla37jfea'/>") + << false; + + // --- Reject --- + + QTest::newRow("validReject") + << QByteArrayLiteral( + "<reject xmlns='urn:xmpp:jingle-message:0' id='a73sjjvkla37jfea'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Busy</text>" + "<busy/>" + "</reason>" + "</reject>") + << true; + QTest::newRow("invalidRejectIdMissing") + << QByteArrayLiteral( + "<reject xmlns='urn:xmpp:jingle-message:0'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Busy</text>" + "<busy/>" + "</reason>" + "</reject>") + << false; + QTest::newRow("invalidRejectNamespaceMissing") + << QByteArrayLiteral( + "<reject id='a73sjjvkla37jfea'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Busy</text>" + "<busy/>" + "</reason>" + "</reject>") + << false; + + // --- Retract --- + + QTest::newRow("validRetract") + << QByteArrayLiteral( + "<retract xmlns='urn:xmpp:jingle-message:0' id='a73sjjvkla37jfea'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Retracted</text>" + "<cancel/>" + "</reason>" + "</retract>") + << true; + QTest::newRow("invalidRetractIdMissing") + << QByteArrayLiteral( + "<retract xmlns='urn:xmpp:jingle-message:0'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Retracted</text>" + "<cancel/>" + "</reason>" + "</retract>") + << false; + QTest::newRow("invalidRetractNamespaceMissing") + << QByteArrayLiteral( + "<retract id='a73sjjvkla37jfea'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Retracted</text>" + "<cancel/>" + "</reason>" + "</retract>") + << false; + + // --- Finish --- + + QTest::newRow("validFinish") + << QByteArrayLiteral( + "<finish xmlns='urn:xmpp:jingle-message:0' id='a73sjjvkla37jfea'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Success</text>" + "<success/>" + "</reason>" + "</finish>") + << true; + QTest::newRow("invalidFinishIdMissing") + << QByteArrayLiteral( + "<finish xmlns='urn:xmpp:jingle-message:0'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Success</text>" + "<success/>" + "</reason>" + "</finish>") + << false; + QTest::newRow("invalidFinishNamespaceMissing") + << QByteArrayLiteral( + "<finish id='a73sjjvkla37jfea'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Success</text>" + "<success/>" + "</reason>" + "</finish>") + << false; +} + +void tst_QXmppJingleData::testIsJingleMessageInitiationElement() +{ + QFETCH(QByteArray, xml); + QFETCH(bool, isValid); + + QCOMPARE(QXmppJingleMessageInitiationElement::isJingleMessageInitiationElement(xmlToDom(xml)), isValid); +} + +void tst_QXmppJingleData::testJingleMessageInitiationElement() +{ + using JmiType = QXmppJingleMessageInitiationElement::Type; + + // --- Propose --- + + const QByteArray proposeXml( + "<propose xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'>" + "<description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'/>" + "</propose>"); + QXmppJingleMessageInitiationElement proposeElement; + proposeElement.setType(JmiType::Propose); + + parsePacket(proposeElement, proposeXml); + QCOMPARE(proposeElement.id(), QStringLiteral("ca3cf894-5325-482f-a412-a6e9f832298d")); + QCOMPARE(proposeElement.description()->type(), QStringLiteral("urn:xmpp:jingle:apps:rtp:1")); + QCOMPARE(proposeElement.description()->media(), QStringLiteral("audio")); + QCOMPARE(proposeElement.containsTieBreak(), false); // single check if containsTieBreak is set correctly when unused + QCOMPARE(proposeElement.reason(), std::nullopt); // single check if reason is set correctly when unused + serializePacket(proposeElement, proposeXml); + + // --- Ringing --- + + const QByteArray ringingXml("<ringing xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'/>"); + QXmppJingleMessageInitiationElement ringingElement; + ringingElement.setType(JmiType::Ringing); + + parsePacket(ringingElement, ringingXml); + QCOMPARE(ringingElement.id(), QStringLiteral("ca3cf894-5325-482f-a412-a6e9f832298d")); + serializePacket(ringingElement, ringingXml); + + // --- Proceed --- + + const QByteArray proceedXml("<proceed xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'/>"); + QXmppJingleMessageInitiationElement proceedElement; + proceedElement.setType(JmiType::Proceed); + + parsePacket(proceedElement, proceedXml); + QCOMPARE(proceedElement.id(), QStringLiteral("ca3cf894-5325-482f-a412-a6e9f832298d")); + serializePacket(proceedElement, proceedXml); + + // --- Reject --- + + using ReasonType = QXmppJingleReason::Type; + + const QByteArray rejectXml( + "<reject xmlns='urn:xmpp:jingle-message:0' id='a73sjjvkla37jfea'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Busy</text>" + "<busy/>" + "</reason>" + "<tie-break/>" + "</reject>"); + QXmppJingleMessageInitiationElement rejectElement; + rejectElement.setType(JmiType::Reject); + + parsePacket(rejectElement, rejectXml); + QCOMPARE(rejectElement.id(), QStringLiteral("a73sjjvkla37jfea")); + QCOMPARE(rejectElement.reason()->text(), QStringLiteral("Busy")); + QCOMPARE(rejectElement.reason()->type(), ReasonType::Busy); + QCOMPARE(rejectElement.containsTieBreak(), true); + serializePacket(rejectElement, rejectXml); + + // --- Retract --- + + const QByteArray retractXml( + "<retract xmlns='urn:xmpp:jingle-message:0' id='a73sjjvkla37jfea'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Retracted</text>" + "<cancel/>" + "</reason>" + "</retract>"); + QXmppJingleMessageInitiationElement retractElement; + retractElement.setType(JmiType::Retract); + + parsePacket(retractElement, retractXml); + QCOMPARE(retractElement.id(), QStringLiteral("a73sjjvkla37jfea")); + QCOMPARE(retractElement.reason()->text(), QStringLiteral("Retracted")); + QCOMPARE(retractElement.reason()->type(), ReasonType::Cancel); + serializePacket(retractElement, retractXml); + + // --- Finish --- + + const QByteArray finishXml( + "<finish xmlns='urn:xmpp:jingle-message:0' id='a73sjjvkla37jfea'>" + "<reason xmlns=\"urn:xmpp:jingle:1\">" + "<text>Success</text>" + "<success/>" + "</reason>" + "<migrated to='989a46a6-f202-4910-a7c3-83c6ba3f3947'/>" + "</finish>"); + QXmppJingleMessageInitiationElement finishElement; + finishElement.setType(JmiType::Finish); + + parsePacket(finishElement, finishXml); + QCOMPARE(finishElement.id(), QStringLiteral("a73sjjvkla37jfea")); + QCOMPARE(finishElement.reason()->text(), QStringLiteral("Success")); + QCOMPARE(finishElement.reason()->type(), ReasonType::Success); + QCOMPARE(finishElement.migratedTo(), QStringLiteral("989a46a6-f202-4910-a7c3-83c6ba3f3947")); + serializePacket(finishElement, finishXml); +} + +QTEST_MAIN(tst_QXmppJingleData) +#include "tst_qxmppjingledata.moc" diff --git a/tests/qxmppjinglemessageinitiationmanager/tst_qxmppjinglemessageinitiationmanager.cpp b/tests/qxmppjinglemessageinitiationmanager/tst_qxmppjinglemessageinitiationmanager.cpp new file mode 100644 index 00000000..e09b6cd8 --- /dev/null +++ b/tests/qxmppjinglemessageinitiationmanager/tst_qxmppjinglemessageinitiationmanager.cpp @@ -0,0 +1,903 @@ +// 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"); + + 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()->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"); + + 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()->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"); + + 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()->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"); + + // 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()); + + 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"); + + 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()); + } + } + }); + + 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"); + + 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()); + } + } + }); + + 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."); + + 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.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."); + + 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.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."); + + 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.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"); + }); + + 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"); + }); + + 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.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" diff --git a/tests/qxmppmessage/tst_qxmppmessage.cpp b/tests/qxmppmessage/tst_qxmppmessage.cpp index 98ca5670..61fbed6f 100644 --- a/tests/qxmppmessage/tst_qxmppmessage.cpp +++ b/tests/qxmppmessage/tst_qxmppmessage.cpp @@ -1,12 +1,14 @@ // SPDX-FileCopyrightText: 2012 Jeremy Lainé <jeremy.laine@m4x.org> // SPDX-FileCopyrightText: 2012 Manjeet Dahiya <manjeetdahiya@gmail.com> // SPDX-FileCopyrightText: 2021 Melvin Keskin <melvo@olomono.de> +// SPDX-FileCopyrightText: 2023 Tibor Csötönyi <work@taibsu.de> // // SPDX-License-Identifier: LGPL-2.1-or-later #include "QXmppBitsOfBinaryContentId.h" #include "QXmppBitsOfBinaryDataList.h" #include "QXmppEncryptedFileSource.h" +#include "QXmppJingleData.h" #include "QXmppMessage.h" #include "QXmppMessageReaction.h" #include "QXmppMixInvitation.h" @@ -58,6 +60,7 @@ private: Q_SLOT void testE2eeFallbackBody(); Q_SLOT void testFileSharing(); Q_SLOT void testEncryptedFileSource(); + Q_SLOT void testJingleMessageInitiationElement(); }; void tst_QXmppMessage::testBasic_data() @@ -1285,5 +1288,26 @@ void tst_QXmppMessage::testEncryptedFileSource() } } +void tst_QXmppMessage::testJingleMessageInitiationElement() +{ + const QByteArray xml( + "<message to='romeo@montague.example' from='juliet@capulet.example/phone' type='chat'>" + "<store xmlns=\"urn:xmpp:hints\"/>" + "<proceed xmlns='urn:xmpp:jingle-message:0' id='ca3cf894-5325-482f-a412-a6e9f832298d'/>" + "</message>"); + + QXmppMessage message1; + QVERIFY(!message1.jingleMessageInitiationElement()); + + parsePacket(message1, xml); + QVERIFY(message1.jingleMessageInitiationElement()); + serializePacket(message1, xml); + + QXmppMessage message2; + message2.addHint(QXmppMessage::Store); + message2.setJingleMessageInitiationElement(QXmppJingleMessageInitiationElement()); + QVERIFY(message2.jingleMessageInitiationElement()); +} + QTEST_MAIN(tst_QXmppMessage) #include "tst_qxmppmessage.moc" |
