aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelvin Keskin <melvo@olomono.de>2022-09-27 22:27:20 +0200
committerLinus Jahn <lnj@kaidan.im>2022-10-01 19:50:31 +0200
commit1814f67c9a41eb750a56fdccd28843b53be17b0d (patch)
tree6b224b53f6fae15609752c4d30c1eb3b155b5efe
parent58646ad17d331f980fa1e03e3831fadea847c2b2 (diff)
downloadqxmpp-1814f67c9a41eb750a56fdccd28843b53be17b0d.tar.gz
Implement XEP-0294: Jingle RTP Header Extensions Negotiation stanzas
-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.cpp263
-rw-r--r--src/base/QXmppJingleIq.h46
-rw-r--r--tests/qxmppjingleiq/tst_qxmppjingleiq.cpp171
6 files changed, 492 insertions, 1 deletions
diff --git a/doc/doap.xml b/doc/doap.xml
index 733fdc65..b3d2c4e0 100644
--- a/doc/doap.xml
+++ b/doc/doap.xml
@@ -420,6 +420,15 @@ SPDX-License-Identifier: CC0-1.0
</implements>
<implements>
<xmpp:SupportedXep>
+ <xmpp:xep rdf:resource='https://xmpp.org/extensions/xep-0294.html'/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>1.1</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 a98e4754..1729d226 100644
--- a/src/base/QXmppConstants.cpp
+++ b/src/base/QXmppConstants.cpp
@@ -135,6 +135,8 @@ const char *ns_muji = "urn:xmpp:jingle:muji:0";
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-0294: Jingle RTP Header Extensions Negotiation
+const char *ns_jingle_rtp_header_extensions_negotiation = "urn:xmpp:jingle:apps:rtp:rtp-hdrext: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 8db12563..1f067b2b 100644
--- a/src/base/QXmppConstants_p.h
+++ b/src/base/QXmppConstants_p.h
@@ -147,6 +147,8 @@ extern const char *ns_muji;
extern const char *ns_carbons;
// XEP-0293: Jingle RTP Feedback Negotiation
extern const char *ns_jingle_rtp_feedback_negotiation;
+// XEP-0294: Jingle RTP Header Extensions Negotiation
+extern const char *ns_jingle_rtp_header_extensions_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 c6ae9ede..ff92993e 100644
--- a/src/base/QXmppJingleIq.cpp
+++ b/src/base/QXmppJingleIq.cpp
@@ -57,6 +57,12 @@ static const char *jingle_reasons[] = {
"unsupported-transports",
};
+static const QStringList JINGLE_RTP_HEADER_EXTENSIONS_SENDERS = {
+ QStringLiteral("both"),
+ QStringLiteral("initiator"),
+ QStringLiteral("responder")
+};
+
static QString formatFingerprint(const QByteArray &digest)
{
QString fingerprint;
@@ -136,7 +142,8 @@ static void parseSdpParameters(const QDomElement &parent, QVector<QXmppSdpParame
}
// Serializes the SDP parameters.
-static void sdpParametersToXml(QXmlStreamWriter *writer, const QVector<QXmppSdpParameter> &parameters) {
+static void sdpParametersToXml(QXmlStreamWriter *writer, const QVector<QXmppSdpParameter> &parameters)
+{
for (const auto &parameter : parameters) {
parameter.toXml(writer);
}
@@ -173,6 +180,37 @@ static void jingleRtpFeedbackNegotiationElementsToXml(QXmlStreamWriter *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:
@@ -202,6 +240,10 @@ public:
// 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()
@@ -481,6 +523,58 @@ void QXmppJingleIq::Content::setRtpFeedbackIntervals(const QVector<QXmppJingleRt
}
///
+/// 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}.
@@ -568,6 +662,7 @@ void QXmppJingleIq::Content::parse(const QDomElement &element)
d->isRtpMultiplexingSupported = !descriptionElement.firstChildElement(QStringLiteral("rtcp-mux")).isNull();
parseJingleRtpFeedbackNegotiationElements(descriptionElement, d->rtpFeedbackProperties, d->rtpFeedbackIntervals);
+ parseJingleRtpHeaderExtensionsNegotiationElements(descriptionElement, d->rtpHeaderExtensionProperties, d->isRtpHeaderExtensionMixingAllowed);
QDomElement child = descriptionElement.firstChildElement(QStringLiteral("payload-type"));
while (!child.isNull()) {
@@ -626,6 +721,7 @@ void QXmppJingleIq::Content::toXml(QXmlStreamWriter *writer) const
}
jingleRtpFeedbackNegotiationElementsToXml(writer, d->rtpFeedbackProperties, d->rtpFeedbackIntervals);
+ jingleRtpHeaderExtensionsNegotiationElementsToXml(writer, d->rtpHeaderExtensionProperties, d->isRtpHeaderExtensionMixingAllowed);
for (const auto &payload : d->payloadTypes) {
payload.toXml(writer);
@@ -2162,3 +2258,168 @@ bool QXmppJingleRtpFeedbackInterval::isJingleRtpFeedbackInterval(const QDomEleme
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;
+}
diff --git a/src/base/QXmppJingleIq.h b/src/base/QXmppJingleIq.h
index 02ecc9a5..8dd3e68f 100644
--- a/src/base/QXmppJingleIq.h
+++ b/src/base/QXmppJingleIq.h
@@ -17,6 +17,7 @@ class QXmppJingleIqContentPrivate;
class QXmppJingleIqPrivate;
class QXmppJinglePayloadTypePrivate;
class QXmppJingleRtpFeedbackPropertyPrivate;
+class QXmppJingleRtpHeaderExtensionPropertyPrivate;
class QXmppSdpParameterPrivate;
class QXMPP_EXPORT QXmppSdpParameter
@@ -91,6 +92,45 @@ private:
uint64_t m_value;
};
+class QXMPP_EXPORT QXmppJingleRtpHeaderExtensionProperty
+{
+public:
+ enum Senders {
+ /// The initiator and the sender are allowed.
+ Both,
+ /// Only the initiator is allowed.
+ Initiator,
+ /// Only the responder is allowed.
+ Responder
+ };
+
+ QXmppJingleRtpHeaderExtensionProperty();
+
+ QXMPP_PRIVATE_DECLARE_RULE_OF_SIX(QXmppJingleRtpHeaderExtensionProperty)
+
+ uint32_t id() const;
+ void setId(uint32_t id);
+
+ QString uri() const;
+ void setUri(const QString &uri);
+
+ Senders senders() const;
+ void setSenders(Senders senders);
+
+ QVector<QXmppSdpParameter> parameters() const;
+ void setParameters(const QVector<QXmppSdpParameter> &parameters);
+
+ /// \cond
+ void parse(const QDomElement &element);
+ void toXml(QXmlStreamWriter *writer) const;
+ /// \endcond
+
+ static bool isJingleRtpHeaderExtensionProperty(const QDomElement &element);
+
+private:
+ QSharedDataPointer<QXmppJingleRtpHeaderExtensionPropertyPrivate> d;
+};
+
///
/// \brief The QXmppJinglePayloadType class represents a payload type
/// as specified by \xep{0167}: Jingle RTP Sessions and RFC 5245.
@@ -330,6 +370,12 @@ public:
QVector<QXmppJingleRtpFeedbackInterval> rtpFeedbackIntervals() const;
void setRtpFeedbackIntervals(const QVector<QXmppJingleRtpFeedbackInterval> &rtpFeedbackIntervals);
+ QVector<QXmppJingleRtpHeaderExtensionProperty> rtpHeaderExtensionProperties() const;
+ void setRtpHeaderExtensionProperties(const QVector<QXmppJingleRtpHeaderExtensionProperty> &rtpHeaderExtensionProperties);
+
+ bool isRtpHeaderExtensionMixingAllowed() const;
+ void setRtpHeaderExtensionMixingAllowed(bool isRtpHeaderExtensionMixingAllowed);
+
// XEP-0320: Use of DTLS-SRTP in Jingle Sessions
QByteArray transportFingerprint() const;
void setTransportFingerprint(const QByteArray &fingerprint);
diff --git a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp b/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp
index be9e3d4c..aaa6822d 100644
--- a/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp
+++ b/tests/qxmppjingleiq/tst_qxmppjingleiq.cpp
@@ -24,6 +24,11 @@ private slots:
void testIsRtpFeedbackInterval_data();
void testIsRtpFeedbackInterval();
void testRtpFeedbackInterval();
+ void testIsRtpHeaderExtensionProperty_data();
+ void testIsRtpHeaderExtensionProperty();
+ void testRtpHeaderExtensionProperty();
+ void testRtpHeaderExtensionPropertyWithSenders();
+ void testRtpHeaderExtensionPropertyWithParameters();
void testCandidate();
void testContent();
void testContentFingerprint();
@@ -32,6 +37,7 @@ private slots:
void testContentSdpFingerprint();
void testContentSdpParameters();
void testContentRtpFeedbackNegotiation();
+ void testContentRtpHeaderExtensionsNegotiation();
void testSession();
void testTerminate();
void testRtpSessionState_data();
@@ -229,6 +235,115 @@ void tst_QXmppJingleIq::testRtpFeedbackInterval()
serializePacket(interval2, xml);
}
+void tst_QXmppJingleIq::testIsRtpHeaderExtensionProperty_data()
+{
+ QTest::addColumn<QByteArray>("xml");
+ QTest::addColumn<bool>("isValid");
+
+ QTest::newRow("valid")
+ << QByteArrayLiteral("<rtp-hdrext xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\"/>")
+ << true;
+ QTest::newRow("invalidTag")
+ << QByteArrayLiteral("<invalid xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\"/>")
+ << false;
+ QTest::newRow("invalidNamespace")
+ << QByteArrayLiteral("<rtp-hdrext xmlns=\"invalid\"/>")
+ << false;
+}
+
+void tst_QXmppJingleIq::testIsRtpHeaderExtensionProperty()
+{
+ QFETCH(QByteArray, xml);
+ QFETCH(bool, isValid);
+
+ QCOMPARE(QXmppJingleRtpHeaderExtensionProperty::isJingleRtpHeaderExtensionProperty(xmlToDom(xml)), isValid);
+}
+
+void tst_QXmppJingleIq::testRtpHeaderExtensionProperty()
+{
+ const QByteArray xml("<rtp-hdrext xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\" id=\"1\" uri=\"urn:ietf:params:rtp-hdrext:toffset\"/>");
+
+ QXmppJingleRtpHeaderExtensionProperty property1;
+ QCOMPARE(property1.id(), 0);
+ QVERIFY(property1.uri().isEmpty());
+ QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Both);
+
+ parsePacket(property1, xml);
+ QCOMPARE(property1.id(), 1);
+ QCOMPARE(property1.uri(), QStringLiteral("urn:ietf:params:rtp-hdrext:toffset"));
+ QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Both);
+
+ serializePacket(property1, xml);
+
+ QXmppJingleRtpHeaderExtensionProperty property2;
+ property2.setId(1);
+ property2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset"));
+ property2.setSenders(QXmppJingleRtpHeaderExtensionProperty::Both);
+
+ QCOMPARE(property1.id(), 1);
+ QCOMPARE(property1.uri(), QStringLiteral("urn:ietf:params:rtp-hdrext:toffset"));
+ QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Both);
+
+ serializePacket(property2, xml);
+}
+
+void tst_QXmppJingleIq::testRtpHeaderExtensionPropertyWithSenders()
+{
+ const QByteArray xml("<rtp-hdrext xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\" id=\"1\" uri=\"urn:ietf:params:rtp-hdrext:toffset\" senders=\"initiator\"/>");
+
+ QXmppJingleRtpHeaderExtensionProperty property1;
+
+ parsePacket(property1, xml);
+ QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Initiator);
+
+ serializePacket(property1, xml);
+
+ QXmppJingleRtpHeaderExtensionProperty property2;
+ property2.setId(1);
+ property2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset"));
+ property2.setSenders(QXmppJingleRtpHeaderExtensionProperty::Initiator);
+
+ QCOMPARE(property1.senders(), QXmppJingleRtpHeaderExtensionProperty::Initiator);
+
+ serializePacket(property2, xml);
+}
+
+void tst_QXmppJingleIq::testRtpHeaderExtensionPropertyWithParameters()
+{
+ const QByteArray xml(
+ "<rtp-hdrext xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\" id=\"1\" uri=\"urn:ietf:params:rtp-hdrext:toffset\">"
+ "<parameter name=\"test-name-1\"/>"
+ "<parameter name=\"test-name-2\"/>"
+ "</rtp-hdrext>");
+
+ QXmppJingleRtpHeaderExtensionProperty property1;
+
+ parsePacket(property1, xml);
+ QCOMPARE(property1.parameters().size(), 2);
+ QCOMPARE(property1.parameters().at(0).name(), QStringLiteral("test-name-1"));
+ QCOMPARE(property1.parameters().at(1).name(), QStringLiteral("test-name-2"));
+
+ serializePacket(property1, xml);
+
+ QXmppJingleRtpHeaderExtensionProperty property2;
+ property2.setId(1);
+ property2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset"));
+
+ QXmppSdpParameter parameter1;
+ parameter1.setName(QStringLiteral("test-name-1"));
+
+ QXmppSdpParameter parameter2;
+ parameter2.setName(QStringLiteral("test-name-2"));
+
+ property2.setParameters({ parameter1, parameter2 });
+
+ QCOMPARE(property2.parameters().size(), 2);
+ QCOMPARE(property2.parameters().at(0).name(), QStringLiteral("test-name-1"));
+ QCOMPARE(property2.parameters().at(1).name(), QStringLiteral("test-name-2"));
+
+ serializePacket(property2, xml);
+}
+
void tst_QXmppJingleIq::testCandidate()
{
const QByteArray xml(
@@ -641,6 +756,62 @@ void tst_QXmppJingleIq::testContentRtpFeedbackNegotiation()
serializePacket(content2, xml);
}
+void tst_QXmppJingleIq::testContentRtpHeaderExtensionsNegotiation()
+{
+ const QByteArray xml(
+ "<content creator=\"initiator\" name=\"voice\">"
+ "<description xmlns=\"urn:xmpp:jingle:apps:rtp:1\">"
+ "<rtp-hdrext xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\" id=\"1\" uri=\"urn:ietf:params:rtp-hdrext:toffset\"/>"
+ "<rtp-hdrext xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\" id=\"2\" uri=\"urn:ietf:params:rtp-hdrext:ntp-64\"/>"
+ "<extmap-allow-mixed xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\"/>"
+ "<payload-type id=\"96\" name=\"speex\"/>"
+ "</description>"
+ "</content>");
+
+ QXmppJingleIq::Content content1;
+ QVERIFY(content1.rtpHeaderExtensionProperties().isEmpty());
+ QVERIFY(!content1.isRtpHeaderExtensionMixingAllowed());
+ parsePacket(content1, xml);
+
+ const auto rtpHeaderExtensionProperties1 = content1.rtpHeaderExtensionProperties();
+ QCOMPARE(rtpHeaderExtensionProperties1.size(), 2);
+ QCOMPARE(rtpHeaderExtensionProperties1[0].id(), 1);
+ QCOMPARE(rtpHeaderExtensionProperties1[1].id(), 2);
+
+ QVERIFY(content1.isRtpHeaderExtensionMixingAllowed());
+
+ serializePacket(content1, xml);
+
+ QXmppJingleRtpHeaderExtensionProperty rtpHeaderExtensionProperty1;
+ rtpHeaderExtensionProperty1.setId(1);
+ rtpHeaderExtensionProperty1.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:toffset"));
+
+ QXmppJingleRtpHeaderExtensionProperty rtpHeaderExtensionProperty2;
+ rtpHeaderExtensionProperty2.setId(2);
+ rtpHeaderExtensionProperty2.setUri(QStringLiteral("urn:ietf:params:rtp-hdrext:ntp-64"));
+
+
+ QXmppJinglePayloadType payloadType;
+ payloadType.setId(96);
+ payloadType.setName(QStringLiteral("speex"));
+
+ QXmppJingleIq::Content content2;
+ content2.setCreator(QStringLiteral("initiator"));
+ content2.setName(QStringLiteral("voice"));
+ content2.addPayloadType(payloadType);
+ content2.setRtpHeaderExtensionProperties({ rtpHeaderExtensionProperty1, rtpHeaderExtensionProperty2 });
+ content2.setRtpHeaderExtensionMixingAllowed(true);
+
+ const auto rtpHeaderExtensionProperties2 = content2.rtpHeaderExtensionProperties();
+ QCOMPARE(rtpHeaderExtensionProperties2.size(), 2);
+ QCOMPARE(rtpHeaderExtensionProperties2[0].id(), 1);
+ QCOMPARE(rtpHeaderExtensionProperties2[1].id(), 2);
+
+ QVERIFY(content2.isRtpHeaderExtensionMixingAllowed());
+
+ serializePacket(content2, xml);
+}
+
void tst_QXmppJingleIq::testSession()
{
const QByteArray xml(