aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelvin Keskin <melvo@olomono.de>2022-09-29 19:25:38 +0200
committerGitHub <noreply@github.com>2022-09-29 19:25:38 +0200
commit61fc5f0a080f27260148f7e34a8650b0e0195809 (patch)
tree920a58deda1d1f76f839f8782336000efe42f911
parent8fb5a45163c29e6ff4747f2ced28a54778904e4e (diff)
downloadqxmpp-61fc5f0a080f27260148f7e34a8650b0e0195809.tar.gz
Implement XEP-0293: Jingle RTP Feedback Negotiation stanza parts (#455)
-rw-r--r--doc/doap.xml9
-rw-r--r--src/base/QXmppConstants.cpp2
-rw-r--r--src/base/QXmppConstants_p.h2
-rw-r--r--src/base/QXmppJingleIq.cpp461
-rw-r--r--src/base/QXmppJingleIq.h86
-rw-r--r--tests/qxmppjingleiq/tst_qxmppjingleiq.cpp324
6 files changed, 883 insertions, 1 deletions
diff --git a/doc/doap.xml b/doc/doap.xml
index 8e5a429a..fc8a1fd7 100644
--- a/doc/doap.xml
+++ b/doc/doap.xml
@@ -409,6 +409,15 @@ SPDX-License-Identifier: CC0-1.0
</implements>
<implements>
<xmpp:SupportedXep>
+ <xmpp:xep rdf:resource='https://xmpp.org/extensions/xep-0293.html'/>
+ <xmpp:status>partial</xmpp:status>
+ <xmpp:version>1.0</xmpp:version>
+ <xmpp:since>1.5</xmpp:since>
+ <xmpp:note>Manager functionality missing</xmpp:note>
+ </xmpp:SupportedXep>
+ </implements>
+ <implements>
+ <xmpp:SupportedXep>
<xmpp:xep rdf:resource='https://xmpp.org/extensions/xep-0300.html'/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0</xmpp:version>
diff --git a/src/base/QXmppConstants.cpp b/src/base/QXmppConstants.cpp
index bdf0a9e3..a98e4754 100644
--- a/src/base/QXmppConstants.cpp
+++ b/src/base/QXmppConstants.cpp
@@ -133,6 +133,8 @@ const char *ns_thumbs = "urn:xmpp:thumbs:1";
const char *ns_muji = "urn:xmpp:jingle:muji:0";
// XEP-0280: Message Carbons
const char *ns_carbons = "urn:xmpp:carbons:2";
+// XEP-0293: Jingle RTP Feedback Negotiation
+const char *ns_jingle_rtp_feedback_negotiation = "urn:xmpp:jingle:apps:rtp:rtcp-fb:0";
// XEP-0297: Stanza Forwarding
const char *ns_forwarding = "urn:xmpp:forward:0";
// XEP-0300: Use of Cryptographic Hash Functions in XMPP
diff --git a/src/base/QXmppConstants_p.h b/src/base/QXmppConstants_p.h
index 5a1e57e7..8db12563 100644
--- a/src/base/QXmppConstants_p.h
+++ b/src/base/QXmppConstants_p.h
@@ -145,6 +145,8 @@ extern const char *ns_thumbs;
extern const char *ns_muji;
// XEP-0280: Message Carbons
extern const char *ns_carbons;
+// XEP-0293: Jingle RTP Feedback Negotiation
+extern const char *ns_jingle_rtp_feedback_negotiation;
// XEP-0297: Stanza Forwarding
extern const char *ns_forwarding;
// XEP-0300: Use of Cryptographic Hash Functions in XMPP
diff --git a/src/base/QXmppJingleIq.cpp b/src/base/QXmppJingleIq.cpp
index 9a365929..af2ca5e3 100644
--- a/src/base/QXmppJingleIq.cpp
+++ b/src/base/QXmppJingleIq.cpp
@@ -121,6 +121,37 @@ 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 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);
+ }
+}
+
class QXmppJingleIqContentPrivate : public QSharedData
{
public:
@@ -146,6 +177,10 @@ public:
QList<QXmppJinglePayloadType> payloadTypes;
QList<QXmppJingleCandidate> transportCandidates;
+
+ // XEP-0293: Jingle RTP Feedback Negotiation
+ QVector<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties;
+ QVector<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals;
};
QXmppJingleIqContentPrivate::QXmppJingleIqContentPrivate()
@@ -319,6 +354,54 @@ void QXmppJingleIq::Content::setTransportPassword(const QString &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 fingerprint hash value for the transport key.
///
/// This is used for DTLS-SRTP as defined in \xep{0320}.
@@ -404,6 +487,9 @@ void QXmppJingleIq::Content::parse(const QDomElement &element)
d->descriptionMedia = descriptionElement.attribute(QStringLiteral("media"));
d->descriptionSsrc = descriptionElement.attribute(QStringLiteral("ssrc")).toULong();
d->isRtpMultiplexingSupported = !descriptionElement.firstChildElement(QStringLiteral("rtcp-mux")).isNull();
+
+ parseJingleRtpFeedbackNegotiationElements(descriptionElement, d->rtpFeedbackProperties, d->rtpFeedbackIntervals);
+
QDomElement child = descriptionElement.firstChildElement(QStringLiteral("payload-type"));
while (!child.isNull()) {
QXmppJinglePayloadType payload;
@@ -424,9 +510,9 @@ void QXmppJingleIq::Content::parse(const QDomElement &element)
d->transportCandidates << candidate;
child = child.nextSiblingElement(QStringLiteral("candidate"));
}
- child = transportElement.firstChildElement(QStringLiteral("fingerprint"));
/// XEP-0320
+ child = transportElement.firstChildElement(QStringLiteral("fingerprint"));
if (!child.isNull()) {
d->transportFingerprint = parseFingerprint(child.text());
d->transportFingerprintHash = child.attribute(QStringLiteral("hash"));
@@ -451,15 +537,21 @@ void QXmppJingleIq::Content::toXml(QXmlStreamWriter *writer) const
writer->writeStartElement(QStringLiteral("description"));
writer->writeDefaultNamespace(d->descriptionType);
helperToXmlAddAttribute(writer, QStringLiteral("media"), d->descriptionMedia);
+
if (d->descriptionSsrc) {
writer->writeAttribute(QStringLiteral("ssrc"), QString::number(d->descriptionSsrc));
}
+
if (d->isRtpMultiplexingSupported) {
writer->writeEmptyElement(QStringLiteral("rtcp-mux"));
}
+
+ jingleRtpFeedbackNegotiationElementsToXml(writer, d->rtpFeedbackProperties, d->rtpFeedbackIntervals);
+
for (const auto &payload : d->payloadTypes) {
payload.toXml(writer);
}
+
writer->writeEndElement();
}
@@ -484,6 +576,7 @@ void QXmppJingleIq::Content::toXml(QXmlStreamWriter *writer) const
}
writer->writeEndElement();
}
+
writer->writeEndElement();
}
@@ -1324,6 +1417,10 @@ public:
QString name;
QMap<QString, QString> parameters;
unsigned int ptime;
+
+ // XEP-0293: Jingle RTP Feedback Negotiation
+ QVector<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties;
+ QVector<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals;
};
QXmppJinglePayloadTypePrivate::QXmppJinglePayloadTypePrivate()
@@ -1465,6 +1562,50 @@ 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)
{
@@ -1483,6 +1624,8 @@ void QXmppJinglePayloadType::parse(const QDomElement &element)
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
@@ -1509,6 +1652,9 @@ void QXmppJinglePayloadType::toXml(QXmlStreamWriter *writer) const
writer->writeAttribute(QStringLiteral("value"), itr.value());
writer->writeEndElement();
}
+
+ jingleRtpFeedbackNegotiationElementsToXml(writer, d->rtpFeedbackProperties, d->rtpFeedbackIntervals);
+
writer->writeEndElement();
}
/// \endcond
@@ -1538,3 +1684,316 @@ bool QXmppJinglePayloadType::operator==(const QXmppJinglePayloadType &other) con
other.d->name.toLower() == d->name.toLower();
}
}
+
+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 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"));
+
+ QVector<QXmppSdpParameter> parameters;
+ for (auto childElement = element.firstChildElement();
+ !childElement.isNull();
+ childElement = childElement.nextSiblingElement()) {
+ if (QXmppSdpParameter::isSdpParameter(childElement)) {
+ QXmppSdpParameter parameter;
+ parameter.parse(childElement);
+ parameters.append(parameter);
+ }
+ }
+ d->parameters = 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()) {
+ for (const auto &parameter : d->parameters) {
+ parameter.toXml(writer);
+ }
+ } 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;
+}
diff --git a/src/base/QXmppJingleIq.h b/src/base/QXmppJingleIq.h
index cbb711d6..61b16461 100644
--- a/src/base/QXmppJingleIq.h
+++ b/src/base/QXmppJingleIq.h
@@ -14,6 +14,80 @@ class QXmppJingleCandidatePrivate;
class QXmppJingleIqContentPrivate;
class QXmppJingleIqPrivate;
class QXmppJinglePayloadTypePrivate;
+class QXmppJingleRtpFeedbackPropertyPrivate;
+class QXmppSdpParameterPrivate;
+
+class QXMPP_EXPORT QXmppSdpParameter
+{
+public:
+ QXmppSdpParameter();
+
+ QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppSdpParameter)
+
+ QString name() const;
+ void setName(const QString &name);
+
+ QString value() const;
+ void setValue(const QString &value);
+
+ /// \cond
+ void parse(const QDomElement &element);
+ void toXml(QXmlStreamWriter *writer) const;
+ /// \endcond
+
+ static bool isSdpParameter(const QDomElement &element);
+
+private:
+ QSharedDataPointer<QXmppSdpParameterPrivate> d;
+};
+
+class QXMPP_EXPORT QXmppJingleRtpFeedbackProperty
+{
+public:
+ QXmppJingleRtpFeedbackProperty();
+
+ QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpFeedbackProperty)
+
+ QString type() const;
+ void setType(const QString &type);
+
+ QString subtype() const;
+ void setSubtype(const QString &subtype);
+
+ QVector<QXmppSdpParameter> parameters() const;
+ void setParameters(const QVector<QXmppSdpParameter> &parameters);
+
+ /// \cond
+ void parse(const QDomElement &element);
+ void toXml(QXmlStreamWriter *writer) const;
+ /// \endcond
+
+ static bool isJingleRtpFeedbackProperty(const QDomElement &element);
+
+private:
+ QSharedDataPointer<QXmppJingleRtpFeedbackPropertyPrivate> d;
+};
+
+class QXMPP_EXPORT QXmppJingleRtpFeedbackInterval
+{
+public:
+ QXmppJingleRtpFeedbackInterval();
+
+ QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpFeedbackInterval)
+
+ uint64_t value() const;
+ void setValue(uint64_t value);
+
+ /// \cond
+ void parse(const QDomElement &element);
+ void toXml(QXmlStreamWriter *writer) const;
+ /// \endcond
+
+ static bool isJingleRtpFeedbackInterval(const QDomElement &element);
+
+private:
+ uint64_t m_value;
+};
///
/// \brief The QXmppJinglePayloadType class represents a payload type
@@ -47,6 +121,12 @@ public:
unsigned int ptime() const;
void setPtime(unsigned int ptime);
+ QVector<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties() const;
+ void setRtpFeedbackProperties(const QVector<QXmppJingleRtpFeedbackProperty> &rtpFeedbackProperties);
+
+ QVector<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals() const;
+ void setRtpFeedbackIntervals(const QVector<QXmppJingleRtpFeedbackInterval> &rtpFeedbackIntervals);
+
/// \cond
void parse(const QDomElement &element);
void toXml(QXmlStreamWriter *writer) const;
@@ -206,6 +286,12 @@ public:
QString transportPassword() const;
void setTransportPassword(const QString &password);
+ QVector<QXmppJingleRtpFeedbackProperty> rtpFeedbackProperties() const;
+ void setRtpFeedbackProperties(const QVector<QXmppJingleRtpFeedbackProperty> &rtpFeedbackProperties);
+
+ QVector<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals() const;
+ void setRtpFeedbackIntervals(const QVector<QXmppJingleRtpFeedbackInterval> &rtpFeedbackIntervals);
+
// XEP-0320: Use of DTLS-SRTP in Jingle Sessions
QByteArray transportFingerprint() const;
void setTransportFingerprint(const QByteArray &fingerprint);
diff --git a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp b/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp
index 8f2e5ab0..7982c93e 100644
--- a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp
+++ b/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp
@@ -13,6 +13,17 @@ class tst_QXmppJingleIq : public QObject
Q_OBJECT
private slots:
+ void testIsSdpParameter_data();
+ void testIsSdpParameter();
+ void testSdpParameter();
+ void testSdpParameterWithoutValue();
+ void testIsRtpFeedbackProperty_data();
+ void testIsRtpFeedbackProperty();
+ void testRtpFeedbackProperty();
+ void testRtpFeedbackPropertyWithParameters();
+ void testIsRtpFeedbackInterval_data();
+ void testIsRtpFeedbackInterval();
+ void testRtpFeedbackInterval();
void testCandidate();
void testContent();
void testContentFingerprint();
@@ -20,13 +31,199 @@ private slots:
void testContentSdpReflexive();
void testContentSdpFingerprint();
void testContentSdpParameters();
+ void testContentRtpFeedbackNegotiation();
void testSession();
void testTerminate();
void testAudioPayloadType();
void testVideoPayloadType();
+ void testPayloadTypeRtpFeedbackNegotiation();
void testRinging();
};
+void tst_QXmppJingleIq::testIsSdpParameter_data()
+{
+ QTest::addColumn<QByteArray>("xml");
+ QTest::addColumn<bool>("isValid");
+
+ QTest::newRow("valid")
+ << QByteArrayLiteral("<parameter name=\"test-name\" value=\"test-value\"/>")
+ << true;
+ QTest::newRow("invalidTag")
+ << QByteArrayLiteral("<invalid name=\"test-name\" value=\"test-value\"/>")
+ << false;
+}
+
+void tst_QXmppJingleIq::testIsSdpParameter()
+{
+ QFETCH(QByteArray, xml);
+ QFETCH(bool, isValid);
+
+ QCOMPARE(QXmppSdpParameter::isSdpParameter(xmlToDom(xml)), isValid);
+}
+
+void tst_QXmppJingleIq::testSdpParameter()
+{
+ const QByteArray xml("<parameter name=\"test-name\" value=\"test-value\"/>");
+
+ 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("<parameter name=\"test-name\"/>");
+
+ 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::testIsRtpFeedbackProperty_data()
+{
+ QTest::addColumn<QByteArray>("xml");
+ QTest::addColumn<bool>("isValid");
+
+ QTest::newRow("valid")
+ << QByteArrayLiteral("<rtcp-fb xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\"/>")
+ << true;
+ QTest::newRow("invalidTag")
+ << QByteArrayLiteral("<invalid xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\"/>")
+ << false;
+ QTest::newRow("invalidNamespace")
+ << QByteArrayLiteral("<rtcp-fb xmlns=\"invalid\"/>")
+ << false;
+}
+
+void tst_QXmppJingleIq::testIsRtpFeedbackProperty()
+{
+ QFETCH(QByteArray, xml);
+ QFETCH(bool, isValid);
+
+ QCOMPARE(QXmppJingleRtpFeedbackProperty::isJingleRtpFeedbackProperty(xmlToDom(xml)), isValid);
+}
+
+void tst_QXmppJingleIq::testRtpFeedbackProperty()
+{
+ const QByteArray xml("<rtcp-fb xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" type=\"nack\" subtype=\"sli\"/>");
+
+ 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"));
+
+ serializePacket(property2, xml);
+}
+
+void tst_QXmppJingleIq::testRtpFeedbackPropertyWithParameters()
+{
+ const QByteArray xml(
+ "<rtcp-fb xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" type=\"test-type\">"
+ "<parameter name=\"test-name-1\"/>"
+ "<parameter name=\"test-name-2\"/>"
+ "</rtcp-fb>");
+
+ 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.type(), QStringLiteral("test-type"));
+ 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<QByteArray>("xml");
+ QTest::addColumn<bool>("isValid");
+
+ QTest::newRow("valid")
+ << QByteArrayLiteral("<rtcp-fb-trr-int xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\"/>")
+ << true;
+ QTest::newRow("invalidTag")
+ << QByteArrayLiteral("<invalid xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\"/>")
+ << false;
+ QTest::newRow("invalidNamespace")
+ << QByteArrayLiteral("<rtcp-fb-trr-int xmlns=\"invalid\"/>")
+ << false;
+}
+
+void tst_QXmppJingleIq::testIsRtpFeedbackInterval()
+{
+ QFETCH(QByteArray, xml);
+ QFETCH(bool, isValid);
+
+ QCOMPARE(QXmppJingleRtpFeedbackInterval::isJingleRtpFeedbackInterval(xmlToDom(xml)), isValid);
+}
+
+void tst_QXmppJingleIq::testRtpFeedbackInterval()
+{
+ const QByteArray xml("<rtcp-fb-trr-int xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" value=\"100\"/>");
+
+ QXmppJingleRtpFeedbackInterval interval1;
+
+ parsePacket(interval1, xml);
+ QCOMPARE(interval1.value(), uint64_t(100));
+
+ serializePacket(interval1, xml);
+
+ QXmppJingleRtpFeedbackInterval interval2;
+ interval2.setValue(100);
+
+ serializePacket(interval2, xml);
+}
+
void tst_QXmppJingleIq::testCandidate()
{
const QByteArray xml(
@@ -371,6 +568,74 @@ void tst_QXmppJingleIq::testContentSdpParameters()
QCOMPARE(content.toSdp(), sdp);
}
+void tst_QXmppJingleIq::testContentRtpFeedbackNegotiation()
+{
+ const QByteArray xml(
+ "<content creator=\"initiator\" name=\"voice\">"
+ "<description xmlns=\"urn:xmpp:jingle:apps:rtp:1\">"
+ "<rtcp-fb xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" type=\"nack\" subtype=\"pli\"/>"
+ "<rtcp-fb xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" type=\"nack\" subtype=\"sli\"/>"
+ "<rtcp-fb-trr-int xmlns='urn:xmpp:jingle:apps:rtp:rtcp-fb:0' value='60'/>"
+ "<rtcp-fb-trr-int xmlns='urn:xmpp:jingle:apps:rtp:rtcp-fb:0' value='80'/>"
+ "<payload-type id=\"96\" name=\"speex\"/>"
+ "</description>"
+ "</content>");
+
+ 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"));
+ content2.addPayloadType(payloadType);
+ 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::testSession()
{
const QByteArray xml(
@@ -462,6 +727,65 @@ void tst_QXmppJingleIq::testVideoPayloadType()
serializePacket(payload, xml);
}
+void tst_QXmppJingleIq::testPayloadTypeRtpFeedbackNegotiation()
+{
+ const QByteArray xml(
+ "<payload-type id=\"96\">"
+ "<rtcp-fb xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" type=\"nack\" subtype=\"pli\"/>"
+ "<rtcp-fb xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" type=\"nack\" subtype=\"sli\"/>"
+ "<rtcp-fb-trr-int xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" value=\"60\"/>"
+ "<rtcp-fb-trr-int xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\" value=\"80\"/>"
+ "</payload-type>");
+
+ 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::testRinging()
{
const QByteArray xml(