From 4d0aaabef3d2458e622e61170f3fcb8d80092aee Mon Sep 17 00:00:00 2001 From: Tibor Csötönyi Date: Wed, 3 May 2023 15:26:55 +0200 Subject: Extract JingleIq::Description to own class Description will be used by JingleMessageInitiationElement as well --- src/base/QXmppJingleIq.cpp | 217 +++++++++++++++++++++++++++++++++------------ src/base/QXmppJingleIq.h | 41 +++++++-- 2 files changed, 191 insertions(+), 67 deletions(-) (limited to 'src/base') diff --git a/src/base/QXmppJingleIq.cpp b/src/base/QXmppJingleIq.cpp index a89f87a6..e3022dcc 100644 --- a/src/base/QXmppJingleIq.cpp +++ b/src/base/QXmppJingleIq.cpp @@ -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 payloadTypes; QList transportCandidates; // XEP-0167: Jingle RTP Sessions @@ -253,8 +250,8 @@ public: }; QXmppJingleIqContentPrivate::QXmppJingleIqContentPrivate() - : descriptionSsrc(0) { + description.setSsrc(0); } /// @@ -362,36 +359,20 @@ void QXmppJingleIq::Content::setSenders(const QString &senders) d->senders = senders; } -QString QXmppJingleIq::Content::descriptionMedia() const -{ - return d->descriptionMedia; -} - -void QXmppJingleIq::Content::setDescriptionMedia(const QString &media) -{ - d->descriptionMedia = media; -} - /// -/// Returns the description's 32-bit synchronization source for the media stream as specified by +/// Returns the description as specified by /// \xep{0167, Jingle RTP Sessions} and RFC 3550. /// /// \since QXmpp 0.9 /// -quint32 QXmppJingleIq::Content::descriptionSsrc() const +QXmppJingleDescription QXmppJingleIq::Content::description() const { - return d->descriptionSsrc; + return d->description; } -/// -/// 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 -/// -void QXmppJingleIq::Content::setDescriptionSsrc(quint32 ssrc) +void QXmppJingleIq::Content::setDescription(const QXmppJingleDescription &description) { - d->descriptionSsrc = ssrc; + d->description = description; } /// @@ -446,23 +427,6 @@ void QXmppJingleIq::Content::setRtpEncryption(const std::optionalrtpEncryption = rtpEncryption; } -void QXmppJingleIq::Content::addPayloadType(const QXmppJinglePayloadType &payload) -{ - d->descriptionType = ns_jingle_rtp; - d->payloadTypes << payload; -} - -QList QXmppJingleIq::Content::payloadTypes() const -{ - return d->payloadTypes; -} - -void QXmppJingleIq::Content::setPayloadTypes(const QList &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 +651,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 +674,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 +713,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 +733,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 +852,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 +861,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 +876,8 @@ bool QXmppJingleIq::Content::parseSdp(const QString &sdp) } } } - setPayloadTypes(payloads); + + d->description.setPayloadTypes(payloads); return true; } @@ -945,7 +910,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 +935,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; @@ -2056,6 +2021,142 @@ bool QXmppJinglePayloadType::operator==(const QXmppJinglePayloadType &other) con } } +class QXmppJingleDescriptionPrivate : public QSharedData +{ +public: + QXmppJingleDescriptionPrivate() = default; + + QString media; + quint32 ssrc; + QString type; + QList 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 &QXmppJingleDescription::payloadTypes() const +{ + return d->payloadTypes; +} + +/// +/// Sets the list of payload types. +/// +void QXmppJingleDescription::setPayloadTypes(const QList &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: diff --git a/src/base/QXmppJingleIq.h b/src/base/QXmppJingleIq.h index f5da136c..20a827ba 100644 --- a/src/base/QXmppJingleIq.h +++ b/src/base/QXmppJingleIq.h @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2010 Jeremy Lainé // SPDX-FileCopyrightText: 2022 Melvin Keskin +// SPDX-FileCopyrightText: 2023 Tibor Csötönyi // // SPDX-License-Identifier: LGPL-2.1-or-later @@ -13,6 +14,7 @@ #include class QXmppJingleCandidatePrivate; +class QXmppJingleDescriptionPrivate; class QXmppJingleIqContentPrivate; class QXmppJingleIqPrivate; class QXmppJinglePayloadTypePrivate; @@ -237,6 +239,34 @@ private: QSharedDataPointer 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 &payloadTypes() const; + void setPayloadTypes(const QList &payloadTypes); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QSharedDataPointer d; +}; + /// /// \brief The QXmppJingleCandidate class represents a transport candidate /// as specified by \xep{0176}: Jingle ICE-UDP Transport Method. @@ -397,11 +427,8 @@ public: 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); + QXmppJingleDescription description() const; + void setDescription(const QXmppJingleDescription &description); bool isRtpMultiplexingSupported() const; void setRtpMultiplexingSupported(bool isRtpMultiplexingSupported); @@ -409,10 +436,6 @@ public: std::optional rtpEncryption() const; void setRtpEncryption(const std::optional &rtpEncryption); - void addPayloadType(const QXmppJinglePayloadType &payload); - QList payloadTypes() const; - void setPayloadTypes(const QList &payloadTypes); - void addTransportCandidate(const QXmppJingleCandidate &candidate); QList transportCandidates() const; void setTransportCandidates(const QList &candidates); -- cgit v1.2.3 From 44e9657c4e1551697f496cc9415f3d458103ca5c Mon Sep 17 00:00:00 2001 From: Tibor Csötönyi Date: Wed, 3 May 2023 15:29:41 +0200 Subject: Extract JingleIq::Reason to own class and add serialization for JMI Reason class will be used by JingleMessageInitiationElement as well --- src/base/QXmppJingleIq.cpp | 108 ++++++++++++++++++------- src/base/QXmppJingleIq.h | 126 +++++++++++++++--------------- tests/qxmppjingleiq/tst_qxmppjingleiq.cpp | 28 +++---- 3 files changed, 156 insertions(+), 106 deletions(-) (limited to 'src/base') diff --git a/src/base/QXmppJingleIq.cpp b/src/base/QXmppJingleIq.cpp index e3022dcc..d769e809 100644 --- a/src/base/QXmppJingleIq.cpp +++ b/src/base/QXmppJingleIq.cpp @@ -961,45 +961,69 @@ QString QXmppJingleIq::Content::toSdp() const /// \endcond +class QXmppJingleIqReasonPrivate : public QSharedData +{ +public: + QXmppJingleIqReasonPrivate(); + + QString m_text; + QXmppJingleReason::Type m_type; + QXmppJingleReason::RtpErrorCondition m_rtpErrorCondition; + QString m_namespaceUri; +}; + +QXmppJingleIqReasonPrivate::QXmppJingleIqReasonPrivate() + : m_type(QXmppJingleReason::Type::None), + m_rtpErrorCondition(QXmppJingleReason::RtpErrorCondition::NoErrorCondition) +{ +} + /// -/// \enum QXmppJingleIq::Reason::RtpErrorCondition +/// \enum QXmppJingleIqReason::RtpErrorCondition /// /// Condition of an RTP-specific error /// /// \since QXmpp 1.5 /// -QXmppJingleIq::Reason::Reason() - : m_type(None) +/// +/// \class QXmppJingleReason +/// +/// The QXmppJingleReason class represents the "reason" element of a +/// QXmppJingle element. +/// + +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; } /// @@ -1009,9 +1033,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; } /// @@ -1021,18 +1045,34 @@ QXmppJingleIq::Reason::RtpErrorCondition QXmppJingleIq::Reason::rtpErrorConditio /// /// \since QXmpp 1.5 /// -void QXmppJingleIq::Reason::setRtpErrorCondition(RtpErrorCondition rtpErrorCondition) +void QXmppJingleReason::setRtpErrorCondition(RtpErrorCondition rtpErrorCondition) +{ + d->m_rtpErrorCondition = rtpErrorCondition; +} + +/// +/// Returns the namespace URI of a reason element. +/// +QString QXmppJingleReason::namespaceUri() const +{ + return d->m_namespaceUri; +} + +/// +/// Sets the namespace URI of a reason element. +/// +void QXmppJingleReason::setNamespaceUri(const QString &namespaceUri) { - m_rtpErrorCondition = rtpErrorCondition; + d->m_namespaceUri = namespaceUri; } /// \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(i); + d->m_type = static_cast(i); break; } } @@ -1043,27 +1083,36 @@ 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; } } + + if (!element.namespaceURI().isEmpty() && !element.parentNode().isNull() && element.parentNode().namespaceURI() != element.namespaceURI()) { + d->m_namespaceUri = element.namespaceURI(); + } } -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); + + if (!d->m_namespaceUri.isEmpty()) { + writer->writeDefaultNamespace(d->m_namespaceUri); } - writer->writeEmptyElement(jingle_reasons[m_type]); - if (m_rtpErrorCondition != NoErrorCondition) { - writer->writeStartElement(JINGLE_RTP_ERROR_CONDITIONS.at(m_rtpErrorCondition)); + if (!d->m_text.isEmpty()) { + helperToXmlAddTextElement(writer, QStringLiteral("text"), d->m_text); + } + writer->writeEmptyElement(jingle_reasons[d->m_type]); + + if (d->m_rtpErrorCondition != NoErrorCondition) { + writer->writeStartElement(JINGLE_RTP_ERROR_CONDITIONS.at(d->m_rtpErrorCondition)); writer->writeDefaultNamespace(ns_jingle_rtp_errors); writer->writeEndElement(); } @@ -1085,7 +1134,7 @@ public: QString mujiGroupChatJid; QList contents; - QXmppJingleIq::Reason reason; + QXmppJingleReason reason; std::optional rtpSessionState; }; @@ -1178,14 +1227,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; } @@ -1343,6 +1392,7 @@ void QXmppJingleIq::parseElementFromChild(const QDomElement &element) addContent(content); contentElement = contentElement.nextSiblingElement(QStringLiteral("content")); } + QDomElement reasonElement = jingleElement.firstChildElement(QStringLiteral("reason")); d->reason.parse(reasonElement); diff --git a/src/base/QXmppJingleIq.h b/src/base/QXmppJingleIq.h index 20a827ba..6589a5cb 100644 --- a/src/base/QXmppJingleIq.h +++ b/src/base/QXmppJingleIq.h @@ -16,6 +16,7 @@ class QXmppJingleCandidatePrivate; class QXmppJingleDescriptionPrivate; class QXmppJingleIqContentPrivate; +class QXmppJingleIqReasonPrivate; class QXmppJingleIqPrivate; class QXmppJinglePayloadTypePrivate; class QXmppJingleRtpCryptoElementPrivate; @@ -337,6 +338,66 @@ private: QSharedDataPointer 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 + }; + + 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); + + QString namespaceUri() const; + void setNamespaceUri(const QString &namespaceUri); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + + /// \endcond + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleReason) + +private: + QSharedDataPointer d; +}; + /// /// \brief The QXmppJingleIq class represents an IQ used for initiating media /// sessions as specified by \xep{0166}: Jingle. @@ -480,67 +541,6 @@ public: QSharedDataPointer 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 &&); @@ -559,8 +559,8 @@ public: QString initiator() const; void setInitiator(const QString &initiator); - Reason &reason(); - const Reason &reason() const; + QXmppJingleReason &reason(); + const QXmppJingleReason &reason() const; QString responder() const; void setResponder(const QString &responder); diff --git a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp b/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp index bb4e7ba1..1aa88866 100644 --- a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp +++ b/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp @@ -1067,7 +1067,7 @@ 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); } @@ -1094,7 +1094,7 @@ 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); } @@ -1327,7 +1327,7 @@ void tst_QXmppJingleIq::testPayloadTypeRtpFeedbackNegotiation() void tst_QXmppJingleIq::testRtpErrorCondition_data() { QTest::addColumn("xml"); - QTest::addColumn("condition"); + QTest::addColumn("condition"); QTest::newRow("NoErrorCondition") << QByteArrayLiteral("" @@ -1337,7 +1337,7 @@ void tst_QXmppJingleIq::testRtpErrorCondition_data() "" "" "") - << QXmppJingleIq::Reason::NoErrorCondition; + << QXmppJingleReason::NoErrorCondition; QTest::newRow("InvalidCrypto") << QByteArrayLiteral("" "" @@ -1347,7 +1347,7 @@ void tst_QXmppJingleIq::testRtpErrorCondition_data() "" "" "") - << QXmppJingleIq::Reason::InvalidCrypto; + << QXmppJingleReason::InvalidCrypto; QTest::newRow("CryptoRequired") << QByteArrayLiteral("" "" @@ -1357,28 +1357,28 @@ void tst_QXmppJingleIq::testRtpErrorCondition_data() "" "" "") - << QXmppJingleIq::Reason::CryptoRequired; + << QXmppJingleReason::CryptoRequired; } void tst_QXmppJingleIq::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; } -- cgit v1.2.3 From 2fde987d39dc66f028ea3ff44929ebd6e2b37f90 Mon Sep 17 00:00:00 2001 From: Tibor Csötönyi Date: Wed, 3 May 2023 15:33:35 +0200 Subject: Add XEP-0353: Jingle Message Initiation data classes --- src/base/QXmppConstants.cpp | 2 + src/base/QXmppConstants_p.h | 2 + src/base/QXmppJingleIq.cpp | 275 +++++++++++++++++++++++++++++ src/base/QXmppJingleIq.h | 51 +++++- src/base/QXmppMessage.cpp | 35 ++++ src/base/QXmppMessage.h | 6 + tests/qxmppjingleiq/tst_qxmppjingleiq.cpp | 284 ++++++++++++++++++++++++++++-- tests/qxmppmessage/tst_qxmppmessage.cpp | 24 +++ 8 files changed, 665 insertions(+), 14 deletions(-) (limited to 'src/base') 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/QXmppJingleIq.cpp index d769e809..67883fc8 100644 --- a/src/base/QXmppJingleIq.cpp +++ b/src/base/QXmppJingleIq.cpp @@ -2930,3 +2930,278 @@ 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 description; + std::optional 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 QXmppJingleMessageInitiationElement::description() const +{ + return d->description; +} + +/// +/// Sets the Jingle Message Initiation element description. +/// +void QXmppJingleMessageInitiationElement::setDescription(std::optional description) +{ + d->description = description; +} + +/// +/// Returns the Jingle Message Initiation element reason. +/// +std::optional QXmppJingleMessageInitiationElement::reason() const +{ + return d->reason; +} + +/// +/// Sets the Jingle Message Initiation element reason. +/// +void QXmppJingleMessageInitiationElement::setReason(std::optional reason) +{ + d->reason = reason; + + if (d->reason) { + d->reason->setNamespaceUri(ns_jingle); + } +} + +/// +/// Returns true if the Jingle Message Initiation element contains a tag. +/// +bool QXmppJingleMessageInitiationElement::containsTieBreak() const +{ + return d->containsTieBreak; +} + +/// +/// Sets if the Jingle Message Initiation element contains a 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) +{ + d->type = stringToJmiElementType(element.nodeName()); + + if (d->type == Type::None) { + return; + } + + 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 (const auto &reasonElement = element.firstChildElement("reason"); !reasonElement.isNull()) { + d->reason = QXmppJingleReason(); + d->reason->parse(reasonElement); + } + + if (const 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 (element.nodeName() == QStringLiteral("propose") || element.nodeName() == QStringLiteral("ringing") || element.nodeName() == QStringLiteral("proceed") || element.nodeName() == QStringLiteral("retract") || element.nodeName() == QStringLiteral("reject") || element.nodeName() == QStringLiteral("finish")) && 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) const +{ + 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. +/// +QXmppJingleMessageInitiationElement::Type QXmppJingleMessageInitiationElement::stringToJmiElementType(const QString &typeStr) const +{ + 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 Type::None; +} diff --git a/src/base/QXmppJingleIq.h b/src/base/QXmppJingleIq.h index 6589a5cb..1e425f4d 100644 --- a/src/base/QXmppJingleIq.h +++ b/src/base/QXmppJingleIq.h @@ -24,6 +24,7 @@ class QXmppJingleRtpEncryptionPrivate; class QXmppJingleRtpFeedbackPropertyPrivate; class QXmppJingleRtpHeaderExtensionPropertyPrivate; class QXmppSdpParameterPrivate; +class QXmppJingleMessageInitiationElementPrivate; class QXMPP_EXPORT QXmppSdpParameter { @@ -595,6 +596,54 @@ private: QSharedDataPointer d; }; -Q_DECLARE_METATYPE(QXmppJingleIq::Reason::RtpErrorCondition) +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 description() const; + void setDescription(std::optional description); + + std::optional reason() const; + void setReason(std::optional 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 &); + +private: + QString jmiElementTypeToString(Type type) const; + Type stringToJmiElementType(const QString &typeStr) const; + + QSharedDataPointer d; +}; + +Q_DECLARE_METATYPE(QXmppJingleReason::RtpErrorCondition) #endif 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é // SPDX-FileCopyrightText: 2018 Linus Jahn // SPDX-FileCopyrightText: 2021 Melvin Keskin +// SPDX-FileCopyrightText: 2023 Tibor Csötönyi // // 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 jingleMessageInitiationElement; + // XEP-0359: Unique and Stable Stanza IDs QString stanzaId; QString stanzaIdBy; @@ -871,6 +876,24 @@ void QXmppMessage::removeAllHints() d->hints = 0; } +/// +/// Returns a Jingle Message Initiation element as defined in \xep{0353}: Jingle Message +/// Initiation. +/// +std::optional 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 &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é // SPDX-FileCopyrightText: 2018 Linus Jahn // SPDX-FileCopyrightText: 2020 Melvin Keskin +// SPDX-FileCopyrightText: 2023 Tibor Csötönyi // // 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 jingleMessageInitiationElement() const; + void setJingleMessageInitiationElement(const std::optional &jingleMessageInitiationElement); + // XEP-0359: Unique and Stable Stanza IDs QString stanzaId() const; void setStanzaId(const QString &id); diff --git a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp b/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp index 1aa88866..1d9c47fc 100644 --- a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp +++ b/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp @@ -56,6 +56,10 @@ 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() @@ -1390,34 +1394,288 @@ 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); } +void tst_QXmppJingleIq::testIsJingleMessageInitiationElement_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("isValid"); + + // --- Propose --- + + QTest::newRow("validPropose") + << QByteArrayLiteral( + "" + "" + "") + << true; + QTest::newRow("invalidProposeIdMissing") + << QByteArrayLiteral( + "" + "" + "") + << false; + QTest::newRow("invalidProposeNamespaceMissing") + << QByteArrayLiteral( + "" + "" + "") + << false; + + // --- Ringing --- + + QTest::newRow("validRinging") + << QByteArrayLiteral("") + << true; + QTest::newRow("invalidRingingIdMissing") + << QByteArrayLiteral("") + << false; + QTest::newRow("invalidRingingNamespaceMissing") + << QByteArrayLiteral("") + << false; + + // --- Proceed --- + + QTest::newRow("validProceed") + << QByteArrayLiteral("") + << true; + QTest::newRow("invalidProceedIdMissing") + << QByteArrayLiteral("") + << false; + QTest::newRow("invalidProceedNamespaceMissing") + << QByteArrayLiteral("") + << false; + + // --- Reject --- + + QTest::newRow("validReject") + << QByteArrayLiteral( + "" + "" + "Busy" + "" + "" + "") + << true; + QTest::newRow("invalidRejectIdMissing") + << QByteArrayLiteral( + "" + "" + "Busy" + "" + "" + "") + << false; + QTest::newRow("invalidRejectNamespaceMissing") + << QByteArrayLiteral( + "" + "" + "Busy" + "" + "" + "") + << false; + + // --- Retract --- + + QTest::newRow("validRetract") + << QByteArrayLiteral( + "" + "" + "Retracted" + "" + "" + "") + << true; + QTest::newRow("invalidRetractIdMissing") + << QByteArrayLiteral( + "" + "" + "Retracted" + "" + "" + "") + << false; + QTest::newRow("invalidRetractNamespaceMissing") + << QByteArrayLiteral( + "" + "" + "Retracted" + "" + "" + "") + << false; + + // --- Finish --- + + QTest::newRow("validFinish") + << QByteArrayLiteral( + "" + "" + "Success" + "" + "" + "") + << true; + QTest::newRow("invalidFinishIdMissing") + << QByteArrayLiteral( + "" + "" + "Success" + "" + "" + "") + << false; + QTest::newRow("invalidFinishNamespaceMissing") + << QByteArrayLiteral( + "" + "" + "Success" + "" + "" + "") + << false; +} + +void tst_QXmppJingleIq::testIsJingleMessageInitiationElement() +{ + QFETCH(QByteArray, xml); + QFETCH(bool, isValid); + + QCOMPARE(QXmppJingleMessageInitiationElement::isJingleMessageInitiationElement(xmlToDom(xml)), isValid); +} + +void tst_QXmppJingleIq::testJingleMessageInitiationElement() +{ + using JmiType = QXmppJingleMessageInitiationElement::Type; + + // --- Propose --- + + const QByteArray proposeXml( + "" + "" + ""); + 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(""); + 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(""); + 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( + "" + "" + "Busy" + "" + "" + "" + ""); + 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.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); + QCOMPARE(rejectElement.containsTieBreak(), true); + serializePacket(rejectElement, rejectXml); + + // --- Retract --- + + const QByteArray retractXml( + "" + "" + "Retracted" + "" + "" + ""); + 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); + QCOMPARE(retractElement.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); + serializePacket(retractElement, retractXml); + + // --- Finish --- + + const QByteArray finishXml( + "" + "" + "Success" + "" + "" + "" + ""); + 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.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); + QCOMPARE(finishElement.migratedTo(), QStringLiteral("989a46a6-f202-4910-a7c3-83c6ba3f3947")); + serializePacket(finishElement, finishXml); +} + QTEST_MAIN(tst_QXmppJingleIq) #include "tst_qxmppjingleiq.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é // SPDX-FileCopyrightText: 2012 Manjeet Dahiya // SPDX-FileCopyrightText: 2021 Melvin Keskin +// SPDX-FileCopyrightText: 2023 Tibor Csötönyi // // 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( + "" + "" + "" + ""); + + 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" -- cgit v1.2.3 From fd1a242b154dd74765e4f29ad26e7f743a8bb478 Mon Sep 17 00:00:00 2001 From: Tibor Csötönyi Date: Wed, 3 May 2023 15:13:18 +0200 Subject: Rename QXmppJingleIq.cpp/h to QXmppJingleData.cpp/h and test --- src/CMakeLists.txt | 4 +- src/base/QXmppJingleData.cpp | 3207 +++++++++++++++++++++++++ src/base/QXmppJingleData.h | 649 +++++ src/base/QXmppJingleIq.cpp | 3207 ------------------------- src/base/QXmppJingleIq.h | 649 ----- tests/CMakeLists.txt | 2 +- tests/qxmppjingledata/tst_qxmppjingledata.cpp | 1681 +++++++++++++ tests/qxmppjingleiq/tst_qxmppjingleiq.cpp | 1681 ------------- 8 files changed, 5540 insertions(+), 5540 deletions(-) create mode 100644 src/base/QXmppJingleData.cpp create mode 100644 src/base/QXmppJingleData.h delete mode 100644 src/base/QXmppJingleIq.cpp delete mode 100644 src/base/QXmppJingleIq.h create mode 100644 tests/qxmppjingledata/tst_qxmppjingledata.cpp delete mode 100644 tests/qxmppjingleiq/tst_qxmppjingleiq.cpp (limited to 'src/base') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7014ae60..56bebdcb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,7 +35,7 @@ set(INSTALL_HEADER_FILES base/QXmppHttpUploadIq.h base/QXmppIbbIq.h base/QXmppIq.h - base/QXmppJingleIq.h + base/QXmppJingleData.h base/QXmppLogger.h base/QXmppMamIq.h base/QXmppMessage.h @@ -173,7 +173,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 diff --git a/src/base/QXmppJingleData.cpp b/src/base/QXmppJingleData.cpp new file mode 100644 index 00000000..2d497115 --- /dev/null +++ b/src/base/QXmppJingleData.cpp @@ -0,0 +1,3207 @@ +// SPDX-FileCopyrightText: 2010 Jeremy Lainé +// SPDX-FileCopyrightText: 2022 Melvin Keskin +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "QXmppJingleData.h" + +#include "QXmppConstants_p.h" +#include "QXmppUtils.h" + +#include +#include +#include +#include + +static const int RTP_COMPONENT = 1; + +static const char *jingle_actions[] = { + "content-accept", + "content-add", + "content-modify", + "content-reject", + "content-remove", + "description-info", + "security-info", + "session-accept", + "session-info", + "session-initiate", + "session-terminate", + "transport-accept", + "transport-info", + "transport-reject", + "transport-replace", +}; + +static const char *jingle_reasons[] = { + "", + "alternative-session", + "busy", + "cancel", + "connectivity-error", + "decline", + "expired", + "failed-application", + "failed-transport", + "general-error", + "gone", + "incompatible-parameters", + "media-error", + "security-error", + "success", + "timeout", + "unsupported-applications", + "unsupported-transports", +}; + +static const QStringList JINGLE_RTP_ERROR_CONDITIONS = { + {}, + QStringLiteral("invalid-crypto"), + QStringLiteral("crypto-required") +}; + +static const QStringList JINGLE_RTP_HEADER_EXTENSIONS_SENDERS = { + QStringLiteral("both"), + QStringLiteral("initiator"), + QStringLiteral("responder") +}; + +static QString formatFingerprint(const QByteArray &digest) +{ + QString fingerprint; + const QString hx = digest.toHex().toUpper(); + for (int i = 0; i < hx.size(); i += 2) { + if (!fingerprint.isEmpty()) { + fingerprint += ':'; + } + fingerprint += hx.mid(i, 2); + } + return fingerprint; +} + +static QByteArray parseFingerprint(const QString &fingerprint) +{ + QString z = fingerprint; + z.replace(':', ""); + return QByteArray::fromHex(z.toUtf8()); +} + +static QString addressToSdp(const QHostAddress &host) +{ + return QStringLiteral("IN %1 %2").arg(host.protocol() == QAbstractSocket::IPv6Protocol ? QStringLiteral("IP6") : QStringLiteral("IP4"), host.toString()); +} + +static bool candidateParseSdp(QXmppJingleCandidate *candidate, const QString &sdp) +{ + if (!sdp.startsWith(QStringLiteral("candidate:"))) { + return false; + } + + const QStringList bits = sdp.mid(10).split(" "); + if (bits.size() < 6) { + return false; + } + + candidate->setFoundation(bits[0]); + candidate->setComponent(bits[1].toInt()); + candidate->setProtocol(bits[2].toLower()); + candidate->setPriority(bits[3].toInt()); + candidate->setHost(QHostAddress(bits[4])); + candidate->setPort(bits[5].toInt()); + for (int i = 6; i < bits.size() - 1; i += 2) { + if (bits[i] == QStringLiteral("typ")) { + bool ok; + candidate->setType(QXmppJingleCandidate::typeFromString(bits[i + 1], &ok)); + if (!ok) { + return false; + } + } else if (bits[i] == QStringLiteral("generation")) { + candidate->setGeneration(bits[i + 1].toInt()); + } else { + qWarning() << "Candidate SDP contains unknown attribute" << bits[i]; + return false; + } + } + return true; +} + +static QString candidateToSdp(const QXmppJingleCandidate &candidate) +{ + return QStringLiteral("candidate:%1 %2 %3 %4 %5 %6 typ %7 generation %8").arg(candidate.foundation(), QString::number(candidate.component()), candidate.protocol(), QString::number(candidate.priority()), candidate.host().toString(), QString::number(candidate.port()), QXmppJingleCandidate::typeToString(candidate.type()), QString::number(candidate.generation())); +} + +// Parses all found SDP parameter elements of parent into parameters. +static void parseSdpParameters(const QDomElement &parent, QVector ¶meters) +{ + for (auto childElement = parent.firstChildElement(); + !childElement.isNull(); + childElement = childElement.nextSiblingElement()) { + if (QXmppSdpParameter::isSdpParameter(childElement)) { + QXmppSdpParameter parameter; + parameter.parse(childElement); + parameters.append(parameter); + } + } +} + +// Serializes the SDP parameters. +static void sdpParametersToXml(QXmlStreamWriter *writer, const QVector ¶meters) +{ + for (const auto ¶meter : parameters) { + parameter.toXml(writer); + } +} + +// Parses all found RTP Feedback Negotiation elements inside of parent into properties and +// intervals. +static void parseJingleRtpFeedbackNegotiationElements(const QDomElement &parent, QVector &properties, QVector &intervals) +{ + for (auto child = parent.firstChildElement(); + !child.isNull(); + child = child.nextSiblingElement()) { + if (QXmppJingleRtpFeedbackProperty::isJingleRtpFeedbackProperty(child)) { + QXmppJingleRtpFeedbackProperty property; + property.parse(child); + properties.append(property); + } else if (QXmppJingleRtpFeedbackInterval::isJingleRtpFeedbackInterval(child)) { + QXmppJingleRtpFeedbackInterval interval; + interval.parse(child); + intervals.append(interval); + } + } +} + +// Serializes the RTP feedback properties and intervals. +static void jingleRtpFeedbackNegotiationElementsToXml(QXmlStreamWriter *writer, const QVector &properties, const QVector &intervals) +{ + for (const auto &property : properties) { + property.toXml(writer); + } + + for (const auto &interval : intervals) { + interval.toXml(writer); + } +} + +// Parses all found RTP Header Extensions Negotiation elements inside of parent into properties and +// isRtpHeaderExtensionMixingAllowed. +static void parseJingleRtpHeaderExtensionsNegotiationElements(const QDomElement &parent, QVector &properties, bool &isRtpHeaderExtensionMixingAllowed) +{ + for (auto child = parent.firstChildElement(); + !child.isNull(); + child = child.nextSiblingElement()) { + if (QXmppJingleRtpHeaderExtensionProperty::isJingleRtpHeaderExtensionProperty(child)) { + QXmppJingleRtpHeaderExtensionProperty property; + property.parse(child); + properties.append(property); + } else if (child.tagName() == QStringLiteral("extmap-allow-mixed") && child.namespaceURI() == ns_jingle_rtp_header_extensions_negotiation) { + isRtpHeaderExtensionMixingAllowed = true; + } + } +} + +// Serializes the RTP header extension properties and isRtpHeaderExtensionMixingAllowed. +static void jingleRtpHeaderExtensionsNegotiationElementsToXml(QXmlStreamWriter *writer, const QVector &properties, bool isRtpHeaderExtensionMixingAllowed) +{ + for (const auto &property : properties) { + property.toXml(writer); + } + + if (isRtpHeaderExtensionMixingAllowed) { + writer->writeStartElement(QStringLiteral("extmap-allow-mixed")); + writer->writeDefaultNamespace(ns_jingle_rtp_header_extensions_negotiation); + writer->writeEndElement(); + } +} + +class QXmppJingleIqContentPrivate : public QSharedData +{ +public: + QXmppJingleIqContentPrivate(); + + QString creator; + QString disposition; + QString name; + QString senders; + + QXmppJingleDescription description; + bool isRtpMultiplexingSupported = false; + + QString transportType; + QString transportUser; + QString transportPassword; + + QByteArray transportFingerprint; + QString transportFingerprintHash; + QString transportFingerprintSetup; + + QList transportCandidates; + + // XEP-0167: Jingle RTP Sessions + std::optional rtpEncryption; + + // XEP-0293: Jingle RTP Feedback Negotiation + QVector rtpFeedbackProperties; + QVector rtpFeedbackIntervals; + + // XEP-0294: Jingle RTP Header Extensions Negotiation + QVector rtpHeaderExtensionProperties; + bool isRtpHeaderExtensionMixingAllowed = false; +}; + +QXmppJingleIqContentPrivate::QXmppJingleIqContentPrivate() +{ + description.setSsrc(0); +} + +/// +/// \enum QXmppJingleIq::Creator +/// +/// Party that originially generated the content type +/// +/// \since QXmpp 1.5 +/// + +/// +/// \struct QXmppJingleIq::RtpSessionStateActive +/// +/// Actively participating in the session after having been on mute or having put the other party on +/// hold +/// +/// \since QXmpp 1.5 +/// + +/// +/// \struct QXmppJingleIq::RtpSessionStateHold +/// +/// Temporarily not listening for media from the other party +/// +/// \since QXmpp 1.5 +/// + +/// +/// \struct QXmppJingleIq::RtpSessionStateUnhold +/// +/// Ending hold state +/// +/// \since QXmpp 1.5 +/// + +/// +/// \struct QXmppJingleIq::RtpSessionStateMuting +/// +/// State for muting or unmuting +/// +/// \since QXmpp 1.5 +/// + +/// +/// \struct QXmppJingleIq::RtpSessionStateRinging +/// +/// State after the callee acknowledged the call but did not yet interacted with it +/// +/// \since QXmpp 1.5 +/// + +/// +/// \typedef QXmppJingleIq::RtpSessionState +/// +/// Contains the state of an RTP session as specified by \xep{0167, Jingle RTP Sessions} +/// Informational Messages. +/// +/// \since QXmpp 1.5 +/// + +/// Constructs an empty content. +QXmppJingleIq::Content::Content() + : d(new QXmppJingleIqContentPrivate()) +{ +} + +/// Copy-constructor. +QXmppJingleIq::Content::Content(const QXmppJingleIq::Content &other) = default; +/// Move-constructor. +QXmppJingleIq::Content::Content(QXmppJingleIq::Content &&) = default; +/// Assignment operator. +QXmppJingleIq::Content &QXmppJingleIq::Content::operator=(const QXmppJingleIq::Content &) = default; +/// Move-assignment operator. +QXmppJingleIq::Content &QXmppJingleIq::Content::operator=(QXmppJingleIq::Content &&) = default; + +QXmppJingleIq::Content::~Content() = default; + +QString QXmppJingleIq::Content::creator() const +{ + return d->creator; +} + +void QXmppJingleIq::Content::setCreator(const QString &creator) +{ + d->creator = creator; +} + +QString QXmppJingleIq::Content::name() const +{ + return d->name; +} + +void QXmppJingleIq::Content::setName(const QString &name) +{ + d->name = name; +} + +QString QXmppJingleIq::Content::senders() const +{ + return d->senders; +} + +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; +} + +/// +/// Returns whether multiplexing of RTP data and control packets on a single port is supported as +/// specified by \xep{0167, Jingle RTP Sessions} and RFC 5761. +/// +/// \return whether multiplexing of RTP data and control packets is supported +/// +/// \since QXmpp 1.5 +/// +bool QXmppJingleIq::Content::isRtpMultiplexingSupported() const +{ + return d->isRtpMultiplexingSupported; +} + +/// +/// Sets whether multiplexing of RTP data and control packets on a single port is supported as +/// specified by \xep{0167, Jingle RTP Sessions} and RFC 5761. +/// +/// \param isRtpMultiplexingSupported whether multiplexing of RTP data and control packets is +/// supported +/// +/// \since QXmpp 1.5 +/// +void QXmppJingleIq::Content::setRtpMultiplexingSupported(bool isRtpMultiplexingSupported) +{ + d->isRtpMultiplexingSupported = isRtpMultiplexingSupported; +} + +/// +/// Returns the encryption used for SRTP negotiation as specified by +/// \xep{0167, Jingle RTP Sessions}. +/// +/// \return the RTP encryption via SRTP +/// +/// \since QXmpp 1.5 +/// +std::optional QXmppJingleIq::Content::rtpEncryption() const +{ + return d->rtpEncryption; +} + +/// +/// Sets the encryption used for SRTP negotiation as specified by \xep{0167, Jingle RTP Sessions}. +/// +/// \param rtpEncryption RTP encryption via SRTP +/// +/// \since QXmpp 1.5 +/// +void QXmppJingleIq::Content::setRtpEncryption(const std::optional &rtpEncryption) +{ + d->rtpEncryption = rtpEncryption; +} + +void QXmppJingleIq::Content::addTransportCandidate(const QXmppJingleCandidate &candidate) +{ + d->transportType = ns_jingle_ice_udp; + d->transportCandidates << candidate; +} + +QList QXmppJingleIq::Content::transportCandidates() const +{ + return d->transportCandidates; +} + +/// +/// Sets a list of transport candidates. +/// +/// \since QXmpp 0.9.2 +/// +void QXmppJingleIq::Content::setTransportCandidates(const QList &candidates) +{ + d->transportType = candidates.isEmpty() ? QString() : ns_jingle_ice_udp; + d->transportCandidates = candidates; +} + +QString QXmppJingleIq::Content::transportUser() const +{ + return d->transportUser; +} + +void QXmppJingleIq::Content::setTransportUser(const QString &user) +{ + d->transportUser = user; +} + +QString QXmppJingleIq::Content::transportPassword() const +{ + return d->transportPassword; +} + +void QXmppJingleIq::Content::setTransportPassword(const QString &password) +{ + d->transportPassword = password; +} + +/// +/// Returns the properties of RTP feedback. +/// +/// \return the RTP feedback properties +/// +/// \since QXmpp 1.5 +/// +QVector QXmppJingleIq::Content::rtpFeedbackProperties() const +{ + return d->rtpFeedbackProperties; +} + +/// +/// Sets the properties of RTP feedback. +/// +/// \param rtpFeedbackProperties RTP feedback properties +/// +/// \since QXmpp 1.5 +/// +void QXmppJingleIq::Content::setRtpFeedbackProperties(const QVector &rtpFeedbackProperties) +{ + d->rtpFeedbackProperties = rtpFeedbackProperties; +} + +/// +/// Returns the intervals of RTP feedback. +/// +/// \return the RTP feedback intervals +/// +/// \since QXmpp 1.5 +/// +QVector QXmppJingleIq::Content::rtpFeedbackIntervals() const +{ + return d->rtpFeedbackIntervals; +} + +/// +/// Sets the intervals of RTP feedback. +/// +/// \param rtpFeedbackIntervals RTP feedback intervals +/// +/// \since QXmpp 1.5 +/// +void QXmppJingleIq::Content::setRtpFeedbackIntervals(const QVector &rtpFeedbackIntervals) +{ + d->rtpFeedbackIntervals = rtpFeedbackIntervals; +} + +/// +/// Returns the RTP header extension properties. +/// +/// \return the RTP header extension properties +/// +/// \since QXmpp 1.5 +/// +QVector QXmppJingleIq::Content::rtpHeaderExtensionProperties() const +{ + return d->rtpHeaderExtensionProperties; +} + +/// +/// Sets the RTP header extension properties. +/// +/// \param rtpHeaderExtensionProperties RTP header extension properties +/// +/// \since QXmpp 1.5 +/// +void QXmppJingleIq::Content::setRtpHeaderExtensionProperties(const QVector &rtpHeaderExtensionProperties) +{ + d->rtpHeaderExtensionProperties = rtpHeaderExtensionProperties; +} + +/// +/// Returns whether mixing of RTP header extensions is allowed corresponding to the +/// "extmap-allow-mixed" element as specified by +/// \xep{0293, Jingle RTP Header Extensions Negotiation}. +/// +/// \return whether mixing of RTP header extensions is allowed +/// +/// \since QXmpp 1.5 +/// +bool QXmppJingleIq::Content::isRtpHeaderExtensionMixingAllowed() const +{ + return d->isRtpHeaderExtensionMixingAllowed; +} + +/// +/// Sets whether mixing of RTP header extensions is allowed corresponding to the +/// "extmap-allow-mixed" element as specified by +/// \xep{0293, Jingle RTP Header Extensions Negotiation}. +/// +/// \param isAllowed whether mixing of RTP header extensions is allowed +/// +/// \since QXmpp 1.5 +/// +void QXmppJingleIq::Content::setRtpHeaderExtensionMixingAllowed(bool isRtpHeaderExtensionMixingAllowed) +{ + d->isRtpHeaderExtensionMixingAllowed = isRtpHeaderExtensionMixingAllowed; +} + +/// +/// Returns the fingerprint hash value for the transport key. +/// +/// This is used for DTLS-SRTP as defined in \xep{0320}. +/// +/// \since QXmpp 0.9 +/// +QByteArray QXmppJingleIq::Content::transportFingerprint() const +{ + return d->transportFingerprint; +} + +/// +/// Sets the fingerprint hash value for the transport key. +/// +/// This is used for DTLS-SRTP as defined in \xep{0320}. +/// +/// \since QXmpp 0.9 +/// +void QXmppJingleIq::Content::setTransportFingerprint(const QByteArray &fingerprint) +{ + d->transportFingerprint = fingerprint; +} + +/// +/// Returns the fingerprint hash algorithm for the transport key. +/// +/// This is used for DTLS-SRTP as defined in \xep{0320}. +/// +/// \since QXmpp 0.9 +/// +QString QXmppJingleIq::Content::transportFingerprintHash() const +{ + return d->transportFingerprintHash; +} + +/// +/// Sets the fingerprint hash algorithm for the transport key. +/// +/// This is used for DTLS-SRTP as defined in \xep{0320}. +/// +/// \since QXmpp 0.9 +/// +void QXmppJingleIq::Content::setTransportFingerprintHash(const QString &hash) +{ + d->transportFingerprintHash = hash; +} + +/// +/// Returns the setup role for the encrypted transport. +/// +/// This is used for DTLS-SRTP as defined in \xep{0320}. +/// +/// \since QXmpp 0.9 +/// +QString QXmppJingleIq::Content::transportFingerprintSetup() const +{ + return d->transportFingerprintSetup; +} + +/// +/// Sets the setup role for the encrypted transport. +/// +/// This is used for DTLS-SRTP as defined in \xep{0320}. +/// +/// \since QXmpp 0.9 +/// +void QXmppJingleIq::Content::setTransportFingerprintSetup(const QString &setup) +{ + d->transportFingerprintSetup = setup; +} + +/// \cond +void QXmppJingleIq::Content::parse(const QDomElement &element) +{ + d->creator = element.attribute(QStringLiteral("creator")); + d->disposition = element.attribute(QStringLiteral("disposition")); + d->name = element.attribute(QStringLiteral("name")); + d->senders = element.attribute(QStringLiteral("senders")); + + // description + QDomElement descriptionElement = element.firstChildElement(QStringLiteral("description")); + 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(); + !childElement.isNull(); + childElement = childElement.nextSiblingElement()) { + if (QXmppJingleRtpEncryption::isJingleRtpEncryption(childElement)) { + QXmppJingleRtpEncryption encryption; + encryption.parse(childElement); + d->rtpEncryption = encryption; + break; + } + } + + parseJingleRtpFeedbackNegotiationElements(descriptionElement, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); + parseJingleRtpHeaderExtensionsNegotiationElements(descriptionElement, d->rtpHeaderExtensionProperties, d->isRtpHeaderExtensionMixingAllowed); + + QDomElement child = descriptionElement.firstChildElement(QStringLiteral("payload-type")); + while (!child.isNull()) { + QXmppJinglePayloadType payload; + payload.parse(child); + d->description.addPayloadType(payload); + child = child.nextSiblingElement(QStringLiteral("payload-type")); + } + + // transport + QDomElement transportElement = element.firstChildElement(QStringLiteral("transport")); + d->transportType = transportElement.namespaceURI(); + d->transportUser = transportElement.attribute(QStringLiteral("ufrag")); + d->transportPassword = transportElement.attribute(QStringLiteral("pwd")); + child = transportElement.firstChildElement(QStringLiteral("candidate")); + while (!child.isNull()) { + QXmppJingleCandidate candidate; + candidate.parse(child); + d->transportCandidates << candidate; + child = child.nextSiblingElement(QStringLiteral("candidate")); + } + + /// XEP-0320 + child = transportElement.firstChildElement(QStringLiteral("fingerprint")); + if (!child.isNull()) { + d->transportFingerprint = parseFingerprint(child.text()); + d->transportFingerprintHash = child.attribute(QStringLiteral("hash")); + d->transportFingerprintSetup = child.attribute(QStringLiteral("setup")); + } +} + +void QXmppJingleIq::Content::toXml(QXmlStreamWriter *writer) const +{ + if (d->creator.isEmpty() || d->name.isEmpty()) { + return; + } + + writer->writeStartElement(QStringLiteral("content")); + helperToXmlAddAttribute(writer, QStringLiteral("creator"), d->creator); + helperToXmlAddAttribute(writer, QStringLiteral("disposition"), d->disposition); + helperToXmlAddAttribute(writer, QStringLiteral("name"), d->name); + helperToXmlAddAttribute(writer, QStringLiteral("senders"), d->senders); + + // description + if (!d->description.type().isEmpty() || !d->description.payloadTypes().isEmpty()) { + writer->writeStartElement(QStringLiteral("description")); + writer->writeDefaultNamespace(d->description.type()); + helperToXmlAddAttribute(writer, QStringLiteral("media"), d->description.media()); + + if (d->description.ssrc()) { + writer->writeAttribute(QStringLiteral("ssrc"), QString::number(d->description.ssrc())); + } + + if (d->isRtpMultiplexingSupported) { + writer->writeEmptyElement(QStringLiteral("rtcp-mux")); + } + + if (d->rtpEncryption) { + d->rtpEncryption->toXml(writer); + } + + jingleRtpFeedbackNegotiationElementsToXml(writer, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); + jingleRtpHeaderExtensionsNegotiationElementsToXml(writer, d->rtpHeaderExtensionProperties, d->isRtpHeaderExtensionMixingAllowed); + + for (const auto &payload : d->description.payloadTypes()) { + payload.toXml(writer); + } + + writer->writeEndElement(); + } + + // transport + if (!d->transportType.isEmpty() || !d->transportCandidates.isEmpty()) { + writer->writeStartElement(QStringLiteral("transport")); + writer->writeDefaultNamespace(d->transportType); + helperToXmlAddAttribute(writer, QStringLiteral("ufrag"), d->transportUser); + helperToXmlAddAttribute(writer, QStringLiteral("pwd"), d->transportPassword); + for (const auto &candidate : d->transportCandidates) { + candidate.toXml(writer); + } + + // XEP-0320: Use of DTLS-SRTP in Jingle Sessions + if (!d->transportFingerprint.isEmpty() && !d->transportFingerprintHash.isEmpty()) { + writer->writeStartElement(QStringLiteral("fingerprint")); + writer->writeDefaultNamespace(ns_jingle_dtls); + writer->writeAttribute(QStringLiteral("hash"), d->transportFingerprintHash); + writer->writeAttribute(QStringLiteral("setup"), d->transportFingerprintSetup); + writer->writeCharacters(formatFingerprint(d->transportFingerprint)); + writer->writeEndElement(); + } + writer->writeEndElement(); + } + + writer->writeEndElement(); +} + +bool QXmppJingleIq::Content::parseSdp(const QString &sdp) +{ + QList payloads; + for (auto &line : sdp.split(QChar(u'\n'))) { + if (line.endsWith('\r')) { + line.resize(line.size() - 1); + } + if (line.startsWith(QStringLiteral("a="))) { + int idx = line.indexOf(':'); + const QString attrName = idx != -1 ? line.mid(2, idx - 2) : line.mid(2); + const QString attrValue = idx != -1 ? line.mid(idx + 1) : ""; + + if (attrName == QStringLiteral("candidate")) { + QXmppJingleCandidate candidate; + if (!candidateParseSdp(&candidate, line.mid(2))) { + qWarning() << "Could not parse candidate" << line; + return false; + } + addTransportCandidate(candidate); + } else if (attrName == QStringLiteral("fingerprint")) { + const QStringList bits = attrValue.split(' '); + if (bits.size() > 1) { + d->transportFingerprintHash = bits[0]; + d->transportFingerprint = parseFingerprint(bits[1]); + } + } else if (attrName == QStringLiteral("fmtp")) { + int spIdx = attrValue.indexOf(' '); + if (spIdx == -1) { + qWarning() << "Could not parse payload parameters" << line; + return false; + } + const int id = attrValue.left(spIdx).toInt(); + const QString paramStr = attrValue.mid(spIdx + 1); + for (auto &payload : payloads) { + if (payload.id() == id) { + QMap params; + if (payload.name() == QStringLiteral("telephone-event")) { + params.insert(QStringLiteral("events"), paramStr); + } else { + thread_local static const auto regex = QRegularExpression(";\\s*"); + const auto paramParts = paramStr.split(regex); + for (const auto &p : paramParts) { + const QStringList bits = p.split('='); + if (bits.size() == 2) { + params.insert(bits.at(0), bits.at(1)); + } + } + } + payload.setParameters(params); + } + } + } else if (attrName == QStringLiteral("rtpmap")) { + // payload type map + const QStringList bits = attrValue.split(' '); + if (bits.size() != 2) { + continue; + } + bool ok = false; + const int id = bits[0].toInt(&ok); + if (!ok) { + continue; + } + + const QStringList args = bits[1].split('/'); + for (auto &payload : payloads) { + if (payload.id() == id) { + payload.setName(args[0]); + if (args.size() > 1) { + payload.setClockrate(args[1].toInt()); + } + if (args.size() > 2) { + payload.setChannels(args[2].toInt()); + } + } + } + } else if (attrName == QStringLiteral("ice-ufrag")) { + d->transportUser = attrValue; + } else if (attrName == QStringLiteral("ice-pwd")) { + d->transportPassword = attrValue; + } else if (attrName == QStringLiteral("setup")) { + d->transportFingerprintSetup = attrValue; + } else if (attrName == QStringLiteral("ssrc")) { + const QStringList bits = attrValue.split(' '); + if (bits.isEmpty()) { + qWarning() << "Could not parse ssrc" << line; + return false; + } + d->description.setSsrc(bits[0].toULong()); + } + } else if (line.startsWith(QStringLiteral("m="))) { + // FIXME: what do we do with the profile (bits[2]) ? + QStringList bits = line.mid(2).split(' '); + if (bits.size() < 3) { + qWarning() << "Could not parse media" << line; + return false; + } + d->description.setMedia(bits[0]); + + // parse payload types + for (int i = 3; i < bits.size(); ++i) { + bool ok = false; + int id = bits[i].toInt(&ok); + if (!ok) { + continue; + } + QXmppJinglePayloadType payload; + payload.setId(id); + payloads << payload; + } + } + } + + d->description.setPayloadTypes(payloads); + return true; +} + +static bool candidateLessThan(const QXmppJingleCandidate &c1, const QXmppJingleCandidate &c2) +{ + if (c1.type() == c2.type()) { + return c1.priority() > c2.priority(); + } else { + return c1.type() == QXmppJingleCandidate::ServerReflexiveType; + } +} + +QString QXmppJingleIq::Content::toSdp() const +{ + // get default candidate + QHostAddress localRtpAddress = QHostAddress::Any; + quint16 localRtpPort = 0; + QList sortedCandidates = d->transportCandidates; + std::sort(sortedCandidates.begin(), sortedCandidates.end(), candidateLessThan); + for (const auto &candidate : sortedCandidates) { + if (candidate.component() == RTP_COMPONENT) { + localRtpAddress = candidate.host(); + localRtpPort = candidate.port(); + break; + } + } + + QStringList sdp; + + // media + QString payloads; + QStringList attrs; + 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) { + rtpmap += "/" + QString::number(payload.channels()); + } + attrs << "a=rtpmap:" + rtpmap; + + // payload parameters + QStringList paramList; + const QMap params = payload.parameters(); + if (payload.name() == QStringLiteral("telephone-event")) { + if (params.contains(QStringLiteral("events"))) { + paramList << params.value(QStringLiteral("events")); + } + } else { + QMap::const_iterator i; + for (i = params.begin(); i != params.end(); ++i) { + paramList << i.key() + QStringLiteral("=") + i.value(); + } + } + if (!paramList.isEmpty()) { + attrs << QStringLiteral("a=fmtp:") + QByteArray::number(payload.id()) + QStringLiteral(" ") + paramList.join("; "); + } + } + 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; + + // transport + for (const auto &candidate : d->transportCandidates) { + sdp << QStringLiteral("a=%1").arg(candidateToSdp(candidate)); + } + if (!d->transportUser.isEmpty()) { + sdp << QStringLiteral("a=ice-ufrag:%1").arg(d->transportUser); + } + if (!d->transportPassword.isEmpty()) { + sdp << QStringLiteral("a=ice-pwd:%1").arg(d->transportPassword); + } + if (!d->transportFingerprint.isEmpty() && !d->transportFingerprintHash.isEmpty()) { + sdp << QStringLiteral("a=fingerprint:%1 %2").arg(d->transportFingerprintHash, formatFingerprint(d->transportFingerprint)); + } + if (!d->transportFingerprintSetup.isEmpty()) { + sdp << QStringLiteral("a=setup:%1").arg(d->transportFingerprintSetup); + } + + return sdp.join("\r\n") + "\r\n"; +} + +/// \endcond + +class QXmppJingleIqReasonPrivate : public QSharedData +{ +public: + QXmppJingleIqReasonPrivate(); + + QString m_text; + QXmppJingleReason::Type m_type; + QXmppJingleReason::RtpErrorCondition m_rtpErrorCondition; + QString m_namespaceUri; +}; + +QXmppJingleIqReasonPrivate::QXmppJingleIqReasonPrivate() + : m_type(QXmppJingleReason::Type::None), + m_rtpErrorCondition(QXmppJingleReason::RtpErrorCondition::NoErrorCondition) +{ +} + +/// +/// \enum QXmppJingleIqReason::RtpErrorCondition +/// +/// Condition of an RTP-specific error +/// +/// \since QXmpp 1.5 +/// + +/// +/// \class QXmppJingleReason +/// +/// The QXmppJingleReason class represents the "reason" element of a +/// QXmppJingle element. +/// + +QXmppJingleReason::QXmppJingleReason() + : d(new QXmppJingleIqReasonPrivate()) +{ +} + +/// Returns the reason's textual description. + +QString QXmppJingleReason::text() const +{ + return d->m_text; +} + +/// Sets the reason's textual description. + +void QXmppJingleReason::setText(const QString &text) +{ + d->m_text = text; +} + +/// Gets the reason's type. + +QXmppJingleReason::Type QXmppJingleReason::type() const +{ + return d->m_type; +} + +/// Sets the reason's type. + +void QXmppJingleReason::setType(QXmppJingleReason::Type type) +{ + d->m_type = type; +} + +/// +/// Returns the RTP error condition as specified by \xep{0167, Jingle RTP Sessions}. +/// +/// \return the RTP error condition +/// +/// \since QXmpp 1.5 +/// +QXmppJingleReason::RtpErrorCondition QXmppJingleReason::rtpErrorCondition() const +{ + return d->m_rtpErrorCondition; +} + +/// +/// Sets the RTP error condition as specified by \xep{0167, Jingle RTP Sessions}. +/// +/// \param rtpErrorCondition RTP error condition +/// +/// \since QXmpp 1.5 +/// +void QXmppJingleReason::setRtpErrorCondition(RtpErrorCondition rtpErrorCondition) +{ + d->m_rtpErrorCondition = rtpErrorCondition; +} + +/// +/// Returns the namespace URI of a reason element. +/// +QString QXmppJingleReason::namespaceUri() const +{ + return d->m_namespaceUri; +} + +/// +/// Sets the namespace URI of a reason element. +/// +void QXmppJingleReason::setNamespaceUri(const QString &namespaceUri) +{ + d->m_namespaceUri = namespaceUri; +} + +/// \cond +void QXmppJingleReason::parse(const QDomElement &element) +{ + d->m_text = element.firstChildElement(QStringLiteral("text")).text(); + for (int i = AlternativeSession; i <= UnsupportedTransports; i++) { + if (!element.firstChildElement(jingle_reasons[i]).isNull()) { + d->m_type = static_cast(i); + break; + } + } + + for (auto child = element.firstChildElement(); + !child.isNull(); + child = child.nextSiblingElement()) { + if (child.namespaceURI() == ns_jingle_rtp_errors) { + if (const auto index = JINGLE_RTP_ERROR_CONDITIONS.indexOf(child.tagName()); + index != -1) { + d->m_rtpErrorCondition = RtpErrorCondition(index); + } + break; + } + } + + if (!element.namespaceURI().isEmpty() && !element.parentNode().isNull() && element.parentNode().namespaceURI() != element.namespaceURI()) { + d->m_namespaceUri = element.namespaceURI(); + } +} + +void QXmppJingleReason::toXml(QXmlStreamWriter *writer) const +{ + if (d->m_type < AlternativeSession || d->m_type > UnsupportedTransports) { + return; + } + + writer->writeStartElement(QStringLiteral("reason")); + + if (!d->m_namespaceUri.isEmpty()) { + writer->writeDefaultNamespace(d->m_namespaceUri); + } + + if (!d->m_text.isEmpty()) { + helperToXmlAddTextElement(writer, QStringLiteral("text"), d->m_text); + } + writer->writeEmptyElement(jingle_reasons[d->m_type]); + + if (d->m_rtpErrorCondition != NoErrorCondition) { + writer->writeStartElement(JINGLE_RTP_ERROR_CONDITIONS.at(d->m_rtpErrorCondition)); + writer->writeDefaultNamespace(ns_jingle_rtp_errors); + writer->writeEndElement(); + } + + writer->writeEndElement(); +} +/// \endcond + +class QXmppJingleIqPrivate : public QSharedData +{ +public: + QXmppJingleIqPrivate(); + + QXmppJingleIq::Action action; + QString initiator; + QString responder; + QString sid; + + QString mujiGroupChatJid; + + QList contents; + QXmppJingleReason reason; + + std::optional rtpSessionState; +}; + +QXmppJingleIqPrivate::QXmppJingleIqPrivate() + : action(QXmppJingleIq::ContentAccept) +{ +} + +/// Constructs a QXmppJingleIq. +QXmppJingleIq::QXmppJingleIq() + : d(new QXmppJingleIqPrivate()) +{ +} + +/// Copy-constructor. +QXmppJingleIq::QXmppJingleIq(const QXmppJingleIq &) = default; +/// Move-constructor. +QXmppJingleIq::QXmppJingleIq(QXmppJingleIq &&) = default; + +QXmppJingleIq::~QXmppJingleIq() = default; + +/// Assignment operator. +QXmppJingleIq &QXmppJingleIq::operator=(const QXmppJingleIq &) = default; +/// Move-assignment operator. +QXmppJingleIq &QXmppJingleIq::operator=(QXmppJingleIq &&) = default; + +/// +/// Returns the Jingle IQ's action. +/// +QXmppJingleIq::Action QXmppJingleIq::action() const +{ + return d->action; +} + +/// Sets the Jingle IQ's action. +/// +/// \param action + +void QXmppJingleIq::setAction(QXmppJingleIq::Action action) +{ + d->action = action; +} + +/// +/// Adds an element to the IQ's content elements. +/// +/// \since QXmpp 0.9.2 +/// +void QXmppJingleIq::addContent(const QXmppJingleIq::Content &content) +{ + d->contents << content; +} + +/// +/// Returns the IQ's content elements. +/// +/// \since QXmpp 0.9.2 +/// +QList QXmppJingleIq::contents() const +{ + return d->contents; +} + +/// +/// Sets the IQ's content elements. +/// +/// \since QXmpp 0.9.2 +/// +void QXmppJingleIq::setContents(const QList &contents) +{ + d->contents = contents; +} + +/// Returns the session initiator. + +QString QXmppJingleIq::initiator() const +{ + return d->initiator; +} + +/// Sets the session initiator. +/// +/// \param initiator + +void QXmppJingleIq::setInitiator(const QString &initiator) +{ + d->initiator = initiator; +} + +/// Returns a reference to the IQ's reason element. + +QXmppJingleReason &QXmppJingleIq::reason() +{ + return d->reason; +} + +/// Returns a const reference to the IQ's reason element. + +const QXmppJingleReason &QXmppJingleIq::reason() const +{ + return d->reason; +} + +/// Returns the session responder. + +QString QXmppJingleIq::responder() const +{ + return d->responder; +} + +/// Sets the session responder. +/// +/// \param responder + +void QXmppJingleIq::setResponder(const QString &responder) +{ + d->responder = responder; +} + +/// +/// Returns true if the call is ringing. +/// +/// \deprecated This method is deprecated since QXmpp 1.5. Use \c QXmppJingleIq::rtpSessionState() +/// instead. +/// +bool QXmppJingleIq::ringing() const +{ + if (d->rtpSessionState) { + return std::holds_alternative(*d->rtpSessionState); + } + + return false; +} + +/// +/// Set to true if the call is ringing. +/// +/// \param ringing +/// +/// \deprecated This method is deprecated since QXmpp 1.5. Use +/// \c QXmppJingleIq::setRtpSessionState() instead. +/// +void QXmppJingleIq::setRinging(bool ringing) +{ + if (ringing) { + d->rtpSessionState = RtpSessionStateRinging(); + } else { + d->rtpSessionState = std::nullopt; + } +} + +/// Returns the session ID. +QString QXmppJingleIq::sid() const +{ + return d->sid; +} + +/// Sets the session ID. +/// +/// \param sid + +void QXmppJingleIq::setSid(const QString &sid) +{ + d->sid = sid; +} + +/// +/// Returns the JID of the \xep{0272, Multiparty Jingle (Muji)} group chat. +/// +/// \return the Muji group chat JID +/// +/// \since QXmpp 1.5 +/// +QString QXmppJingleIq::mujiGroupChatJid() const +{ + return d->mujiGroupChatJid; +} + +/// +/// Sets the JID of the \xep{0272, Multiparty Jingle (Muji)} group chat. +/// +/// \param mujiGroupChatJid Muji group chat JID +/// +/// \since QXmpp 1.5 +/// +void QXmppJingleIq::setMujiGroupChatJid(const QString &mujiGroupChatJid) +{ + d->mujiGroupChatJid = mujiGroupChatJid; +} + +/// +/// Returns the state of an RTP session as specified by \xep{0167, Jingle RTP Sessions} +/// Informational Messages. +/// +/// \return the session's state +/// +/// \since QXmpp 1.5 +/// +std::optional QXmppJingleIq::rtpSessionState() const +{ + return d->rtpSessionState; +} + +/// +/// Sets the state of an RTP session as specified by \xep{0167, Jingle RTP Sessions} Informational +/// Messages. +/// +/// The appropriate action is set as well. +/// Thus, it is not needed to set it manually. +/// +/// \param rtpSessionState session's state +/// +/// \since QXmpp 1.5 +/// +void QXmppJingleIq::setRtpSessionState(const std::optional &rtpSessionState) +{ + d->rtpSessionState = rtpSessionState; + d->action = Action::SessionInfo; +} + +/// \cond +bool QXmppJingleIq::isJingleIq(const QDomElement &element) +{ + QDomElement jingleElement = element.firstChildElement(QStringLiteral("jingle")); + return (jingleElement.namespaceURI() == ns_jingle); +} + +void QXmppJingleIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement jingleElement = element.firstChildElement(QStringLiteral("jingle")); + const QString action = jingleElement.attribute(QStringLiteral("action")); + for (int i = ContentAccept; i <= TransportReplace; i++) { + if (action == jingle_actions[i]) { + d->action = static_cast(i); + break; + } + } + d->initiator = jingleElement.attribute(QStringLiteral("initiator")); + d->responder = jingleElement.attribute(QStringLiteral("responder")); + d->sid = jingleElement.attribute(QStringLiteral("sid")); + + // XEP-0272: Multiparty Jingle (Muji) + if (const auto mujiGroupChatElement = jingleElement.firstChildElement(QStringLiteral("muji")); + mujiGroupChatElement.namespaceURI() == ns_muji) { + d->mujiGroupChatJid = mujiGroupChatElement.attribute(QStringLiteral("room")); + } + + // content + d->contents.clear(); + QDomElement contentElement = jingleElement.firstChildElement(QStringLiteral("content")); + while (!contentElement.isNull()) { + Content content; + content.parse(contentElement); + addContent(content); + contentElement = contentElement.nextSiblingElement(QStringLiteral("content")); + } + + QDomElement reasonElement = jingleElement.firstChildElement(QStringLiteral("reason")); + d->reason.parse(reasonElement); + + for (auto childElement = jingleElement.firstChildElement(); + !childElement.isNull(); + childElement = childElement.nextSiblingElement()) { + if (childElement.namespaceURI() == ns_jingle_rtp_info) { + const auto elementTag = childElement.tagName(); + + if (elementTag == QStringLiteral("active")) { + d->rtpSessionState = RtpSessionStateActive(); + } else if (elementTag == QStringLiteral("hold")) { + d->rtpSessionState = RtpSessionStateHold(); + } else if (elementTag == QStringLiteral("unhold")) { + d->rtpSessionState = RtpSessionStateUnhold(); + } else if (const auto isMute = elementTag == QStringLiteral("mute"); isMute || elementTag == QStringLiteral("unmute")) { + RtpSessionStateMuting muting; + muting.isMute = isMute; + + if (const auto creator = childElement.attribute(QStringLiteral("creator")); creator == QStringLiteral("initiator")) { + muting.creator = Initiator; + } else if (creator == QStringLiteral("responder")) { + muting.creator = Responder; + } + + muting.name = childElement.attribute(QStringLiteral("name")); + + d->rtpSessionState = muting; + } else if (elementTag == QStringLiteral("ringing")) { + d->rtpSessionState = RtpSessionStateRinging(); + } + } + } +} + +void QXmppJingleIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("jingle")); + writer->writeDefaultNamespace(ns_jingle); + helperToXmlAddAttribute(writer, QStringLiteral("action"), jingle_actions[d->action]); + helperToXmlAddAttribute(writer, QStringLiteral("initiator"), d->initiator); + helperToXmlAddAttribute(writer, QStringLiteral("responder"), d->responder); + helperToXmlAddAttribute(writer, QStringLiteral("sid"), d->sid); + + // XEP-0272: Multiparty Jingle (Muji) + if (!d->mujiGroupChatJid.isEmpty()) { + writer->writeStartElement(QStringLiteral("muji")); + writer->writeDefaultNamespace(ns_muji); + helperToXmlAddAttribute(writer, QStringLiteral("room"), d->mujiGroupChatJid); + writer->writeEndElement(); + } + + for (const auto &content : d->contents) { + content.toXml(writer); + } + + d->reason.toXml(writer); + + const auto writeStartElementWithNamespace = [=](const QString &tagName) { + writer->writeStartElement(tagName); + writer->writeDefaultNamespace(ns_jingle_rtp_info); + }; + + if (d->rtpSessionState) { + if (std::holds_alternative(*d->rtpSessionState)) { + writeStartElementWithNamespace(QStringLiteral("active")); + } else if (std::holds_alternative(*d->rtpSessionState)) { + writeStartElementWithNamespace(QStringLiteral("hold")); + } else if (std::holds_alternative(*d->rtpSessionState)) { + writeStartElementWithNamespace(QStringLiteral("unhold")); + } else if (auto rtpSessionStateMuting = std::get_if(&(*d->rtpSessionState))) { + if (rtpSessionStateMuting->isMute) { + writeStartElementWithNamespace(QStringLiteral("mute")); + } else { + writeStartElementWithNamespace(QStringLiteral("unmute")); + } + + if (rtpSessionStateMuting->creator == Initiator) { + helperToXmlAddAttribute(writer, QStringLiteral("creator"), QStringLiteral("initiator")); + } else if (rtpSessionStateMuting->creator == Responder) { + helperToXmlAddAttribute(writer, QStringLiteral("creator"), QStringLiteral("responder")); + } + + helperToXmlAddAttribute(writer, QStringLiteral("name"), rtpSessionStateMuting->name); + } else { + writeStartElementWithNamespace(QStringLiteral("ringing")); + } + + writer->writeEndElement(); + } + + writer->writeEndElement(); +} +/// \endcond + +class QXmppJingleCandidatePrivate : public QSharedData +{ +public: + QXmppJingleCandidatePrivate(); + + int component; + QString foundation; + int generation; + QHostAddress host; + QString id; + int network; + quint16 port; + QString protocol; + int priority; + QXmppJingleCandidate::Type type; +}; + +QXmppJingleCandidatePrivate::QXmppJingleCandidatePrivate() + : component(0), generation(0), network(0), port(0), priority(0), type(QXmppJingleCandidate::HostType) +{ +} + +/// +/// Constructs an empty candidate. +/// +QXmppJingleCandidate::QXmppJingleCandidate() + : d(new QXmppJingleCandidatePrivate()) +{ +} + +/// Copy-constructor. +QXmppJingleCandidate::QXmppJingleCandidate(const QXmppJingleCandidate &other) = default; +/// Move-constructor. +QXmppJingleCandidate::QXmppJingleCandidate(QXmppJingleCandidate &&) = default; +QXmppJingleCandidate::~QXmppJingleCandidate() = default; +/// Assignment operator. +QXmppJingleCandidate &QXmppJingleCandidate::operator=(const QXmppJingleCandidate &other) = default; +/// Move-assignment operator. +QXmppJingleCandidate &QXmppJingleCandidate::operator=(QXmppJingleCandidate &&) = default; + +/// Returns the candidate's component ID. + +int QXmppJingleCandidate::component() const +{ + return d->component; +} + +/// Sets the candidates's component ID. +/// +/// \param component + +void QXmppJingleCandidate::setComponent(int component) +{ + d->component = component; +} + +/// +/// Returns the candidate's foundation. +/// +/// \since QXmpp 0.9 +/// +QString QXmppJingleCandidate::foundation() const +{ + return d->foundation; +} + +/// +/// Sets the candidate's foundation. +/// +/// \param foundation +/// +/// \since QXmpp 0.9 +/// +void QXmppJingleCandidate::setFoundation(const QString &foundation) +{ + d->foundation = foundation; +} + +/// +/// Returns the candidate's generation. +/// +/// \since QXmpp 0.9 +/// +int QXmppJingleCandidate::generation() const +{ + return d->generation; +} + +/// +/// Sets the candidate's generation. +/// +/// \param generation +/// +/// \since QXmpp 0.9 +/// +void QXmppJingleCandidate::setGeneration(int generation) +{ + d->generation = generation; +} + +/// Returns the candidate's host address. +/// + +QHostAddress QXmppJingleCandidate::host() const +{ + return d->host; +} + +/// Sets the candidate's host address. +/// +/// \param host + +void QXmppJingleCandidate::setHost(const QHostAddress &host) +{ + d->host = host; +} + +/// Returns the candidate's unique identifier. +/// + +QString QXmppJingleCandidate::id() const +{ + return d->id; +} + +/// Sets the candidate's unique identifier. +/// +/// \param id + +void QXmppJingleCandidate::setId(const QString &id) +{ + d->id = id; +} + +/// Returns the network index (starting at 0) the candidate is on. +/// + +int QXmppJingleCandidate::network() const +{ + return d->network; +} + +/// Sets the network index (starting at 0) the candidate is on. +/// +/// \param network + +void QXmppJingleCandidate::setNetwork(int network) +{ + d->network = network; +} + +/// Returns the candidate's port number. +/// + +quint16 QXmppJingleCandidate::port() const +{ + return d->port; +} + +/// Sets the candidate's port number. +/// +/// \param port + +void QXmppJingleCandidate::setPort(quint16 port) +{ + d->port = port; +} + +/// Returns the candidate's priority. +/// + +int QXmppJingleCandidate::priority() const +{ + return d->priority; +} + +/// Sets the candidate's priority. +/// +/// \param priority + +void QXmppJingleCandidate::setPriority(int priority) +{ + d->priority = priority; +} + +/// Returns the candidate's protocol (e.g. "udp"). +/// + +QString QXmppJingleCandidate::protocol() const +{ + return d->protocol; +} + +/// Sets the candidate's protocol (e.g. "udp"). +/// +/// \param protocol + +void QXmppJingleCandidate::setProtocol(const QString &protocol) +{ + d->protocol = protocol; +} + +/// Returns the candidate type (e.g. "host"). +/// + +QXmppJingleCandidate::Type QXmppJingleCandidate::type() const +{ + return d->type; +} + +/// Sets the candidate type (e.g. "host"). +/// +/// \param type + +void QXmppJingleCandidate::setType(QXmppJingleCandidate::Type type) +{ + d->type = type; +} + +/// Returns true if the host address or port are empty. +/// + +bool QXmppJingleCandidate::isNull() const +{ + return d->host.isNull() || !d->port; +} + +/// \cond +void QXmppJingleCandidate::parse(const QDomElement &element) +{ + d->component = element.attribute(QStringLiteral("component")).toInt(); + d->foundation = element.attribute(QStringLiteral("foundation")); + d->generation = element.attribute(QStringLiteral("generation")).toInt(); + d->host = QHostAddress(element.attribute(QStringLiteral("ip"))); + d->id = element.attribute(QStringLiteral("id")); + d->network = element.attribute(QStringLiteral("network")).toInt(); + d->port = element.attribute(QStringLiteral("port")).toInt(); + d->priority = element.attribute(QStringLiteral("priority")).toInt(); + d->protocol = element.attribute(QStringLiteral("protocol")); + d->type = typeFromString(element.attribute(QStringLiteral("type"))); +} + +void QXmppJingleCandidate::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("candidate")); + helperToXmlAddAttribute(writer, QStringLiteral("component"), QString::number(d->component)); + helperToXmlAddAttribute(writer, QStringLiteral("foundation"), d->foundation); + helperToXmlAddAttribute(writer, QStringLiteral("generation"), QString::number(d->generation)); + helperToXmlAddAttribute(writer, QStringLiteral("id"), d->id); + helperToXmlAddAttribute(writer, QStringLiteral("ip"), d->host.toString()); + helperToXmlAddAttribute(writer, QStringLiteral("network"), QString::number(d->network)); + helperToXmlAddAttribute(writer, QStringLiteral("port"), QString::number(d->port)); + helperToXmlAddAttribute(writer, QStringLiteral("priority"), QString::number(d->priority)); + helperToXmlAddAttribute(writer, QStringLiteral("protocol"), d->protocol); + helperToXmlAddAttribute(writer, QStringLiteral("type"), typeToString(d->type)); + writer->writeEndElement(); +} + +QXmppJingleCandidate::Type QXmppJingleCandidate::typeFromString(const QString &typeStr, bool *ok) +{ + QXmppJingleCandidate::Type type; + if (typeStr == QStringLiteral("host")) { + type = HostType; + } else if (typeStr == QStringLiteral("prflx")) { + type = PeerReflexiveType; + } else if (typeStr == QStringLiteral("srflx")) { + type = ServerReflexiveType; + } else if (typeStr == QStringLiteral("relay")) { + type = RelayedType; + } else { + qWarning() << "Unknown candidate type" << typeStr; + if (ok) { + *ok = false; + } + return HostType; + } + if (ok) { + *ok = true; + } + return type; +} + +QString QXmppJingleCandidate::typeToString(QXmppJingleCandidate::Type type) +{ + QString typeStr; + switch (type) { + case HostType: + typeStr = QStringLiteral("host"); + break; + case PeerReflexiveType: + typeStr = QStringLiteral("prflx"); + break; + case ServerReflexiveType: + typeStr = QStringLiteral("srflx"); + break; + case RelayedType: + typeStr = QStringLiteral("relay"); + break; + } + return typeStr; +} +/// \endcond + +class QXmppJinglePayloadTypePrivate : public QSharedData +{ +public: + QXmppJinglePayloadTypePrivate(); + + unsigned char channels; + unsigned int clockrate; + unsigned char id; + unsigned int maxptime; + QString name; + QMap parameters; + unsigned int ptime; + + // XEP-0293: Jingle RTP Feedback Negotiation + QVector rtpFeedbackProperties; + QVector rtpFeedbackIntervals; +}; + +QXmppJinglePayloadTypePrivate::QXmppJinglePayloadTypePrivate() + : channels(1), clockrate(0), id(0), maxptime(0), ptime(0) +{ +} + +QXmppJinglePayloadType::QXmppJinglePayloadType() + : d(new QXmppJinglePayloadTypePrivate()) +{ +} + +/// Constructs a copy of other. +/// +/// \param other + +QXmppJinglePayloadType::QXmppJinglePayloadType(const QXmppJinglePayloadType &other) + : d(other.d) +{ +} + +QXmppJinglePayloadType::~QXmppJinglePayloadType() +{ +} + +/// Returns the number of channels (e.g. 1 for mono, 2 for stereo). +/// + +unsigned char QXmppJinglePayloadType::channels() const +{ + return d->channels; +} + +/// Sets the number of channels (e.g. 1 for mono, 2 for stereo). +/// +/// \param channels + +void QXmppJinglePayloadType::setChannels(unsigned char channels) +{ + d->channels = channels; +} + +/// Returns the clockrate in Hz, i.e. the number of samples per second. +/// + +unsigned int QXmppJinglePayloadType::clockrate() const +{ + return d->clockrate; +} + +/// Sets the clockrate in Hz, i.e. the number of samples per second. +/// +/// \param clockrate + +void QXmppJinglePayloadType::setClockrate(unsigned int clockrate) +{ + d->clockrate = clockrate; +} + +/// Returns the payload type identifier. +/// + +unsigned char QXmppJinglePayloadType::id() const +{ + return d->id; +} + +/// Sets the payload type identifier. +/// + +void QXmppJinglePayloadType::setId(unsigned char id) +{ + Q_ASSERT(id <= 127); + d->id = id; +} + +/// Returns the maximum packet time in milliseconds. +/// + +unsigned int QXmppJinglePayloadType::maxptime() const +{ + return d->maxptime; +} + +/// Sets the maximum packet type in milliseconds. +/// +/// \param maxptime + +void QXmppJinglePayloadType::setMaxptime(unsigned int maxptime) +{ + d->maxptime = maxptime; +} + +/// Returns the payload type name. +/// + +QString QXmppJinglePayloadType::name() const +{ + return d->name; +} + +/// Sets the payload type name. +/// +/// \param name + +void QXmppJinglePayloadType::setName(const QString &name) +{ + d->name = name; +} + +/// Returns the payload parameters. + +QMap QXmppJinglePayloadType::parameters() const +{ + return d->parameters; +} + +/// Sets the payload parameters. + +void QXmppJinglePayloadType::setParameters(const QMap ¶meters) +{ + d->parameters = parameters; +} + +/// Returns the packet time in milliseconds (20 by default). +/// + +unsigned int QXmppJinglePayloadType::ptime() const +{ + return d->ptime ? d->ptime : 20; +} + +/// Sets the packet time in milliseconds (20 by default). +/// +/// \param ptime + +void QXmppJinglePayloadType::setPtime(unsigned int ptime) +{ + d->ptime = ptime; +} + +/// +/// Returns the properties of RTP feedback. +/// +/// \return the RTP feedback properties +/// +/// \since QXmpp 1.5 +/// +QVector QXmppJinglePayloadType::rtpFeedbackProperties() const +{ + return d->rtpFeedbackProperties; +} + +/// +/// Sets the properties of RTP feedback. +/// +/// \param rtpFeedbackProperties RTP feedback properties +/// +/// \since QXmpp 1.5 +/// +void QXmppJinglePayloadType::setRtpFeedbackProperties(const QVector &rtpFeedbackProperties) +{ + d->rtpFeedbackProperties = rtpFeedbackProperties; +} + +/// +/// Returns the intervals of RTP feedback. +/// +/// \return the RTP feedback intervals +/// +QVector QXmppJinglePayloadType::rtpFeedbackIntervals() const +{ + return d->rtpFeedbackIntervals; +} + +/// +/// Sets the intervals of RTP feedback. +/// +/// \param rtpFeedbackIntervals RTP feedback intervals +/// +void QXmppJinglePayloadType::setRtpFeedbackIntervals(const QVector &rtpFeedbackIntervals) +{ + d->rtpFeedbackIntervals = rtpFeedbackIntervals; +} + +/// \cond +void QXmppJinglePayloadType::parse(const QDomElement &element) +{ + d->id = element.attribute(QStringLiteral("id")).toInt(); + d->name = element.attribute(QStringLiteral("name")); + d->channels = element.attribute(QStringLiteral("channels")).toInt(); + if (!d->channels) { + d->channels = 1; + } + d->clockrate = element.attribute(QStringLiteral("clockrate")).toInt(); + d->maxptime = element.attribute(QStringLiteral("maxptime")).toInt(); + d->ptime = element.attribute(QStringLiteral("ptime")).toInt(); + + QDomElement child = element.firstChildElement(QStringLiteral("parameter")); + while (!child.isNull()) { + d->parameters.insert(child.attribute(QStringLiteral("name")), child.attribute(QStringLiteral("value"))); + child = child.nextSiblingElement(QStringLiteral("parameter")); + } + + parseJingleRtpFeedbackNegotiationElements(element, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); +} + +void QXmppJinglePayloadType::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("payload-type")); + helperToXmlAddAttribute(writer, QStringLiteral("id"), QString::number(d->id)); + helperToXmlAddAttribute(writer, QStringLiteral("name"), d->name); + if (d->channels > 1) { + helperToXmlAddAttribute(writer, QStringLiteral("channels"), QString::number(d->channels)); + } + if (d->clockrate > 0) { + helperToXmlAddAttribute(writer, QStringLiteral("clockrate"), QString::number(d->clockrate)); + } + if (d->maxptime > 0) { + helperToXmlAddAttribute(writer, QStringLiteral("maxptime"), QString::number(d->maxptime)); + } + if (d->ptime > 0) { + helperToXmlAddAttribute(writer, QStringLiteral("ptime"), QString::number(d->ptime)); + } + + for (auto itr = d->parameters.begin(); itr != d->parameters.end(); itr++) { + writer->writeStartElement(QStringLiteral("parameter")); + writer->writeAttribute(QStringLiteral("name"), itr.key()); + writer->writeAttribute(QStringLiteral("value"), itr.value()); + writer->writeEndElement(); + } + + jingleRtpFeedbackNegotiationElementsToXml(writer, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); + + writer->writeEndElement(); +} +/// \endcond + +/// Assigns the other payload type to this one. +/// +/// \param other + +QXmppJinglePayloadType &QXmppJinglePayloadType::operator=(const QXmppJinglePayloadType &other) +{ + d = other.d; + return *this; +} + +/// Returns true if this QXmppJinglePayloadType and \a other refer to the same payload type. +/// +/// \param other + +bool QXmppJinglePayloadType::operator==(const QXmppJinglePayloadType &other) const +{ + // FIXME : what to do with m_ptime and m_maxptime? + if (d->id <= 95) { + return other.d->id == d->id && other.d->clockrate == d->clockrate; + } else { + return other.d->channels == d->channels && + other.d->clockrate == d->clockrate && + other.d->name.toLower() == d->name.toLower(); + } +} + +class QXmppJingleDescriptionPrivate : public QSharedData +{ +public: + QXmppJingleDescriptionPrivate() = default; + + QString media; + quint32 ssrc; + QString type; + QList 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 &QXmppJingleDescription::payloadTypes() const +{ + return d->payloadTypes; +} + +/// +/// Sets the list of payload types. +/// +void QXmppJingleDescription::setPayloadTypes(const QList &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: + QString name; + QString value; +}; + +/// +/// \class QXmppSdpParameter +/// +/// \brief The QXmppSdpParameter class represents a Session Description Protocol (SDP) parameter +/// specified by RFC 4566 and used by several XEPs based on \xep{0166, Jingle}. +/// +/// \since QXmpp 1.5 +/// + +/// +/// Constructs a Session Description Protocol parameter. +/// +QXmppSdpParameter::QXmppSdpParameter() + : d(new QXmppSdpParameterPrivate()) +{ +} + +QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppSdpParameter) + +/// +/// Returns the name of the parameter. +/// +/// \return the parameter's name +/// +QString QXmppSdpParameter::name() const +{ + return d->name; +} + +/// +/// Sets the name of the parameter. +/// +/// \param name parameter's name +/// +void QXmppSdpParameter::setName(const QString &name) +{ + d->name = name; +} + +/// +/// Returns the value of the parameter. +/// +/// \return the parameter's value +/// +QString QXmppSdpParameter::value() const +{ + return d->value; +} + +/// +/// Sets the value of the parameter. +/// +/// A parameter in the form "a=b" can be created by this method. +/// Any other form of parameters can be created by not using this method. +/// The value stays a default-constructed QString then. +/// +/// \param value parameter's value +/// +void QXmppSdpParameter::setValue(const QString &value) +{ + d->value = value; +} + +/// \cond +void QXmppSdpParameter::parse(const QDomElement &element) +{ + d->name = element.attribute(QStringLiteral("name")); + d->value = element.attribute(QStringLiteral("value")); +} + +void QXmppSdpParameter::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("parameter")); + helperToXmlAddAttribute(writer, QStringLiteral("name"), d->name); + + if (!d->value.isEmpty()) { + helperToXmlAddAttribute(writer, QStringLiteral("value"), d->value); + } + + writer->writeEndElement(); +} +/// \endcond + +/// +/// Determines whether the given DOM element is a Session Description Protocol parameter element. +/// +/// \param element DOM element being checked +/// +/// \return whether element is a Session Description Protocol parameter element +/// +bool QXmppSdpParameter::isSdpParameter(const QDomElement &element) +{ + return element.tagName() == QStringLiteral("parameter"); +} + +class QXmppJingleRtpCryptoElementPrivate : public QSharedData +{ +public: + uint32_t tag = 0; + QString cryptoSuite; + QString keyParams; + QString sessionParams; +}; + +/// +/// \class QXmppJingleRtpCryptoElement +/// +/// \brief The QXmppJingleRtpCryptoElement class represents the \xep{0167: Jingle RTP Sessions} +/// "crypto" element used for SRTP negotiation. +/// +/// \since QXmpp 1.5 +/// + +/// +/// Constructs a Jingle RTP crypto element. +/// +QXmppJingleRtpCryptoElement::QXmppJingleRtpCryptoElement() + : d(new QXmppJingleRtpCryptoElementPrivate()) +{ +} + +QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpCryptoElement) + +/// +/// Returns the tag used as an identifier for the crypto element. +/// +/// \return the identifying tag +/// +uint32_t QXmppJingleRtpCryptoElement::tag() const +{ + return d->tag; +} + +/// +/// Sets the tag used as an identifier for the crypto element. +/// +/// \param tag identifying tag +/// +void QXmppJingleRtpCryptoElement::setTag(uint32_t tag) +{ + d->tag = tag; +} + +/// +/// Returns the crypto suite used as an identifier for describing the encryption and authentication +/// algorithms. +/// +/// \return the identifying crypto suite +/// +QString QXmppJingleRtpCryptoElement::cryptoSuite() const +{ + return d->cryptoSuite; +} + +/// +/// Sets the crypto suite used as an identifier for describing the encryption and authentication +/// algorithms. +/// +/// \param cryptoSuite identifying crypto suite +/// +void QXmppJingleRtpCryptoElement::setCryptoSuite(const QString &cryptoSuite) +{ + d->cryptoSuite = cryptoSuite; +} + +/// +/// Returns the key parameters providing one or more sets of keying material for the crypto suite. +/// +/// \return the key parameters providing one or more sets of keying material +/// +QString QXmppJingleRtpCryptoElement::keyParams() const +{ + return d->keyParams; +} + +/// +/// Sets the key parameters providing one or more sets of keying material for the crypto suite. +/// +/// \param keyParams key parameters providing one or more sets of keying material +/// +void QXmppJingleRtpCryptoElement::setKeyParams(const QString &keyParams) +{ + d->keyParams = keyParams; +} + +/// +/// Returns the session parameters providing transport-specific data. +/// +/// \return the session parameters providing transport-specific data +/// +QString QXmppJingleRtpCryptoElement::sessionParams() const +{ + return d->sessionParams; +} + +/// +/// Sets the session parameters providing transport-specific data. +/// +/// \param sessionParams session parameters providing transport-specific data +/// +void QXmppJingleRtpCryptoElement::setSessionParams(const QString &sessionParams) +{ + d->sessionParams = sessionParams; +} + +/// \cond +void QXmppJingleRtpCryptoElement::parse(const QDomElement &element) +{ + d->tag = element.attribute(QStringLiteral("tag")).toUInt(); + d->cryptoSuite = element.attribute(QStringLiteral("crypto-suite")); + d->keyParams = element.attribute(QStringLiteral("key-params")); + d->sessionParams = element.attribute(QStringLiteral("session-params")); +} + +void QXmppJingleRtpCryptoElement::toXml(QXmlStreamWriter *writer) const +{ + if (!d->cryptoSuite.isEmpty() && !d->keyParams.isEmpty()) { + writer->writeStartElement(QStringLiteral("crypto")); + writer->writeAttribute(QStringLiteral("tag"), QString::number(d->tag)); + writer->writeAttribute(QStringLiteral("crypto-suite"), d->cryptoSuite); + writer->writeAttribute(QStringLiteral("key-params"), d->keyParams); + helperToXmlAddAttribute(writer, QStringLiteral("session-params"), d->sessionParams); + writer->writeEndElement(); + } +} +/// \endcond + +/// +/// Determines whether the given DOM element is an RTP crypto element. +/// +/// \param element DOM element being checked +/// +/// \return whether element is an RTP crypto element +/// +bool QXmppJingleRtpCryptoElement::isJingleRtpCryptoElement(const QDomElement &element) +{ + return element.tagName() == QStringLiteral("crypto"); +} + +class QXmppJingleRtpEncryptionPrivate : public QSharedData +{ +public: + bool isRequired = false; + QVector cryptoElements; +}; + +/// +/// \class QXmppJingleRtpEncryption +/// +/// \brief The QXmppJingleRtpEncryption class represents the \xep{0167: Jingle RTP Sessions} +/// "encryption" element used for SRTP negotiation. +/// +/// \since QXmpp 1.5 +/// + +/// +/// Constructs a Jingle RTP encryption. +/// +QXmppJingleRtpEncryption::QXmppJingleRtpEncryption() + : d(new QXmppJingleRtpEncryptionPrivate()) +{ +} + +QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpEncryption) + +/// +/// Returns whether encryption via SRTP is required. +/// +/// \return whether encryption is required +/// +bool QXmppJingleRtpEncryption::isRequired() const +{ + return d->isRequired; +} + +/// +/// Sets whether encryption via SRTP is required. +/// +/// \param isRequired whether encryption is required +/// +void QXmppJingleRtpEncryption::setRequired(bool isRequired) +{ + d->isRequired = isRequired; +} + +/// +/// Returns the crypto elements used for encryption via SRTP. +/// +/// \return the crypto elements +/// +QVector QXmppJingleRtpEncryption::cryptoElements() const +{ + return d->cryptoElements; +} + +/// +/// Sets the crypto elements used for encryption via SRTP. +/// +/// \param cryptoElements the crypto elements +/// +void QXmppJingleRtpEncryption::setCryptoElements(const QVector &cryptoElements) +{ + d->cryptoElements = cryptoElements; +} + +/// \cond +void QXmppJingleRtpEncryption::parse(const QDomElement &element) +{ + d->isRequired = element.attribute(QStringLiteral("required")) == QStringLiteral("true") || + element.attribute(QStringLiteral("required")) == QStringLiteral("1"); + + for (auto childElement = element.firstChildElement(); + !childElement.isNull(); + childElement = childElement.nextSiblingElement()) { + if (QXmppJingleRtpCryptoElement::isJingleRtpCryptoElement(childElement)) { + QXmppJingleRtpCryptoElement cryptoElement; + cryptoElement.parse(childElement); + d->cryptoElements.append(std::move(cryptoElement)); + } + } +} + +void QXmppJingleRtpEncryption::toXml(QXmlStreamWriter *writer) const +{ + if (!d->cryptoElements.isEmpty()) { + writer->writeStartElement(QStringLiteral("encryption")); + writer->writeDefaultNamespace(ns_jingle_rtp); + + if (d->isRequired) { + writer->writeAttribute(QStringLiteral("required"), QStringLiteral("1")); + } + + for (const auto &cryptoElement : std::as_const(d->cryptoElements)) { + cryptoElement.toXml(writer); + } + + writer->writeEndElement(); + } +} +/// \endcond + +/// +/// Determines whether the given DOM element is an RTP encryption element. +/// +/// \param element DOM element being checked +/// +/// \return whether element is an RTP encryption element +/// +bool QXmppJingleRtpEncryption::isJingleRtpEncryption(const QDomElement &element) +{ + return element.tagName() == QStringLiteral("encryption") && + element.namespaceURI() == ns_jingle_rtp; +} + +class QXmppJingleRtpFeedbackPropertyPrivate : public QSharedData +{ +public: + QString type; + QString subtype; + QVector parameters; +}; + +/// +/// \class QXmppJingleRtpFeedbackProperty +/// +/// \brief The QXmppJingleRtpFeedbackProperty class represents the +/// \xep{0293, Jingle RTP Feedback Negotiation} "rtcp-fb" element. +/// +/// \since QXmpp 1.5 +/// + +/// +/// Constructs a Jingle RTP feedback property. +/// +QXmppJingleRtpFeedbackProperty::QXmppJingleRtpFeedbackProperty() + : d(new QXmppJingleRtpFeedbackPropertyPrivate()) +{ +} + +QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpFeedbackProperty) + +/// +/// Returns the type of RTP feedback. +/// +/// \return the RTP feedback type +/// +QString QXmppJingleRtpFeedbackProperty::type() const +{ + return d->type; +} + +/// +/// Sets the type of RTP feedback. +/// +/// \param type RTP feedback type +/// +void QXmppJingleRtpFeedbackProperty::setType(const QString &type) +{ + d->type = type; +} + +/// +/// Returns the subtype for RTP feedback. +/// +/// \return the RTP feedback subtype +/// +QString QXmppJingleRtpFeedbackProperty::subtype() const +{ + return d->subtype; +} + +/// +/// Sets the subtype of RTP feedback. +/// +/// If there is more than one parameter, use QXmppJingleRtpFeedbackProperty::setParameters() +/// instead of this method. +/// +/// \param subtype RTP feedback subtype +/// +void QXmppJingleRtpFeedbackProperty::setSubtype(const QString &subtype) +{ + d->subtype = subtype; +} + +/// +/// Returns the parameters of RTP feedback. +/// +/// \return the RTP feedback parameters +/// +QVector QXmppJingleRtpFeedbackProperty::parameters() const +{ + return d->parameters; +} + +/// +/// Sets the parameters of RTP feedback. +/// +/// Additional parameters can be set by this method. +/// If there is only one parameter, use QXmppJingleRtpFeedbackProperty::setSubtype() +/// instead of this method. +/// +/// \param parameters RTP feedback parameters +/// +void QXmppJingleRtpFeedbackProperty::setParameters(const QVector ¶meters) +{ + d->parameters = parameters; +} + +/// \cond +void QXmppJingleRtpFeedbackProperty::parse(const QDomElement &element) +{ + d->type = element.attribute(QStringLiteral("type")); + d->subtype = element.attribute(QStringLiteral("subtype")); + parseSdpParameters(element, d->parameters); +} + +void QXmppJingleRtpFeedbackProperty::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("rtcp-fb")); + writer->writeDefaultNamespace(ns_jingle_rtp_feedback_negotiation); + helperToXmlAddAttribute(writer, QStringLiteral("type"), d->type); + + // If there are parameters, they must be used instead of the subtype. + if (d->subtype.isEmpty()) { + sdpParametersToXml(writer, d->parameters); + } else { + helperToXmlAddAttribute(writer, QStringLiteral("subtype"), d->subtype); + } + + writer->writeEndElement(); +} +/// \endcond + +/// +/// Determines whether the given DOM element is an RTP feedback property element. +/// +/// \param element DOM element being checked +/// +/// \return whether element is an RTP feedback property element +/// +bool QXmppJingleRtpFeedbackProperty::isJingleRtpFeedbackProperty(const QDomElement &element) +{ + return element.tagName() == QStringLiteral("rtcp-fb") && + element.namespaceURI() == ns_jingle_rtp_feedback_negotiation; +} + +/// +/// \class QXmppJingleRtpFeedbackInterval +/// +/// \brief The QXmppJingleRtpFeedbackInterval class represents the +/// \xep{0293, Jingle RTP Feedback Negotiation} "rtcp-fb-trr-int" element. +/// +/// \since QXmpp 1.5 +/// + +/// +/// Constructs a Jingle RTP feedback interval. +/// +QXmppJingleRtpFeedbackInterval::QXmppJingleRtpFeedbackInterval() +{ +} + +QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpFeedbackInterval) + +/// +/// Returns the value of the RTP feedback interval. +/// +/// \return the RTP feedback interval value +/// +uint64_t QXmppJingleRtpFeedbackInterval::value() const +{ + return m_value; +} + +/// +/// Sets the value of the RTP feedback interval. +/// +/// \param value RTP feedback interval value +/// +void QXmppJingleRtpFeedbackInterval::setValue(uint64_t value) +{ + m_value = value; +} + +/// \cond +void QXmppJingleRtpFeedbackInterval::parse(const QDomElement &element) +{ + m_value = element.attribute(QStringLiteral("value")).toUInt(); +} + +void QXmppJingleRtpFeedbackInterval::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("rtcp-fb-trr-int")); + writer->writeDefaultNamespace(ns_jingle_rtp_feedback_negotiation); + helperToXmlAddAttribute(writer, QStringLiteral("value"), QString::number(m_value)); + writer->writeEndElement(); +} +/// \endcond + +/// +/// Determines whether the given DOM element is an RTP feedback interval element. +/// +/// \param element DOM element being checked +/// +/// \return whether element is an RTP feedback interval element +/// +bool QXmppJingleRtpFeedbackInterval::isJingleRtpFeedbackInterval(const QDomElement &element) +{ + return element.tagName() == QStringLiteral("rtcp-fb-trr-int") && + element.namespaceURI() == ns_jingle_rtp_feedback_negotiation; +} + +class QXmppJingleRtpHeaderExtensionPropertyPrivate : public QSharedData +{ +public: + uint32_t id = 0; + QString uri; + QXmppJingleRtpHeaderExtensionProperty::Senders senders = QXmppJingleRtpHeaderExtensionProperty::Both; + QVector parameters; +}; + +/// +/// \enum QXmppJingleRtpHeaderExtensionProperty::Senders +/// +/// Parties that are allowed to send the negotiated RTP header extension +/// + +/// +/// \class QXmppJingleRtpHeaderExtensionProperty +/// +/// \brief The QXmppJingleRtpHeaderExtensionProperty class represents the +/// \xep{0294, Jingle RTP Header Extensions Negotiation} "rtp-hdrext" element. +/// +/// \since QXmpp 1.5 +/// + +/// +/// Constructs a Jingle RTP header extension property. +/// +QXmppJingleRtpHeaderExtensionProperty::QXmppJingleRtpHeaderExtensionProperty() + : d(new QXmppJingleRtpHeaderExtensionPropertyPrivate()) +{ +} + +QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpHeaderExtensionProperty) + +/// +/// Returns the ID of the RTP header extension. +/// +/// The ID is 0 if it is unset. +/// +/// \return the RTP header extension's ID +/// +uint32_t QXmppJingleRtpHeaderExtensionProperty::id() const +{ + return d->id; +} + +/// +/// Sets the ID of the RTP header extension. +/// +/// The ID must either be at least 1 and at most 256 or at least 4096 and at most 4351. +/// +/// \param id RTP header extension's ID +/// +void QXmppJingleRtpHeaderExtensionProperty::setId(uint32_t id) +{ + d->id = id; +} + +/// +/// Returns the URI defning the RTP header extension. +/// +/// \return the RTP header extension's URI +/// +QString QXmppJingleRtpHeaderExtensionProperty::uri() const +{ + return d->uri; +} + +/// +/// Sets the URI defning the RTP header extension. +/// +/// \param uri RTP header extension's URI +/// +void QXmppJingleRtpHeaderExtensionProperty::setUri(const QString &uri) +{ + d->uri = uri; +} + +/// +/// Returns the parties that are allowed to send the negotiated RTP header extensions. +/// +/// \return the parties that are allowed to send the RTP header extensions +/// +QXmppJingleRtpHeaderExtensionProperty::Senders QXmppJingleRtpHeaderExtensionProperty::senders() const +{ + return d->senders; +} + +/// +/// Sets the parties that are allowed to send the negotiated RTP header extensions. +/// +/// \param senders parties that are allowed to send the RTP header extensions +/// +void QXmppJingleRtpHeaderExtensionProperty::setSenders(Senders senders) +{ + d->senders = senders; +} + +/// +/// Returns the parameters of the RTP header extension. +/// +/// \return the RTP header extension's parameters +/// +QVector QXmppJingleRtpHeaderExtensionProperty::parameters() const +{ + return d->parameters; +} + +/// +/// Sets the parameters of the RTP header extension. +/// +/// Additional parameters can be set by this method. +/// +/// \param parameters RTP header extension's parameters +/// +void QXmppJingleRtpHeaderExtensionProperty::setParameters(const QVector ¶meters) +{ + d->parameters = parameters; +} + +/// \cond +void QXmppJingleRtpHeaderExtensionProperty::parse(const QDomElement &element) +{ + if (element.tagName() == QStringLiteral("rtp-hdrext") && element.namespaceURI() == ns_jingle_rtp_header_extensions_negotiation) { + d->id = element.attribute(QStringLiteral("id")).toUInt(); + d->uri = element.attribute(QStringLiteral("uri")); + + if (const auto senders = JINGLE_RTP_HEADER_EXTENSIONS_SENDERS.indexOf(element.attribute(QStringLiteral("senders"))); senders > QXmppJingleRtpHeaderExtensionProperty::Both) { + d->senders = static_cast(senders); + } + + parseSdpParameters(element, d->parameters); + } +} + +void QXmppJingleRtpHeaderExtensionProperty::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("rtp-hdrext")); + writer->writeDefaultNamespace(ns_jingle_rtp_header_extensions_negotiation); + helperToXmlAddAttribute(writer, QStringLiteral("id"), QString::number(d->id)); + helperToXmlAddAttribute(writer, QStringLiteral("uri"), d->uri); + + if (d->senders != QXmppJingleRtpHeaderExtensionProperty::Both) { + helperToXmlAddAttribute(writer, QStringLiteral("senders"), JINGLE_RTP_HEADER_EXTENSIONS_SENDERS.at(d->senders)); + } + + sdpParametersToXml(writer, d->parameters); + + writer->writeEndElement(); +} +/// \endcond + +/// +/// Determines whether the given DOM element is an RTP header extensions property element. +/// +/// \param element DOM element being checked +/// +/// \return whether element is an RTP header extension property element +/// +bool QXmppJingleRtpHeaderExtensionProperty::isJingleRtpHeaderExtensionProperty(const QDomElement &element) +{ + 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 description; + std::optional 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 QXmppJingleMessageInitiationElement::description() const +{ + return d->description; +} + +/// +/// Sets the Jingle Message Initiation element description. +/// +void QXmppJingleMessageInitiationElement::setDescription(std::optional description) +{ + d->description = description; +} + +/// +/// Returns the Jingle Message Initiation element reason. +/// +std::optional QXmppJingleMessageInitiationElement::reason() const +{ + return d->reason; +} + +/// +/// Sets the Jingle Message Initiation element reason. +/// +void QXmppJingleMessageInitiationElement::setReason(std::optional reason) +{ + d->reason = reason; + + if (d->reason) { + d->reason->setNamespaceUri(ns_jingle); + } +} + +/// +/// Returns true if the Jingle Message Initiation element contains a tag. +/// +bool QXmppJingleMessageInitiationElement::containsTieBreak() const +{ + return d->containsTieBreak; +} + +/// +/// Sets if the Jingle Message Initiation element contains a 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) +{ + d->type = stringToJmiElementType(element.nodeName()); + + if (d->type == Type::None) { + return; + } + + 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 (const auto &reasonElement = element.firstChildElement("reason"); !reasonElement.isNull()) { + d->reason = QXmppJingleReason(); + d->reason->parse(reasonElement); + } + + if (const 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 (element.nodeName() == QStringLiteral("propose") || element.nodeName() == QStringLiteral("ringing") || element.nodeName() == QStringLiteral("proceed") || element.nodeName() == QStringLiteral("retract") || element.nodeName() == QStringLiteral("reject") || element.nodeName() == QStringLiteral("finish")) && 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) const +{ + 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. +/// +QXmppJingleMessageInitiationElement::Type QXmppJingleMessageInitiationElement::stringToJmiElementType(const QString &typeStr) const +{ + 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 Type::None; +} diff --git a/src/base/QXmppJingleData.h b/src/base/QXmppJingleData.h new file mode 100644 index 00000000..1e425f4d --- /dev/null +++ b/src/base/QXmppJingleData.h @@ -0,0 +1,649 @@ +// SPDX-FileCopyrightText: 2010 Jeremy Lainé +// SPDX-FileCopyrightText: 2022 Melvin Keskin +// SPDX-FileCopyrightText: 2023 Tibor Csötönyi +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef QXMPPJINGLEIQ_H +#define QXMPPJINGLEIQ_H + +#include "QXmppIq.h" + +#include + +#include + +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 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 d; +}; + +class QXMPP_EXPORT QXmppJingleRtpEncryption +{ +public: + QXmppJingleRtpEncryption(); + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpEncryption) + + bool isRequired() const; + void setRequired(bool isRequired); + + QVector cryptoElements() const; + void setCryptoElements(const QVector &cryptoElements); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + static bool isJingleRtpEncryption(const QDomElement &element); + +private: + QSharedDataPointer 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 parameters() const; + void setParameters(const QVector ¶meters); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + static bool isJingleRtpFeedbackProperty(const QDomElement &element); + +private: + QSharedDataPointer 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 parameters() const; + void setParameters(const QVector ¶meters); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + static bool isJingleRtpHeaderExtensionProperty(const QDomElement &element); + +private: + QSharedDataPointer 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 parameters() const; + void setParameters(const QMap ¶meters); + + unsigned int ptime() const; + void setPtime(unsigned int ptime); + + QVector rtpFeedbackProperties() const; + void setRtpFeedbackProperties(const QVector &rtpFeedbackProperties); + + QVector rtpFeedbackIntervals() const; + void setRtpFeedbackIntervals(const QVector &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 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 &payloadTypes() const; + void setPayloadTypes(const QList &payloadTypes); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QSharedDataPointer 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 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 + }; + + 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); + + QString namespaceUri() const; + void setNamespaceUri(const QString &namespaceUri); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + + /// \endcond + + QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleReason) + +private: + QSharedDataPointer 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; + + /// \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); + + bool isRtpMultiplexingSupported() const; + void setRtpMultiplexingSupported(bool isRtpMultiplexingSupported); + + std::optional rtpEncryption() const; + void setRtpEncryption(const std::optional &rtpEncryption); + + void addTransportCandidate(const QXmppJingleCandidate &candidate); + QList transportCandidates() const; + void setTransportCandidates(const QList &candidates); + + QString transportUser() const; + void setTransportUser(const QString &user); + + QString transportPassword() const; + void setTransportPassword(const QString &password); + + QVector rtpFeedbackProperties() const; + void setRtpFeedbackProperties(const QVector &rtpFeedbackProperties); + + QVector rtpFeedbackIntervals() const; + void setRtpFeedbackIntervals(const QVector &rtpFeedbackIntervals); + + QVector rtpHeaderExtensionProperties() const; + void setRtpHeaderExtensionProperties(const QVector &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 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 contents() const; + void setContents(const QList &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() const; + void setRtpSessionState(const std::optional &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 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 description() const; + void setDescription(std::optional description); + + std::optional reason() const; + void setReason(std::optional 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 &); + +private: + QString jmiElementTypeToString(Type type) const; + Type stringToJmiElementType(const QString &typeStr) const; + + QSharedDataPointer d; +}; + +Q_DECLARE_METATYPE(QXmppJingleReason::RtpErrorCondition) + +#endif diff --git a/src/base/QXmppJingleIq.cpp b/src/base/QXmppJingleIq.cpp deleted file mode 100644 index 67883fc8..00000000 --- a/src/base/QXmppJingleIq.cpp +++ /dev/null @@ -1,3207 +0,0 @@ -// SPDX-FileCopyrightText: 2010 Jeremy Lainé -// SPDX-FileCopyrightText: 2022 Melvin Keskin -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "QXmppJingleIq.h" - -#include "QXmppConstants_p.h" -#include "QXmppUtils.h" - -#include -#include -#include -#include - -static const int RTP_COMPONENT = 1; - -static const char *jingle_actions[] = { - "content-accept", - "content-add", - "content-modify", - "content-reject", - "content-remove", - "description-info", - "security-info", - "session-accept", - "session-info", - "session-initiate", - "session-terminate", - "transport-accept", - "transport-info", - "transport-reject", - "transport-replace", -}; - -static const char *jingle_reasons[] = { - "", - "alternative-session", - "busy", - "cancel", - "connectivity-error", - "decline", - "expired", - "failed-application", - "failed-transport", - "general-error", - "gone", - "incompatible-parameters", - "media-error", - "security-error", - "success", - "timeout", - "unsupported-applications", - "unsupported-transports", -}; - -static const QStringList JINGLE_RTP_ERROR_CONDITIONS = { - {}, - QStringLiteral("invalid-crypto"), - QStringLiteral("crypto-required") -}; - -static const QStringList JINGLE_RTP_HEADER_EXTENSIONS_SENDERS = { - QStringLiteral("both"), - QStringLiteral("initiator"), - QStringLiteral("responder") -}; - -static QString formatFingerprint(const QByteArray &digest) -{ - QString fingerprint; - const QString hx = digest.toHex().toUpper(); - for (int i = 0; i < hx.size(); i += 2) { - if (!fingerprint.isEmpty()) { - fingerprint += ':'; - } - fingerprint += hx.mid(i, 2); - } - return fingerprint; -} - -static QByteArray parseFingerprint(const QString &fingerprint) -{ - QString z = fingerprint; - z.replace(':', ""); - return QByteArray::fromHex(z.toUtf8()); -} - -static QString addressToSdp(const QHostAddress &host) -{ - return QStringLiteral("IN %1 %2").arg(host.protocol() == QAbstractSocket::IPv6Protocol ? QStringLiteral("IP6") : QStringLiteral("IP4"), host.toString()); -} - -static bool candidateParseSdp(QXmppJingleCandidate *candidate, const QString &sdp) -{ - if (!sdp.startsWith(QStringLiteral("candidate:"))) { - return false; - } - - const QStringList bits = sdp.mid(10).split(" "); - if (bits.size() < 6) { - return false; - } - - candidate->setFoundation(bits[0]); - candidate->setComponent(bits[1].toInt()); - candidate->setProtocol(bits[2].toLower()); - candidate->setPriority(bits[3].toInt()); - candidate->setHost(QHostAddress(bits[4])); - candidate->setPort(bits[5].toInt()); - for (int i = 6; i < bits.size() - 1; i += 2) { - if (bits[i] == QStringLiteral("typ")) { - bool ok; - candidate->setType(QXmppJingleCandidate::typeFromString(bits[i + 1], &ok)); - if (!ok) { - return false; - } - } else if (bits[i] == QStringLiteral("generation")) { - candidate->setGeneration(bits[i + 1].toInt()); - } else { - qWarning() << "Candidate SDP contains unknown attribute" << bits[i]; - return false; - } - } - return true; -} - -static QString candidateToSdp(const QXmppJingleCandidate &candidate) -{ - return QStringLiteral("candidate:%1 %2 %3 %4 %5 %6 typ %7 generation %8").arg(candidate.foundation(), QString::number(candidate.component()), candidate.protocol(), QString::number(candidate.priority()), candidate.host().toString(), QString::number(candidate.port()), QXmppJingleCandidate::typeToString(candidate.type()), QString::number(candidate.generation())); -} - -// Parses all found SDP parameter elements of parent into parameters. -static void parseSdpParameters(const QDomElement &parent, QVector ¶meters) -{ - for (auto childElement = parent.firstChildElement(); - !childElement.isNull(); - childElement = childElement.nextSiblingElement()) { - if (QXmppSdpParameter::isSdpParameter(childElement)) { - QXmppSdpParameter parameter; - parameter.parse(childElement); - parameters.append(parameter); - } - } -} - -// Serializes the SDP parameters. -static void sdpParametersToXml(QXmlStreamWriter *writer, const QVector ¶meters) -{ - for (const auto ¶meter : parameters) { - parameter.toXml(writer); - } -} - -// Parses all found RTP Feedback Negotiation elements inside of parent into properties and -// intervals. -static void parseJingleRtpFeedbackNegotiationElements(const QDomElement &parent, QVector &properties, QVector &intervals) -{ - for (auto child = parent.firstChildElement(); - !child.isNull(); - child = child.nextSiblingElement()) { - if (QXmppJingleRtpFeedbackProperty::isJingleRtpFeedbackProperty(child)) { - QXmppJingleRtpFeedbackProperty property; - property.parse(child); - properties.append(property); - } else if (QXmppJingleRtpFeedbackInterval::isJingleRtpFeedbackInterval(child)) { - QXmppJingleRtpFeedbackInterval interval; - interval.parse(child); - intervals.append(interval); - } - } -} - -// Serializes the RTP feedback properties and intervals. -static void jingleRtpFeedbackNegotiationElementsToXml(QXmlStreamWriter *writer, const QVector &properties, const QVector &intervals) -{ - for (const auto &property : properties) { - property.toXml(writer); - } - - for (const auto &interval : intervals) { - interval.toXml(writer); - } -} - -// Parses all found RTP Header Extensions Negotiation elements inside of parent into properties and -// isRtpHeaderExtensionMixingAllowed. -static void parseJingleRtpHeaderExtensionsNegotiationElements(const QDomElement &parent, QVector &properties, bool &isRtpHeaderExtensionMixingAllowed) -{ - for (auto child = parent.firstChildElement(); - !child.isNull(); - child = child.nextSiblingElement()) { - if (QXmppJingleRtpHeaderExtensionProperty::isJingleRtpHeaderExtensionProperty(child)) { - QXmppJingleRtpHeaderExtensionProperty property; - property.parse(child); - properties.append(property); - } else if (child.tagName() == QStringLiteral("extmap-allow-mixed") && child.namespaceURI() == ns_jingle_rtp_header_extensions_negotiation) { - isRtpHeaderExtensionMixingAllowed = true; - } - } -} - -// Serializes the RTP header extension properties and isRtpHeaderExtensionMixingAllowed. -static void jingleRtpHeaderExtensionsNegotiationElementsToXml(QXmlStreamWriter *writer, const QVector &properties, bool isRtpHeaderExtensionMixingAllowed) -{ - for (const auto &property : properties) { - property.toXml(writer); - } - - if (isRtpHeaderExtensionMixingAllowed) { - writer->writeStartElement(QStringLiteral("extmap-allow-mixed")); - writer->writeDefaultNamespace(ns_jingle_rtp_header_extensions_negotiation); - writer->writeEndElement(); - } -} - -class QXmppJingleIqContentPrivate : public QSharedData -{ -public: - QXmppJingleIqContentPrivate(); - - QString creator; - QString disposition; - QString name; - QString senders; - - QXmppJingleDescription description; - bool isRtpMultiplexingSupported = false; - - QString transportType; - QString transportUser; - QString transportPassword; - - QByteArray transportFingerprint; - QString transportFingerprintHash; - QString transportFingerprintSetup; - - QList transportCandidates; - - // XEP-0167: Jingle RTP Sessions - std::optional rtpEncryption; - - // XEP-0293: Jingle RTP Feedback Negotiation - QVector rtpFeedbackProperties; - QVector rtpFeedbackIntervals; - - // XEP-0294: Jingle RTP Header Extensions Negotiation - QVector rtpHeaderExtensionProperties; - bool isRtpHeaderExtensionMixingAllowed = false; -}; - -QXmppJingleIqContentPrivate::QXmppJingleIqContentPrivate() -{ - description.setSsrc(0); -} - -/// -/// \enum QXmppJingleIq::Creator -/// -/// Party that originially generated the content type -/// -/// \since QXmpp 1.5 -/// - -/// -/// \struct QXmppJingleIq::RtpSessionStateActive -/// -/// Actively participating in the session after having been on mute or having put the other party on -/// hold -/// -/// \since QXmpp 1.5 -/// - -/// -/// \struct QXmppJingleIq::RtpSessionStateHold -/// -/// Temporarily not listening for media from the other party -/// -/// \since QXmpp 1.5 -/// - -/// -/// \struct QXmppJingleIq::RtpSessionStateUnhold -/// -/// Ending hold state -/// -/// \since QXmpp 1.5 -/// - -/// -/// \struct QXmppJingleIq::RtpSessionStateMuting -/// -/// State for muting or unmuting -/// -/// \since QXmpp 1.5 -/// - -/// -/// \struct QXmppJingleIq::RtpSessionStateRinging -/// -/// State after the callee acknowledged the call but did not yet interacted with it -/// -/// \since QXmpp 1.5 -/// - -/// -/// \typedef QXmppJingleIq::RtpSessionState -/// -/// Contains the state of an RTP session as specified by \xep{0167, Jingle RTP Sessions} -/// Informational Messages. -/// -/// \since QXmpp 1.5 -/// - -/// Constructs an empty content. -QXmppJingleIq::Content::Content() - : d(new QXmppJingleIqContentPrivate()) -{ -} - -/// Copy-constructor. -QXmppJingleIq::Content::Content(const QXmppJingleIq::Content &other) = default; -/// Move-constructor. -QXmppJingleIq::Content::Content(QXmppJingleIq::Content &&) = default; -/// Assignment operator. -QXmppJingleIq::Content &QXmppJingleIq::Content::operator=(const QXmppJingleIq::Content &) = default; -/// Move-assignment operator. -QXmppJingleIq::Content &QXmppJingleIq::Content::operator=(QXmppJingleIq::Content &&) = default; - -QXmppJingleIq::Content::~Content() = default; - -QString QXmppJingleIq::Content::creator() const -{ - return d->creator; -} - -void QXmppJingleIq::Content::setCreator(const QString &creator) -{ - d->creator = creator; -} - -QString QXmppJingleIq::Content::name() const -{ - return d->name; -} - -void QXmppJingleIq::Content::setName(const QString &name) -{ - d->name = name; -} - -QString QXmppJingleIq::Content::senders() const -{ - return d->senders; -} - -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; -} - -/// -/// Returns whether multiplexing of RTP data and control packets on a single port is supported as -/// specified by \xep{0167, Jingle RTP Sessions} and RFC 5761. -/// -/// \return whether multiplexing of RTP data and control packets is supported -/// -/// \since QXmpp 1.5 -/// -bool QXmppJingleIq::Content::isRtpMultiplexingSupported() const -{ - return d->isRtpMultiplexingSupported; -} - -/// -/// Sets whether multiplexing of RTP data and control packets on a single port is supported as -/// specified by \xep{0167, Jingle RTP Sessions} and RFC 5761. -/// -/// \param isRtpMultiplexingSupported whether multiplexing of RTP data and control packets is -/// supported -/// -/// \since QXmpp 1.5 -/// -void QXmppJingleIq::Content::setRtpMultiplexingSupported(bool isRtpMultiplexingSupported) -{ - d->isRtpMultiplexingSupported = isRtpMultiplexingSupported; -} - -/// -/// Returns the encryption used for SRTP negotiation as specified by -/// \xep{0167, Jingle RTP Sessions}. -/// -/// \return the RTP encryption via SRTP -/// -/// \since QXmpp 1.5 -/// -std::optional QXmppJingleIq::Content::rtpEncryption() const -{ - return d->rtpEncryption; -} - -/// -/// Sets the encryption used for SRTP negotiation as specified by \xep{0167, Jingle RTP Sessions}. -/// -/// \param rtpEncryption RTP encryption via SRTP -/// -/// \since QXmpp 1.5 -/// -void QXmppJingleIq::Content::setRtpEncryption(const std::optional &rtpEncryption) -{ - d->rtpEncryption = rtpEncryption; -} - -void QXmppJingleIq::Content::addTransportCandidate(const QXmppJingleCandidate &candidate) -{ - d->transportType = ns_jingle_ice_udp; - d->transportCandidates << candidate; -} - -QList QXmppJingleIq::Content::transportCandidates() const -{ - return d->transportCandidates; -} - -/// -/// Sets a list of transport candidates. -/// -/// \since QXmpp 0.9.2 -/// -void QXmppJingleIq::Content::setTransportCandidates(const QList &candidates) -{ - d->transportType = candidates.isEmpty() ? QString() : ns_jingle_ice_udp; - d->transportCandidates = candidates; -} - -QString QXmppJingleIq::Content::transportUser() const -{ - return d->transportUser; -} - -void QXmppJingleIq::Content::setTransportUser(const QString &user) -{ - d->transportUser = user; -} - -QString QXmppJingleIq::Content::transportPassword() const -{ - return d->transportPassword; -} - -void QXmppJingleIq::Content::setTransportPassword(const QString &password) -{ - d->transportPassword = password; -} - -/// -/// Returns the properties of RTP feedback. -/// -/// \return the RTP feedback properties -/// -/// \since QXmpp 1.5 -/// -QVector QXmppJingleIq::Content::rtpFeedbackProperties() const -{ - return d->rtpFeedbackProperties; -} - -/// -/// Sets the properties of RTP feedback. -/// -/// \param rtpFeedbackProperties RTP feedback properties -/// -/// \since QXmpp 1.5 -/// -void QXmppJingleIq::Content::setRtpFeedbackProperties(const QVector &rtpFeedbackProperties) -{ - d->rtpFeedbackProperties = rtpFeedbackProperties; -} - -/// -/// Returns the intervals of RTP feedback. -/// -/// \return the RTP feedback intervals -/// -/// \since QXmpp 1.5 -/// -QVector QXmppJingleIq::Content::rtpFeedbackIntervals() const -{ - return d->rtpFeedbackIntervals; -} - -/// -/// Sets the intervals of RTP feedback. -/// -/// \param rtpFeedbackIntervals RTP feedback intervals -/// -/// \since QXmpp 1.5 -/// -void QXmppJingleIq::Content::setRtpFeedbackIntervals(const QVector &rtpFeedbackIntervals) -{ - d->rtpFeedbackIntervals = rtpFeedbackIntervals; -} - -/// -/// Returns the RTP header extension properties. -/// -/// \return the RTP header extension properties -/// -/// \since QXmpp 1.5 -/// -QVector QXmppJingleIq::Content::rtpHeaderExtensionProperties() const -{ - return d->rtpHeaderExtensionProperties; -} - -/// -/// Sets the RTP header extension properties. -/// -/// \param rtpHeaderExtensionProperties RTP header extension properties -/// -/// \since QXmpp 1.5 -/// -void QXmppJingleIq::Content::setRtpHeaderExtensionProperties(const QVector &rtpHeaderExtensionProperties) -{ - d->rtpHeaderExtensionProperties = rtpHeaderExtensionProperties; -} - -/// -/// Returns whether mixing of RTP header extensions is allowed corresponding to the -/// "extmap-allow-mixed" element as specified by -/// \xep{0293, Jingle RTP Header Extensions Negotiation}. -/// -/// \return whether mixing of RTP header extensions is allowed -/// -/// \since QXmpp 1.5 -/// -bool QXmppJingleIq::Content::isRtpHeaderExtensionMixingAllowed() const -{ - return d->isRtpHeaderExtensionMixingAllowed; -} - -/// -/// Sets whether mixing of RTP header extensions is allowed corresponding to the -/// "extmap-allow-mixed" element as specified by -/// \xep{0293, Jingle RTP Header Extensions Negotiation}. -/// -/// \param isAllowed whether mixing of RTP header extensions is allowed -/// -/// \since QXmpp 1.5 -/// -void QXmppJingleIq::Content::setRtpHeaderExtensionMixingAllowed(bool isRtpHeaderExtensionMixingAllowed) -{ - d->isRtpHeaderExtensionMixingAllowed = isRtpHeaderExtensionMixingAllowed; -} - -/// -/// Returns the fingerprint hash value for the transport key. -/// -/// This is used for DTLS-SRTP as defined in \xep{0320}. -/// -/// \since QXmpp 0.9 -/// -QByteArray QXmppJingleIq::Content::transportFingerprint() const -{ - return d->transportFingerprint; -} - -/// -/// Sets the fingerprint hash value for the transport key. -/// -/// This is used for DTLS-SRTP as defined in \xep{0320}. -/// -/// \since QXmpp 0.9 -/// -void QXmppJingleIq::Content::setTransportFingerprint(const QByteArray &fingerprint) -{ - d->transportFingerprint = fingerprint; -} - -/// -/// Returns the fingerprint hash algorithm for the transport key. -/// -/// This is used for DTLS-SRTP as defined in \xep{0320}. -/// -/// \since QXmpp 0.9 -/// -QString QXmppJingleIq::Content::transportFingerprintHash() const -{ - return d->transportFingerprintHash; -} - -/// -/// Sets the fingerprint hash algorithm for the transport key. -/// -/// This is used for DTLS-SRTP as defined in \xep{0320}. -/// -/// \since QXmpp 0.9 -/// -void QXmppJingleIq::Content::setTransportFingerprintHash(const QString &hash) -{ - d->transportFingerprintHash = hash; -} - -/// -/// Returns the setup role for the encrypted transport. -/// -/// This is used for DTLS-SRTP as defined in \xep{0320}. -/// -/// \since QXmpp 0.9 -/// -QString QXmppJingleIq::Content::transportFingerprintSetup() const -{ - return d->transportFingerprintSetup; -} - -/// -/// Sets the setup role for the encrypted transport. -/// -/// This is used for DTLS-SRTP as defined in \xep{0320}. -/// -/// \since QXmpp 0.9 -/// -void QXmppJingleIq::Content::setTransportFingerprintSetup(const QString &setup) -{ - d->transportFingerprintSetup = setup; -} - -/// \cond -void QXmppJingleIq::Content::parse(const QDomElement &element) -{ - d->creator = element.attribute(QStringLiteral("creator")); - d->disposition = element.attribute(QStringLiteral("disposition")); - d->name = element.attribute(QStringLiteral("name")); - d->senders = element.attribute(QStringLiteral("senders")); - - // description - QDomElement descriptionElement = element.firstChildElement(QStringLiteral("description")); - 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(); - !childElement.isNull(); - childElement = childElement.nextSiblingElement()) { - if (QXmppJingleRtpEncryption::isJingleRtpEncryption(childElement)) { - QXmppJingleRtpEncryption encryption; - encryption.parse(childElement); - d->rtpEncryption = encryption; - break; - } - } - - parseJingleRtpFeedbackNegotiationElements(descriptionElement, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); - parseJingleRtpHeaderExtensionsNegotiationElements(descriptionElement, d->rtpHeaderExtensionProperties, d->isRtpHeaderExtensionMixingAllowed); - - QDomElement child = descriptionElement.firstChildElement(QStringLiteral("payload-type")); - while (!child.isNull()) { - QXmppJinglePayloadType payload; - payload.parse(child); - d->description.addPayloadType(payload); - child = child.nextSiblingElement(QStringLiteral("payload-type")); - } - - // transport - QDomElement transportElement = element.firstChildElement(QStringLiteral("transport")); - d->transportType = transportElement.namespaceURI(); - d->transportUser = transportElement.attribute(QStringLiteral("ufrag")); - d->transportPassword = transportElement.attribute(QStringLiteral("pwd")); - child = transportElement.firstChildElement(QStringLiteral("candidate")); - while (!child.isNull()) { - QXmppJingleCandidate candidate; - candidate.parse(child); - d->transportCandidates << candidate; - child = child.nextSiblingElement(QStringLiteral("candidate")); - } - - /// XEP-0320 - child = transportElement.firstChildElement(QStringLiteral("fingerprint")); - if (!child.isNull()) { - d->transportFingerprint = parseFingerprint(child.text()); - d->transportFingerprintHash = child.attribute(QStringLiteral("hash")); - d->transportFingerprintSetup = child.attribute(QStringLiteral("setup")); - } -} - -void QXmppJingleIq::Content::toXml(QXmlStreamWriter *writer) const -{ - if (d->creator.isEmpty() || d->name.isEmpty()) { - return; - } - - writer->writeStartElement(QStringLiteral("content")); - helperToXmlAddAttribute(writer, QStringLiteral("creator"), d->creator); - helperToXmlAddAttribute(writer, QStringLiteral("disposition"), d->disposition); - helperToXmlAddAttribute(writer, QStringLiteral("name"), d->name); - helperToXmlAddAttribute(writer, QStringLiteral("senders"), d->senders); - - // description - if (!d->description.type().isEmpty() || !d->description.payloadTypes().isEmpty()) { - writer->writeStartElement(QStringLiteral("description")); - writer->writeDefaultNamespace(d->description.type()); - helperToXmlAddAttribute(writer, QStringLiteral("media"), d->description.media()); - - if (d->description.ssrc()) { - writer->writeAttribute(QStringLiteral("ssrc"), QString::number(d->description.ssrc())); - } - - if (d->isRtpMultiplexingSupported) { - writer->writeEmptyElement(QStringLiteral("rtcp-mux")); - } - - if (d->rtpEncryption) { - d->rtpEncryption->toXml(writer); - } - - jingleRtpFeedbackNegotiationElementsToXml(writer, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); - jingleRtpHeaderExtensionsNegotiationElementsToXml(writer, d->rtpHeaderExtensionProperties, d->isRtpHeaderExtensionMixingAllowed); - - for (const auto &payload : d->description.payloadTypes()) { - payload.toXml(writer); - } - - writer->writeEndElement(); - } - - // transport - if (!d->transportType.isEmpty() || !d->transportCandidates.isEmpty()) { - writer->writeStartElement(QStringLiteral("transport")); - writer->writeDefaultNamespace(d->transportType); - helperToXmlAddAttribute(writer, QStringLiteral("ufrag"), d->transportUser); - helperToXmlAddAttribute(writer, QStringLiteral("pwd"), d->transportPassword); - for (const auto &candidate : d->transportCandidates) { - candidate.toXml(writer); - } - - // XEP-0320: Use of DTLS-SRTP in Jingle Sessions - if (!d->transportFingerprint.isEmpty() && !d->transportFingerprintHash.isEmpty()) { - writer->writeStartElement(QStringLiteral("fingerprint")); - writer->writeDefaultNamespace(ns_jingle_dtls); - writer->writeAttribute(QStringLiteral("hash"), d->transportFingerprintHash); - writer->writeAttribute(QStringLiteral("setup"), d->transportFingerprintSetup); - writer->writeCharacters(formatFingerprint(d->transportFingerprint)); - writer->writeEndElement(); - } - writer->writeEndElement(); - } - - writer->writeEndElement(); -} - -bool QXmppJingleIq::Content::parseSdp(const QString &sdp) -{ - QList payloads; - for (auto &line : sdp.split(QChar(u'\n'))) { - if (line.endsWith('\r')) { - line.resize(line.size() - 1); - } - if (line.startsWith(QStringLiteral("a="))) { - int idx = line.indexOf(':'); - const QString attrName = idx != -1 ? line.mid(2, idx - 2) : line.mid(2); - const QString attrValue = idx != -1 ? line.mid(idx + 1) : ""; - - if (attrName == QStringLiteral("candidate")) { - QXmppJingleCandidate candidate; - if (!candidateParseSdp(&candidate, line.mid(2))) { - qWarning() << "Could not parse candidate" << line; - return false; - } - addTransportCandidate(candidate); - } else if (attrName == QStringLiteral("fingerprint")) { - const QStringList bits = attrValue.split(' '); - if (bits.size() > 1) { - d->transportFingerprintHash = bits[0]; - d->transportFingerprint = parseFingerprint(bits[1]); - } - } else if (attrName == QStringLiteral("fmtp")) { - int spIdx = attrValue.indexOf(' '); - if (spIdx == -1) { - qWarning() << "Could not parse payload parameters" << line; - return false; - } - const int id = attrValue.left(spIdx).toInt(); - const QString paramStr = attrValue.mid(spIdx + 1); - for (auto &payload : payloads) { - if (payload.id() == id) { - QMap params; - if (payload.name() == QStringLiteral("telephone-event")) { - params.insert(QStringLiteral("events"), paramStr); - } else { - thread_local static const auto regex = QRegularExpression(";\\s*"); - const auto paramParts = paramStr.split(regex); - for (const auto &p : paramParts) { - const QStringList bits = p.split('='); - if (bits.size() == 2) { - params.insert(bits.at(0), bits.at(1)); - } - } - } - payload.setParameters(params); - } - } - } else if (attrName == QStringLiteral("rtpmap")) { - // payload type map - const QStringList bits = attrValue.split(' '); - if (bits.size() != 2) { - continue; - } - bool ok = false; - const int id = bits[0].toInt(&ok); - if (!ok) { - continue; - } - - const QStringList args = bits[1].split('/'); - for (auto &payload : payloads) { - if (payload.id() == id) { - payload.setName(args[0]); - if (args.size() > 1) { - payload.setClockrate(args[1].toInt()); - } - if (args.size() > 2) { - payload.setChannels(args[2].toInt()); - } - } - } - } else if (attrName == QStringLiteral("ice-ufrag")) { - d->transportUser = attrValue; - } else if (attrName == QStringLiteral("ice-pwd")) { - d->transportPassword = attrValue; - } else if (attrName == QStringLiteral("setup")) { - d->transportFingerprintSetup = attrValue; - } else if (attrName == QStringLiteral("ssrc")) { - const QStringList bits = attrValue.split(' '); - if (bits.isEmpty()) { - qWarning() << "Could not parse ssrc" << line; - return false; - } - d->description.setSsrc(bits[0].toULong()); - } - } else if (line.startsWith(QStringLiteral("m="))) { - // FIXME: what do we do with the profile (bits[2]) ? - QStringList bits = line.mid(2).split(' '); - if (bits.size() < 3) { - qWarning() << "Could not parse media" << line; - return false; - } - d->description.setMedia(bits[0]); - - // parse payload types - for (int i = 3; i < bits.size(); ++i) { - bool ok = false; - int id = bits[i].toInt(&ok); - if (!ok) { - continue; - } - QXmppJinglePayloadType payload; - payload.setId(id); - payloads << payload; - } - } - } - - d->description.setPayloadTypes(payloads); - return true; -} - -static bool candidateLessThan(const QXmppJingleCandidate &c1, const QXmppJingleCandidate &c2) -{ - if (c1.type() == c2.type()) { - return c1.priority() > c2.priority(); - } else { - return c1.type() == QXmppJingleCandidate::ServerReflexiveType; - } -} - -QString QXmppJingleIq::Content::toSdp() const -{ - // get default candidate - QHostAddress localRtpAddress = QHostAddress::Any; - quint16 localRtpPort = 0; - QList sortedCandidates = d->transportCandidates; - std::sort(sortedCandidates.begin(), sortedCandidates.end(), candidateLessThan); - for (const auto &candidate : sortedCandidates) { - if (candidate.component() == RTP_COMPONENT) { - localRtpAddress = candidate.host(); - localRtpPort = candidate.port(); - break; - } - } - - QStringList sdp; - - // media - QString payloads; - QStringList attrs; - 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) { - rtpmap += "/" + QString::number(payload.channels()); - } - attrs << "a=rtpmap:" + rtpmap; - - // payload parameters - QStringList paramList; - const QMap params = payload.parameters(); - if (payload.name() == QStringLiteral("telephone-event")) { - if (params.contains(QStringLiteral("events"))) { - paramList << params.value(QStringLiteral("events")); - } - } else { - QMap::const_iterator i; - for (i = params.begin(); i != params.end(); ++i) { - paramList << i.key() + QStringLiteral("=") + i.value(); - } - } - if (!paramList.isEmpty()) { - attrs << QStringLiteral("a=fmtp:") + QByteArray::number(payload.id()) + QStringLiteral(" ") + paramList.join("; "); - } - } - 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; - - // transport - for (const auto &candidate : d->transportCandidates) { - sdp << QStringLiteral("a=%1").arg(candidateToSdp(candidate)); - } - if (!d->transportUser.isEmpty()) { - sdp << QStringLiteral("a=ice-ufrag:%1").arg(d->transportUser); - } - if (!d->transportPassword.isEmpty()) { - sdp << QStringLiteral("a=ice-pwd:%1").arg(d->transportPassword); - } - if (!d->transportFingerprint.isEmpty() && !d->transportFingerprintHash.isEmpty()) { - sdp << QStringLiteral("a=fingerprint:%1 %2").arg(d->transportFingerprintHash, formatFingerprint(d->transportFingerprint)); - } - if (!d->transportFingerprintSetup.isEmpty()) { - sdp << QStringLiteral("a=setup:%1").arg(d->transportFingerprintSetup); - } - - return sdp.join("\r\n") + "\r\n"; -} - -/// \endcond - -class QXmppJingleIqReasonPrivate : public QSharedData -{ -public: - QXmppJingleIqReasonPrivate(); - - QString m_text; - QXmppJingleReason::Type m_type; - QXmppJingleReason::RtpErrorCondition m_rtpErrorCondition; - QString m_namespaceUri; -}; - -QXmppJingleIqReasonPrivate::QXmppJingleIqReasonPrivate() - : m_type(QXmppJingleReason::Type::None), - m_rtpErrorCondition(QXmppJingleReason::RtpErrorCondition::NoErrorCondition) -{ -} - -/// -/// \enum QXmppJingleIqReason::RtpErrorCondition -/// -/// Condition of an RTP-specific error -/// -/// \since QXmpp 1.5 -/// - -/// -/// \class QXmppJingleReason -/// -/// The QXmppJingleReason class represents the "reason" element of a -/// QXmppJingle element. -/// - -QXmppJingleReason::QXmppJingleReason() - : d(new QXmppJingleIqReasonPrivate()) -{ -} - -/// Returns the reason's textual description. - -QString QXmppJingleReason::text() const -{ - return d->m_text; -} - -/// Sets the reason's textual description. - -void QXmppJingleReason::setText(const QString &text) -{ - d->m_text = text; -} - -/// Gets the reason's type. - -QXmppJingleReason::Type QXmppJingleReason::type() const -{ - return d->m_type; -} - -/// Sets the reason's type. - -void QXmppJingleReason::setType(QXmppJingleReason::Type type) -{ - d->m_type = type; -} - -/// -/// Returns the RTP error condition as specified by \xep{0167, Jingle RTP Sessions}. -/// -/// \return the RTP error condition -/// -/// \since QXmpp 1.5 -/// -QXmppJingleReason::RtpErrorCondition QXmppJingleReason::rtpErrorCondition() const -{ - return d->m_rtpErrorCondition; -} - -/// -/// Sets the RTP error condition as specified by \xep{0167, Jingle RTP Sessions}. -/// -/// \param rtpErrorCondition RTP error condition -/// -/// \since QXmpp 1.5 -/// -void QXmppJingleReason::setRtpErrorCondition(RtpErrorCondition rtpErrorCondition) -{ - d->m_rtpErrorCondition = rtpErrorCondition; -} - -/// -/// Returns the namespace URI of a reason element. -/// -QString QXmppJingleReason::namespaceUri() const -{ - return d->m_namespaceUri; -} - -/// -/// Sets the namespace URI of a reason element. -/// -void QXmppJingleReason::setNamespaceUri(const QString &namespaceUri) -{ - d->m_namespaceUri = namespaceUri; -} - -/// \cond -void QXmppJingleReason::parse(const QDomElement &element) -{ - d->m_text = element.firstChildElement(QStringLiteral("text")).text(); - for (int i = AlternativeSession; i <= UnsupportedTransports; i++) { - if (!element.firstChildElement(jingle_reasons[i]).isNull()) { - d->m_type = static_cast(i); - break; - } - } - - for (auto child = element.firstChildElement(); - !child.isNull(); - child = child.nextSiblingElement()) { - if (child.namespaceURI() == ns_jingle_rtp_errors) { - if (const auto index = JINGLE_RTP_ERROR_CONDITIONS.indexOf(child.tagName()); - index != -1) { - d->m_rtpErrorCondition = RtpErrorCondition(index); - } - break; - } - } - - if (!element.namespaceURI().isEmpty() && !element.parentNode().isNull() && element.parentNode().namespaceURI() != element.namespaceURI()) { - d->m_namespaceUri = element.namespaceURI(); - } -} - -void QXmppJingleReason::toXml(QXmlStreamWriter *writer) const -{ - if (d->m_type < AlternativeSession || d->m_type > UnsupportedTransports) { - return; - } - - writer->writeStartElement(QStringLiteral("reason")); - - if (!d->m_namespaceUri.isEmpty()) { - writer->writeDefaultNamespace(d->m_namespaceUri); - } - - if (!d->m_text.isEmpty()) { - helperToXmlAddTextElement(writer, QStringLiteral("text"), d->m_text); - } - writer->writeEmptyElement(jingle_reasons[d->m_type]); - - if (d->m_rtpErrorCondition != NoErrorCondition) { - writer->writeStartElement(JINGLE_RTP_ERROR_CONDITIONS.at(d->m_rtpErrorCondition)); - writer->writeDefaultNamespace(ns_jingle_rtp_errors); - writer->writeEndElement(); - } - - writer->writeEndElement(); -} -/// \endcond - -class QXmppJingleIqPrivate : public QSharedData -{ -public: - QXmppJingleIqPrivate(); - - QXmppJingleIq::Action action; - QString initiator; - QString responder; - QString sid; - - QString mujiGroupChatJid; - - QList contents; - QXmppJingleReason reason; - - std::optional rtpSessionState; -}; - -QXmppJingleIqPrivate::QXmppJingleIqPrivate() - : action(QXmppJingleIq::ContentAccept) -{ -} - -/// Constructs a QXmppJingleIq. -QXmppJingleIq::QXmppJingleIq() - : d(new QXmppJingleIqPrivate()) -{ -} - -/// Copy-constructor. -QXmppJingleIq::QXmppJingleIq(const QXmppJingleIq &) = default; -/// Move-constructor. -QXmppJingleIq::QXmppJingleIq(QXmppJingleIq &&) = default; - -QXmppJingleIq::~QXmppJingleIq() = default; - -/// Assignment operator. -QXmppJingleIq &QXmppJingleIq::operator=(const QXmppJingleIq &) = default; -/// Move-assignment operator. -QXmppJingleIq &QXmppJingleIq::operator=(QXmppJingleIq &&) = default; - -/// -/// Returns the Jingle IQ's action. -/// -QXmppJingleIq::Action QXmppJingleIq::action() const -{ - return d->action; -} - -/// Sets the Jingle IQ's action. -/// -/// \param action - -void QXmppJingleIq::setAction(QXmppJingleIq::Action action) -{ - d->action = action; -} - -/// -/// Adds an element to the IQ's content elements. -/// -/// \since QXmpp 0.9.2 -/// -void QXmppJingleIq::addContent(const QXmppJingleIq::Content &content) -{ - d->contents << content; -} - -/// -/// Returns the IQ's content elements. -/// -/// \since QXmpp 0.9.2 -/// -QList QXmppJingleIq::contents() const -{ - return d->contents; -} - -/// -/// Sets the IQ's content elements. -/// -/// \since QXmpp 0.9.2 -/// -void QXmppJingleIq::setContents(const QList &contents) -{ - d->contents = contents; -} - -/// Returns the session initiator. - -QString QXmppJingleIq::initiator() const -{ - return d->initiator; -} - -/// Sets the session initiator. -/// -/// \param initiator - -void QXmppJingleIq::setInitiator(const QString &initiator) -{ - d->initiator = initiator; -} - -/// Returns a reference to the IQ's reason element. - -QXmppJingleReason &QXmppJingleIq::reason() -{ - return d->reason; -} - -/// Returns a const reference to the IQ's reason element. - -const QXmppJingleReason &QXmppJingleIq::reason() const -{ - return d->reason; -} - -/// Returns the session responder. - -QString QXmppJingleIq::responder() const -{ - return d->responder; -} - -/// Sets the session responder. -/// -/// \param responder - -void QXmppJingleIq::setResponder(const QString &responder) -{ - d->responder = responder; -} - -/// -/// Returns true if the call is ringing. -/// -/// \deprecated This method is deprecated since QXmpp 1.5. Use \c QXmppJingleIq::rtpSessionState() -/// instead. -/// -bool QXmppJingleIq::ringing() const -{ - if (d->rtpSessionState) { - return std::holds_alternative(*d->rtpSessionState); - } - - return false; -} - -/// -/// Set to true if the call is ringing. -/// -/// \param ringing -/// -/// \deprecated This method is deprecated since QXmpp 1.5. Use -/// \c QXmppJingleIq::setRtpSessionState() instead. -/// -void QXmppJingleIq::setRinging(bool ringing) -{ - if (ringing) { - d->rtpSessionState = RtpSessionStateRinging(); - } else { - d->rtpSessionState = std::nullopt; - } -} - -/// Returns the session ID. -QString QXmppJingleIq::sid() const -{ - return d->sid; -} - -/// Sets the session ID. -/// -/// \param sid - -void QXmppJingleIq::setSid(const QString &sid) -{ - d->sid = sid; -} - -/// -/// Returns the JID of the \xep{0272, Multiparty Jingle (Muji)} group chat. -/// -/// \return the Muji group chat JID -/// -/// \since QXmpp 1.5 -/// -QString QXmppJingleIq::mujiGroupChatJid() const -{ - return d->mujiGroupChatJid; -} - -/// -/// Sets the JID of the \xep{0272, Multiparty Jingle (Muji)} group chat. -/// -/// \param mujiGroupChatJid Muji group chat JID -/// -/// \since QXmpp 1.5 -/// -void QXmppJingleIq::setMujiGroupChatJid(const QString &mujiGroupChatJid) -{ - d->mujiGroupChatJid = mujiGroupChatJid; -} - -/// -/// Returns the state of an RTP session as specified by \xep{0167, Jingle RTP Sessions} -/// Informational Messages. -/// -/// \return the session's state -/// -/// \since QXmpp 1.5 -/// -std::optional QXmppJingleIq::rtpSessionState() const -{ - return d->rtpSessionState; -} - -/// -/// Sets the state of an RTP session as specified by \xep{0167, Jingle RTP Sessions} Informational -/// Messages. -/// -/// The appropriate action is set as well. -/// Thus, it is not needed to set it manually. -/// -/// \param rtpSessionState session's state -/// -/// \since QXmpp 1.5 -/// -void QXmppJingleIq::setRtpSessionState(const std::optional &rtpSessionState) -{ - d->rtpSessionState = rtpSessionState; - d->action = Action::SessionInfo; -} - -/// \cond -bool QXmppJingleIq::isJingleIq(const QDomElement &element) -{ - QDomElement jingleElement = element.firstChildElement(QStringLiteral("jingle")); - return (jingleElement.namespaceURI() == ns_jingle); -} - -void QXmppJingleIq::parseElementFromChild(const QDomElement &element) -{ - QDomElement jingleElement = element.firstChildElement(QStringLiteral("jingle")); - const QString action = jingleElement.attribute(QStringLiteral("action")); - for (int i = ContentAccept; i <= TransportReplace; i++) { - if (action == jingle_actions[i]) { - d->action = static_cast(i); - break; - } - } - d->initiator = jingleElement.attribute(QStringLiteral("initiator")); - d->responder = jingleElement.attribute(QStringLiteral("responder")); - d->sid = jingleElement.attribute(QStringLiteral("sid")); - - // XEP-0272: Multiparty Jingle (Muji) - if (const auto mujiGroupChatElement = jingleElement.firstChildElement(QStringLiteral("muji")); - mujiGroupChatElement.namespaceURI() == ns_muji) { - d->mujiGroupChatJid = mujiGroupChatElement.attribute(QStringLiteral("room")); - } - - // content - d->contents.clear(); - QDomElement contentElement = jingleElement.firstChildElement(QStringLiteral("content")); - while (!contentElement.isNull()) { - Content content; - content.parse(contentElement); - addContent(content); - contentElement = contentElement.nextSiblingElement(QStringLiteral("content")); - } - - QDomElement reasonElement = jingleElement.firstChildElement(QStringLiteral("reason")); - d->reason.parse(reasonElement); - - for (auto childElement = jingleElement.firstChildElement(); - !childElement.isNull(); - childElement = childElement.nextSiblingElement()) { - if (childElement.namespaceURI() == ns_jingle_rtp_info) { - const auto elementTag = childElement.tagName(); - - if (elementTag == QStringLiteral("active")) { - d->rtpSessionState = RtpSessionStateActive(); - } else if (elementTag == QStringLiteral("hold")) { - d->rtpSessionState = RtpSessionStateHold(); - } else if (elementTag == QStringLiteral("unhold")) { - d->rtpSessionState = RtpSessionStateUnhold(); - } else if (const auto isMute = elementTag == QStringLiteral("mute"); isMute || elementTag == QStringLiteral("unmute")) { - RtpSessionStateMuting muting; - muting.isMute = isMute; - - if (const auto creator = childElement.attribute(QStringLiteral("creator")); creator == QStringLiteral("initiator")) { - muting.creator = Initiator; - } else if (creator == QStringLiteral("responder")) { - muting.creator = Responder; - } - - muting.name = childElement.attribute(QStringLiteral("name")); - - d->rtpSessionState = muting; - } else if (elementTag == QStringLiteral("ringing")) { - d->rtpSessionState = RtpSessionStateRinging(); - } - } - } -} - -void QXmppJingleIq::toXmlElementFromChild(QXmlStreamWriter *writer) const -{ - writer->writeStartElement(QStringLiteral("jingle")); - writer->writeDefaultNamespace(ns_jingle); - helperToXmlAddAttribute(writer, QStringLiteral("action"), jingle_actions[d->action]); - helperToXmlAddAttribute(writer, QStringLiteral("initiator"), d->initiator); - helperToXmlAddAttribute(writer, QStringLiteral("responder"), d->responder); - helperToXmlAddAttribute(writer, QStringLiteral("sid"), d->sid); - - // XEP-0272: Multiparty Jingle (Muji) - if (!d->mujiGroupChatJid.isEmpty()) { - writer->writeStartElement(QStringLiteral("muji")); - writer->writeDefaultNamespace(ns_muji); - helperToXmlAddAttribute(writer, QStringLiteral("room"), d->mujiGroupChatJid); - writer->writeEndElement(); - } - - for (const auto &content : d->contents) { - content.toXml(writer); - } - - d->reason.toXml(writer); - - const auto writeStartElementWithNamespace = [=](const QString &tagName) { - writer->writeStartElement(tagName); - writer->writeDefaultNamespace(ns_jingle_rtp_info); - }; - - if (d->rtpSessionState) { - if (std::holds_alternative(*d->rtpSessionState)) { - writeStartElementWithNamespace(QStringLiteral("active")); - } else if (std::holds_alternative(*d->rtpSessionState)) { - writeStartElementWithNamespace(QStringLiteral("hold")); - } else if (std::holds_alternative(*d->rtpSessionState)) { - writeStartElementWithNamespace(QStringLiteral("unhold")); - } else if (auto rtpSessionStateMuting = std::get_if(&(*d->rtpSessionState))) { - if (rtpSessionStateMuting->isMute) { - writeStartElementWithNamespace(QStringLiteral("mute")); - } else { - writeStartElementWithNamespace(QStringLiteral("unmute")); - } - - if (rtpSessionStateMuting->creator == Initiator) { - helperToXmlAddAttribute(writer, QStringLiteral("creator"), QStringLiteral("initiator")); - } else if (rtpSessionStateMuting->creator == Responder) { - helperToXmlAddAttribute(writer, QStringLiteral("creator"), QStringLiteral("responder")); - } - - helperToXmlAddAttribute(writer, QStringLiteral("name"), rtpSessionStateMuting->name); - } else { - writeStartElementWithNamespace(QStringLiteral("ringing")); - } - - writer->writeEndElement(); - } - - writer->writeEndElement(); -} -/// \endcond - -class QXmppJingleCandidatePrivate : public QSharedData -{ -public: - QXmppJingleCandidatePrivate(); - - int component; - QString foundation; - int generation; - QHostAddress host; - QString id; - int network; - quint16 port; - QString protocol; - int priority; - QXmppJingleCandidate::Type type; -}; - -QXmppJingleCandidatePrivate::QXmppJingleCandidatePrivate() - : component(0), generation(0), network(0), port(0), priority(0), type(QXmppJingleCandidate::HostType) -{ -} - -/// -/// Constructs an empty candidate. -/// -QXmppJingleCandidate::QXmppJingleCandidate() - : d(new QXmppJingleCandidatePrivate()) -{ -} - -/// Copy-constructor. -QXmppJingleCandidate::QXmppJingleCandidate(const QXmppJingleCandidate &other) = default; -/// Move-constructor. -QXmppJingleCandidate::QXmppJingleCandidate(QXmppJingleCandidate &&) = default; -QXmppJingleCandidate::~QXmppJingleCandidate() = default; -/// Assignment operator. -QXmppJingleCandidate &QXmppJingleCandidate::operator=(const QXmppJingleCandidate &other) = default; -/// Move-assignment operator. -QXmppJingleCandidate &QXmppJingleCandidate::operator=(QXmppJingleCandidate &&) = default; - -/// Returns the candidate's component ID. - -int QXmppJingleCandidate::component() const -{ - return d->component; -} - -/// Sets the candidates's component ID. -/// -/// \param component - -void QXmppJingleCandidate::setComponent(int component) -{ - d->component = component; -} - -/// -/// Returns the candidate's foundation. -/// -/// \since QXmpp 0.9 -/// -QString QXmppJingleCandidate::foundation() const -{ - return d->foundation; -} - -/// -/// Sets the candidate's foundation. -/// -/// \param foundation -/// -/// \since QXmpp 0.9 -/// -void QXmppJingleCandidate::setFoundation(const QString &foundation) -{ - d->foundation = foundation; -} - -/// -/// Returns the candidate's generation. -/// -/// \since QXmpp 0.9 -/// -int QXmppJingleCandidate::generation() const -{ - return d->generation; -} - -/// -/// Sets the candidate's generation. -/// -/// \param generation -/// -/// \since QXmpp 0.9 -/// -void QXmppJingleCandidate::setGeneration(int generation) -{ - d->generation = generation; -} - -/// Returns the candidate's host address. -/// - -QHostAddress QXmppJingleCandidate::host() const -{ - return d->host; -} - -/// Sets the candidate's host address. -/// -/// \param host - -void QXmppJingleCandidate::setHost(const QHostAddress &host) -{ - d->host = host; -} - -/// Returns the candidate's unique identifier. -/// - -QString QXmppJingleCandidate::id() const -{ - return d->id; -} - -/// Sets the candidate's unique identifier. -/// -/// \param id - -void QXmppJingleCandidate::setId(const QString &id) -{ - d->id = id; -} - -/// Returns the network index (starting at 0) the candidate is on. -/// - -int QXmppJingleCandidate::network() const -{ - return d->network; -} - -/// Sets the network index (starting at 0) the candidate is on. -/// -/// \param network - -void QXmppJingleCandidate::setNetwork(int network) -{ - d->network = network; -} - -/// Returns the candidate's port number. -/// - -quint16 QXmppJingleCandidate::port() const -{ - return d->port; -} - -/// Sets the candidate's port number. -/// -/// \param port - -void QXmppJingleCandidate::setPort(quint16 port) -{ - d->port = port; -} - -/// Returns the candidate's priority. -/// - -int QXmppJingleCandidate::priority() const -{ - return d->priority; -} - -/// Sets the candidate's priority. -/// -/// \param priority - -void QXmppJingleCandidate::setPriority(int priority) -{ - d->priority = priority; -} - -/// Returns the candidate's protocol (e.g. "udp"). -/// - -QString QXmppJingleCandidate::protocol() const -{ - return d->protocol; -} - -/// Sets the candidate's protocol (e.g. "udp"). -/// -/// \param protocol - -void QXmppJingleCandidate::setProtocol(const QString &protocol) -{ - d->protocol = protocol; -} - -/// Returns the candidate type (e.g. "host"). -/// - -QXmppJingleCandidate::Type QXmppJingleCandidate::type() const -{ - return d->type; -} - -/// Sets the candidate type (e.g. "host"). -/// -/// \param type - -void QXmppJingleCandidate::setType(QXmppJingleCandidate::Type type) -{ - d->type = type; -} - -/// Returns true if the host address or port are empty. -/// - -bool QXmppJingleCandidate::isNull() const -{ - return d->host.isNull() || !d->port; -} - -/// \cond -void QXmppJingleCandidate::parse(const QDomElement &element) -{ - d->component = element.attribute(QStringLiteral("component")).toInt(); - d->foundation = element.attribute(QStringLiteral("foundation")); - d->generation = element.attribute(QStringLiteral("generation")).toInt(); - d->host = QHostAddress(element.attribute(QStringLiteral("ip"))); - d->id = element.attribute(QStringLiteral("id")); - d->network = element.attribute(QStringLiteral("network")).toInt(); - d->port = element.attribute(QStringLiteral("port")).toInt(); - d->priority = element.attribute(QStringLiteral("priority")).toInt(); - d->protocol = element.attribute(QStringLiteral("protocol")); - d->type = typeFromString(element.attribute(QStringLiteral("type"))); -} - -void QXmppJingleCandidate::toXml(QXmlStreamWriter *writer) const -{ - writer->writeStartElement(QStringLiteral("candidate")); - helperToXmlAddAttribute(writer, QStringLiteral("component"), QString::number(d->component)); - helperToXmlAddAttribute(writer, QStringLiteral("foundation"), d->foundation); - helperToXmlAddAttribute(writer, QStringLiteral("generation"), QString::number(d->generation)); - helperToXmlAddAttribute(writer, QStringLiteral("id"), d->id); - helperToXmlAddAttribute(writer, QStringLiteral("ip"), d->host.toString()); - helperToXmlAddAttribute(writer, QStringLiteral("network"), QString::number(d->network)); - helperToXmlAddAttribute(writer, QStringLiteral("port"), QString::number(d->port)); - helperToXmlAddAttribute(writer, QStringLiteral("priority"), QString::number(d->priority)); - helperToXmlAddAttribute(writer, QStringLiteral("protocol"), d->protocol); - helperToXmlAddAttribute(writer, QStringLiteral("type"), typeToString(d->type)); - writer->writeEndElement(); -} - -QXmppJingleCandidate::Type QXmppJingleCandidate::typeFromString(const QString &typeStr, bool *ok) -{ - QXmppJingleCandidate::Type type; - if (typeStr == QStringLiteral("host")) { - type = HostType; - } else if (typeStr == QStringLiteral("prflx")) { - type = PeerReflexiveType; - } else if (typeStr == QStringLiteral("srflx")) { - type = ServerReflexiveType; - } else if (typeStr == QStringLiteral("relay")) { - type = RelayedType; - } else { - qWarning() << "Unknown candidate type" << typeStr; - if (ok) { - *ok = false; - } - return HostType; - } - if (ok) { - *ok = true; - } - return type; -} - -QString QXmppJingleCandidate::typeToString(QXmppJingleCandidate::Type type) -{ - QString typeStr; - switch (type) { - case HostType: - typeStr = QStringLiteral("host"); - break; - case PeerReflexiveType: - typeStr = QStringLiteral("prflx"); - break; - case ServerReflexiveType: - typeStr = QStringLiteral("srflx"); - break; - case RelayedType: - typeStr = QStringLiteral("relay"); - break; - } - return typeStr; -} -/// \endcond - -class QXmppJinglePayloadTypePrivate : public QSharedData -{ -public: - QXmppJinglePayloadTypePrivate(); - - unsigned char channels; - unsigned int clockrate; - unsigned char id; - unsigned int maxptime; - QString name; - QMap parameters; - unsigned int ptime; - - // XEP-0293: Jingle RTP Feedback Negotiation - QVector rtpFeedbackProperties; - QVector rtpFeedbackIntervals; -}; - -QXmppJinglePayloadTypePrivate::QXmppJinglePayloadTypePrivate() - : channels(1), clockrate(0), id(0), maxptime(0), ptime(0) -{ -} - -QXmppJinglePayloadType::QXmppJinglePayloadType() - : d(new QXmppJinglePayloadTypePrivate()) -{ -} - -/// Constructs a copy of other. -/// -/// \param other - -QXmppJinglePayloadType::QXmppJinglePayloadType(const QXmppJinglePayloadType &other) - : d(other.d) -{ -} - -QXmppJinglePayloadType::~QXmppJinglePayloadType() -{ -} - -/// Returns the number of channels (e.g. 1 for mono, 2 for stereo). -/// - -unsigned char QXmppJinglePayloadType::channels() const -{ - return d->channels; -} - -/// Sets the number of channels (e.g. 1 for mono, 2 for stereo). -/// -/// \param channels - -void QXmppJinglePayloadType::setChannels(unsigned char channels) -{ - d->channels = channels; -} - -/// Returns the clockrate in Hz, i.e. the number of samples per second. -/// - -unsigned int QXmppJinglePayloadType::clockrate() const -{ - return d->clockrate; -} - -/// Sets the clockrate in Hz, i.e. the number of samples per second. -/// -/// \param clockrate - -void QXmppJinglePayloadType::setClockrate(unsigned int clockrate) -{ - d->clockrate = clockrate; -} - -/// Returns the payload type identifier. -/// - -unsigned char QXmppJinglePayloadType::id() const -{ - return d->id; -} - -/// Sets the payload type identifier. -/// - -void QXmppJinglePayloadType::setId(unsigned char id) -{ - Q_ASSERT(id <= 127); - d->id = id; -} - -/// Returns the maximum packet time in milliseconds. -/// - -unsigned int QXmppJinglePayloadType::maxptime() const -{ - return d->maxptime; -} - -/// Sets the maximum packet type in milliseconds. -/// -/// \param maxptime - -void QXmppJinglePayloadType::setMaxptime(unsigned int maxptime) -{ - d->maxptime = maxptime; -} - -/// Returns the payload type name. -/// - -QString QXmppJinglePayloadType::name() const -{ - return d->name; -} - -/// Sets the payload type name. -/// -/// \param name - -void QXmppJinglePayloadType::setName(const QString &name) -{ - d->name = name; -} - -/// Returns the payload parameters. - -QMap QXmppJinglePayloadType::parameters() const -{ - return d->parameters; -} - -/// Sets the payload parameters. - -void QXmppJinglePayloadType::setParameters(const QMap ¶meters) -{ - d->parameters = parameters; -} - -/// Returns the packet time in milliseconds (20 by default). -/// - -unsigned int QXmppJinglePayloadType::ptime() const -{ - return d->ptime ? d->ptime : 20; -} - -/// Sets the packet time in milliseconds (20 by default). -/// -/// \param ptime - -void QXmppJinglePayloadType::setPtime(unsigned int ptime) -{ - d->ptime = ptime; -} - -/// -/// Returns the properties of RTP feedback. -/// -/// \return the RTP feedback properties -/// -/// \since QXmpp 1.5 -/// -QVector QXmppJinglePayloadType::rtpFeedbackProperties() const -{ - return d->rtpFeedbackProperties; -} - -/// -/// Sets the properties of RTP feedback. -/// -/// \param rtpFeedbackProperties RTP feedback properties -/// -/// \since QXmpp 1.5 -/// -void QXmppJinglePayloadType::setRtpFeedbackProperties(const QVector &rtpFeedbackProperties) -{ - d->rtpFeedbackProperties = rtpFeedbackProperties; -} - -/// -/// Returns the intervals of RTP feedback. -/// -/// \return the RTP feedback intervals -/// -QVector QXmppJinglePayloadType::rtpFeedbackIntervals() const -{ - return d->rtpFeedbackIntervals; -} - -/// -/// Sets the intervals of RTP feedback. -/// -/// \param rtpFeedbackIntervals RTP feedback intervals -/// -void QXmppJinglePayloadType::setRtpFeedbackIntervals(const QVector &rtpFeedbackIntervals) -{ - d->rtpFeedbackIntervals = rtpFeedbackIntervals; -} - -/// \cond -void QXmppJinglePayloadType::parse(const QDomElement &element) -{ - d->id = element.attribute(QStringLiteral("id")).toInt(); - d->name = element.attribute(QStringLiteral("name")); - d->channels = element.attribute(QStringLiteral("channels")).toInt(); - if (!d->channels) { - d->channels = 1; - } - d->clockrate = element.attribute(QStringLiteral("clockrate")).toInt(); - d->maxptime = element.attribute(QStringLiteral("maxptime")).toInt(); - d->ptime = element.attribute(QStringLiteral("ptime")).toInt(); - - QDomElement child = element.firstChildElement(QStringLiteral("parameter")); - while (!child.isNull()) { - d->parameters.insert(child.attribute(QStringLiteral("name")), child.attribute(QStringLiteral("value"))); - child = child.nextSiblingElement(QStringLiteral("parameter")); - } - - parseJingleRtpFeedbackNegotiationElements(element, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); -} - -void QXmppJinglePayloadType::toXml(QXmlStreamWriter *writer) const -{ - writer->writeStartElement(QStringLiteral("payload-type")); - helperToXmlAddAttribute(writer, QStringLiteral("id"), QString::number(d->id)); - helperToXmlAddAttribute(writer, QStringLiteral("name"), d->name); - if (d->channels > 1) { - helperToXmlAddAttribute(writer, QStringLiteral("channels"), QString::number(d->channels)); - } - if (d->clockrate > 0) { - helperToXmlAddAttribute(writer, QStringLiteral("clockrate"), QString::number(d->clockrate)); - } - if (d->maxptime > 0) { - helperToXmlAddAttribute(writer, QStringLiteral("maxptime"), QString::number(d->maxptime)); - } - if (d->ptime > 0) { - helperToXmlAddAttribute(writer, QStringLiteral("ptime"), QString::number(d->ptime)); - } - - for (auto itr = d->parameters.begin(); itr != d->parameters.end(); itr++) { - writer->writeStartElement(QStringLiteral("parameter")); - writer->writeAttribute(QStringLiteral("name"), itr.key()); - writer->writeAttribute(QStringLiteral("value"), itr.value()); - writer->writeEndElement(); - } - - jingleRtpFeedbackNegotiationElementsToXml(writer, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); - - writer->writeEndElement(); -} -/// \endcond - -/// Assigns the other payload type to this one. -/// -/// \param other - -QXmppJinglePayloadType &QXmppJinglePayloadType::operator=(const QXmppJinglePayloadType &other) -{ - d = other.d; - return *this; -} - -/// Returns true if this QXmppJinglePayloadType and \a other refer to the same payload type. -/// -/// \param other - -bool QXmppJinglePayloadType::operator==(const QXmppJinglePayloadType &other) const -{ - // FIXME : what to do with m_ptime and m_maxptime? - if (d->id <= 95) { - return other.d->id == d->id && other.d->clockrate == d->clockrate; - } else { - return other.d->channels == d->channels && - other.d->clockrate == d->clockrate && - other.d->name.toLower() == d->name.toLower(); - } -} - -class QXmppJingleDescriptionPrivate : public QSharedData -{ -public: - QXmppJingleDescriptionPrivate() = default; - - QString media; - quint32 ssrc; - QString type; - QList 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 &QXmppJingleDescription::payloadTypes() const -{ - return d->payloadTypes; -} - -/// -/// Sets the list of payload types. -/// -void QXmppJingleDescription::setPayloadTypes(const QList &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: - QString name; - QString value; -}; - -/// -/// \class QXmppSdpParameter -/// -/// \brief The QXmppSdpParameter class represents a Session Description Protocol (SDP) parameter -/// specified by RFC 4566 and used by several XEPs based on \xep{0166, Jingle}. -/// -/// \since QXmpp 1.5 -/// - -/// -/// Constructs a Session Description Protocol parameter. -/// -QXmppSdpParameter::QXmppSdpParameter() - : d(new QXmppSdpParameterPrivate()) -{ -} - -QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppSdpParameter) - -/// -/// Returns the name of the parameter. -/// -/// \return the parameter's name -/// -QString QXmppSdpParameter::name() const -{ - return d->name; -} - -/// -/// Sets the name of the parameter. -/// -/// \param name parameter's name -/// -void QXmppSdpParameter::setName(const QString &name) -{ - d->name = name; -} - -/// -/// Returns the value of the parameter. -/// -/// \return the parameter's value -/// -QString QXmppSdpParameter::value() const -{ - return d->value; -} - -/// -/// Sets the value of the parameter. -/// -/// A parameter in the form "a=b" can be created by this method. -/// Any other form of parameters can be created by not using this method. -/// The value stays a default-constructed QString then. -/// -/// \param value parameter's value -/// -void QXmppSdpParameter::setValue(const QString &value) -{ - d->value = value; -} - -/// \cond -void QXmppSdpParameter::parse(const QDomElement &element) -{ - d->name = element.attribute(QStringLiteral("name")); - d->value = element.attribute(QStringLiteral("value")); -} - -void QXmppSdpParameter::toXml(QXmlStreamWriter *writer) const -{ - writer->writeStartElement(QStringLiteral("parameter")); - helperToXmlAddAttribute(writer, QStringLiteral("name"), d->name); - - if (!d->value.isEmpty()) { - helperToXmlAddAttribute(writer, QStringLiteral("value"), d->value); - } - - writer->writeEndElement(); -} -/// \endcond - -/// -/// Determines whether the given DOM element is a Session Description Protocol parameter element. -/// -/// \param element DOM element being checked -/// -/// \return whether element is a Session Description Protocol parameter element -/// -bool QXmppSdpParameter::isSdpParameter(const QDomElement &element) -{ - return element.tagName() == QStringLiteral("parameter"); -} - -class QXmppJingleRtpCryptoElementPrivate : public QSharedData -{ -public: - uint32_t tag = 0; - QString cryptoSuite; - QString keyParams; - QString sessionParams; -}; - -/// -/// \class QXmppJingleRtpCryptoElement -/// -/// \brief The QXmppJingleRtpCryptoElement class represents the \xep{0167: Jingle RTP Sessions} -/// "crypto" element used for SRTP negotiation. -/// -/// \since QXmpp 1.5 -/// - -/// -/// Constructs a Jingle RTP crypto element. -/// -QXmppJingleRtpCryptoElement::QXmppJingleRtpCryptoElement() - : d(new QXmppJingleRtpCryptoElementPrivate()) -{ -} - -QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpCryptoElement) - -/// -/// Returns the tag used as an identifier for the crypto element. -/// -/// \return the identifying tag -/// -uint32_t QXmppJingleRtpCryptoElement::tag() const -{ - return d->tag; -} - -/// -/// Sets the tag used as an identifier for the crypto element. -/// -/// \param tag identifying tag -/// -void QXmppJingleRtpCryptoElement::setTag(uint32_t tag) -{ - d->tag = tag; -} - -/// -/// Returns the crypto suite used as an identifier for describing the encryption and authentication -/// algorithms. -/// -/// \return the identifying crypto suite -/// -QString QXmppJingleRtpCryptoElement::cryptoSuite() const -{ - return d->cryptoSuite; -} - -/// -/// Sets the crypto suite used as an identifier for describing the encryption and authentication -/// algorithms. -/// -/// \param cryptoSuite identifying crypto suite -/// -void QXmppJingleRtpCryptoElement::setCryptoSuite(const QString &cryptoSuite) -{ - d->cryptoSuite = cryptoSuite; -} - -/// -/// Returns the key parameters providing one or more sets of keying material for the crypto suite. -/// -/// \return the key parameters providing one or more sets of keying material -/// -QString QXmppJingleRtpCryptoElement::keyParams() const -{ - return d->keyParams; -} - -/// -/// Sets the key parameters providing one or more sets of keying material for the crypto suite. -/// -/// \param keyParams key parameters providing one or more sets of keying material -/// -void QXmppJingleRtpCryptoElement::setKeyParams(const QString &keyParams) -{ - d->keyParams = keyParams; -} - -/// -/// Returns the session parameters providing transport-specific data. -/// -/// \return the session parameters providing transport-specific data -/// -QString QXmppJingleRtpCryptoElement::sessionParams() const -{ - return d->sessionParams; -} - -/// -/// Sets the session parameters providing transport-specific data. -/// -/// \param sessionParams session parameters providing transport-specific data -/// -void QXmppJingleRtpCryptoElement::setSessionParams(const QString &sessionParams) -{ - d->sessionParams = sessionParams; -} - -/// \cond -void QXmppJingleRtpCryptoElement::parse(const QDomElement &element) -{ - d->tag = element.attribute(QStringLiteral("tag")).toUInt(); - d->cryptoSuite = element.attribute(QStringLiteral("crypto-suite")); - d->keyParams = element.attribute(QStringLiteral("key-params")); - d->sessionParams = element.attribute(QStringLiteral("session-params")); -} - -void QXmppJingleRtpCryptoElement::toXml(QXmlStreamWriter *writer) const -{ - if (!d->cryptoSuite.isEmpty() && !d->keyParams.isEmpty()) { - writer->writeStartElement(QStringLiteral("crypto")); - writer->writeAttribute(QStringLiteral("tag"), QString::number(d->tag)); - writer->writeAttribute(QStringLiteral("crypto-suite"), d->cryptoSuite); - writer->writeAttribute(QStringLiteral("key-params"), d->keyParams); - helperToXmlAddAttribute(writer, QStringLiteral("session-params"), d->sessionParams); - writer->writeEndElement(); - } -} -/// \endcond - -/// -/// Determines whether the given DOM element is an RTP crypto element. -/// -/// \param element DOM element being checked -/// -/// \return whether element is an RTP crypto element -/// -bool QXmppJingleRtpCryptoElement::isJingleRtpCryptoElement(const QDomElement &element) -{ - return element.tagName() == QStringLiteral("crypto"); -} - -class QXmppJingleRtpEncryptionPrivate : public QSharedData -{ -public: - bool isRequired = false; - QVector cryptoElements; -}; - -/// -/// \class QXmppJingleRtpEncryption -/// -/// \brief The QXmppJingleRtpEncryption class represents the \xep{0167: Jingle RTP Sessions} -/// "encryption" element used for SRTP negotiation. -/// -/// \since QXmpp 1.5 -/// - -/// -/// Constructs a Jingle RTP encryption. -/// -QXmppJingleRtpEncryption::QXmppJingleRtpEncryption() - : d(new QXmppJingleRtpEncryptionPrivate()) -{ -} - -QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpEncryption) - -/// -/// Returns whether encryption via SRTP is required. -/// -/// \return whether encryption is required -/// -bool QXmppJingleRtpEncryption::isRequired() const -{ - return d->isRequired; -} - -/// -/// Sets whether encryption via SRTP is required. -/// -/// \param isRequired whether encryption is required -/// -void QXmppJingleRtpEncryption::setRequired(bool isRequired) -{ - d->isRequired = isRequired; -} - -/// -/// Returns the crypto elements used for encryption via SRTP. -/// -/// \return the crypto elements -/// -QVector QXmppJingleRtpEncryption::cryptoElements() const -{ - return d->cryptoElements; -} - -/// -/// Sets the crypto elements used for encryption via SRTP. -/// -/// \param cryptoElements the crypto elements -/// -void QXmppJingleRtpEncryption::setCryptoElements(const QVector &cryptoElements) -{ - d->cryptoElements = cryptoElements; -} - -/// \cond -void QXmppJingleRtpEncryption::parse(const QDomElement &element) -{ - d->isRequired = element.attribute(QStringLiteral("required")) == QStringLiteral("true") || - element.attribute(QStringLiteral("required")) == QStringLiteral("1"); - - for (auto childElement = element.firstChildElement(); - !childElement.isNull(); - childElement = childElement.nextSiblingElement()) { - if (QXmppJingleRtpCryptoElement::isJingleRtpCryptoElement(childElement)) { - QXmppJingleRtpCryptoElement cryptoElement; - cryptoElement.parse(childElement); - d->cryptoElements.append(std::move(cryptoElement)); - } - } -} - -void QXmppJingleRtpEncryption::toXml(QXmlStreamWriter *writer) const -{ - if (!d->cryptoElements.isEmpty()) { - writer->writeStartElement(QStringLiteral("encryption")); - writer->writeDefaultNamespace(ns_jingle_rtp); - - if (d->isRequired) { - writer->writeAttribute(QStringLiteral("required"), QStringLiteral("1")); - } - - for (const auto &cryptoElement : std::as_const(d->cryptoElements)) { - cryptoElement.toXml(writer); - } - - writer->writeEndElement(); - } -} -/// \endcond - -/// -/// Determines whether the given DOM element is an RTP encryption element. -/// -/// \param element DOM element being checked -/// -/// \return whether element is an RTP encryption element -/// -bool QXmppJingleRtpEncryption::isJingleRtpEncryption(const QDomElement &element) -{ - return element.tagName() == QStringLiteral("encryption") && - element.namespaceURI() == ns_jingle_rtp; -} - -class QXmppJingleRtpFeedbackPropertyPrivate : public QSharedData -{ -public: - QString type; - QString subtype; - QVector parameters; -}; - -/// -/// \class QXmppJingleRtpFeedbackProperty -/// -/// \brief The QXmppJingleRtpFeedbackProperty class represents the -/// \xep{0293, Jingle RTP Feedback Negotiation} "rtcp-fb" element. -/// -/// \since QXmpp 1.5 -/// - -/// -/// Constructs a Jingle RTP feedback property. -/// -QXmppJingleRtpFeedbackProperty::QXmppJingleRtpFeedbackProperty() - : d(new QXmppJingleRtpFeedbackPropertyPrivate()) -{ -} - -QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpFeedbackProperty) - -/// -/// Returns the type of RTP feedback. -/// -/// \return the RTP feedback type -/// -QString QXmppJingleRtpFeedbackProperty::type() const -{ - return d->type; -} - -/// -/// Sets the type of RTP feedback. -/// -/// \param type RTP feedback type -/// -void QXmppJingleRtpFeedbackProperty::setType(const QString &type) -{ - d->type = type; -} - -/// -/// Returns the subtype for RTP feedback. -/// -/// \return the RTP feedback subtype -/// -QString QXmppJingleRtpFeedbackProperty::subtype() const -{ - return d->subtype; -} - -/// -/// Sets the subtype of RTP feedback. -/// -/// If there is more than one parameter, use QXmppJingleRtpFeedbackProperty::setParameters() -/// instead of this method. -/// -/// \param subtype RTP feedback subtype -/// -void QXmppJingleRtpFeedbackProperty::setSubtype(const QString &subtype) -{ - d->subtype = subtype; -} - -/// -/// Returns the parameters of RTP feedback. -/// -/// \return the RTP feedback parameters -/// -QVector QXmppJingleRtpFeedbackProperty::parameters() const -{ - return d->parameters; -} - -/// -/// Sets the parameters of RTP feedback. -/// -/// Additional parameters can be set by this method. -/// If there is only one parameter, use QXmppJingleRtpFeedbackProperty::setSubtype() -/// instead of this method. -/// -/// \param parameters RTP feedback parameters -/// -void QXmppJingleRtpFeedbackProperty::setParameters(const QVector ¶meters) -{ - d->parameters = parameters; -} - -/// \cond -void QXmppJingleRtpFeedbackProperty::parse(const QDomElement &element) -{ - d->type = element.attribute(QStringLiteral("type")); - d->subtype = element.attribute(QStringLiteral("subtype")); - parseSdpParameters(element, d->parameters); -} - -void QXmppJingleRtpFeedbackProperty::toXml(QXmlStreamWriter *writer) const -{ - writer->writeStartElement(QStringLiteral("rtcp-fb")); - writer->writeDefaultNamespace(ns_jingle_rtp_feedback_negotiation); - helperToXmlAddAttribute(writer, QStringLiteral("type"), d->type); - - // If there are parameters, they must be used instead of the subtype. - if (d->subtype.isEmpty()) { - sdpParametersToXml(writer, d->parameters); - } else { - helperToXmlAddAttribute(writer, QStringLiteral("subtype"), d->subtype); - } - - writer->writeEndElement(); -} -/// \endcond - -/// -/// Determines whether the given DOM element is an RTP feedback property element. -/// -/// \param element DOM element being checked -/// -/// \return whether element is an RTP feedback property element -/// -bool QXmppJingleRtpFeedbackProperty::isJingleRtpFeedbackProperty(const QDomElement &element) -{ - return element.tagName() == QStringLiteral("rtcp-fb") && - element.namespaceURI() == ns_jingle_rtp_feedback_negotiation; -} - -/// -/// \class QXmppJingleRtpFeedbackInterval -/// -/// \brief The QXmppJingleRtpFeedbackInterval class represents the -/// \xep{0293, Jingle RTP Feedback Negotiation} "rtcp-fb-trr-int" element. -/// -/// \since QXmpp 1.5 -/// - -/// -/// Constructs a Jingle RTP feedback interval. -/// -QXmppJingleRtpFeedbackInterval::QXmppJingleRtpFeedbackInterval() -{ -} - -QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpFeedbackInterval) - -/// -/// Returns the value of the RTP feedback interval. -/// -/// \return the RTP feedback interval value -/// -uint64_t QXmppJingleRtpFeedbackInterval::value() const -{ - return m_value; -} - -/// -/// Sets the value of the RTP feedback interval. -/// -/// \param value RTP feedback interval value -/// -void QXmppJingleRtpFeedbackInterval::setValue(uint64_t value) -{ - m_value = value; -} - -/// \cond -void QXmppJingleRtpFeedbackInterval::parse(const QDomElement &element) -{ - m_value = element.attribute(QStringLiteral("value")).toUInt(); -} - -void QXmppJingleRtpFeedbackInterval::toXml(QXmlStreamWriter *writer) const -{ - writer->writeStartElement(QStringLiteral("rtcp-fb-trr-int")); - writer->writeDefaultNamespace(ns_jingle_rtp_feedback_negotiation); - helperToXmlAddAttribute(writer, QStringLiteral("value"), QString::number(m_value)); - writer->writeEndElement(); -} -/// \endcond - -/// -/// Determines whether the given DOM element is an RTP feedback interval element. -/// -/// \param element DOM element being checked -/// -/// \return whether element is an RTP feedback interval element -/// -bool QXmppJingleRtpFeedbackInterval::isJingleRtpFeedbackInterval(const QDomElement &element) -{ - return element.tagName() == QStringLiteral("rtcp-fb-trr-int") && - element.namespaceURI() == ns_jingle_rtp_feedback_negotiation; -} - -class QXmppJingleRtpHeaderExtensionPropertyPrivate : public QSharedData -{ -public: - uint32_t id = 0; - QString uri; - QXmppJingleRtpHeaderExtensionProperty::Senders senders = QXmppJingleRtpHeaderExtensionProperty::Both; - QVector parameters; -}; - -/// -/// \enum QXmppJingleRtpHeaderExtensionProperty::Senders -/// -/// Parties that are allowed to send the negotiated RTP header extension -/// - -/// -/// \class QXmppJingleRtpHeaderExtensionProperty -/// -/// \brief The QXmppJingleRtpHeaderExtensionProperty class represents the -/// \xep{0294, Jingle RTP Header Extensions Negotiation} "rtp-hdrext" element. -/// -/// \since QXmpp 1.5 -/// - -/// -/// Constructs a Jingle RTP header extension property. -/// -QXmppJingleRtpHeaderExtensionProperty::QXmppJingleRtpHeaderExtensionProperty() - : d(new QXmppJingleRtpHeaderExtensionPropertyPrivate()) -{ -} - -QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpHeaderExtensionProperty) - -/// -/// Returns the ID of the RTP header extension. -/// -/// The ID is 0 if it is unset. -/// -/// \return the RTP header extension's ID -/// -uint32_t QXmppJingleRtpHeaderExtensionProperty::id() const -{ - return d->id; -} - -/// -/// Sets the ID of the RTP header extension. -/// -/// The ID must either be at least 1 and at most 256 or at least 4096 and at most 4351. -/// -/// \param id RTP header extension's ID -/// -void QXmppJingleRtpHeaderExtensionProperty::setId(uint32_t id) -{ - d->id = id; -} - -/// -/// Returns the URI defning the RTP header extension. -/// -/// \return the RTP header extension's URI -/// -QString QXmppJingleRtpHeaderExtensionProperty::uri() const -{ - return d->uri; -} - -/// -/// Sets the URI defning the RTP header extension. -/// -/// \param uri RTP header extension's URI -/// -void QXmppJingleRtpHeaderExtensionProperty::setUri(const QString &uri) -{ - d->uri = uri; -} - -/// -/// Returns the parties that are allowed to send the negotiated RTP header extensions. -/// -/// \return the parties that are allowed to send the RTP header extensions -/// -QXmppJingleRtpHeaderExtensionProperty::Senders QXmppJingleRtpHeaderExtensionProperty::senders() const -{ - return d->senders; -} - -/// -/// Sets the parties that are allowed to send the negotiated RTP header extensions. -/// -/// \param senders parties that are allowed to send the RTP header extensions -/// -void QXmppJingleRtpHeaderExtensionProperty::setSenders(Senders senders) -{ - d->senders = senders; -} - -/// -/// Returns the parameters of the RTP header extension. -/// -/// \return the RTP header extension's parameters -/// -QVector QXmppJingleRtpHeaderExtensionProperty::parameters() const -{ - return d->parameters; -} - -/// -/// Sets the parameters of the RTP header extension. -/// -/// Additional parameters can be set by this method. -/// -/// \param parameters RTP header extension's parameters -/// -void QXmppJingleRtpHeaderExtensionProperty::setParameters(const QVector ¶meters) -{ - d->parameters = parameters; -} - -/// \cond -void QXmppJingleRtpHeaderExtensionProperty::parse(const QDomElement &element) -{ - if (element.tagName() == QStringLiteral("rtp-hdrext") && element.namespaceURI() == ns_jingle_rtp_header_extensions_negotiation) { - d->id = element.attribute(QStringLiteral("id")).toUInt(); - d->uri = element.attribute(QStringLiteral("uri")); - - if (const auto senders = JINGLE_RTP_HEADER_EXTENSIONS_SENDERS.indexOf(element.attribute(QStringLiteral("senders"))); senders > QXmppJingleRtpHeaderExtensionProperty::Both) { - d->senders = static_cast(senders); - } - - parseSdpParameters(element, d->parameters); - } -} - -void QXmppJingleRtpHeaderExtensionProperty::toXml(QXmlStreamWriter *writer) const -{ - writer->writeStartElement(QStringLiteral("rtp-hdrext")); - writer->writeDefaultNamespace(ns_jingle_rtp_header_extensions_negotiation); - helperToXmlAddAttribute(writer, QStringLiteral("id"), QString::number(d->id)); - helperToXmlAddAttribute(writer, QStringLiteral("uri"), d->uri); - - if (d->senders != QXmppJingleRtpHeaderExtensionProperty::Both) { - helperToXmlAddAttribute(writer, QStringLiteral("senders"), JINGLE_RTP_HEADER_EXTENSIONS_SENDERS.at(d->senders)); - } - - sdpParametersToXml(writer, d->parameters); - - writer->writeEndElement(); -} -/// \endcond - -/// -/// Determines whether the given DOM element is an RTP header extensions property element. -/// -/// \param element DOM element being checked -/// -/// \return whether element is an RTP header extension property element -/// -bool QXmppJingleRtpHeaderExtensionProperty::isJingleRtpHeaderExtensionProperty(const QDomElement &element) -{ - 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 description; - std::optional 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 QXmppJingleMessageInitiationElement::description() const -{ - return d->description; -} - -/// -/// Sets the Jingle Message Initiation element description. -/// -void QXmppJingleMessageInitiationElement::setDescription(std::optional description) -{ - d->description = description; -} - -/// -/// Returns the Jingle Message Initiation element reason. -/// -std::optional QXmppJingleMessageInitiationElement::reason() const -{ - return d->reason; -} - -/// -/// Sets the Jingle Message Initiation element reason. -/// -void QXmppJingleMessageInitiationElement::setReason(std::optional reason) -{ - d->reason = reason; - - if (d->reason) { - d->reason->setNamespaceUri(ns_jingle); - } -} - -/// -/// Returns true if the Jingle Message Initiation element contains a tag. -/// -bool QXmppJingleMessageInitiationElement::containsTieBreak() const -{ - return d->containsTieBreak; -} - -/// -/// Sets if the Jingle Message Initiation element contains a 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) -{ - d->type = stringToJmiElementType(element.nodeName()); - - if (d->type == Type::None) { - return; - } - - 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 (const auto &reasonElement = element.firstChildElement("reason"); !reasonElement.isNull()) { - d->reason = QXmppJingleReason(); - d->reason->parse(reasonElement); - } - - if (const 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 (element.nodeName() == QStringLiteral("propose") || element.nodeName() == QStringLiteral("ringing") || element.nodeName() == QStringLiteral("proceed") || element.nodeName() == QStringLiteral("retract") || element.nodeName() == QStringLiteral("reject") || element.nodeName() == QStringLiteral("finish")) && 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) const -{ - 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. -/// -QXmppJingleMessageInitiationElement::Type QXmppJingleMessageInitiationElement::stringToJmiElementType(const QString &typeStr) const -{ - 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 Type::None; -} diff --git a/src/base/QXmppJingleIq.h b/src/base/QXmppJingleIq.h deleted file mode 100644 index 1e425f4d..00000000 --- a/src/base/QXmppJingleIq.h +++ /dev/null @@ -1,649 +0,0 @@ -// SPDX-FileCopyrightText: 2010 Jeremy Lainé -// SPDX-FileCopyrightText: 2022 Melvin Keskin -// SPDX-FileCopyrightText: 2023 Tibor Csötönyi -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifndef QXMPPJINGLEIQ_H -#define QXMPPJINGLEIQ_H - -#include "QXmppIq.h" - -#include - -#include - -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 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 d; -}; - -class QXMPP_EXPORT QXmppJingleRtpEncryption -{ -public: - QXmppJingleRtpEncryption(); - - QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpEncryption) - - bool isRequired() const; - void setRequired(bool isRequired); - - QVector cryptoElements() const; - void setCryptoElements(const QVector &cryptoElements); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - - static bool isJingleRtpEncryption(const QDomElement &element); - -private: - QSharedDataPointer 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 parameters() const; - void setParameters(const QVector ¶meters); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - - static bool isJingleRtpFeedbackProperty(const QDomElement &element); - -private: - QSharedDataPointer 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 parameters() const; - void setParameters(const QVector ¶meters); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - - static bool isJingleRtpHeaderExtensionProperty(const QDomElement &element); - -private: - QSharedDataPointer 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 parameters() const; - void setParameters(const QMap ¶meters); - - unsigned int ptime() const; - void setPtime(unsigned int ptime); - - QVector rtpFeedbackProperties() const; - void setRtpFeedbackProperties(const QVector &rtpFeedbackProperties); - - QVector rtpFeedbackIntervals() const; - void setRtpFeedbackIntervals(const QVector &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 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 &payloadTypes() const; - void setPayloadTypes(const QList &payloadTypes); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - /// \endcond - -private: - QSharedDataPointer 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 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 - }; - - 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); - - QString namespaceUri() const; - void setNamespaceUri(const QString &namespaceUri); - - /// \cond - void parse(const QDomElement &element); - void toXml(QXmlStreamWriter *writer) const; - - /// \endcond - - QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleReason) - -private: - QSharedDataPointer 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; - - /// \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); - - bool isRtpMultiplexingSupported() const; - void setRtpMultiplexingSupported(bool isRtpMultiplexingSupported); - - std::optional rtpEncryption() const; - void setRtpEncryption(const std::optional &rtpEncryption); - - void addTransportCandidate(const QXmppJingleCandidate &candidate); - QList transportCandidates() const; - void setTransportCandidates(const QList &candidates); - - QString transportUser() const; - void setTransportUser(const QString &user); - - QString transportPassword() const; - void setTransportPassword(const QString &password); - - QVector rtpFeedbackProperties() const; - void setRtpFeedbackProperties(const QVector &rtpFeedbackProperties); - - QVector rtpFeedbackIntervals() const; - void setRtpFeedbackIntervals(const QVector &rtpFeedbackIntervals); - - QVector rtpHeaderExtensionProperties() const; - void setRtpHeaderExtensionProperties(const QVector &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 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 contents() const; - void setContents(const QList &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() const; - void setRtpSessionState(const std::optional &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 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 description() const; - void setDescription(std::optional description); - - std::optional reason() const; - void setReason(std::optional 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 &); - -private: - QString jmiElementTypeToString(Type type) const; - Type stringToJmiElementType(const QString &typeStr) const; - - QSharedDataPointer d; -}; - -Q_DECLARE_METATYPE(QXmppJingleReason::RtpErrorCondition) - -#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ee5064be..4e0b23c6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,7 +41,7 @@ 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) diff --git a/tests/qxmppjingledata/tst_qxmppjingledata.cpp b/tests/qxmppjingledata/tst_qxmppjingledata.cpp new file mode 100644 index 00000000..62187998 --- /dev/null +++ b/tests/qxmppjingledata/tst_qxmppjingledata.cpp @@ -0,0 +1,1681 @@ +// SPDX-FileCopyrightText: 2012 Jeremy Lainé +// SPDX-FileCopyrightText: 2022 Melvin Keskin +// SPDX-FileCopyrightText: 2023 Tibor Csötönyi +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "QXmppJingleData.h" + +#include "util.h" +#include + +class tst_QXmppJingleData : public QObject +{ + Q_OBJECT + +private: + Q_SLOT void testIsSdpParameter_data(); + Q_SLOT void testIsSdpParameter(); + Q_SLOT void testSdpParameter(); + Q_SLOT void testSdpParameterWithoutValue(); + Q_SLOT void testIsRtpCryptoElement_data(); + Q_SLOT void testIsRtpCryptoElement(); + Q_SLOT void testRtpCryptoElement_data(); + Q_SLOT void testRtpCryptoElement(); + Q_SLOT void testIsRtpEncryption_data(); + Q_SLOT void testIsRtpEncryption(); + Q_SLOT void testRtpEncryption_data(); + Q_SLOT void testRtpEncryption(); + Q_SLOT void testIsRtpFeedbackProperty_data(); + Q_SLOT void testIsRtpFeedbackProperty(); + Q_SLOT void testRtpFeedbackProperty(); + Q_SLOT void testRtpFeedbackPropertyWithParameters(); + Q_SLOT void testIsRtpFeedbackInterval_data(); + Q_SLOT void testIsRtpFeedbackInterval(); + Q_SLOT void testRtpFeedbackInterval(); + Q_SLOT void testIsRtpHeaderExtensionProperty_data(); + Q_SLOT void testIsRtpHeaderExtensionProperty(); + Q_SLOT void testRtpHeaderExtensionProperty(); + Q_SLOT void testRtpHeaderExtensionPropertyWithSenders(); + Q_SLOT void testRtpHeaderExtensionPropertyWithParameters(); + Q_SLOT void testCandidate(); + Q_SLOT void testContent(); + Q_SLOT void testContentFingerprint(); + Q_SLOT void testContentSdp(); + Q_SLOT void testContentSdpReflexive(); + Q_SLOT void testContentSdpFingerprint(); + Q_SLOT void testContentSdpParameters(); + Q_SLOT void testContentRtpFeedbackNegotiation(); + Q_SLOT void testContentRtpHeaderExtensionsNegotiation(); + Q_SLOT void testSession(); + Q_SLOT void testTerminate(); + Q_SLOT void testRtpSessionState_data(); + Q_SLOT void testRtpSessionState(); + Q_SLOT void testAudioPayloadType(); + Q_SLOT void testVideoPayloadType(); + 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_QXmppJingleData::testIsSdpParameter_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("isValid"); + + QTest::newRow("valid") + << QByteArrayLiteral("") + << true; + QTest::newRow("invalidTag") + << QByteArrayLiteral("") + << false; +} + +void tst_QXmppJingleData::testIsSdpParameter() +{ + QFETCH(QByteArray, xml); + QFETCH(bool, isValid); + + QCOMPARE(QXmppSdpParameter::isSdpParameter(xmlToDom(xml)), isValid); +} + +void tst_QXmppJingleData::testSdpParameter() +{ + const QByteArray xml(""); + + QXmppSdpParameter parameter1; + QVERIFY(parameter1.name().isEmpty()); + QVERIFY(parameter1.value().isEmpty()); + + parsePacket(parameter1, xml); + QCOMPARE(parameter1.name(), QStringLiteral("test-name")); + QCOMPARE(parameter1.value(), QStringLiteral("test-value")); + + serializePacket(parameter1, xml); + + QXmppSdpParameter parameter2; + parameter2.setName(QStringLiteral("test-name")); + parameter2.setValue(QStringLiteral("test-value")); + + serializePacket(parameter2, xml); +} + +void tst_QXmppJingleData::testSdpParameterWithoutValue() +{ + const QByteArray xml(""); + + QXmppSdpParameter parameter1; + + parsePacket(parameter1, xml); + QCOMPARE(parameter1.name(), QStringLiteral("test-name")); + QVERIFY(parameter1.value().isEmpty()); + + serializePacket(parameter1, xml); + + QXmppSdpParameter parameter2; + parameter2.setName(QStringLiteral("test-name")); + + serializePacket(parameter2, xml); +} + +void tst_QXmppJingleData::testIsRtpCryptoElement_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("isValid"); + + QTest::newRow("valid") + << QByteArrayLiteral("") + << true; + QTest::newRow("invalidTag") + << QByteArrayLiteral("") + << false; +} + +void tst_QXmppJingleData::testIsRtpCryptoElement() +{ + QFETCH(QByteArray, xml); + QFETCH(bool, isValid); + + QCOMPARE(QXmppJingleRtpCryptoElement::isJingleRtpCryptoElement(xmlToDom(xml)), isValid); +} + +void tst_QXmppJingleData::testRtpCryptoElement_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("hasSessionParams"); + + QTest::newRow("withoutSessionParams") + << QByteArrayLiteral("") + << false; + QTest::newRow("withSessionParams") + << QByteArrayLiteral("") + << true; +} + +void tst_QXmppJingleData::testRtpCryptoElement() +{ + QFETCH(QByteArray, xml); + QFETCH(bool, hasSessionParams); + + QXmppJingleRtpCryptoElement rtpCryptoElement1; + QCOMPARE(rtpCryptoElement1.tag(), uint32_t(0)); + QVERIFY(rtpCryptoElement1.cryptoSuite().isEmpty()); + QVERIFY(rtpCryptoElement1.keyParams().isEmpty()); + QVERIFY(rtpCryptoElement1.sessionParams().isEmpty()); + parsePacket(rtpCryptoElement1, xml); + + QCOMPARE(rtpCryptoElement1.tag(), uint32_t(1)); + QCOMPARE(rtpCryptoElement1.cryptoSuite(), QStringLiteral("AES_CM_128_HMAC_SHA1_80")); + QCOMPARE(rtpCryptoElement1.keyParams(), QStringLiteral("inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:32")); + if (hasSessionParams) { + QCOMPARE(rtpCryptoElement1.sessionParams(), QStringLiteral("KDR=1 UNENCRYPTED_SRTCP")); + } else { + QVERIFY(rtpCryptoElement1.sessionParams().isEmpty()); + } + + serializePacket(rtpCryptoElement1, xml); + + QXmppJingleRtpCryptoElement rtpCryptoElement2; + rtpCryptoElement2.setTag(1); + rtpCryptoElement2.setCryptoSuite(QStringLiteral("AES_CM_128_HMAC_SHA1_80")); + rtpCryptoElement2.setKeyParams(QStringLiteral("inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:32")); + + if (hasSessionParams) { + rtpCryptoElement2.setSessionParams(QStringLiteral("KDR=1 UNENCRYPTED_SRTCP")); + } + + QCOMPARE(rtpCryptoElement2.tag(), uint32_t(1)); + QCOMPARE(rtpCryptoElement2.cryptoSuite(), QStringLiteral("AES_CM_128_HMAC_SHA1_80")); + QCOMPARE(rtpCryptoElement2.keyParams(), QStringLiteral("inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:32")); + if (hasSessionParams) { + QCOMPARE(rtpCryptoElement2.sessionParams(), QStringLiteral("KDR=1 UNENCRYPTED_SRTCP")); + } else { + QVERIFY(rtpCryptoElement2.sessionParams().isEmpty()); + } + + serializePacket(rtpCryptoElement2, xml); +} + +void tst_QXmppJingleData::testIsRtpEncryption_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("isValid"); + + QTest::newRow("valid") + << QByteArrayLiteral("") + << true; + QTest::newRow("invalidTag") + << QByteArrayLiteral("") + << false; + QTest::newRow("invalidNamespace") + << QByteArrayLiteral("") + << false; +} + +void tst_QXmppJingleData::testIsRtpEncryption() +{ + QFETCH(QByteArray, xml); + QFETCH(bool, isValid); + + QCOMPARE(QXmppJingleRtpEncryption::isJingleRtpEncryption(xmlToDom(xml)), isValid); +} + +void tst_QXmppJingleData::testRtpEncryption_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("isRequired"); + QTest::addColumn("cryptoElementCount"); + + QTest::newRow("required") + << QByteArrayLiteral("" + "" + "") + << true + << 1; + QTest::newRow("optional") + << QByteArrayLiteral("" + "" + "") + << false + << 1; + QTest::newRow("optionalWithMultipleCryptoElements") + << QByteArrayLiteral("" + "" + "" + "") + << false + << 2; +} + +void tst_QXmppJingleData::testRtpEncryption() +{ + QFETCH(QByteArray, xml); + QFETCH(bool, isRequired); + QFETCH(int, cryptoElementCount); + + QXmppJingleRtpEncryption rtpEncryption1; + QVERIFY(!rtpEncryption1.isRequired()); + QVERIFY(rtpEncryption1.cryptoElements().isEmpty()); + + parsePacket(rtpEncryption1, xml); + + QCOMPARE(rtpEncryption1.isRequired(), isRequired); + QCOMPARE(rtpEncryption1.cryptoElements().size(), cryptoElementCount); + + serializePacket(rtpEncryption1, xml); + + QXmppJingleRtpCryptoElement rtpCryptoElement2; + rtpCryptoElement2.setTag(1); + rtpCryptoElement2.setCryptoSuite(QStringLiteral("AES_CM_128_HMAC_SHA1_80")); + rtpCryptoElement2.setKeyParams(QStringLiteral("inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:32")); + + QXmppJingleRtpEncryption rtpEncryption2; + rtpEncryption2.setRequired(isRequired); + + if (cryptoElementCount == 2) { + auto rtpCryptoElement3 = rtpCryptoElement2; + rtpCryptoElement3.setTag(2); + + rtpEncryption2.setCryptoElements({ rtpCryptoElement2, rtpCryptoElement3 }); + } else { + rtpEncryption2.setCryptoElements({ rtpCryptoElement2 }); + } + + QCOMPARE(rtpEncryption2.isRequired(), isRequired); + QCOMPARE(rtpEncryption2.cryptoElements().size(), cryptoElementCount); + QCOMPARE(rtpEncryption2.cryptoElements().at(0).tag(), uint32_t(1)); + + if (cryptoElementCount == 2) { + QCOMPARE(rtpEncryption2.cryptoElements().at(1).tag(), uint32_t(2)); + } + + serializePacket(rtpEncryption2, xml); +} + +void tst_QXmppJingleData::testIsRtpFeedbackProperty_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("isValid"); + + QTest::newRow("valid") + << QByteArrayLiteral("") + << true; + QTest::newRow("invalidTag") + << QByteArrayLiteral("") + << false; + QTest::newRow("invalidNamespace") + << QByteArrayLiteral("") + << false; +} + +void tst_QXmppJingleData::testIsRtpFeedbackProperty() +{ + QFETCH(QByteArray, xml); + QFETCH(bool, isValid); + + QCOMPARE(QXmppJingleRtpFeedbackProperty::isJingleRtpFeedbackProperty(xmlToDom(xml)), isValid); +} + +void tst_QXmppJingleData::testRtpFeedbackProperty() +{ + const QByteArray xml(""); + + QXmppJingleRtpFeedbackProperty property1; + QVERIFY(property1.type().isEmpty()); + QVERIFY(property1.subtype().isEmpty()); + + parsePacket(property1, xml); + QCOMPARE(property1.type(), QStringLiteral("nack")); + QCOMPARE(property1.subtype(), QStringLiteral("sli")); + + serializePacket(property1, xml); + + QXmppJingleRtpFeedbackProperty property2; + property2.setType(QStringLiteral("nack")); + property2.setSubtype(QStringLiteral("sli")); + + QCOMPARE(property1.type(), QStringLiteral("nack")); + QCOMPARE(property1.subtype(), QStringLiteral("sli")); + + serializePacket(property2, xml); +} + +void tst_QXmppJingleData::testRtpFeedbackPropertyWithParameters() +{ + const QByteArray xml( + "" + "" + "" + ""); + + QXmppJingleRtpFeedbackProperty property1; + + parsePacket(property1, xml); + QCOMPARE(property1.type(), QStringLiteral("test-type")); + QVERIFY(property1.subtype().isEmpty()); + QCOMPARE(property1.parameters().size(), 2); + QCOMPARE(property1.parameters().at(0).name(), QStringLiteral("test-name-1")); + QCOMPARE(property1.parameters().at(1).name(), QStringLiteral("test-name-2")); + + serializePacket(property1, xml); + + QXmppJingleRtpFeedbackProperty property2; + property2.setType(QStringLiteral("test-type")); + + QXmppSdpParameter parameter1; + parameter1.setName(QStringLiteral("test-name-1")); + + QXmppSdpParameter parameter2; + parameter2.setName(QStringLiteral("test-name-2")); + + property2.setParameters({ parameter1, parameter2 }); + + QCOMPARE(property2.parameters().size(), 2); + QCOMPARE(property2.parameters().at(0).name(), QStringLiteral("test-name-1")); + QCOMPARE(property2.parameters().at(1).name(), QStringLiteral("test-name-2")); + + serializePacket(property2, xml); +} + +void tst_QXmppJingleData::testIsRtpFeedbackInterval_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("isValid"); + + QTest::newRow("valid") + << QByteArrayLiteral("") + << true; + QTest::newRow("invalidTag") + << QByteArrayLiteral("") + << false; + QTest::newRow("invalidNamespace") + << QByteArrayLiteral("") + << false; +} + +void tst_QXmppJingleData::testIsRtpFeedbackInterval() +{ + QFETCH(QByteArray, xml); + QFETCH(bool, isValid); + + QCOMPARE(QXmppJingleRtpFeedbackInterval::isJingleRtpFeedbackInterval(xmlToDom(xml)), isValid); +} + +void tst_QXmppJingleData::testRtpFeedbackInterval() +{ + const QByteArray xml(""); + + QXmppJingleRtpFeedbackInterval interval1; + + parsePacket(interval1, xml); + QCOMPARE(interval1.value(), uint64_t(100)); + + serializePacket(interval1, xml); + + QXmppJingleRtpFeedbackInterval interval2; + interval2.setValue(100); + + QCOMPARE(interval1.value(), uint64_t(100)); + + serializePacket(interval2, xml); +} + +void tst_QXmppJingleData::testIsRtpHeaderExtensionProperty_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("isValid"); + + QTest::newRow("valid") + << QByteArrayLiteral("") + << true; + QTest::newRow("invalidTag") + << QByteArrayLiteral("") + << false; + QTest::newRow("invalidNamespace") + << QByteArrayLiteral("") + << false; +} + +void tst_QXmppJingleData::testIsRtpHeaderExtensionProperty() +{ + QFETCH(QByteArray, xml); + QFETCH(bool, isValid); + + QCOMPARE(QXmppJingleRtpHeaderExtensionProperty::isJingleRtpHeaderExtensionProperty(xmlToDom(xml)), isValid); +} + +void tst_QXmppJingleData::testRtpHeaderExtensionProperty() +{ + const QByteArray xml(""); + + QXmppJingleRtpHeaderExtensionProperty property1; + QCOMPARE(property1.id(), uint32_t(0)); + QVERIFY(property1.uri().isEmpty()); + QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Both); + + parsePacket(property1, xml); + QCOMPARE(property1.id(), uint32_t(1)); + QCOMPARE(property1.uri(), QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); + QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Both); + + serializePacket(property1, xml); + + QXmppJingleRtpHeaderExtensionProperty property2; + property2.setId(1); + property2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); + property2.setSenders(QXmppJingleRtpHeaderExtensionProperty::Both); + + QCOMPARE(property1.id(), uint32_t(1)); + QCOMPARE(property1.uri(), QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); + QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Both); + + serializePacket(property2, xml); +} + +void tst_QXmppJingleData::testRtpHeaderExtensionPropertyWithSenders() +{ + const QByteArray xml(""); + + QXmppJingleRtpHeaderExtensionProperty property1; + + parsePacket(property1, xml); + QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Initiator); + + serializePacket(property1, xml); + + QXmppJingleRtpHeaderExtensionProperty property2; + property2.setId(1); + property2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); + property2.setSenders(QXmppJingleRtpHeaderExtensionProperty::Initiator); + + QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Initiator); + + serializePacket(property2, xml); +} + +void tst_QXmppJingleData::testRtpHeaderExtensionPropertyWithParameters() +{ + const QByteArray xml( + "" + "" + "" + ""); + + QXmppJingleRtpHeaderExtensionProperty property1; + + parsePacket(property1, xml); + QCOMPARE(property1.parameters().size(), 2); + QCOMPARE(property1.parameters().at(0).name(), QStringLiteral("test-name-1")); + QCOMPARE(property1.parameters().at(1).name(), QStringLiteral("test-name-2")); + + serializePacket(property1, xml); + + QXmppJingleRtpHeaderExtensionProperty property2; + property2.setId(1); + property2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); + + QXmppSdpParameter parameter1; + parameter1.setName(QStringLiteral("test-name-1")); + + QXmppSdpParameter parameter2; + parameter2.setName(QStringLiteral("test-name-2")); + + property2.setParameters({ parameter1, parameter2 }); + + QCOMPARE(property2.parameters().size(), 2); + QCOMPARE(property2.parameters().at(0).name(), QStringLiteral("test-name-1")); + QCOMPARE(property2.parameters().at(1).name(), QStringLiteral("test-name-2")); + + serializePacket(property2, xml); +} + +void tst_QXmppJingleData::testCandidate() +{ + const QByteArray xml( + ""); + + QXmppJingleCandidate candidate; + parsePacket(candidate, xml); + QCOMPARE(candidate.foundation(), QLatin1String("1")); + QCOMPARE(candidate.generation(), 0); + QCOMPARE(candidate.id(), QLatin1String("el0747fg11")); + QCOMPARE(candidate.host(), QHostAddress("10.0.1.1")); + QCOMPARE(candidate.network(), 1); + QCOMPARE(candidate.port(), quint16(8998)); + QCOMPARE(candidate.priority(), 2130706431); + QCOMPARE(candidate.protocol(), QLatin1String("udp")); + QCOMPARE(candidate.type(), QXmppJingleCandidate::HostType); + serializePacket(candidate, xml); +}; + +void tst_QXmppJingleData::testContent() +{ + const QByteArray xml( + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""); + + QXmppJingleIq::Content content1; + QVERIFY(content1.creator().isEmpty()); + QVERIFY(content1.name().isEmpty()); + QVERIFY(content1.description().media().isEmpty()); + QCOMPARE(content1.description().ssrc(), quint32(0)); + QVERIFY(!content1.isRtpMultiplexingSupported()); + QVERIFY(!content1.rtpEncryption()); + QCOMPARE(content1.description().payloadTypes().size(), 0); + QVERIFY(content1.transportUser().isEmpty()); + QVERIFY(content1.transportPassword().isEmpty()); + QCOMPARE(content1.transportCandidates().size(), 0); + parsePacket(content1, xml); + + QCOMPARE(content1.creator(), QStringLiteral("initiator")); + QCOMPARE(content1.name(), QStringLiteral("voice")); + QCOMPARE(content1.description().media(), QStringLiteral("audio")); + QCOMPARE(content1.description().ssrc(), quint32(0)); + QVERIFY(content1.isRtpMultiplexingSupported()); + QVERIFY(content1.rtpEncryption()); + 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); + QCOMPARE(content1.transportCandidates().at(0).id(), QStringLiteral("el0747fg11")); + QCOMPARE(content1.transportCandidates().at(1).id(), QStringLiteral("y3s2b30v3r")); + serializePacket(content1, xml); + + QXmppJingleIq::Content content2; + content2.setCreator(QStringLiteral("initiator")); + content2.setName(QStringLiteral("voice")); + QXmppJingleDescription content2desc; + content2desc.setMedia(QStringLiteral("audio")); + content2desc.setSsrc(quint32(0)); + content2.setRtpMultiplexingSupported(true); + QXmppJingleRtpCryptoElement rtpCryptoElement; + rtpCryptoElement.setTag(1); + rtpCryptoElement.setCryptoSuite(QStringLiteral("AES_CM_128_HMAC_SHA1_80")); + rtpCryptoElement.setKeyParams(QStringLiteral("inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:32")); + QXmppJingleRtpEncryption rtpEncryption; + rtpEncryption.setCryptoElements({ rtpCryptoElement }); + content2.setRtpEncryption(rtpEncryption); + QXmppJinglePayloadType payloadType1; + payloadType1.setId(quint8(96)); + content2desc.setPayloadTypes({ payloadType1 }); + QXmppJinglePayloadType payloadType2; + payloadType2.setId(quint8(97)); + content2desc.addPayloadType(payloadType2); + content2.setDescription(content2desc); + content2.setTransportUser(QStringLiteral("8hhy")); + content2.setTransportPassword(QStringLiteral("asd88fgpdd777uzjYhagZg")); + QXmppJingleCandidate transportCandidate1; + transportCandidate1.setId(QStringLiteral("el0747fg11")); + content2.setTransportCandidates({ transportCandidate1 }); + QXmppJingleCandidate transportCandidate2; + transportCandidate2.setId(QStringLiteral("y3s2b30v3r")); + content2.addTransportCandidate(transportCandidate2); + + QCOMPARE(content2.creator(), QStringLiteral("initiator")); + QCOMPARE(content2.name(), QStringLiteral("voice")); + QCOMPARE(content2.description().media(), QStringLiteral("audio")); + QCOMPARE(content2.description().ssrc(), quint32(0)); + QVERIFY(content2.isRtpMultiplexingSupported()); + QVERIFY(content2.rtpEncryption()); + 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); + QCOMPARE(content2.transportCandidates().at(0).id(), QStringLiteral("el0747fg11")); + QCOMPARE(content2.transportCandidates().at(1).id(), QStringLiteral("y3s2b30v3r")); + serializePacket(content2, xml); +} + +void tst_QXmppJingleData::testContentFingerprint() +{ + const QByteArray xml( + "" + "" + "" + "" + "" + "" + "" + "02:1A:CC:54:27:AB:EB:9C:53:3F:3E:4B:65:2E:7D:46:3F:54:42:CD:54:F1:7A:03:A2:7D:F9:B0:7F:46:19:B2" + "" + "" + ""); + + QXmppJingleIq::Content content; + parsePacket(content, xml); + + QCOMPARE(content.creator(), QLatin1String("initiator")); + QCOMPARE(content.name(), QLatin1String("voice")); + 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")); + QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1")); + QCOMPARE(content.transportCandidates()[0].port(), quint16(8998)); + QCOMPARE(content.transportCandidates()[0].priority(), 2130706431); + QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp")); + QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType); + QCOMPARE(content.transportUser(), QLatin1String("8hhy")); + QCOMPARE(content.transportPassword(), QLatin1String("asd88fgpdd777uzjYhagZg")); + QCOMPARE(content.transportFingerprint(), QByteArray::fromHex("021acc5427abeb9c533f3e4b652e7d463f5442cd54f17a03a27df9b07f4619b2")); + QCOMPARE(content.transportFingerprintHash(), QLatin1String("sha-256")); + QCOMPARE(content.transportFingerprintSetup(), QLatin1String("actpass")); + + serializePacket(content, xml); +} + +void tst_QXmppJingleData::testContentSdp() +{ + const QString sdp( + "m=audio 8998 RTP/AVP 96 97 18 0 103 98\r\n" + "c=IN IP4 10.0.1.1\r\n" + "a=rtpmap:96 speex/16000\r\n" + "a=rtpmap:97 speex/8000\r\n" + "a=rtpmap:18 G729/0\r\n" + "a=rtpmap:0 PCMU/0\r\n" + "a=rtpmap:103 L16/16000/2\r\n" + "a=rtpmap:98 x-ISAC/8000\r\n" + "a=candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0\r\n" + "a=candidate:2 1 udp 1694498815 192.0.2.3 45664 typ host generation 0\r\n" + "a=ice-ufrag:8hhy\r\n" + "a=ice-pwd:asd88fgpdd777uzjYhagZg\r\n"); + + QXmppJingleIq::Content content; + QVERIFY(content.parseSdp(sdp)); + + 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")); + QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1")); + QCOMPARE(content.transportCandidates()[0].port(), quint16(8998)); + QCOMPARE(content.transportCandidates()[0].priority(), 2130706431); + QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp")); + QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType); + QCOMPARE(content.transportCandidates()[1].component(), 1); + QCOMPARE(content.transportCandidates()[1].foundation(), QLatin1String("2")); + QCOMPARE(content.transportCandidates()[1].host(), QHostAddress("192.0.2.3")); + QCOMPARE(content.transportCandidates()[1].port(), quint16(45664)); + QCOMPARE(content.transportCandidates()[1].priority(), 1694498815); + QCOMPARE(content.transportCandidates()[1].protocol(), QLatin1String("udp")); + QCOMPARE(content.transportCandidates()[1].type(), QXmppJingleCandidate::HostType); + QCOMPARE(content.transportUser(), QLatin1String("8hhy")); + QCOMPARE(content.transportPassword(), QLatin1String("asd88fgpdd777uzjYhagZg")); + + QCOMPARE(content.toSdp(), sdp); +} + +void tst_QXmppJingleData::testContentSdpReflexive() +{ + const QString sdp( + "m=audio 45664 RTP/AVP 96 97 18 0 103 98\r\n" + "c=IN IP4 192.0.2.3\r\n" + "a=rtpmap:96 speex/16000\r\n" + "a=rtpmap:97 speex/8000\r\n" + "a=rtpmap:18 G729/0\r\n" + "a=rtpmap:0 PCMU/0\r\n" + "a=rtpmap:103 L16/16000/2\r\n" + "a=rtpmap:98 x-ISAC/8000\r\n" + "a=candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0\r\n" + "a=candidate:2 1 udp 1694498815 192.0.2.3 45664 typ srflx generation 0\r\n" + "a=ice-ufrag:8hhy\r\n" + "a=ice-pwd:asd88fgpdd777uzjYhagZg\r\n"); + + QXmppJingleIq::Content content; + QVERIFY(content.parseSdp(sdp)); + + 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")); + QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1")); + QCOMPARE(content.transportCandidates()[0].port(), quint16(8998)); + QCOMPARE(content.transportCandidates()[0].priority(), 2130706431); + QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp")); + QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType); + QCOMPARE(content.transportCandidates()[1].component(), 1); + QCOMPARE(content.transportCandidates()[1].foundation(), QLatin1String("2")); + QCOMPARE(content.transportCandidates()[1].host(), QHostAddress("192.0.2.3")); + QCOMPARE(content.transportCandidates()[1].port(), quint16(45664)); + QCOMPARE(content.transportCandidates()[1].priority(), 1694498815); + QCOMPARE(content.transportCandidates()[1].protocol(), QLatin1String("udp")); + QCOMPARE(content.transportCandidates()[1].type(), QXmppJingleCandidate::ServerReflexiveType); + QCOMPARE(content.transportUser(), QLatin1String("8hhy")); + QCOMPARE(content.transportPassword(), QLatin1String("asd88fgpdd777uzjYhagZg")); + + QCOMPARE(content.toSdp(), sdp); +} + +void tst_QXmppJingleData::testContentSdpFingerprint() +{ + const QString sdp( + "m=audio 8998 RTP/AVP 96 100\r\n" + "c=IN IP4 10.0.1.1\r\n" + "a=rtpmap:96 speex/16000\r\n" + "a=fmtp:96 cng=on; vbr=on\r\n" + "a=rtpmap:100 telephone-event/8000\r\n" + "a=fmtp:100 0-15,66,70\r\n" + "a=candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0\r\n" + "a=fingerprint:sha-256 02:1A:CC:54:27:AB:EB:9C:53:3F:3E:4B:65:2E:7D:46:3F:54:42:CD:54:F1:7A:03:A2:7D:F9:B0:7F:46:19:B2\r\n" + "a=setup:actpass\r\n"); + + QXmppJingleIq::Content content; + QVERIFY(content.parseSdp(sdp)); + + 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")); + QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1")); + QCOMPARE(content.transportCandidates()[0].port(), quint16(8998)); + QCOMPARE(content.transportCandidates()[0].priority(), 2130706431); + QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp")); + QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType); + QCOMPARE(content.transportFingerprint(), QByteArray::fromHex("021acc5427abeb9c533f3e4b652e7d463f5442cd54f17a03a27df9b07f4619b2")); + QCOMPARE(content.transportFingerprintHash(), QLatin1String("sha-256")); + QCOMPARE(content.transportFingerprintSetup(), QLatin1String("actpass")); + + QCOMPARE(content.toSdp(), sdp); +} + +void tst_QXmppJingleData::testContentSdpParameters() +{ + const QString sdp( + "m=audio 8998 RTP/AVP 96 100\r\n" + "c=IN IP4 10.0.1.1\r\n" + "a=rtpmap:96 speex/16000\r\n" + "a=fmtp:96 cng=on; vbr=on\r\n" + "a=rtpmap:100 telephone-event/8000\r\n" + "a=fmtp:100 0-15,66,70\r\n" + "a=candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0\r\n"); + + QXmppJingleIq::Content content; + QVERIFY(content.parseSdp(sdp)); + + 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")); + QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1")); + QCOMPARE(content.transportCandidates()[0].port(), quint16(8998)); + QCOMPARE(content.transportCandidates()[0].priority(), 2130706431); + QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp")); + QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType); + + QCOMPARE(content.toSdp(), sdp); +} + +void tst_QXmppJingleData::testContentRtpFeedbackNegotiation() +{ + const QByteArray xml( + "" + "" + "" + "" + "" + "" + "" + "" + ""); + + QXmppJingleIq::Content content1; + QVERIFY(content1.rtpFeedbackProperties().isEmpty()); + QVERIFY(content1.rtpFeedbackIntervals().isEmpty()); + parsePacket(content1, xml); + + const auto rtpFeedbackProperties1 = content1.rtpFeedbackProperties(); + QCOMPARE(rtpFeedbackProperties1.size(), 2); + QCOMPARE(rtpFeedbackProperties1[0].subtype(), QStringLiteral("pli")); + QCOMPARE(rtpFeedbackProperties1[1].subtype(), QStringLiteral("sli")); + + const auto rtpFeedbackIntervals1 = content1.rtpFeedbackIntervals(); + QCOMPARE(rtpFeedbackIntervals1.size(), 2); + QCOMPARE(rtpFeedbackIntervals1[0].value(), uint64_t(60)); + QCOMPARE(rtpFeedbackIntervals1[1].value(), uint64_t(80)); + + serializePacket(content1, xml); + + QXmppJingleRtpFeedbackProperty rtpFeedbackProperty1; + rtpFeedbackProperty1.setType(QStringLiteral("nack")); + rtpFeedbackProperty1.setSubtype(QStringLiteral("pli")); + + QXmppJingleRtpFeedbackProperty rtpFeedbackProperty2; + rtpFeedbackProperty2.setType(QStringLiteral("nack")); + rtpFeedbackProperty2.setSubtype(QStringLiteral("sli")); + + QXmppJingleRtpFeedbackInterval rtpFeedbackInterval1; + rtpFeedbackInterval1.setValue(60); + + QXmppJingleRtpFeedbackInterval rtpFeedbackInterval2; + rtpFeedbackInterval2.setValue(80); + + QXmppJinglePayloadType payloadType; + payloadType.setId(96); + payloadType.setName(QStringLiteral("speex")); + + QXmppJingleIq::Content content2; + content2.setCreator(QStringLiteral("initiator")); + content2.setName(QStringLiteral("voice")); + QXmppJingleDescription content2desc; + content2desc.addPayloadType(payloadType); + content2.setDescription(content2desc); + content2.setRtpFeedbackProperties({ rtpFeedbackProperty1, rtpFeedbackProperty2 }); + content2.setRtpFeedbackIntervals({ rtpFeedbackInterval1, rtpFeedbackInterval2 }); + + const auto rtpFeedbackProperties2 = content2.rtpFeedbackProperties(); + QCOMPARE(rtpFeedbackProperties2.size(), 2); + QCOMPARE(rtpFeedbackProperties2[0].subtype(), QStringLiteral("pli")); + QCOMPARE(rtpFeedbackProperties2[1].subtype(), QStringLiteral("sli")); + + const auto rtpFeedbackIntervals2 = content2.rtpFeedbackIntervals(); + QCOMPARE(rtpFeedbackIntervals2.size(), 2); + QCOMPARE(rtpFeedbackIntervals2[0].value(), uint64_t(60)); + QCOMPARE(rtpFeedbackIntervals2[1].value(), uint64_t(80)); + + serializePacket(content2, xml); +} + +void tst_QXmppJingleData::testContentRtpHeaderExtensionsNegotiation() +{ + const QByteArray xml( + "" + "" + "" + "" + "" + "" + "" + ""); + + QXmppJingleIq::Content content1; + QVERIFY(content1.rtpHeaderExtensionProperties().isEmpty()); + QVERIFY(!content1.isRtpHeaderExtensionMixingAllowed()); + parsePacket(content1, xml); + + const auto rtpHeaderExtensionProperties1 = content1.rtpHeaderExtensionProperties(); + QCOMPARE(rtpHeaderExtensionProperties1.size(), 2); + QCOMPARE(rtpHeaderExtensionProperties1[0].id(), uint32_t(1)); + QCOMPARE(rtpHeaderExtensionProperties1[1].id(), uint32_t(2)); + + QVERIFY(content1.isRtpHeaderExtensionMixingAllowed()); + + serializePacket(content1, xml); + + QXmppJingleRtpHeaderExtensionProperty rtpHeaderExtensionProperty1; + rtpHeaderExtensionProperty1.setId(uint32_t(1)); + rtpHeaderExtensionProperty1.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); + + QXmppJingleRtpHeaderExtensionProperty rtpHeaderExtensionProperty2; + rtpHeaderExtensionProperty2.setId(uint32_t(2)); + rtpHeaderExtensionProperty2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:ntp-64")); + + QXmppJinglePayloadType payloadType; + payloadType.setId(96); + payloadType.setName(QStringLiteral("speex")); + + QXmppJingleIq::Content content2; + content2.setCreator(QStringLiteral("initiator")); + content2.setName(QStringLiteral("voice")); + QXmppJingleDescription content2desc; + content2desc.addPayloadType(payloadType); + content2.setDescription(content2desc); + content2.setRtpHeaderExtensionProperties({ rtpHeaderExtensionProperty1, rtpHeaderExtensionProperty2 }); + content2.setRtpHeaderExtensionMixingAllowed(true); + + const auto rtpHeaderExtensionProperties2 = content2.rtpHeaderExtensionProperties(); + QCOMPARE(rtpHeaderExtensionProperties2.size(), 2); + QCOMPARE(rtpHeaderExtensionProperties2[0].id(), uint32_t(1)); + QCOMPARE(rtpHeaderExtensionProperties2[1].id(), uint32_t(2)); + + QVERIFY(content2.isRtpHeaderExtensionMixingAllowed()); + + serializePacket(content2, xml); +} + +void tst_QXmppJingleData::testSession() +{ + const QByteArray xml( + "" + "" + "" + "" + "" + "" + "" + "" + ""); + + QXmppJingleIq session; + parsePacket(session, xml); + QCOMPARE(session.action(), QXmppJingleIq::SessionInitiate); + QCOMPARE(session.initiator(), QLatin1String("romeo@montague.lit/orchard")); + QCOMPARE(session.sid(), QLatin1String("a73sjjvkla37jfea")); + QCOMPARE(session.mujiGroupChatJid(), QStringLiteral("darkcave@chat.shakespeare.lit")); + QCOMPARE(session.contents().size(), 1); + 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(), QXmppJingleReason::None); + serializePacket(session, xml); +} + +void tst_QXmppJingleData::testTerminate() +{ + const QByteArray xml( + "" + "" + "" + "" + "" + "" + ""); + + QXmppJingleIq session; + parsePacket(session, xml); + QCOMPARE(session.action(), QXmppJingleIq::SessionTerminate); + QCOMPARE(session.initiator(), QString()); + QCOMPARE(session.sid(), QLatin1String("a73sjjvkla37jfea")); + QCOMPARE(session.reason().text(), QString()); + QCOMPARE(session.reason().type(), QXmppJingleReason::Success); + serializePacket(session, xml); +} + +void tst_QXmppJingleData::testRtpSessionState_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("state"); + + QTest::newRow("active") + << QByteArrayLiteral("" + "" + "" + "" + "") + << QStringLiteral("active"); + QTest::newRow("hold") + << QByteArrayLiteral("" + "" + "" + "" + "") + << QStringLiteral("hold"); + QTest::newRow("unhold") + << QByteArrayLiteral("" + "" + "" + "" + "") + << QStringLiteral("unhold"); + QTest::newRow("mute") + << QByteArrayLiteral("" + "" + "" + "" + "") + << QStringLiteral("mute"); + QTest::newRow("unmute") + << QByteArrayLiteral("" + "" + "" + "" + "") + << QStringLiteral("unmute"); + QTest::newRow("ringing") + << QByteArrayLiteral("" + "" + "" + "" + "") + << QStringLiteral("ringing"); +} + +void tst_QXmppJingleData::testRtpSessionState() +{ + QFETCH(QByteArray, xml); + QFETCH(QString, state); + + QXmppJingleIq iq1; + QVERIFY(!iq1.rtpSessionState()); + parsePacket(iq1, xml); + + const auto rtpSessionState1 = *iq1.rtpSessionState(); + if (state == QStringLiteral("active")) { + QVERIFY(std::holds_alternative(rtpSessionState1)); + } else if (state == QStringLiteral("hold")) { + QVERIFY(std::holds_alternative(rtpSessionState1)); + } else if (state == QStringLiteral("unhold")) { + QVERIFY(std::holds_alternative(rtpSessionState1)); + } else if (const auto isMute = state == QStringLiteral("mute"); isMute || state == QStringLiteral("unmute")) { + QVERIFY(std::holds_alternative(rtpSessionState1)); + + const auto stateMuting = std::get(rtpSessionState1); + QCOMPARE(stateMuting.isMute, isMute); + + if (isMute) { + QCOMPARE(stateMuting.creator, QXmppJingleIq::Initiator); + QCOMPARE(stateMuting.name, QStringLiteral("voice")); + } else { + QCOMPARE(stateMuting.creator, QXmppJingleIq::Responder); + QVERIFY(stateMuting.name.isEmpty()); + } + } else if (state == QStringLiteral("ringing")) { + QVERIFY(std::holds_alternative(rtpSessionState1)); + } + + serializePacket(iq1, xml); + + QXmppJingleIq iq2; + iq2.setType(QXmppIq::Set); + iq2.setId({}); + + if (state == QStringLiteral("active")) { + iq2.setRtpSessionState(QXmppJingleIq::RtpSessionStateActive()); + } else if (state == QStringLiteral("hold")) { + iq2.setRtpSessionState(QXmppJingleIq::RtpSessionStateHold()); + } else if (state == QStringLiteral("unhold")) { + iq2.setRtpSessionState(QXmppJingleIq::RtpSessionStateUnhold()); + } else if (const auto isMute = state == QStringLiteral("mute"); isMute || state == QStringLiteral("unmute")) { + QXmppJingleIq::RtpSessionStateMuting stateMuting; + stateMuting.isMute = isMute; + + if (isMute) { + stateMuting.creator = QXmppJingleIq::Initiator; + stateMuting.name = QStringLiteral("voice"); + } else { + stateMuting.creator = QXmppJingleIq::Responder; + } + + iq2.setRtpSessionState(stateMuting); + } else if (state == QStringLiteral("ringing")) { + iq2.setRtpSessionState(QXmppJingleIq::RtpSessionStateRinging()); + } + + const auto rtpSessionState2 = *iq2.rtpSessionState(); + if (state == QStringLiteral("active")) { + QVERIFY(std::holds_alternative(rtpSessionState2)); + } else if (state == QStringLiteral("hold")) { + QVERIFY(std::holds_alternative(rtpSessionState2)); + } else if (state == QStringLiteral("unhold")) { + QVERIFY(std::holds_alternative(rtpSessionState2)); + } else if (const auto isMute = state == QStringLiteral("mute"); isMute || state == QStringLiteral("unmute")) { + QVERIFY(std::holds_alternative(rtpSessionState2)); + + const auto stateMuting = std::get(rtpSessionState2); + QCOMPARE(stateMuting.isMute, isMute); + + if (isMute) { + QCOMPARE(stateMuting.creator, QXmppJingleIq::Initiator); + QCOMPARE(stateMuting.name, QStringLiteral("voice")); + } else { + QCOMPARE(stateMuting.creator, QXmppJingleIq::Responder); + QVERIFY(stateMuting.name.isEmpty()); + } + } else if (state == QStringLiteral("ringing")) { + QVERIFY(std::holds_alternative(rtpSessionState2)); + } + + serializePacket(iq2, xml); +} + +void tst_QXmppJingleData::testAudioPayloadType() +{ + const QByteArray xml(R"()"); + QXmppJinglePayloadType payload; + parsePacket(payload, xml); + QCOMPARE(payload.id(), static_cast(103)); + QCOMPARE(payload.name(), QLatin1String("L16")); + QCOMPARE(payload.channels(), static_cast(2)); + QCOMPARE(payload.clockrate(), 16000u); + serializePacket(payload, xml); +} + +void tst_QXmppJingleData::testVideoPayloadType() +{ + const QByteArray xml( + "" + "" + "" + ""); + QXmppJinglePayloadType payload; + parsePacket(payload, xml); + QCOMPARE(payload.id(), static_cast(98)); + QCOMPARE(payload.name(), QLatin1String("theora")); + QCOMPARE(payload.clockrate(), 90000u); + QCOMPARE(payload.parameters().size(), 2); + QCOMPARE(payload.parameters().value("height"), QLatin1String("768")); + QCOMPARE(payload.parameters().value("width"), QLatin1String("1024")); + serializePacket(payload, xml); +} + +void tst_QXmppJingleData::testPayloadTypeRtpFeedbackNegotiation() +{ + const QByteArray xml( + "" + "" + "" + "" + "" + ""); + + QXmppJinglePayloadType payload1; + QVERIFY(payload1.rtpFeedbackProperties().isEmpty()); + QVERIFY(payload1.rtpFeedbackIntervals().isEmpty()); + parsePacket(payload1, xml); + + const auto rtpFeedbackProperties1 = payload1.rtpFeedbackProperties(); + QCOMPARE(rtpFeedbackProperties1.size(), 2); + QCOMPARE(rtpFeedbackProperties1[0].subtype(), QStringLiteral("pli")); + QCOMPARE(rtpFeedbackProperties1[1].subtype(), QStringLiteral("sli")); + + const auto rtpFeedbackIntervals1 = payload1.rtpFeedbackIntervals(); + QCOMPARE(rtpFeedbackIntervals1.size(), 2); + QCOMPARE(rtpFeedbackIntervals1[0].value(), uint64_t(60)); + QCOMPARE(rtpFeedbackIntervals1[1].value(), uint64_t(80)); + + serializePacket(payload1, xml); + + QXmppJingleRtpFeedbackProperty rtpFeedbackProperty1; + rtpFeedbackProperty1.setType(QStringLiteral("nack")); + rtpFeedbackProperty1.setSubtype(QStringLiteral("pli")); + + QXmppJingleRtpFeedbackProperty rtpFeedbackProperty2; + rtpFeedbackProperty2.setType(QStringLiteral("nack")); + rtpFeedbackProperty2.setSubtype(QStringLiteral("sli")); + + QXmppJingleRtpFeedbackInterval rtpFeedbackInterval1; + rtpFeedbackInterval1.setValue(60); + + QXmppJingleRtpFeedbackInterval rtpFeedbackInterval2; + rtpFeedbackInterval2.setValue(80); + + QXmppJinglePayloadType payload2; + payload2.setId(96); + payload2.setRtpFeedbackProperties({ rtpFeedbackProperty1, rtpFeedbackProperty2 }); + payload2.setRtpFeedbackIntervals({ rtpFeedbackInterval1, rtpFeedbackInterval2 }); + + const auto rtpFeedbackProperties2 = payload2.rtpFeedbackProperties(); + QCOMPARE(rtpFeedbackProperties2.size(), 2); + QCOMPARE(rtpFeedbackProperties2[0].subtype(), QStringLiteral("pli")); + QCOMPARE(rtpFeedbackProperties2[1].subtype(), QStringLiteral("sli")); + + const auto rtpFeedbackIntervals2 = payload2.rtpFeedbackIntervals(); + QCOMPARE(rtpFeedbackIntervals2.size(), 2); + QCOMPARE(rtpFeedbackIntervals2[0].value(), uint64_t(60)); + QCOMPARE(rtpFeedbackIntervals2[1].value(), uint64_t(80)); + + serializePacket(payload2, xml); +} + +void tst_QXmppJingleData::testRtpErrorCondition_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("condition"); + + QTest::newRow("NoErrorCondition") + << QByteArrayLiteral("" + "" + "" + "" + "" + "" + "") + << QXmppJingleReason::NoErrorCondition; + QTest::newRow("InvalidCrypto") + << QByteArrayLiteral("" + "" + "" + "" + "" + "" + "" + "") + << QXmppJingleReason::InvalidCrypto; + QTest::newRow("CryptoRequired") + << QByteArrayLiteral("" + "" + "" + "" + "" + "" + "" + "") + << QXmppJingleReason::CryptoRequired; +} + +void tst_QXmppJingleData::testRtpErrorCondition() +{ + QFETCH(QByteArray, xml); + QFETCH(QXmppJingleReason::RtpErrorCondition, condition); + + QXmppJingleIq iq1; + QCOMPARE(iq1.reason().rtpErrorCondition(), QXmppJingleReason::NoErrorCondition); + parsePacket(iq1, xml); + + const auto rtpErrorCondition1 = iq1.reason().rtpErrorCondition(); + switch (condition) { + case QXmppJingleReason::NoErrorCondition: + QVERIFY(rtpErrorCondition1 == QXmppJingleReason::NoErrorCondition); + break; + case QXmppJingleReason::InvalidCrypto: + QVERIFY(rtpErrorCondition1 == QXmppJingleReason::InvalidCrypto); + break; + case QXmppJingleReason::CryptoRequired: + QVERIFY(rtpErrorCondition1 == QXmppJingleReason::CryptoRequired); + break; + } + + serializePacket(iq1, xml); + + QXmppJingleIq iq2; + iq2.setType(QXmppIq::Set); + iq2.setId({}); + iq2.setAction(QXmppJingleIq::SessionTerminate); + + switch (condition) { + case QXmppJingleReason::NoErrorCondition: + iq2.reason().setRtpErrorCondition(QXmppJingleReason::NoErrorCondition); + break; + case QXmppJingleReason::InvalidCrypto: + iq2.reason().setRtpErrorCondition(QXmppJingleReason::InvalidCrypto); + break; + case QXmppJingleReason::CryptoRequired: + iq2.reason().setRtpErrorCondition(QXmppJingleReason::CryptoRequired); + break; + } + + iq2.reason().setType(QXmppJingleReason::SecurityError); + + const auto rtpErrorCondition2 = iq2.reason().rtpErrorCondition(); + switch (condition) { + case QXmppJingleReason::NoErrorCondition: + QVERIFY(rtpErrorCondition2 == QXmppJingleReason::NoErrorCondition); + break; + case QXmppJingleReason::InvalidCrypto: + QVERIFY(rtpErrorCondition2 == QXmppJingleReason::InvalidCrypto); + break; + case QXmppJingleReason::CryptoRequired: + QVERIFY(rtpErrorCondition2 == QXmppJingleReason::CryptoRequired); + break; + } + + serializePacket(iq2, xml); +} + +void tst_QXmppJingleData::testIsJingleMessageInitiationElement_data() +{ + QTest::addColumn("xml"); + QTest::addColumn("isValid"); + + // --- Propose --- + + QTest::newRow("validPropose") + << QByteArrayLiteral( + "" + "" + "") + << true; + QTest::newRow("invalidProposeIdMissing") + << QByteArrayLiteral( + "" + "" + "") + << false; + QTest::newRow("invalidProposeNamespaceMissing") + << QByteArrayLiteral( + "" + "" + "") + << false; + + // --- Ringing --- + + QTest::newRow("validRinging") + << QByteArrayLiteral("") + << true; + QTest::newRow("invalidRingingIdMissing") + << QByteArrayLiteral("") + << false; + QTest::newRow("invalidRingingNamespaceMissing") + << QByteArrayLiteral("") + << false; + + // --- Proceed --- + + QTest::newRow("validProceed") + << QByteArrayLiteral("") + << true; + QTest::newRow("invalidProceedIdMissing") + << QByteArrayLiteral("") + << false; + QTest::newRow("invalidProceedNamespaceMissing") + << QByteArrayLiteral("") + << false; + + // --- Reject --- + + QTest::newRow("validReject") + << QByteArrayLiteral( + "" + "" + "Busy" + "" + "" + "") + << true; + QTest::newRow("invalidRejectIdMissing") + << QByteArrayLiteral( + "" + "" + "Busy" + "" + "" + "") + << false; + QTest::newRow("invalidRejectNamespaceMissing") + << QByteArrayLiteral( + "" + "" + "Busy" + "" + "" + "") + << false; + + // --- Retract --- + + QTest::newRow("validRetract") + << QByteArrayLiteral( + "" + "" + "Retracted" + "" + "" + "") + << true; + QTest::newRow("invalidRetractIdMissing") + << QByteArrayLiteral( + "" + "" + "Retracted" + "" + "" + "") + << false; + QTest::newRow("invalidRetractNamespaceMissing") + << QByteArrayLiteral( + "" + "" + "Retracted" + "" + "" + "") + << false; + + // --- Finish --- + + QTest::newRow("validFinish") + << QByteArrayLiteral( + "" + "" + "Success" + "" + "" + "") + << true; + QTest::newRow("invalidFinishIdMissing") + << QByteArrayLiteral( + "" + "" + "Success" + "" + "" + "") + << false; + QTest::newRow("invalidFinishNamespaceMissing") + << QByteArrayLiteral( + "" + "" + "Success" + "" + "" + "") + << 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( + "" + "" + ""); + 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(""); + 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(""); + 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( + "" + "" + "Busy" + "" + "" + "" + ""); + 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.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); + QCOMPARE(rejectElement.containsTieBreak(), true); + serializePacket(rejectElement, rejectXml); + + // --- Retract --- + + const QByteArray retractXml( + "" + "" + "Retracted" + "" + "" + ""); + 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); + QCOMPARE(retractElement.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); + serializePacket(retractElement, retractXml); + + // --- Finish --- + + const QByteArray finishXml( + "" + "" + "Success" + "" + "" + "" + ""); + 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.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); + QCOMPARE(finishElement.migratedTo(), QStringLiteral("989a46a6-f202-4910-a7c3-83c6ba3f3947")); + serializePacket(finishElement, finishXml); +} + +QTEST_MAIN(tst_QXmppJingleData) +#include "tst_qxmppjingledata.moc" diff --git a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp b/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp deleted file mode 100644 index 1d9c47fc..00000000 --- a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp +++ /dev/null @@ -1,1681 +0,0 @@ -// SPDX-FileCopyrightText: 2012 Jeremy Lainé -// SPDX-FileCopyrightText: 2022 Melvin Keskin -// SPDX-FileCopyrightText: 2023 Tibor Csötönyi -// -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "QXmppJingleIq.h" - -#include "util.h" -#include - -class tst_QXmppJingleIq : public QObject -{ - Q_OBJECT - -private: - Q_SLOT void testIsSdpParameter_data(); - Q_SLOT void testIsSdpParameter(); - Q_SLOT void testSdpParameter(); - Q_SLOT void testSdpParameterWithoutValue(); - Q_SLOT void testIsRtpCryptoElement_data(); - Q_SLOT void testIsRtpCryptoElement(); - Q_SLOT void testRtpCryptoElement_data(); - Q_SLOT void testRtpCryptoElement(); - Q_SLOT void testIsRtpEncryption_data(); - Q_SLOT void testIsRtpEncryption(); - Q_SLOT void testRtpEncryption_data(); - Q_SLOT void testRtpEncryption(); - Q_SLOT void testIsRtpFeedbackProperty_data(); - Q_SLOT void testIsRtpFeedbackProperty(); - Q_SLOT void testRtpFeedbackProperty(); - Q_SLOT void testRtpFeedbackPropertyWithParameters(); - Q_SLOT void testIsRtpFeedbackInterval_data(); - Q_SLOT void testIsRtpFeedbackInterval(); - Q_SLOT void testRtpFeedbackInterval(); - Q_SLOT void testIsRtpHeaderExtensionProperty_data(); - Q_SLOT void testIsRtpHeaderExtensionProperty(); - Q_SLOT void testRtpHeaderExtensionProperty(); - Q_SLOT void testRtpHeaderExtensionPropertyWithSenders(); - Q_SLOT void testRtpHeaderExtensionPropertyWithParameters(); - Q_SLOT void testCandidate(); - Q_SLOT void testContent(); - Q_SLOT void testContentFingerprint(); - Q_SLOT void testContentSdp(); - Q_SLOT void testContentSdpReflexive(); - Q_SLOT void testContentSdpFingerprint(); - Q_SLOT void testContentSdpParameters(); - Q_SLOT void testContentRtpFeedbackNegotiation(); - Q_SLOT void testContentRtpHeaderExtensionsNegotiation(); - Q_SLOT void testSession(); - Q_SLOT void testTerminate(); - Q_SLOT void testRtpSessionState_data(); - Q_SLOT void testRtpSessionState(); - Q_SLOT void testAudioPayloadType(); - Q_SLOT void testVideoPayloadType(); - 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() -{ - QTest::addColumn("xml"); - QTest::addColumn("isValid"); - - QTest::newRow("valid") - << QByteArrayLiteral("") - << true; - QTest::newRow("invalidTag") - << QByteArrayLiteral("") - << false; -} - -void tst_QXmppJingleIq::testIsSdpParameter() -{ - QFETCH(QByteArray, xml); - QFETCH(bool, isValid); - - QCOMPARE(QXmppSdpParameter::isSdpParameter(xmlToDom(xml)), isValid); -} - -void tst_QXmppJingleIq::testSdpParameter() -{ - const QByteArray xml(""); - - QXmppSdpParameter parameter1; - QVERIFY(parameter1.name().isEmpty()); - QVERIFY(parameter1.value().isEmpty()); - - parsePacket(parameter1, xml); - QCOMPARE(parameter1.name(), QStringLiteral("test-name")); - QCOMPARE(parameter1.value(), QStringLiteral("test-value")); - - serializePacket(parameter1, xml); - - QXmppSdpParameter parameter2; - parameter2.setName(QStringLiteral("test-name")); - parameter2.setValue(QStringLiteral("test-value")); - - serializePacket(parameter2, xml); -} - -void tst_QXmppJingleIq::testSdpParameterWithoutValue() -{ - const QByteArray xml(""); - - QXmppSdpParameter parameter1; - - parsePacket(parameter1, xml); - QCOMPARE(parameter1.name(), QStringLiteral("test-name")); - QVERIFY(parameter1.value().isEmpty()); - - serializePacket(parameter1, xml); - - QXmppSdpParameter parameter2; - parameter2.setName(QStringLiteral("test-name")); - - serializePacket(parameter2, xml); -} - -void tst_QXmppJingleIq::testIsRtpCryptoElement_data() -{ - QTest::addColumn("xml"); - QTest::addColumn("isValid"); - - QTest::newRow("valid") - << QByteArrayLiteral("") - << true; - QTest::newRow("invalidTag") - << QByteArrayLiteral("") - << false; -} - -void tst_QXmppJingleIq::testIsRtpCryptoElement() -{ - QFETCH(QByteArray, xml); - QFETCH(bool, isValid); - - QCOMPARE(QXmppJingleRtpCryptoElement::isJingleRtpCryptoElement(xmlToDom(xml)), isValid); -} - -void tst_QXmppJingleIq::testRtpCryptoElement_data() -{ - QTest::addColumn("xml"); - QTest::addColumn("hasSessionParams"); - - QTest::newRow("withoutSessionParams") - << QByteArrayLiteral("") - << false; - QTest::newRow("withSessionParams") - << QByteArrayLiteral("") - << true; -} - -void tst_QXmppJingleIq::testRtpCryptoElement() -{ - QFETCH(QByteArray, xml); - QFETCH(bool, hasSessionParams); - - QXmppJingleRtpCryptoElement rtpCryptoElement1; - QCOMPARE(rtpCryptoElement1.tag(), uint32_t(0)); - QVERIFY(rtpCryptoElement1.cryptoSuite().isEmpty()); - QVERIFY(rtpCryptoElement1.keyParams().isEmpty()); - QVERIFY(rtpCryptoElement1.sessionParams().isEmpty()); - parsePacket(rtpCryptoElement1, xml); - - QCOMPARE(rtpCryptoElement1.tag(), uint32_t(1)); - QCOMPARE(rtpCryptoElement1.cryptoSuite(), QStringLiteral("AES_CM_128_HMAC_SHA1_80")); - QCOMPARE(rtpCryptoElement1.keyParams(), QStringLiteral("inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:32")); - if (hasSessionParams) { - QCOMPARE(rtpCryptoElement1.sessionParams(), QStringLiteral("KDR=1 UNENCRYPTED_SRTCP")); - } else { - QVERIFY(rtpCryptoElement1.sessionParams().isEmpty()); - } - - serializePacket(rtpCryptoElement1, xml); - - QXmppJingleRtpCryptoElement rtpCryptoElement2; - rtpCryptoElement2.setTag(1); - rtpCryptoElement2.setCryptoSuite(QStringLiteral("AES_CM_128_HMAC_SHA1_80")); - rtpCryptoElement2.setKeyParams(QStringLiteral("inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:32")); - - if (hasSessionParams) { - rtpCryptoElement2.setSessionParams(QStringLiteral("KDR=1 UNENCRYPTED_SRTCP")); - } - - QCOMPARE(rtpCryptoElement2.tag(), uint32_t(1)); - QCOMPARE(rtpCryptoElement2.cryptoSuite(), QStringLiteral("AES_CM_128_HMAC_SHA1_80")); - QCOMPARE(rtpCryptoElement2.keyParams(), QStringLiteral("inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:32")); - if (hasSessionParams) { - QCOMPARE(rtpCryptoElement2.sessionParams(), QStringLiteral("KDR=1 UNENCRYPTED_SRTCP")); - } else { - QVERIFY(rtpCryptoElement2.sessionParams().isEmpty()); - } - - serializePacket(rtpCryptoElement2, xml); -} - -void tst_QXmppJingleIq::testIsRtpEncryption_data() -{ - QTest::addColumn("xml"); - QTest::addColumn("isValid"); - - QTest::newRow("valid") - << QByteArrayLiteral("") - << true; - QTest::newRow("invalidTag") - << QByteArrayLiteral("") - << false; - QTest::newRow("invalidNamespace") - << QByteArrayLiteral("") - << false; -} - -void tst_QXmppJingleIq::testIsRtpEncryption() -{ - QFETCH(QByteArray, xml); - QFETCH(bool, isValid); - - QCOMPARE(QXmppJingleRtpEncryption::isJingleRtpEncryption(xmlToDom(xml)), isValid); -} - -void tst_QXmppJingleIq::testRtpEncryption_data() -{ - QTest::addColumn("xml"); - QTest::addColumn("isRequired"); - QTest::addColumn("cryptoElementCount"); - - QTest::newRow("required") - << QByteArrayLiteral("" - "" - "") - << true - << 1; - QTest::newRow("optional") - << QByteArrayLiteral("" - "" - "") - << false - << 1; - QTest::newRow("optionalWithMultipleCryptoElements") - << QByteArrayLiteral("" - "" - "" - "") - << false - << 2; -} - -void tst_QXmppJingleIq::testRtpEncryption() -{ - QFETCH(QByteArray, xml); - QFETCH(bool, isRequired); - QFETCH(int, cryptoElementCount); - - QXmppJingleRtpEncryption rtpEncryption1; - QVERIFY(!rtpEncryption1.isRequired()); - QVERIFY(rtpEncryption1.cryptoElements().isEmpty()); - - parsePacket(rtpEncryption1, xml); - - QCOMPARE(rtpEncryption1.isRequired(), isRequired); - QCOMPARE(rtpEncryption1.cryptoElements().size(), cryptoElementCount); - - serializePacket(rtpEncryption1, xml); - - QXmppJingleRtpCryptoElement rtpCryptoElement2; - rtpCryptoElement2.setTag(1); - rtpCryptoElement2.setCryptoSuite(QStringLiteral("AES_CM_128_HMAC_SHA1_80")); - rtpCryptoElement2.setKeyParams(QStringLiteral("inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:32")); - - QXmppJingleRtpEncryption rtpEncryption2; - rtpEncryption2.setRequired(isRequired); - - if (cryptoElementCount == 2) { - auto rtpCryptoElement3 = rtpCryptoElement2; - rtpCryptoElement3.setTag(2); - - rtpEncryption2.setCryptoElements({ rtpCryptoElement2, rtpCryptoElement3 }); - } else { - rtpEncryption2.setCryptoElements({ rtpCryptoElement2 }); - } - - QCOMPARE(rtpEncryption2.isRequired(), isRequired); - QCOMPARE(rtpEncryption2.cryptoElements().size(), cryptoElementCount); - QCOMPARE(rtpEncryption2.cryptoElements().at(0).tag(), uint32_t(1)); - - if (cryptoElementCount == 2) { - QCOMPARE(rtpEncryption2.cryptoElements().at(1).tag(), uint32_t(2)); - } - - serializePacket(rtpEncryption2, xml); -} - -void tst_QXmppJingleIq::testIsRtpFeedbackProperty_data() -{ - QTest::addColumn("xml"); - QTest::addColumn("isValid"); - - QTest::newRow("valid") - << QByteArrayLiteral("") - << true; - QTest::newRow("invalidTag") - << QByteArrayLiteral("") - << false; - QTest::newRow("invalidNamespace") - << QByteArrayLiteral("") - << false; -} - -void tst_QXmppJingleIq::testIsRtpFeedbackProperty() -{ - QFETCH(QByteArray, xml); - QFETCH(bool, isValid); - - QCOMPARE(QXmppJingleRtpFeedbackProperty::isJingleRtpFeedbackProperty(xmlToDom(xml)), isValid); -} - -void tst_QXmppJingleIq::testRtpFeedbackProperty() -{ - const QByteArray xml(""); - - QXmppJingleRtpFeedbackProperty property1; - QVERIFY(property1.type().isEmpty()); - QVERIFY(property1.subtype().isEmpty()); - - parsePacket(property1, xml); - QCOMPARE(property1.type(), QStringLiteral("nack")); - QCOMPARE(property1.subtype(), QStringLiteral("sli")); - - serializePacket(property1, xml); - - QXmppJingleRtpFeedbackProperty property2; - property2.setType(QStringLiteral("nack")); - property2.setSubtype(QStringLiteral("sli")); - - QCOMPARE(property1.type(), QStringLiteral("nack")); - QCOMPARE(property1.subtype(), QStringLiteral("sli")); - - serializePacket(property2, xml); -} - -void tst_QXmppJingleIq::testRtpFeedbackPropertyWithParameters() -{ - const QByteArray xml( - "" - "" - "" - ""); - - QXmppJingleRtpFeedbackProperty property1; - - parsePacket(property1, xml); - QCOMPARE(property1.type(), QStringLiteral("test-type")); - QVERIFY(property1.subtype().isEmpty()); - QCOMPARE(property1.parameters().size(), 2); - QCOMPARE(property1.parameters().at(0).name(), QStringLiteral("test-name-1")); - QCOMPARE(property1.parameters().at(1).name(), QStringLiteral("test-name-2")); - - serializePacket(property1, xml); - - QXmppJingleRtpFeedbackProperty property2; - property2.setType(QStringLiteral("test-type")); - - QXmppSdpParameter parameter1; - parameter1.setName(QStringLiteral("test-name-1")); - - QXmppSdpParameter parameter2; - parameter2.setName(QStringLiteral("test-name-2")); - - property2.setParameters({ parameter1, parameter2 }); - - QCOMPARE(property2.parameters().size(), 2); - QCOMPARE(property2.parameters().at(0).name(), QStringLiteral("test-name-1")); - QCOMPARE(property2.parameters().at(1).name(), QStringLiteral("test-name-2")); - - serializePacket(property2, xml); -} - -void tst_QXmppJingleIq::testIsRtpFeedbackInterval_data() -{ - QTest::addColumn("xml"); - QTest::addColumn("isValid"); - - QTest::newRow("valid") - << QByteArrayLiteral("") - << true; - QTest::newRow("invalidTag") - << QByteArrayLiteral("") - << false; - QTest::newRow("invalidNamespace") - << QByteArrayLiteral("") - << false; -} - -void tst_QXmppJingleIq::testIsRtpFeedbackInterval() -{ - QFETCH(QByteArray, xml); - QFETCH(bool, isValid); - - QCOMPARE(QXmppJingleRtpFeedbackInterval::isJingleRtpFeedbackInterval(xmlToDom(xml)), isValid); -} - -void tst_QXmppJingleIq::testRtpFeedbackInterval() -{ - const QByteArray xml(""); - - QXmppJingleRtpFeedbackInterval interval1; - - parsePacket(interval1, xml); - QCOMPARE(interval1.value(), uint64_t(100)); - - serializePacket(interval1, xml); - - QXmppJingleRtpFeedbackInterval interval2; - interval2.setValue(100); - - QCOMPARE(interval1.value(), uint64_t(100)); - - serializePacket(interval2, xml); -} - -void tst_QXmppJingleIq::testIsRtpHeaderExtensionProperty_data() -{ - QTest::addColumn("xml"); - QTest::addColumn("isValid"); - - QTest::newRow("valid") - << QByteArrayLiteral("") - << true; - QTest::newRow("invalidTag") - << QByteArrayLiteral("") - << false; - QTest::newRow("invalidNamespace") - << QByteArrayLiteral("") - << false; -} - -void tst_QXmppJingleIq::testIsRtpHeaderExtensionProperty() -{ - QFETCH(QByteArray, xml); - QFETCH(bool, isValid); - - QCOMPARE(QXmppJingleRtpHeaderExtensionProperty::isJingleRtpHeaderExtensionProperty(xmlToDom(xml)), isValid); -} - -void tst_QXmppJingleIq::testRtpHeaderExtensionProperty() -{ - const QByteArray xml(""); - - QXmppJingleRtpHeaderExtensionProperty property1; - QCOMPARE(property1.id(), uint32_t(0)); - QVERIFY(property1.uri().isEmpty()); - QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Both); - - parsePacket(property1, xml); - QCOMPARE(property1.id(), uint32_t(1)); - QCOMPARE(property1.uri(), QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); - QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Both); - - serializePacket(property1, xml); - - QXmppJingleRtpHeaderExtensionProperty property2; - property2.setId(1); - property2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); - property2.setSenders(QXmppJingleRtpHeaderExtensionProperty::Both); - - QCOMPARE(property1.id(), uint32_t(1)); - QCOMPARE(property1.uri(), QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); - QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Both); - - serializePacket(property2, xml); -} - -void tst_QXmppJingleIq::testRtpHeaderExtensionPropertyWithSenders() -{ - const QByteArray xml(""); - - QXmppJingleRtpHeaderExtensionProperty property1; - - parsePacket(property1, xml); - QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Initiator); - - serializePacket(property1, xml); - - QXmppJingleRtpHeaderExtensionProperty property2; - property2.setId(1); - property2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); - property2.setSenders(QXmppJingleRtpHeaderExtensionProperty::Initiator); - - QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Initiator); - - serializePacket(property2, xml); -} - -void tst_QXmppJingleIq::testRtpHeaderExtensionPropertyWithParameters() -{ - const QByteArray xml( - "" - "" - "" - ""); - - QXmppJingleRtpHeaderExtensionProperty property1; - - parsePacket(property1, xml); - QCOMPARE(property1.parameters().size(), 2); - QCOMPARE(property1.parameters().at(0).name(), QStringLiteral("test-name-1")); - QCOMPARE(property1.parameters().at(1).name(), QStringLiteral("test-name-2")); - - serializePacket(property1, xml); - - QXmppJingleRtpHeaderExtensionProperty property2; - property2.setId(1); - property2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); - - QXmppSdpParameter parameter1; - parameter1.setName(QStringLiteral("test-name-1")); - - QXmppSdpParameter parameter2; - parameter2.setName(QStringLiteral("test-name-2")); - - property2.setParameters({ parameter1, parameter2 }); - - QCOMPARE(property2.parameters().size(), 2); - QCOMPARE(property2.parameters().at(0).name(), QStringLiteral("test-name-1")); - QCOMPARE(property2.parameters().at(1).name(), QStringLiteral("test-name-2")); - - serializePacket(property2, xml); -} - -void tst_QXmppJingleIq::testCandidate() -{ - const QByteArray xml( - ""); - - QXmppJingleCandidate candidate; - parsePacket(candidate, xml); - QCOMPARE(candidate.foundation(), QLatin1String("1")); - QCOMPARE(candidate.generation(), 0); - QCOMPARE(candidate.id(), QLatin1String("el0747fg11")); - QCOMPARE(candidate.host(), QHostAddress("10.0.1.1")); - QCOMPARE(candidate.network(), 1); - QCOMPARE(candidate.port(), quint16(8998)); - QCOMPARE(candidate.priority(), 2130706431); - QCOMPARE(candidate.protocol(), QLatin1String("udp")); - QCOMPARE(candidate.type(), QXmppJingleCandidate::HostType); - serializePacket(candidate, xml); -}; - -void tst_QXmppJingleIq::testContent() -{ - const QByteArray xml( - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - ""); - - QXmppJingleIq::Content content1; - QVERIFY(content1.creator().isEmpty()); - QVERIFY(content1.name().isEmpty()); - QVERIFY(content1.description().media().isEmpty()); - QCOMPARE(content1.description().ssrc(), quint32(0)); - QVERIFY(!content1.isRtpMultiplexingSupported()); - QVERIFY(!content1.rtpEncryption()); - QCOMPARE(content1.description().payloadTypes().size(), 0); - QVERIFY(content1.transportUser().isEmpty()); - QVERIFY(content1.transportPassword().isEmpty()); - QCOMPARE(content1.transportCandidates().size(), 0); - parsePacket(content1, xml); - - QCOMPARE(content1.creator(), QStringLiteral("initiator")); - QCOMPARE(content1.name(), QStringLiteral("voice")); - QCOMPARE(content1.description().media(), QStringLiteral("audio")); - QCOMPARE(content1.description().ssrc(), quint32(0)); - QVERIFY(content1.isRtpMultiplexingSupported()); - QVERIFY(content1.rtpEncryption()); - 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); - QCOMPARE(content1.transportCandidates().at(0).id(), QStringLiteral("el0747fg11")); - QCOMPARE(content1.transportCandidates().at(1).id(), QStringLiteral("y3s2b30v3r")); - serializePacket(content1, xml); - - QXmppJingleIq::Content content2; - content2.setCreator(QStringLiteral("initiator")); - content2.setName(QStringLiteral("voice")); - QXmppJingleDescription content2desc; - content2desc.setMedia(QStringLiteral("audio")); - content2desc.setSsrc(quint32(0)); - content2.setRtpMultiplexingSupported(true); - QXmppJingleRtpCryptoElement rtpCryptoElement; - rtpCryptoElement.setTag(1); - rtpCryptoElement.setCryptoSuite(QStringLiteral("AES_CM_128_HMAC_SHA1_80")); - rtpCryptoElement.setKeyParams(QStringLiteral("inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:32")); - QXmppJingleRtpEncryption rtpEncryption; - rtpEncryption.setCryptoElements({ rtpCryptoElement }); - content2.setRtpEncryption(rtpEncryption); - QXmppJinglePayloadType payloadType1; - payloadType1.setId(quint8(96)); - content2desc.setPayloadTypes({ payloadType1 }); - QXmppJinglePayloadType payloadType2; - payloadType2.setId(quint8(97)); - content2desc.addPayloadType(payloadType2); - content2.setDescription(content2desc); - content2.setTransportUser(QStringLiteral("8hhy")); - content2.setTransportPassword(QStringLiteral("asd88fgpdd777uzjYhagZg")); - QXmppJingleCandidate transportCandidate1; - transportCandidate1.setId(QStringLiteral("el0747fg11")); - content2.setTransportCandidates({ transportCandidate1 }); - QXmppJingleCandidate transportCandidate2; - transportCandidate2.setId(QStringLiteral("y3s2b30v3r")); - content2.addTransportCandidate(transportCandidate2); - - QCOMPARE(content2.creator(), QStringLiteral("initiator")); - QCOMPARE(content2.name(), QStringLiteral("voice")); - QCOMPARE(content2.description().media(), QStringLiteral("audio")); - QCOMPARE(content2.description().ssrc(), quint32(0)); - QVERIFY(content2.isRtpMultiplexingSupported()); - QVERIFY(content2.rtpEncryption()); - 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); - QCOMPARE(content2.transportCandidates().at(0).id(), QStringLiteral("el0747fg11")); - QCOMPARE(content2.transportCandidates().at(1).id(), QStringLiteral("y3s2b30v3r")); - serializePacket(content2, xml); -} - -void tst_QXmppJingleIq::testContentFingerprint() -{ - const QByteArray xml( - "" - "" - "" - "" - "" - "" - "" - "02:1A:CC:54:27:AB:EB:9C:53:3F:3E:4B:65:2E:7D:46:3F:54:42:CD:54:F1:7A:03:A2:7D:F9:B0:7F:46:19:B2" - "" - "" - ""); - - QXmppJingleIq::Content content; - parsePacket(content, xml); - - QCOMPARE(content.creator(), QLatin1String("initiator")); - QCOMPARE(content.name(), QLatin1String("voice")); - 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")); - QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1")); - QCOMPARE(content.transportCandidates()[0].port(), quint16(8998)); - QCOMPARE(content.transportCandidates()[0].priority(), 2130706431); - QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp")); - QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType); - QCOMPARE(content.transportUser(), QLatin1String("8hhy")); - QCOMPARE(content.transportPassword(), QLatin1String("asd88fgpdd777uzjYhagZg")); - QCOMPARE(content.transportFingerprint(), QByteArray::fromHex("021acc5427abeb9c533f3e4b652e7d463f5442cd54f17a03a27df9b07f4619b2")); - QCOMPARE(content.transportFingerprintHash(), QLatin1String("sha-256")); - QCOMPARE(content.transportFingerprintSetup(), QLatin1String("actpass")); - - serializePacket(content, xml); -} - -void tst_QXmppJingleIq::testContentSdp() -{ - const QString sdp( - "m=audio 8998 RTP/AVP 96 97 18 0 103 98\r\n" - "c=IN IP4 10.0.1.1\r\n" - "a=rtpmap:96 speex/16000\r\n" - "a=rtpmap:97 speex/8000\r\n" - "a=rtpmap:18 G729/0\r\n" - "a=rtpmap:0 PCMU/0\r\n" - "a=rtpmap:103 L16/16000/2\r\n" - "a=rtpmap:98 x-ISAC/8000\r\n" - "a=candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0\r\n" - "a=candidate:2 1 udp 1694498815 192.0.2.3 45664 typ host generation 0\r\n" - "a=ice-ufrag:8hhy\r\n" - "a=ice-pwd:asd88fgpdd777uzjYhagZg\r\n"); - - QXmppJingleIq::Content content; - QVERIFY(content.parseSdp(sdp)); - - 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")); - QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1")); - QCOMPARE(content.transportCandidates()[0].port(), quint16(8998)); - QCOMPARE(content.transportCandidates()[0].priority(), 2130706431); - QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp")); - QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType); - QCOMPARE(content.transportCandidates()[1].component(), 1); - QCOMPARE(content.transportCandidates()[1].foundation(), QLatin1String("2")); - QCOMPARE(content.transportCandidates()[1].host(), QHostAddress("192.0.2.3")); - QCOMPARE(content.transportCandidates()[1].port(), quint16(45664)); - QCOMPARE(content.transportCandidates()[1].priority(), 1694498815); - QCOMPARE(content.transportCandidates()[1].protocol(), QLatin1String("udp")); - QCOMPARE(content.transportCandidates()[1].type(), QXmppJingleCandidate::HostType); - QCOMPARE(content.transportUser(), QLatin1String("8hhy")); - QCOMPARE(content.transportPassword(), QLatin1String("asd88fgpdd777uzjYhagZg")); - - QCOMPARE(content.toSdp(), sdp); -} - -void tst_QXmppJingleIq::testContentSdpReflexive() -{ - const QString sdp( - "m=audio 45664 RTP/AVP 96 97 18 0 103 98\r\n" - "c=IN IP4 192.0.2.3\r\n" - "a=rtpmap:96 speex/16000\r\n" - "a=rtpmap:97 speex/8000\r\n" - "a=rtpmap:18 G729/0\r\n" - "a=rtpmap:0 PCMU/0\r\n" - "a=rtpmap:103 L16/16000/2\r\n" - "a=rtpmap:98 x-ISAC/8000\r\n" - "a=candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0\r\n" - "a=candidate:2 1 udp 1694498815 192.0.2.3 45664 typ srflx generation 0\r\n" - "a=ice-ufrag:8hhy\r\n" - "a=ice-pwd:asd88fgpdd777uzjYhagZg\r\n"); - - QXmppJingleIq::Content content; - QVERIFY(content.parseSdp(sdp)); - - 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")); - QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1")); - QCOMPARE(content.transportCandidates()[0].port(), quint16(8998)); - QCOMPARE(content.transportCandidates()[0].priority(), 2130706431); - QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp")); - QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType); - QCOMPARE(content.transportCandidates()[1].component(), 1); - QCOMPARE(content.transportCandidates()[1].foundation(), QLatin1String("2")); - QCOMPARE(content.transportCandidates()[1].host(), QHostAddress("192.0.2.3")); - QCOMPARE(content.transportCandidates()[1].port(), quint16(45664)); - QCOMPARE(content.transportCandidates()[1].priority(), 1694498815); - QCOMPARE(content.transportCandidates()[1].protocol(), QLatin1String("udp")); - QCOMPARE(content.transportCandidates()[1].type(), QXmppJingleCandidate::ServerReflexiveType); - QCOMPARE(content.transportUser(), QLatin1String("8hhy")); - QCOMPARE(content.transportPassword(), QLatin1String("asd88fgpdd777uzjYhagZg")); - - QCOMPARE(content.toSdp(), sdp); -} - -void tst_QXmppJingleIq::testContentSdpFingerprint() -{ - const QString sdp( - "m=audio 8998 RTP/AVP 96 100\r\n" - "c=IN IP4 10.0.1.1\r\n" - "a=rtpmap:96 speex/16000\r\n" - "a=fmtp:96 cng=on; vbr=on\r\n" - "a=rtpmap:100 telephone-event/8000\r\n" - "a=fmtp:100 0-15,66,70\r\n" - "a=candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0\r\n" - "a=fingerprint:sha-256 02:1A:CC:54:27:AB:EB:9C:53:3F:3E:4B:65:2E:7D:46:3F:54:42:CD:54:F1:7A:03:A2:7D:F9:B0:7F:46:19:B2\r\n" - "a=setup:actpass\r\n"); - - QXmppJingleIq::Content content; - QVERIFY(content.parseSdp(sdp)); - - 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")); - QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1")); - QCOMPARE(content.transportCandidates()[0].port(), quint16(8998)); - QCOMPARE(content.transportCandidates()[0].priority(), 2130706431); - QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp")); - QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType); - QCOMPARE(content.transportFingerprint(), QByteArray::fromHex("021acc5427abeb9c533f3e4b652e7d463f5442cd54f17a03a27df9b07f4619b2")); - QCOMPARE(content.transportFingerprintHash(), QLatin1String("sha-256")); - QCOMPARE(content.transportFingerprintSetup(), QLatin1String("actpass")); - - QCOMPARE(content.toSdp(), sdp); -} - -void tst_QXmppJingleIq::testContentSdpParameters() -{ - const QString sdp( - "m=audio 8998 RTP/AVP 96 100\r\n" - "c=IN IP4 10.0.1.1\r\n" - "a=rtpmap:96 speex/16000\r\n" - "a=fmtp:96 cng=on; vbr=on\r\n" - "a=rtpmap:100 telephone-event/8000\r\n" - "a=fmtp:100 0-15,66,70\r\n" - "a=candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0\r\n"); - - QXmppJingleIq::Content content; - QVERIFY(content.parseSdp(sdp)); - - 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")); - QCOMPARE(content.transportCandidates()[0].host(), QHostAddress("10.0.1.1")); - QCOMPARE(content.transportCandidates()[0].port(), quint16(8998)); - QCOMPARE(content.transportCandidates()[0].priority(), 2130706431); - QCOMPARE(content.transportCandidates()[0].protocol(), QLatin1String("udp")); - QCOMPARE(content.transportCandidates()[0].type(), QXmppJingleCandidate::HostType); - - QCOMPARE(content.toSdp(), sdp); -} - -void tst_QXmppJingleIq::testContentRtpFeedbackNegotiation() -{ - const QByteArray xml( - "" - "" - "" - "" - "" - "" - "" - "" - ""); - - QXmppJingleIq::Content content1; - QVERIFY(content1.rtpFeedbackProperties().isEmpty()); - QVERIFY(content1.rtpFeedbackIntervals().isEmpty()); - parsePacket(content1, xml); - - const auto rtpFeedbackProperties1 = content1.rtpFeedbackProperties(); - QCOMPARE(rtpFeedbackProperties1.size(), 2); - QCOMPARE(rtpFeedbackProperties1[0].subtype(), QStringLiteral("pli")); - QCOMPARE(rtpFeedbackProperties1[1].subtype(), QStringLiteral("sli")); - - const auto rtpFeedbackIntervals1 = content1.rtpFeedbackIntervals(); - QCOMPARE(rtpFeedbackIntervals1.size(), 2); - QCOMPARE(rtpFeedbackIntervals1[0].value(), uint64_t(60)); - QCOMPARE(rtpFeedbackIntervals1[1].value(), uint64_t(80)); - - serializePacket(content1, xml); - - QXmppJingleRtpFeedbackProperty rtpFeedbackProperty1; - rtpFeedbackProperty1.setType(QStringLiteral("nack")); - rtpFeedbackProperty1.setSubtype(QStringLiteral("pli")); - - QXmppJingleRtpFeedbackProperty rtpFeedbackProperty2; - rtpFeedbackProperty2.setType(QStringLiteral("nack")); - rtpFeedbackProperty2.setSubtype(QStringLiteral("sli")); - - QXmppJingleRtpFeedbackInterval rtpFeedbackInterval1; - rtpFeedbackInterval1.setValue(60); - - QXmppJingleRtpFeedbackInterval rtpFeedbackInterval2; - rtpFeedbackInterval2.setValue(80); - - QXmppJinglePayloadType payloadType; - payloadType.setId(96); - payloadType.setName(QStringLiteral("speex")); - - QXmppJingleIq::Content content2; - content2.setCreator(QStringLiteral("initiator")); - content2.setName(QStringLiteral("voice")); - QXmppJingleDescription content2desc; - content2desc.addPayloadType(payloadType); - content2.setDescription(content2desc); - content2.setRtpFeedbackProperties({ rtpFeedbackProperty1, rtpFeedbackProperty2 }); - content2.setRtpFeedbackIntervals({ rtpFeedbackInterval1, rtpFeedbackInterval2 }); - - const auto rtpFeedbackProperties2 = content2.rtpFeedbackProperties(); - QCOMPARE(rtpFeedbackProperties2.size(), 2); - QCOMPARE(rtpFeedbackProperties2[0].subtype(), QStringLiteral("pli")); - QCOMPARE(rtpFeedbackProperties2[1].subtype(), QStringLiteral("sli")); - - const auto rtpFeedbackIntervals2 = content2.rtpFeedbackIntervals(); - QCOMPARE(rtpFeedbackIntervals2.size(), 2); - QCOMPARE(rtpFeedbackIntervals2[0].value(), uint64_t(60)); - QCOMPARE(rtpFeedbackIntervals2[1].value(), uint64_t(80)); - - serializePacket(content2, xml); -} - -void tst_QXmppJingleIq::testContentRtpHeaderExtensionsNegotiation() -{ - const QByteArray xml( - "" - "" - "" - "" - "" - "" - "" - ""); - - QXmppJingleIq::Content content1; - QVERIFY(content1.rtpHeaderExtensionProperties().isEmpty()); - QVERIFY(!content1.isRtpHeaderExtensionMixingAllowed()); - parsePacket(content1, xml); - - const auto rtpHeaderExtensionProperties1 = content1.rtpHeaderExtensionProperties(); - QCOMPARE(rtpHeaderExtensionProperties1.size(), 2); - QCOMPARE(rtpHeaderExtensionProperties1[0].id(), uint32_t(1)); - QCOMPARE(rtpHeaderExtensionProperties1[1].id(), uint32_t(2)); - - QVERIFY(content1.isRtpHeaderExtensionMixingAllowed()); - - serializePacket(content1, xml); - - QXmppJingleRtpHeaderExtensionProperty rtpHeaderExtensionProperty1; - rtpHeaderExtensionProperty1.setId(uint32_t(1)); - rtpHeaderExtensionProperty1.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset")); - - QXmppJingleRtpHeaderExtensionProperty rtpHeaderExtensionProperty2; - rtpHeaderExtensionProperty2.setId(uint32_t(2)); - rtpHeaderExtensionProperty2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:ntp-64")); - - QXmppJinglePayloadType payloadType; - payloadType.setId(96); - payloadType.setName(QStringLiteral("speex")); - - QXmppJingleIq::Content content2; - content2.setCreator(QStringLiteral("initiator")); - content2.setName(QStringLiteral("voice")); - QXmppJingleDescription content2desc; - content2desc.addPayloadType(payloadType); - content2.setDescription(content2desc); - content2.setRtpHeaderExtensionProperties({ rtpHeaderExtensionProperty1, rtpHeaderExtensionProperty2 }); - content2.setRtpHeaderExtensionMixingAllowed(true); - - const auto rtpHeaderExtensionProperties2 = content2.rtpHeaderExtensionProperties(); - QCOMPARE(rtpHeaderExtensionProperties2.size(), 2); - QCOMPARE(rtpHeaderExtensionProperties2[0].id(), uint32_t(1)); - QCOMPARE(rtpHeaderExtensionProperties2[1].id(), uint32_t(2)); - - QVERIFY(content2.isRtpHeaderExtensionMixingAllowed()); - - serializePacket(content2, xml); -} - -void tst_QXmppJingleIq::testSession() -{ - const QByteArray xml( - "" - "" - "" - "" - "" - "" - "" - "" - ""); - - QXmppJingleIq session; - parsePacket(session, xml); - QCOMPARE(session.action(), QXmppJingleIq::SessionInitiate); - QCOMPARE(session.initiator(), QLatin1String("romeo@montague.lit/orchard")); - QCOMPARE(session.sid(), QLatin1String("a73sjjvkla37jfea")); - QCOMPARE(session.mujiGroupChatJid(), QStringLiteral("darkcave@chat.shakespeare.lit")); - QCOMPARE(session.contents().size(), 1); - 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(), QXmppJingleReason::None); - serializePacket(session, xml); -} - -void tst_QXmppJingleIq::testTerminate() -{ - const QByteArray xml( - "" - "" - "" - "" - "" - "" - ""); - - QXmppJingleIq session; - parsePacket(session, xml); - QCOMPARE(session.action(), QXmppJingleIq::SessionTerminate); - QCOMPARE(session.initiator(), QString()); - QCOMPARE(session.sid(), QLatin1String("a73sjjvkla37jfea")); - QCOMPARE(session.reason().text(), QString()); - QCOMPARE(session.reason().type(), QXmppJingleReason::Success); - serializePacket(session, xml); -} - -void tst_QXmppJingleIq::testRtpSessionState_data() -{ - QTest::addColumn("xml"); - QTest::addColumn("state"); - - QTest::newRow("active") - << QByteArrayLiteral("" - "" - "" - "" - "") - << QStringLiteral("active"); - QTest::newRow("hold") - << QByteArrayLiteral("" - "" - "" - "" - "") - << QStringLiteral("hold"); - QTest::newRow("unhold") - << QByteArrayLiteral("" - "" - "" - "" - "") - << QStringLiteral("unhold"); - QTest::newRow("mute") - << QByteArrayLiteral("" - "" - "" - "" - "") - << QStringLiteral("mute"); - QTest::newRow("unmute") - << QByteArrayLiteral("" - "" - "" - "" - "") - << QStringLiteral("unmute"); - QTest::newRow("ringing") - << QByteArrayLiteral("" - "" - "" - "" - "") - << QStringLiteral("ringing"); -} - -void tst_QXmppJingleIq::testRtpSessionState() -{ - QFETCH(QByteArray, xml); - QFETCH(QString, state); - - QXmppJingleIq iq1; - QVERIFY(!iq1.rtpSessionState()); - parsePacket(iq1, xml); - - const auto rtpSessionState1 = *iq1.rtpSessionState(); - if (state == QStringLiteral("active")) { - QVERIFY(std::holds_alternative(rtpSessionState1)); - } else if (state == QStringLiteral("hold")) { - QVERIFY(std::holds_alternative(rtpSessionState1)); - } else if (state == QStringLiteral("unhold")) { - QVERIFY(std::holds_alternative(rtpSessionState1)); - } else if (const auto isMute = state == QStringLiteral("mute"); isMute || state == QStringLiteral("unmute")) { - QVERIFY(std::holds_alternative(rtpSessionState1)); - - const auto stateMuting = std::get(rtpSessionState1); - QCOMPARE(stateMuting.isMute, isMute); - - if (isMute) { - QCOMPARE(stateMuting.creator, QXmppJingleIq::Initiator); - QCOMPARE(stateMuting.name, QStringLiteral("voice")); - } else { - QCOMPARE(stateMuting.creator, QXmppJingleIq::Responder); - QVERIFY(stateMuting.name.isEmpty()); - } - } else if (state == QStringLiteral("ringing")) { - QVERIFY(std::holds_alternative(rtpSessionState1)); - } - - serializePacket(iq1, xml); - - QXmppJingleIq iq2; - iq2.setType(QXmppIq::Set); - iq2.setId({}); - - if (state == QStringLiteral("active")) { - iq2.setRtpSessionState(QXmppJingleIq::RtpSessionStateActive()); - } else if (state == QStringLiteral("hold")) { - iq2.setRtpSessionState(QXmppJingleIq::RtpSessionStateHold()); - } else if (state == QStringLiteral("unhold")) { - iq2.setRtpSessionState(QXmppJingleIq::RtpSessionStateUnhold()); - } else if (const auto isMute = state == QStringLiteral("mute"); isMute || state == QStringLiteral("unmute")) { - QXmppJingleIq::RtpSessionStateMuting stateMuting; - stateMuting.isMute = isMute; - - if (isMute) { - stateMuting.creator = QXmppJingleIq::Initiator; - stateMuting.name = QStringLiteral("voice"); - } else { - stateMuting.creator = QXmppJingleIq::Responder; - } - - iq2.setRtpSessionState(stateMuting); - } else if (state == QStringLiteral("ringing")) { - iq2.setRtpSessionState(QXmppJingleIq::RtpSessionStateRinging()); - } - - const auto rtpSessionState2 = *iq2.rtpSessionState(); - if (state == QStringLiteral("active")) { - QVERIFY(std::holds_alternative(rtpSessionState2)); - } else if (state == QStringLiteral("hold")) { - QVERIFY(std::holds_alternative(rtpSessionState2)); - } else if (state == QStringLiteral("unhold")) { - QVERIFY(std::holds_alternative(rtpSessionState2)); - } else if (const auto isMute = state == QStringLiteral("mute"); isMute || state == QStringLiteral("unmute")) { - QVERIFY(std::holds_alternative(rtpSessionState2)); - - const auto stateMuting = std::get(rtpSessionState2); - QCOMPARE(stateMuting.isMute, isMute); - - if (isMute) { - QCOMPARE(stateMuting.creator, QXmppJingleIq::Initiator); - QCOMPARE(stateMuting.name, QStringLiteral("voice")); - } else { - QCOMPARE(stateMuting.creator, QXmppJingleIq::Responder); - QVERIFY(stateMuting.name.isEmpty()); - } - } else if (state == QStringLiteral("ringing")) { - QVERIFY(std::holds_alternative(rtpSessionState2)); - } - - serializePacket(iq2, xml); -} - -void tst_QXmppJingleIq::testAudioPayloadType() -{ - const QByteArray xml(R"()"); - QXmppJinglePayloadType payload; - parsePacket(payload, xml); - QCOMPARE(payload.id(), static_cast(103)); - QCOMPARE(payload.name(), QLatin1String("L16")); - QCOMPARE(payload.channels(), static_cast(2)); - QCOMPARE(payload.clockrate(), 16000u); - serializePacket(payload, xml); -} - -void tst_QXmppJingleIq::testVideoPayloadType() -{ - const QByteArray xml( - "" - "" - "" - ""); - QXmppJinglePayloadType payload; - parsePacket(payload, xml); - QCOMPARE(payload.id(), static_cast(98)); - QCOMPARE(payload.name(), QLatin1String("theora")); - QCOMPARE(payload.clockrate(), 90000u); - QCOMPARE(payload.parameters().size(), 2); - QCOMPARE(payload.parameters().value("height"), QLatin1String("768")); - QCOMPARE(payload.parameters().value("width"), QLatin1String("1024")); - serializePacket(payload, xml); -} - -void tst_QXmppJingleIq::testPayloadTypeRtpFeedbackNegotiation() -{ - const QByteArray xml( - "" - "" - "" - "" - "" - ""); - - QXmppJinglePayloadType payload1; - QVERIFY(payload1.rtpFeedbackProperties().isEmpty()); - QVERIFY(payload1.rtpFeedbackIntervals().isEmpty()); - parsePacket(payload1, xml); - - const auto rtpFeedbackProperties1 = payload1.rtpFeedbackProperties(); - QCOMPARE(rtpFeedbackProperties1.size(), 2); - QCOMPARE(rtpFeedbackProperties1[0].subtype(), QStringLiteral("pli")); - QCOMPARE(rtpFeedbackProperties1[1].subtype(), QStringLiteral("sli")); - - const auto rtpFeedbackIntervals1 = payload1.rtpFeedbackIntervals(); - QCOMPARE(rtpFeedbackIntervals1.size(), 2); - QCOMPARE(rtpFeedbackIntervals1[0].value(), uint64_t(60)); - QCOMPARE(rtpFeedbackIntervals1[1].value(), uint64_t(80)); - - serializePacket(payload1, xml); - - QXmppJingleRtpFeedbackProperty rtpFeedbackProperty1; - rtpFeedbackProperty1.setType(QStringLiteral("nack")); - rtpFeedbackProperty1.setSubtype(QStringLiteral("pli")); - - QXmppJingleRtpFeedbackProperty rtpFeedbackProperty2; - rtpFeedbackProperty2.setType(QStringLiteral("nack")); - rtpFeedbackProperty2.setSubtype(QStringLiteral("sli")); - - QXmppJingleRtpFeedbackInterval rtpFeedbackInterval1; - rtpFeedbackInterval1.setValue(60); - - QXmppJingleRtpFeedbackInterval rtpFeedbackInterval2; - rtpFeedbackInterval2.setValue(80); - - QXmppJinglePayloadType payload2; - payload2.setId(96); - payload2.setRtpFeedbackProperties({ rtpFeedbackProperty1, rtpFeedbackProperty2 }); - payload2.setRtpFeedbackIntervals({ rtpFeedbackInterval1, rtpFeedbackInterval2 }); - - const auto rtpFeedbackProperties2 = payload2.rtpFeedbackProperties(); - QCOMPARE(rtpFeedbackProperties2.size(), 2); - QCOMPARE(rtpFeedbackProperties2[0].subtype(), QStringLiteral("pli")); - QCOMPARE(rtpFeedbackProperties2[1].subtype(), QStringLiteral("sli")); - - const auto rtpFeedbackIntervals2 = payload2.rtpFeedbackIntervals(); - QCOMPARE(rtpFeedbackIntervals2.size(), 2); - QCOMPARE(rtpFeedbackIntervals2[0].value(), uint64_t(60)); - QCOMPARE(rtpFeedbackIntervals2[1].value(), uint64_t(80)); - - serializePacket(payload2, xml); -} - -void tst_QXmppJingleIq::testRtpErrorCondition_data() -{ - QTest::addColumn("xml"); - QTest::addColumn("condition"); - - QTest::newRow("NoErrorCondition") - << QByteArrayLiteral("" - "" - "" - "" - "" - "" - "") - << QXmppJingleReason::NoErrorCondition; - QTest::newRow("InvalidCrypto") - << QByteArrayLiteral("" - "" - "" - "" - "" - "" - "" - "") - << QXmppJingleReason::InvalidCrypto; - QTest::newRow("CryptoRequired") - << QByteArrayLiteral("" - "" - "" - "" - "" - "" - "" - "") - << QXmppJingleReason::CryptoRequired; -} - -void tst_QXmppJingleIq::testRtpErrorCondition() -{ - QFETCH(QByteArray, xml); - QFETCH(QXmppJingleReason::RtpErrorCondition, condition); - - QXmppJingleIq iq1; - QCOMPARE(iq1.reason().rtpErrorCondition(), QXmppJingleReason::NoErrorCondition); - parsePacket(iq1, xml); - - const auto rtpErrorCondition1 = iq1.reason().rtpErrorCondition(); - switch (condition) { - case QXmppJingleReason::NoErrorCondition: - QVERIFY(rtpErrorCondition1 == QXmppJingleReason::NoErrorCondition); - break; - case QXmppJingleReason::InvalidCrypto: - QVERIFY(rtpErrorCondition1 == QXmppJingleReason::InvalidCrypto); - break; - case QXmppJingleReason::CryptoRequired: - QVERIFY(rtpErrorCondition1 == QXmppJingleReason::CryptoRequired); - break; - } - - serializePacket(iq1, xml); - - QXmppJingleIq iq2; - iq2.setType(QXmppIq::Set); - iq2.setId({}); - iq2.setAction(QXmppJingleIq::SessionTerminate); - - switch (condition) { - case QXmppJingleReason::NoErrorCondition: - iq2.reason().setRtpErrorCondition(QXmppJingleReason::NoErrorCondition); - break; - case QXmppJingleReason::InvalidCrypto: - iq2.reason().setRtpErrorCondition(QXmppJingleReason::InvalidCrypto); - break; - case QXmppJingleReason::CryptoRequired: - iq2.reason().setRtpErrorCondition(QXmppJingleReason::CryptoRequired); - break; - } - - iq2.reason().setType(QXmppJingleReason::SecurityError); - - const auto rtpErrorCondition2 = iq2.reason().rtpErrorCondition(); - switch (condition) { - case QXmppJingleReason::NoErrorCondition: - QVERIFY(rtpErrorCondition2 == QXmppJingleReason::NoErrorCondition); - break; - case QXmppJingleReason::InvalidCrypto: - QVERIFY(rtpErrorCondition2 == QXmppJingleReason::InvalidCrypto); - break; - case QXmppJingleReason::CryptoRequired: - QVERIFY(rtpErrorCondition2 == QXmppJingleReason::CryptoRequired); - break; - } - - serializePacket(iq2, xml); -} - -void tst_QXmppJingleIq::testIsJingleMessageInitiationElement_data() -{ - QTest::addColumn("xml"); - QTest::addColumn("isValid"); - - // --- Propose --- - - QTest::newRow("validPropose") - << QByteArrayLiteral( - "" - "" - "") - << true; - QTest::newRow("invalidProposeIdMissing") - << QByteArrayLiteral( - "" - "" - "") - << false; - QTest::newRow("invalidProposeNamespaceMissing") - << QByteArrayLiteral( - "" - "" - "") - << false; - - // --- Ringing --- - - QTest::newRow("validRinging") - << QByteArrayLiteral("") - << true; - QTest::newRow("invalidRingingIdMissing") - << QByteArrayLiteral("") - << false; - QTest::newRow("invalidRingingNamespaceMissing") - << QByteArrayLiteral("") - << false; - - // --- Proceed --- - - QTest::newRow("validProceed") - << QByteArrayLiteral("") - << true; - QTest::newRow("invalidProceedIdMissing") - << QByteArrayLiteral("") - << false; - QTest::newRow("invalidProceedNamespaceMissing") - << QByteArrayLiteral("") - << false; - - // --- Reject --- - - QTest::newRow("validReject") - << QByteArrayLiteral( - "" - "" - "Busy" - "" - "" - "") - << true; - QTest::newRow("invalidRejectIdMissing") - << QByteArrayLiteral( - "" - "" - "Busy" - "" - "" - "") - << false; - QTest::newRow("invalidRejectNamespaceMissing") - << QByteArrayLiteral( - "" - "" - "Busy" - "" - "" - "") - << false; - - // --- Retract --- - - QTest::newRow("validRetract") - << QByteArrayLiteral( - "" - "" - "Retracted" - "" - "" - "") - << true; - QTest::newRow("invalidRetractIdMissing") - << QByteArrayLiteral( - "" - "" - "Retracted" - "" - "" - "") - << false; - QTest::newRow("invalidRetractNamespaceMissing") - << QByteArrayLiteral( - "" - "" - "Retracted" - "" - "" - "") - << false; - - // --- Finish --- - - QTest::newRow("validFinish") - << QByteArrayLiteral( - "" - "" - "Success" - "" - "" - "") - << true; - QTest::newRow("invalidFinishIdMissing") - << QByteArrayLiteral( - "" - "" - "Success" - "" - "" - "") - << false; - QTest::newRow("invalidFinishNamespaceMissing") - << QByteArrayLiteral( - "" - "" - "Success" - "" - "" - "") - << false; -} - -void tst_QXmppJingleIq::testIsJingleMessageInitiationElement() -{ - QFETCH(QByteArray, xml); - QFETCH(bool, isValid); - - QCOMPARE(QXmppJingleMessageInitiationElement::isJingleMessageInitiationElement(xmlToDom(xml)), isValid); -} - -void tst_QXmppJingleIq::testJingleMessageInitiationElement() -{ - using JmiType = QXmppJingleMessageInitiationElement::Type; - - // --- Propose --- - - const QByteArray proposeXml( - "" - "" - ""); - 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(""); - 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(""); - 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( - "" - "" - "Busy" - "" - "" - "" - ""); - 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.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); - QCOMPARE(rejectElement.containsTieBreak(), true); - serializePacket(rejectElement, rejectXml); - - // --- Retract --- - - const QByteArray retractXml( - "" - "" - "Retracted" - "" - "" - ""); - 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); - QCOMPARE(retractElement.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); - serializePacket(retractElement, retractXml); - - // --- Finish --- - - const QByteArray finishXml( - "" - "" - "Success" - "" - "" - "" - ""); - 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.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); - QCOMPARE(finishElement.migratedTo(), QStringLiteral("989a46a6-f202-4910-a7c3-83c6ba3f3947")); - serializePacket(finishElement, finishXml); -} - -QTEST_MAIN(tst_QXmppJingleIq) -#include "tst_qxmppjingleiq.moc" -- cgit v1.2.3 From eab17404ecccc6719decb7e8846d9c8e75e917c1 Mon Sep 17 00:00:00 2001 From: Tibor Csötönyi Date: Sun, 7 May 2023 21:29:44 +0200 Subject: Code review fixes --- src/CMakeLists.txt | 5 ++-- src/base/QXmppJingleData.cpp | 71 +++++++++++++++++++++++++++++++++++++++----- src/base/QXmppJingleData.h | 19 ++++++++++-- src/base/QXmppJingleIq.h | 5 ++++ 4 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 src/base/QXmppJingleIq.h (limited to 'src/base') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 56bebdcb..7e94efdc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,7 +35,8 @@ set(INSTALL_HEADER_FILES base/QXmppHttpUploadIq.h base/QXmppIbbIq.h base/QXmppIq.h - base/QXmppJingleData.h + base/QXmppJingleIq.h + base/QXmppJingleData.h base/QXmppLogger.h base/QXmppMamIq.h base/QXmppMessage.h @@ -173,7 +174,7 @@ set(SOURCE_FILES base/QXmppHttpUploadIq.cpp base/QXmppIbbIq.cpp base/QXmppIq.cpp - base/QXmppJingleData.cpp + base/QXmppJingleData.cpp base/QXmppLogger.cpp base/QXmppMamIq.cpp base/QXmppMessage.cpp diff --git a/src/base/QXmppJingleData.cpp b/src/base/QXmppJingleData.cpp index 2d497115..78cd4017 100644 --- a/src/base/QXmppJingleData.cpp +++ b/src/base/QXmppJingleData.cpp @@ -375,6 +375,60 @@ void QXmppJingleIq::Content::setDescription(const QXmppJingleDescription &descri 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->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->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->description.ssrc(); +} + +/// \deprecated This method is deprecated since QXmpp 1.6. Use +/// \c QXmppJingleIq::Conent::description().setSsrc() instead. +void QXmppJingleIq::Content::setDescriptionSsrc(quint32 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 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 &payloadTypes) +{ + d->description.setPayloadTypes(payloadTypes); +} + /// /// Returns whether multiplexing of RTP data and control packets on a single port is supported as /// specified by \xep{0167, Jingle RTP Sessions} and RFC 5761. @@ -3079,12 +3133,13 @@ void QXmppJingleMessageInitiationElement::setMigratedTo(const QString &migratedT /// \cond void QXmppJingleMessageInitiationElement::parse(const QDomElement &element) { - d->type = stringToJmiElementType(element.nodeName()); + std::optional type { stringToJmiElementType(element.nodeName()) }; - if (d->type == Type::None) { + 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. @@ -3108,12 +3163,12 @@ void QXmppJingleMessageInitiationElement::parse(const QDomElement &element) break; case Type::Finish: - if (const auto &reasonElement = element.firstChildElement("reason"); !reasonElement.isNull()) { + if (auto reasonElement = element.firstChildElement("reason"); !reasonElement.isNull()) { d->reason = QXmppJingleReason(); d->reason->parse(reasonElement); } - if (const auto &migratedToElement = element.firstChildElement("migrated"); !migratedToElement.isNull()) { + if (auto migratedToElement = element.firstChildElement("migrated"); !migratedToElement.isNull()) { d->migratedTo = migratedToElement.attribute("to"); } @@ -3158,13 +3213,13 @@ QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleMessageInitiationElement) /// bool QXmppJingleMessageInitiationElement::isJingleMessageInitiationElement(const QDomElement &element) { - return (element.nodeName() == QStringLiteral("propose") || element.nodeName() == QStringLiteral("ringing") || element.nodeName() == QStringLiteral("proceed") || element.nodeName() == QStringLiteral("retract") || element.nodeName() == QStringLiteral("reject") || element.nodeName() == QStringLiteral("finish")) && element.hasAttribute(QStringLiteral("id")) && element.namespaceURI() == ns_jingle_message_initiation; + 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) const +QString QXmppJingleMessageInitiationElement::jmiElementTypeToString(Type type) { switch (type) { case Type::Propose: @@ -3187,7 +3242,7 @@ QString QXmppJingleMessageInitiationElement::jmiElementTypeToString(Type type) c /// /// Takes a string and parses it to a Jingle Message Initiation element type. /// -QXmppJingleMessageInitiationElement::Type QXmppJingleMessageInitiationElement::stringToJmiElementType(const QString &typeStr) const +std::optional QXmppJingleMessageInitiationElement::stringToJmiElementType(const QString &typeStr) { if (typeStr == "propose") { return Type::Propose; @@ -3203,5 +3258,5 @@ QXmppJingleMessageInitiationElement::Type QXmppJingleMessageInitiationElement::s return Type::Finish; } - return Type::None; + return std::nullopt; } diff --git a/src/base/QXmppJingleData.h b/src/base/QXmppJingleData.h index 1e425f4d..82ef8c00 100644 --- a/src/base/QXmppJingleData.h +++ b/src/base/QXmppJingleData.h @@ -463,6 +463,8 @@ public: using RtpSessionState = std::variant; + using Reason = QXmppJingleReason; + /// \internal /// /// The QXmppJingleIq::Content class represents the "content" element of a @@ -492,6 +494,18 @@ public: 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 payloadTypes() const; + void setPayloadTypes(const QList &payloadTypes); +#endif + bool isRtpMultiplexingSupported() const; void setRtpMultiplexingSupported(bool isRtpMultiplexingSupported); @@ -636,11 +650,10 @@ public: /// \endcond static bool isJingleMessageInitiationElement(const QDomElement &); + static QString jmiElementTypeToString(Type type); + static std::optional stringToJmiElementType(const QString &typeStr); private: - QString jmiElementTypeToString(Type type) const; - Type stringToJmiElementType(const QString &typeStr) const; - QSharedDataPointer d; }; diff --git a/src/base/QXmppJingleIq.h b/src/base/QXmppJingleIq.h new file mode 100644 index 00000000..358f7d0b --- /dev/null +++ b/src/base/QXmppJingleIq.h @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2010 Jeremy Lainé +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "QXmppJingleData.h" -- cgit v1.2.3 From 8fc2225897de24f29bc3c36383cf97d5dc89a719 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Tue, 9 May 2023 22:22:54 +0200 Subject: Jingle: Remove namespaceUri attribute --- src/base/QXmppJingleData.cpp | 30 +--------------------- src/base/QXmppJingleData.h | 3 --- tests/qxmppjingledata/tst_qxmppjingledata.cpp | 11 +++----- .../tst_qxmppjinglemessageinitiationmanager.cpp | 21 --------------- 4 files changed, 5 insertions(+), 60 deletions(-) (limited to 'src/base') diff --git a/src/base/QXmppJingleData.cpp b/src/base/QXmppJingleData.cpp index 78cd4017..ec926556 100644 --- a/src/base/QXmppJingleData.cpp +++ b/src/base/QXmppJingleData.cpp @@ -1023,7 +1023,6 @@ public: QString m_text; QXmppJingleReason::Type m_type; QXmppJingleReason::RtpErrorCondition m_rtpErrorCondition; - QString m_namespaceUri; }; QXmppJingleIqReasonPrivate::QXmppJingleIqReasonPrivate() @@ -1104,22 +1103,6 @@ void QXmppJingleReason::setRtpErrorCondition(RtpErrorCondition rtpErrorCondition d->m_rtpErrorCondition = rtpErrorCondition; } -/// -/// Returns the namespace URI of a reason element. -/// -QString QXmppJingleReason::namespaceUri() const -{ - return d->m_namespaceUri; -} - -/// -/// Sets the namespace URI of a reason element. -/// -void QXmppJingleReason::setNamespaceUri(const QString &namespaceUri) -{ - d->m_namespaceUri = namespaceUri; -} - /// \cond void QXmppJingleReason::parse(const QDomElement &element) { @@ -1142,10 +1125,6 @@ void QXmppJingleReason::parse(const QDomElement &element) break; } } - - if (!element.namespaceURI().isEmpty() && !element.parentNode().isNull() && element.parentNode().namespaceURI() != element.namespaceURI()) { - d->m_namespaceUri = element.namespaceURI(); - } } void QXmppJingleReason::toXml(QXmlStreamWriter *writer) const @@ -1155,10 +1134,7 @@ void QXmppJingleReason::toXml(QXmlStreamWriter *writer) const } writer->writeStartElement(QStringLiteral("reason")); - - if (!d->m_namespaceUri.isEmpty()) { - writer->writeDefaultNamespace(d->m_namespaceUri); - } + writer->writeDefaultNamespace(ns_jingle); if (!d->m_text.isEmpty()) { helperToXmlAddTextElement(writer, QStringLiteral("text"), d->m_text); @@ -3090,10 +3066,6 @@ std::optional QXmppJingleMessageInitiationElement::reason() c void QXmppJingleMessageInitiationElement::setReason(std::optional reason) { d->reason = reason; - - if (d->reason) { - d->reason->setNamespaceUri(ns_jingle); - } } /// diff --git a/src/base/QXmppJingleData.h b/src/base/QXmppJingleData.h index 82ef8c00..997eb3c3 100644 --- a/src/base/QXmppJingleData.h +++ b/src/base/QXmppJingleData.h @@ -384,9 +384,6 @@ public: RtpErrorCondition rtpErrorCondition() const; void setRtpErrorCondition(RtpErrorCondition rtpErrorCondition); - QString namespaceUri() const; - void setNamespaceUri(const QString &namespaceUri); - /// \cond void parse(const QDomElement &element); void toXml(QXmlStreamWriter *writer) const; diff --git a/tests/qxmppjingledata/tst_qxmppjingledata.cpp b/tests/qxmppjingledata/tst_qxmppjingledata.cpp index 62187998..9322f242 100644 --- a/tests/qxmppjingledata/tst_qxmppjingledata.cpp +++ b/tests/qxmppjingledata/tst_qxmppjingledata.cpp @@ -1086,7 +1086,7 @@ void tst_QXmppJingleData::testTerminate() "" - "" + "" "" "" "" @@ -1336,7 +1336,7 @@ void tst_QXmppJingleData::testRtpErrorCondition_data() QTest::newRow("NoErrorCondition") << QByteArrayLiteral("" "" - "" + "" "" "" "" @@ -1345,7 +1345,7 @@ void tst_QXmppJingleData::testRtpErrorCondition_data() QTest::newRow("InvalidCrypto") << QByteArrayLiteral("" "" - "" + "" "" "" "" @@ -1355,7 +1355,7 @@ void tst_QXmppJingleData::testRtpErrorCondition_data() QTest::newRow("CryptoRequired") << QByteArrayLiteral("" "" - "" + "" "" "" "" @@ -1632,7 +1632,6 @@ void tst_QXmppJingleData::testJingleMessageInitiationElement() QCOMPARE(rejectElement.id(), QStringLiteral("a73sjjvkla37jfea")); QCOMPARE(rejectElement.reason()->text(), QStringLiteral("Busy")); QCOMPARE(rejectElement.reason()->type(), ReasonType::Busy); - QCOMPARE(rejectElement.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); QCOMPARE(rejectElement.containsTieBreak(), true); serializePacket(rejectElement, rejectXml); @@ -1652,7 +1651,6 @@ void tst_QXmppJingleData::testJingleMessageInitiationElement() QCOMPARE(retractElement.id(), QStringLiteral("a73sjjvkla37jfea")); QCOMPARE(retractElement.reason()->text(), QStringLiteral("Retracted")); QCOMPARE(retractElement.reason()->type(), ReasonType::Cancel); - QCOMPARE(retractElement.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); serializePacket(retractElement, retractXml); // --- Finish --- @@ -1672,7 +1670,6 @@ void tst_QXmppJingleData::testJingleMessageInitiationElement() QCOMPARE(finishElement.id(), QStringLiteral("a73sjjvkla37jfea")); QCOMPARE(finishElement.reason()->text(), QStringLiteral("Success")); QCOMPARE(finishElement.reason()->type(), ReasonType::Success); - QCOMPARE(finishElement.reason()->namespaceUri(), QStringLiteral("urn:xmpp:jingle:1")); QCOMPARE(finishElement.migratedTo(), QStringLiteral("989a46a6-f202-4910-a7c3-83c6ba3f3947")); serializePacket(finishElement, finishXml); } diff --git a/tests/qxmppjinglemessageinitiationmanager/tst_qxmppjinglemessageinitiationmanager.cpp b/tests/qxmppjinglemessageinitiationmanager/tst_qxmppjinglemessageinitiationmanager.cpp index 00b599aa..e09b6cd8 100644 --- a/tests/qxmppjinglemessageinitiationmanager/tst_qxmppjinglemessageinitiationmanager.cpp +++ b/tests/qxmppjinglemessageinitiationmanager/tst_qxmppjinglemessageinitiationmanager.cpp @@ -154,7 +154,6 @@ void tst_QXmppJingleMessageInitiationManager::testReject() QXmppJingleReason reason; reason.setType(QXmppJingleReason::Decline); reason.setText("Declined"); - reason.setNamespaceUri(ns_jingle); connect(&m_logger, &QXmppLogger::message, this, [jmiCallPartnerJid = jmi->callPartnerJid()](QXmppLogger::MessageType type, const QString &text) { if (type == QXmppLogger::SentMessage) { @@ -166,7 +165,6 @@ void tst_QXmppJingleMessageInitiationManager::testReject() QCOMPARE(message.jingleMessageInitiationElement()->type(), JmiType::Reject); QCOMPARE(message.jingleMessageInitiationElement()->reason()->type(), QXmppJingleReason::Decline); QCOMPARE(message.jingleMessageInitiationElement()->reason()->text(), "Declined"); - QCOMPARE(message.jingleMessageInitiationElement()->reason()->namespaceUri(), ns_jingle); QCOMPARE(message.jingleMessageInitiationElement()->containsTieBreak(), true); } } @@ -190,7 +188,6 @@ void tst_QXmppJingleMessageInitiationManager::testRetract() QXmppJingleReason reason; reason.setType(QXmppJingleReason::Gone); reason.setText("Gone"); - reason.setNamespaceUri(ns_jingle); connect(&m_logger, &QXmppLogger::message, this, [jmicallPartnerJid = jmi->callPartnerJid()](QXmppLogger::MessageType type, const QString &text) { if (type == QXmppLogger::SentMessage) { @@ -202,7 +199,6 @@ void tst_QXmppJingleMessageInitiationManager::testRetract() QCOMPARE(message.jingleMessageInitiationElement()->type(), JmiType::Retract); QCOMPARE(message.jingleMessageInitiationElement()->reason()->type(), QXmppJingleReason::Gone); QCOMPARE(message.jingleMessageInitiationElement()->reason()->text(), "Gone"); - QCOMPARE(message.jingleMessageInitiationElement()->reason()->namespaceUri(), ns_jingle); QCOMPARE(message.jingleMessageInitiationElement()->containsTieBreak(), true); } } @@ -226,7 +222,6 @@ void tst_QXmppJingleMessageInitiationManager::testFinish() QXmppJingleReason reason; reason.setType(QXmppJingleReason::Success); reason.setText("Finished"); - reason.setNamespaceUri(ns_jingle); connect(&m_logger, &QXmppLogger::message, this, [jmicallPartnerJid = jmi->callPartnerJid()](QXmppLogger::MessageType type, const QString &text) { if (type == QXmppLogger::SentMessage) { @@ -238,7 +233,6 @@ void tst_QXmppJingleMessageInitiationManager::testFinish() QCOMPARE(message.jingleMessageInitiationElement()->type(), JmiType::Finish); QCOMPARE(message.jingleMessageInitiationElement()->reason()->type(), QXmppJingleReason::Success); QCOMPARE(message.jingleMessageInitiationElement()->reason()->text(), "Finished"); - QCOMPARE(message.jingleMessageInitiationElement()->reason()->namespaceUri(), ns_jingle); QCOMPARE(message.jingleMessageInitiationElement()->migratedTo(), "fecbea35-08d3-404f-9ec7-2b57c566fa74"); } } @@ -347,7 +341,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleNonExistingSessionLowerI QXmppJingleReason reason; reason.setType(QXmppJingleReason::Expired); reason.setText("Tie-Break"); - reason.setNamespaceUri(ns_jingle); // make sure that request with higher ID is being retracted connect(&m_logger, &QXmppLogger::message, this, [jmiWithHigherId, reason](QXmppLogger::MessageType type, const QString &text) { @@ -364,7 +357,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleNonExistingSessionLowerI QVERIFY(jmiElement->reason()); QCOMPARE(jmiElement->reason()->type(), reason.type()); QCOMPARE(jmiElement->reason()->text(), reason.text()); - QCOMPARE(jmiElement->reason()->namespaceUri(), reason.namespaceUri()); SKIP_IF_INTEGRATION_TESTS_DISABLED() @@ -397,7 +389,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleNonExistingSessionHigher QXmppJingleReason reason; reason.setType(QXmppJingleReason::Expired); reason.setText("Tie-Break"); - reason.setNamespaceUri(ns_jingle); auto jmiWithLowerId { m_manager.addJmi("julietNonExistingSession@capulet.example") }; jmiWithLowerId->setId("ca3cf894-5325-482f-a412-a6e9f832298d"); @@ -417,7 +408,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleNonExistingSessionHigher QVERIFY(jmiElement->reason()); QCOMPARE(jmiElement->reason()->type(), reason.type()); QCOMPARE(jmiElement->reason()->text(), reason.text()); - QCOMPARE(jmiElement->reason()->namespaceUri(), reason.namespaceUri()); } } }); @@ -449,7 +439,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleExistingSession() QXmppJingleReason reason; reason.setType(QXmppJingleReason::Expired); reason.setText("Session migrated"); - reason.setNamespaceUri(ns_jingle); connect(&m_logger, &QXmppLogger::message, this, [jmi, reason](QXmppLogger::MessageType type, const QString &text) { if (type == QXmppLogger::SentMessage) { @@ -466,7 +455,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleExistingSession() QVERIFY(jmiElement->reason()); QCOMPARE(jmiElement->reason()->type(), reason.type()); QCOMPARE(jmiElement->reason()->text(), reason.text()); - QCOMPARE(jmiElement->reason()->namespaceUri(), reason.namespaceUri()); } } }); @@ -593,7 +581,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleExistingJmi() QXmppJingleReason reason; reason.setType(QXmppJingleReason::Expired); reason.setText("Rejected because expired."); - reason.setNamespaceUri(ns_jingle); jmiElement.setType(JmiType::Reject); jmiElement.setReason(reason); @@ -607,7 +594,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleExistingJmi() QVERIFY(rejectedJmiElement.reason); QCOMPARE(rejectedJmiElement.reason->type(), jmiElement.reason()->type()); QCOMPARE(rejectedJmiElement.reason->text(), jmiElement.reason()->text()); - QCOMPARE(rejectedJmiElement.reason->namespaceUri(), jmiElement.reason()->namespaceUri()); QCOMPARE(rejectedJmiElement.containsTieBreak, jmiElement.containsTieBreak()); }); @@ -621,7 +607,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleExistingJmi() reason.setType(QXmppJingleReason::ConnectivityError); reason.setText("Retracted due to connectivity error."); - reason.setNamespaceUri(ns_jingle); jmiElement.setType(JmiType::Retract); jmiElement.setReason(reason); @@ -635,7 +620,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleExistingJmi() QVERIFY(rejectedJmiElement.reason); QCOMPARE(rejectedJmiElement.reason->type(), jmiElement.reason()->type()); QCOMPARE(rejectedJmiElement.reason->text(), jmiElement.reason()->text()); - QCOMPARE(rejectedJmiElement.reason->namespaceUri(), jmiElement.reason()->namespaceUri()); QCOMPARE(rejectedJmiElement.containsTieBreak, jmiElement.containsTieBreak()); }); @@ -649,7 +633,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleExistingJmi() reason.setType(QXmppJingleReason::Success); reason.setText("Finished."); - reason.setNamespaceUri(ns_jingle); jmiElement.setType(JmiType::Finish); jmiElement.setReason(reason); @@ -664,7 +647,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleExistingJmi() QVERIFY(rejectedJmiElement.reason); QCOMPARE(rejectedJmiElement.reason->type(), jmiElement.reason()->type()); QCOMPARE(rejectedJmiElement.reason->text(), jmiElement.reason()->text()); - QCOMPARE(rejectedJmiElement.reason->namespaceUri(), jmiElement.reason()->namespaceUri()); QCOMPARE(rejectedJmiElement.migratedTo, jmiElement.migratedTo()); }); @@ -839,7 +821,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleMessageClosedRejected() QCOMPARE(rejectedJmiElement.reason->type(), QXmppJingleReason::Busy); QCOMPARE(rejectedJmiElement.reason->text(), "Busy"); - QCOMPARE(rejectedJmiElement.reason->namespaceUri(), ns_jingle); }); message.parse(xmlToDom(xmlReject)); @@ -874,7 +855,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleMessageClosedRetracted() QCOMPARE(retractedJmiElement.reason->type(), QXmppJingleReason::Cancel); QCOMPARE(retractedJmiElement.reason->text(), "Retracted"); - QCOMPARE(retractedJmiElement.reason->namespaceUri(), ns_jingle); }); message.parse(xmlToDom(xmlRetract)); @@ -910,7 +890,6 @@ void tst_QXmppJingleMessageInitiationManager::testHandleMessageClosedFinished() QCOMPARE(finishedJmiElement.reason->type(), QXmppJingleReason::Success); QCOMPARE(finishedJmiElement.reason->text(), "Success"); - QCOMPARE(finishedJmiElement.reason->namespaceUri(), ns_jingle); QCOMPARE(finishedJmiElement.migratedTo, "989a46a6-f202-4910-a7c3-83c6ba3f3947"); }); -- cgit v1.2.3 From 85006abce021819de6af389d04e88756fac0745a Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Sat, 13 May 2023 17:27:45 +0200 Subject: JingleData: Fix doxygen warnings --- src/base/QXmppJingleData.cpp | 8 -------- src/base/QXmppJingleData.h | 3 +++ 2 files changed, 3 insertions(+), 8 deletions(-) (limited to 'src/base') diff --git a/src/base/QXmppJingleData.cpp b/src/base/QXmppJingleData.cpp index ec926556..ceb31b32 100644 --- a/src/base/QXmppJingleData.cpp +++ b/src/base/QXmppJingleData.cpp @@ -1031,14 +1031,6 @@ QXmppJingleIqReasonPrivate::QXmppJingleIqReasonPrivate() { } -/// -/// \enum QXmppJingleIqReason::RtpErrorCondition -/// -/// Condition of an RTP-specific error -/// -/// \since QXmpp 1.5 -/// - /// /// \class QXmppJingleReason /// diff --git a/src/base/QXmppJingleData.h b/src/base/QXmppJingleData.h index 997eb3c3..e36577be 100644 --- a/src/base/QXmppJingleData.h +++ b/src/base/QXmppJingleData.h @@ -364,6 +364,8 @@ public: UnsupportedTransports }; + /// Condition of an RTP-specific error + /// \since QXmpp 1.5 enum RtpErrorCondition { /// There is no error condition. NoErrorCondition, @@ -460,6 +462,7 @@ public: using RtpSessionState = std::variant; + /// Alias to QXmppJingleReason for compatibility. using Reason = QXmppJingleReason; /// \internal -- cgit v1.2.3