aboutsummaryrefslogtreecommitdiff
path: root/src/base/QXmppJingleData.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/base/QXmppJingleData.cpp')
-rw-r--r--src/base/QXmppJingleData.cpp3226
1 files changed, 3226 insertions, 0 deletions
diff --git a/src/base/QXmppJingleData.cpp b/src/base/QXmppJingleData.cpp
new file mode 100644
index 00000000..ceb31b32
--- /dev/null
+++ b/src/base/QXmppJingleData.cpp
@@ -0,0 +1,3226 @@
+// SPDX-FileCopyrightText: 2010 Jeremy Lainé <jeremy.laine@m4x.org>
+// SPDX-FileCopyrightText: 2022 Melvin Keskin <melvo@olomono.de>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "QXmppJingleData.h"
+
+#include "QXmppConstants_p.h"
+#include "QXmppUtils.h"
+
+#include <QDate>
+#include <QDateTime>
+#include <QDomElement>
+#include <QRegularExpression>
+
+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<QXmppSdpParameter> &parameters)
+{
+ 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<QXmppSdpParameter> &parameters)
+{
+ for (const auto &parameter : 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<QXmppJingleRtpFeedbackProperty> &properties, QVector<QXmppJingleRtpFeedbackInterval> &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<QXmppJingleRtpFeedbackProperty> &properties, const QVector<QXmppJingleRtpFeedbackInterval> &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<QXmppJingleRtpHeaderExtensionProperty> &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<QXmppJingleRtpHeaderExtensionProperty> &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<QXmppJingleCandidate> transportCandidates;
+
+ // XEP-0167: Jingle RTP Sessions
+ std::optional<QXmppJingleRtpEncryption> rtpEncryption;
+
+ // XEP-0293: Jingle RTP Feedback Negotiation
+ QVector<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties;
+ QVector<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals;
+
+ // XEP-0294: Jingle RTP Header Extensions Negotiation
+ QVector<QXmppJingleRtpHeaderExtensionProperty> 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;
+}
+
+/// \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<QXmppJinglePayloadType> QXmppJingleIq::Content::payloadTypes() const
+{
+ return d->description.payloadTypes();
+}
+
+/// \deprecated This method is deprecated since QXmpp 1.6. Use
+/// \c QXmppJingleIq::Conent::description().setPayloadTypes() instead.
+void QXmppJingleIq::Content::setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes)
+{
+ d->description.setPayloadTypes(payloadTypes);
+}
+
+///
+/// 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<QXmppJingleRtpEncryption> 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<QXmppJingleRtpEncryption> &rtpEncryption)
+{
+ d->rtpEncryption = rtpEncryption;
+}
+
+void QXmppJingleIq::Content::addTransportCandidate(const QXmppJingleCandidate &candidate)
+{
+ d->transportType = ns_jingle_ice_udp;
+ d->transportCandidates << candidate;
+}
+
+QList<QXmppJingleCandidate> QXmppJingleIq::Content::transportCandidates() const
+{
+ return d->transportCandidates;
+}
+
+///
+/// Sets a list of transport candidates.
+///
+/// \since QXmpp 0.9.2
+///
+void QXmppJingleIq::Content::setTransportCandidates(const QList<QXmppJingleCandidate> &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<QXmppJingleRtpFeedbackProperty> 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<QXmppJingleRtpFeedbackProperty> &rtpFeedbackProperties)
+{
+ d->rtpFeedbackProperties = rtpFeedbackProperties;
+}
+
+///
+/// Returns the intervals of RTP feedback.
+///
+/// \return the RTP feedback intervals
+///
+/// \since QXmpp 1.5
+///
+QVector<QXmppJingleRtpFeedbackInterval> 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<QXmppJingleRtpFeedbackInterval> &rtpFeedbackIntervals)
+{
+ d->rtpFeedbackIntervals = rtpFeedbackIntervals;
+}
+
+///
+/// Returns the RTP header extension properties.
+///
+/// \return the RTP header extension properties
+///
+/// \since QXmpp 1.5
+///
+QVector<QXmppJingleRtpHeaderExtensionProperty> 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<QXmppJingleRtpHeaderExtensionProperty> &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<QXmppJinglePayloadType> 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<QString, QString> 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<QXmppJingleCandidate> 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<QString, QString> params = payload.parameters();
+ if (payload.name() == QStringLiteral("telephone-event")) {
+ if (params.contains(QStringLiteral("events"))) {
+ paramList << params.value(QStringLiteral("events"));
+ }
+ } else {
+ QMap<QString, QString>::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;
+};
+
+QXmppJingleIqReasonPrivate::QXmppJingleIqReasonPrivate()
+ : m_type(QXmppJingleReason::Type::None),
+ m_rtpErrorCondition(QXmppJingleReason::RtpErrorCondition::NoErrorCondition)
+{
+}
+
+///
+/// \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;
+}
+
+/// \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<Type>(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;
+ }
+ }
+}
+
+void QXmppJingleReason::toXml(QXmlStreamWriter *writer) const
+{
+ if (d->m_type < AlternativeSession || d->m_type > UnsupportedTransports) {
+ return;
+ }
+
+ writer->writeStartElement(QStringLiteral("reason"));
+ writer->writeDefaultNamespace(ns_jingle);
+
+ 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<QXmppJingleIq::Content> contents;
+ QXmppJingleReason reason;
+
+ std::optional<QXmppJingleIq::RtpSessionState> 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::Content> QXmppJingleIq::contents() const
+{
+ return d->contents;
+}
+
+///
+/// Sets the IQ's content elements.
+///
+/// \since QXmpp 0.9.2
+///
+void QXmppJingleIq::setContents(const QList<QXmppJingleIq::Content> &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<RtpSessionStateRinging>(*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> 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> &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<Action>(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<RtpSessionStateActive>(*d->rtpSessionState)) {
+ writeStartElementWithNamespace(QStringLiteral("active"));
+ } else if (std::holds_alternative<RtpSessionStateHold>(*d->rtpSessionState)) {
+ writeStartElementWithNamespace(QStringLiteral("hold"));
+ } else if (std::holds_alternative<RtpSessionStateUnhold>(*d->rtpSessionState)) {
+ writeStartElementWithNamespace(QStringLiteral("unhold"));
+ } else if (auto rtpSessionStateMuting = std::get_if<RtpSessionStateMuting>(&(*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<QString, QString> parameters;
+ unsigned int ptime;
+
+ // XEP-0293: Jingle RTP Feedback Negotiation
+ QVector<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties;
+ QVector<QXmppJingleRtpFeedbackInterval> 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<QString, QString> QXmppJinglePayloadType::parameters() const
+{
+ return d->parameters;
+}
+
+/// Sets the payload parameters.
+
+void QXmppJinglePayloadType::setParameters(const QMap<QString, QString> &parameters)
+{
+ 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<QXmppJingleRtpFeedbackProperty> 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<QXmppJingleRtpFeedbackProperty> &rtpFeedbackProperties)
+{
+ d->rtpFeedbackProperties = rtpFeedbackProperties;
+}
+
+///
+/// Returns the intervals of RTP feedback.
+///
+/// \return the RTP feedback intervals
+///
+QVector<QXmppJingleRtpFeedbackInterval> QXmppJinglePayloadType::rtpFeedbackIntervals() const
+{
+ return d->rtpFeedbackIntervals;
+}
+
+///
+/// Sets the intervals of RTP feedback.
+///
+/// \param rtpFeedbackIntervals RTP feedback intervals
+///
+void QXmppJinglePayloadType::setRtpFeedbackIntervals(const QVector<QXmppJingleRtpFeedbackInterval> &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<QXmppJinglePayloadType> payloadTypes;
+};
+
+///
+/// \class QXmppJingleDescription
+///
+/// \brief The QXmppJingleDescription class represents descriptions for Jingle elements including
+/// media type, streaming source, namespace and payload types.
+///
+/// \since QXmpp 1.6
+///
+
+QXmppJingleDescription::QXmppJingleDescription()
+ : d(new QXmppJingleDescriptionPrivate())
+{
+}
+
+QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleDescription)
+
+///
+/// Returns the media type.
+///
+QString QXmppJingleDescription::media() const
+{
+ return d->media;
+}
+
+///
+/// Sets the media type.
+///
+void QXmppJingleDescription::setMedia(const QString &media)
+{
+ d->media = media;
+}
+
+///
+/// Returns the streaming source.
+///
+quint32 QXmppJingleDescription::ssrc() const
+{
+ return d->ssrc;
+}
+
+///
+/// Sets the streaming source.
+///
+void QXmppJingleDescription::setSsrc(quint32 ssrc)
+{
+ d->ssrc = ssrc;
+}
+
+///
+/// Returns the description namespace.
+///
+QString QXmppJingleDescription::type() const
+{
+ return d->type;
+}
+
+///
+/// Sets the description namespace.
+///
+void QXmppJingleDescription::setType(const QString &type)
+{
+ d->type = type;
+}
+
+///
+/// Adds a payload type to the list of payload types.
+///
+void QXmppJingleDescription::addPayloadType(const QXmppJinglePayloadType &payload)
+{
+ d->type = ns_jingle_rtp;
+ d->payloadTypes.append(payload);
+}
+
+///
+/// Returns a list of payload types.
+///
+const QList<QXmppJinglePayloadType> &QXmppJingleDescription::payloadTypes() const
+{
+ return d->payloadTypes;
+}
+
+///
+/// Sets the list of payload types.
+///
+void QXmppJingleDescription::setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes)
+{
+ d->type = payloadTypes.isEmpty() ? QString() : ns_jingle_rtp;
+ d->payloadTypes = payloadTypes;
+}
+
+/// \cond
+void QXmppJingleDescription::parse(const QDomElement &element)
+{
+ d->type = element.namespaceURI();
+ d->media = element.attribute(QStringLiteral("media"));
+ d->ssrc = element.attribute(QStringLiteral("ssrc")).toULong();
+
+ QDomElement child { element.firstChildElement(QStringLiteral("payload-type")) };
+ while (!child.isNull()) {
+ QXmppJinglePayloadType payload;
+ payload.parse(child);
+ d->payloadTypes.append(payload);
+ child = child.nextSiblingElement(QStringLiteral("payload-type"));
+ }
+}
+
+void QXmppJingleDescription::toXml(QXmlStreamWriter *writer) const
+{
+ writer->writeStartElement(QStringLiteral("description"));
+ writer->writeDefaultNamespace(d->type);
+
+ helperToXmlAddAttribute(writer, QStringLiteral("media"), d->media);
+
+ if (d->ssrc) {
+ writer->writeAttribute(QStringLiteral("ssrc"), QString::number(d->ssrc));
+ }
+
+ for (const auto &payloadType : d->payloadTypes) {
+ payloadType.toXml(writer);
+ }
+
+ writer->writeEndElement();
+}
+/// \endcond
+
+class QXmppSdpParameterPrivate : public QSharedData
+{
+public:
+ 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<QXmppJingleRtpCryptoElement> 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<QXmppJingleRtpCryptoElement> 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<QXmppJingleRtpCryptoElement> &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<QXmppSdpParameter> 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<QXmppSdpParameter> 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<QXmppSdpParameter> &parameters)
+{
+ 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<QXmppSdpParameter> 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<QXmppSdpParameter> 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<QXmppSdpParameter> &parameters)
+{
+ 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<QXmppJingleRtpHeaderExtensionProperty::Senders>(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<QXmppJingleDescription> description;
+ std::optional<QXmppJingleReason> reason;
+ QString migratedTo;
+
+ bool containsTieBreak;
+};
+
+QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleReason)
+
+///
+/// \enum QXmppJingleMessageInitiationElement::Type
+///
+/// Possible types of Jingle Message Initiation elements
+///
+
+///
+/// \class QXmppJingleMessageInitiationElement
+///
+/// \brief The QXmppJingleMessageInitiationElement class represents a Jingle Message Initiation
+/// element as specified by \xep{0353}: Jingle Message Initiation.
+///
+/// \ingroup Stanzas
+///
+/// \since QXmpp 1.6
+///
+
+///
+/// \brief Constructs a Jingle Message Initiation element.
+/// \param type The JMI element type
+///
+QXmppJingleMessageInitiationElement::QXmppJingleMessageInitiationElement()
+ : d(new QXmppJingleMessageInitiationElementPrivate())
+{
+}
+
+///
+/// Returns the Jingle Message Initiation element type
+///
+QXmppJingleMessageInitiationElement::Type QXmppJingleMessageInitiationElement::type() const
+{
+ return d->type;
+}
+
+///
+/// Sets the Jingle Message Initiation element type.
+///
+void QXmppJingleMessageInitiationElement::setType(Type type)
+{
+ d->type = type;
+}
+
+///
+/// Returns the Jingle Message Initiation element id.
+///
+QString QXmppJingleMessageInitiationElement::id() const
+{
+ return d->id;
+}
+
+///
+/// Sets the Jingle Message Initiation element id.
+///
+void QXmppJingleMessageInitiationElement::setId(const QString &id)
+{
+ d->id = id;
+}
+
+///
+/// Returns the Jingle Message Initiation element description.
+///
+std::optional<QXmppJingleDescription> QXmppJingleMessageInitiationElement::description() const
+{
+ return d->description;
+}
+
+///
+/// Sets the Jingle Message Initiation element description.
+///
+void QXmppJingleMessageInitiationElement::setDescription(std::optional<QXmppJingleDescription> description)
+{
+ d->description = description;
+}
+
+///
+/// Returns the Jingle Message Initiation element reason.
+///
+std::optional<QXmppJingleReason> QXmppJingleMessageInitiationElement::reason() const
+{
+ return d->reason;
+}
+
+///
+/// Sets the Jingle Message Initiation element reason.
+///
+void QXmppJingleMessageInitiationElement::setReason(std::optional<QXmppJingleReason> reason)
+{
+ d->reason = reason;
+}
+
+///
+/// Returns true if the Jingle Message Initiation element contains a <tie-break/> tag.
+///
+bool QXmppJingleMessageInitiationElement::containsTieBreak() const
+{
+ return d->containsTieBreak;
+}
+
+///
+/// Sets if the Jingle Message Initiation element contains a <tie-break/> tag.
+///
+void QXmppJingleMessageInitiationElement::setContainsTieBreak(bool containsTieBreak)
+{
+ d->containsTieBreak = containsTieBreak;
+}
+
+///
+/// Returns the Jingle Message Initiation element ID migrated to if the Jingle is being migrated
+/// to a different device.
+///
+QString QXmppJingleMessageInitiationElement::migratedTo() const
+{
+ return d->migratedTo;
+}
+
+///
+/// Sets the Jingle Message Initiation element ID migrated to if the Jingle is being migrated
+/// to a different device.
+///
+void QXmppJingleMessageInitiationElement::setMigratedTo(const QString &migratedTo)
+{
+ d->migratedTo = migratedTo;
+}
+
+/// \cond
+void QXmppJingleMessageInitiationElement::parse(const QDomElement &element)
+{
+ std::optional<Type> type { stringToJmiElementType(element.nodeName()) };
+
+ if (!type.has_value()) {
+ return;
+ }
+
+ d->type = type.value();
+ d->id = element.attribute(QStringLiteral("id"));
+
+ // Type::Proceed and Type::Ringing don't need any parsing aside of the id.
+ switch (d->type) {
+ case Type::Propose: {
+ if (const auto &descriptionElement = element.firstChildElement("description"); !descriptionElement.isNull()) {
+ d->description = QXmppJingleDescription();
+ d->description->parse(descriptionElement);
+ }
+
+ break;
+ }
+ case Type::Reject:
+ case Type::Retract:
+ d->containsTieBreak = !element.firstChildElement("tie-break").isNull();
+
+ if (const auto &reasonElement = element.firstChildElement("reason"); !reasonElement.isNull()) {
+ d->reason = QXmppJingleReason();
+ d->reason->parse(reasonElement);
+ }
+
+ break;
+ case Type::Finish:
+ if (auto reasonElement = element.firstChildElement("reason"); !reasonElement.isNull()) {
+ d->reason = QXmppJingleReason();
+ d->reason->parse(reasonElement);
+ }
+
+ if (auto migratedToElement = element.firstChildElement("migrated"); !migratedToElement.isNull()) {
+ d->migratedTo = migratedToElement.attribute("to");
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+void QXmppJingleMessageInitiationElement::toXml(QXmlStreamWriter *writer) const
+{
+ writer->writeStartElement(jmiElementTypeToString(d->type));
+ writer->writeDefaultNamespace(ns_jingle_message_initiation);
+
+ helperToXmlAddAttribute(writer, QStringLiteral("id"), d->id);
+
+ if (d->description) {
+ d->description->toXml(writer);
+ }
+
+ if (d->reason) {
+ d->reason->toXml(writer);
+ }
+
+ if (d->containsTieBreak) {
+ writer->writeEmptyElement(QStringLiteral("tie-break"));
+ }
+
+ if (!d->migratedTo.isEmpty()) {
+ writer->writeEmptyElement(QStringLiteral("migrated"));
+ helperToXmlAddAttribute(writer, QStringLiteral("to"), d->migratedTo);
+ }
+
+ writer->writeEndElement();
+}
+/// \endcond
+
+QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleMessageInitiationElement)
+
+///
+/// Returns true if passed QDomElement is a Jingle Message Initiation element
+///
+bool QXmppJingleMessageInitiationElement::isJingleMessageInitiationElement(const QDomElement &element)
+{
+ return stringToJmiElementType(element.tagName()).has_value() && element.hasAttribute(QStringLiteral("id")) && element.namespaceURI() == ns_jingle_message_initiation;
+}
+
+///
+/// Takes a Jingle Message Initiation element type and parses it to a string.
+///
+QString QXmppJingleMessageInitiationElement::jmiElementTypeToString(Type type)
+{
+ switch (type) {
+ case Type::Propose:
+ return "propose";
+ case Type::Ringing:
+ return "ringing";
+ case Type::Proceed:
+ return "proceed";
+ case Type::Reject:
+ return "reject";
+ case Type::Retract:
+ return "retract";
+ case Type::Finish:
+ return "finish";
+ default:
+ return {};
+ }
+}
+
+///
+/// Takes a string and parses it to a Jingle Message Initiation element type.
+///
+std::optional<QXmppJingleMessageInitiationElement::Type> QXmppJingleMessageInitiationElement::stringToJmiElementType(const QString &typeStr)
+{
+ if (typeStr == "propose") {
+ return Type::Propose;
+ } else if (typeStr == "ringing") {
+ return Type::Ringing;
+ } else if (typeStr == "proceed") {
+ return Type::Proceed;
+ } else if (typeStr == "reject") {
+ return Type::Reject;
+ } else if (typeStr == "retract") {
+ return Type::Retract;
+ } else if (typeStr == "finish") {
+ return Type::Finish;
+ }
+
+ return std::nullopt;
+}