// SPDX-FileCopyrightText: 2010 Jeremy LainĂ© // SPDX-FileCopyrightText: 2022 Melvin Keskin // // SPDX-License-Identifier: LGPL-2.1-or-later #include "QXmppJingleIq.h" #include "QXmppConstants_p.h" #include "QXmppUtils.h" #include #include #include #include static const int RTP_COMPONENT = 1; static const char *jingle_actions[] = { "content-accept", "content-add", "content-modify", "content-reject", "content-remove", "description-info", "security-info", "session-accept", "session-info", "session-initiate", "session-terminate", "transport-accept", "transport-info", "transport-reject", "transport-replace", }; static const char *jingle_reasons[] = { "", "alternative-session", "busy", "cancel", "connectivity-error", "decline", "expired", "failed-application", "failed-transport", "general-error", "gone", "incompatible-parameters", "media-error", "security-error", "success", "timeout", "unsupported-applications", "unsupported-transports", }; static const QStringList JINGLE_RTP_ERROR_CONDITIONS = { {}, QStringLiteral("invalid-crypto"), QStringLiteral("crypto-required") }; static const QStringList JINGLE_RTP_HEADER_EXTENSIONS_SENDERS = { QStringLiteral("both"), QStringLiteral("initiator"), QStringLiteral("responder") }; static QString formatFingerprint(const QByteArray &digest) { QString fingerprint; const QString hx = digest.toHex().toUpper(); for (int i = 0; i < hx.size(); i += 2) { if (!fingerprint.isEmpty()) { fingerprint += ':'; } fingerprint += hx.mid(i, 2); } return fingerprint; } static QByteArray parseFingerprint(const QString &fingerprint) { QString z = fingerprint; z.replace(':', ""); return QByteArray::fromHex(z.toUtf8()); } static QString addressToSdp(const QHostAddress &host) { return QStringLiteral("IN %1 %2").arg(host.protocol() == QAbstractSocket::IPv6Protocol ? QStringLiteral("IP6") : QStringLiteral("IP4"), host.toString()); } static bool candidateParseSdp(QXmppJingleCandidate *candidate, const QString &sdp) { if (!sdp.startsWith(QStringLiteral("candidate:"))) { return false; } const QStringList bits = sdp.mid(10).split(" "); if (bits.size() < 6) { return false; } candidate->setFoundation(bits[0]); candidate->setComponent(bits[1].toInt()); candidate->setProtocol(bits[2].toLower()); candidate->setPriority(bits[3].toInt()); candidate->setHost(QHostAddress(bits[4])); candidate->setPort(bits[5].toInt()); for (int i = 6; i < bits.size() - 1; i += 2) { if (bits[i] == QStringLiteral("typ")) { bool ok; candidate->setType(QXmppJingleCandidate::typeFromString(bits[i + 1], &ok)); if (!ok) { return false; } } else if (bits[i] == QStringLiteral("generation")) { candidate->setGeneration(bits[i + 1].toInt()); } else { qWarning() << "Candidate SDP contains unknown attribute" << bits[i]; return false; } } return true; } static QString candidateToSdp(const QXmppJingleCandidate &candidate) { return QStringLiteral("candidate:%1 %2 %3 %4 %5 %6 typ %7 generation %8").arg(candidate.foundation(), QString::number(candidate.component()), candidate.protocol(), QString::number(candidate.priority()), candidate.host().toString(), QString::number(candidate.port()), QXmppJingleCandidate::typeToString(candidate.type()), QString::number(candidate.generation())); } // Parses all found SDP parameter elements of parent into parameters. static void parseSdpParameters(const QDomElement &parent, QVector ¶meters) { for (auto childElement = parent.firstChildElement(); !childElement.isNull(); childElement = childElement.nextSiblingElement()) { if (QXmppSdpParameter::isSdpParameter(childElement)) { QXmppSdpParameter parameter; parameter.parse(childElement); parameters.append(parameter); } } } // Serializes the SDP parameters. static void sdpParametersToXml(QXmlStreamWriter *writer, const QVector ¶meters) { for (const auto ¶meter : parameters) { parameter.toXml(writer); } } // Parses all found RTP Feedback Negotiation elements inside of parent into properties and // intervals. static void parseJingleRtpFeedbackNegotiationElements(const QDomElement &parent, QVector &properties, QVector &intervals) { for (auto child = parent.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (QXmppJingleRtpFeedbackProperty::isJingleRtpFeedbackProperty(child)) { QXmppJingleRtpFeedbackProperty property; property.parse(child); properties.append(property); } else if (QXmppJingleRtpFeedbackInterval::isJingleRtpFeedbackInterval(child)) { QXmppJingleRtpFeedbackInterval interval; interval.parse(child); intervals.append(interval); } } } // Serializes the RTP feedback properties and intervals. static void jingleRtpFeedbackNegotiationElementsToXml(QXmlStreamWriter *writer, const QVector &properties, const QVector &intervals) { for (const auto &property : properties) { property.toXml(writer); } for (const auto &interval : intervals) { interval.toXml(writer); } } // Parses all found RTP Header Extensions Negotiation elements inside of parent into properties and // isRtpHeaderExtensionMixingAllowed. static void parseJingleRtpHeaderExtensionsNegotiationElements(const QDomElement &parent, QVector &properties, bool &isRtpHeaderExtensionMixingAllowed) { for (auto child = parent.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (QXmppJingleRtpHeaderExtensionProperty::isJingleRtpHeaderExtensionProperty(child)) { QXmppJingleRtpHeaderExtensionProperty property; property.parse(child); properties.append(property); } else if (child.tagName() == QStringLiteral("extmap-allow-mixed") && child.namespaceURI() == ns_jingle_rtp_header_extensions_negotiation) { isRtpHeaderExtensionMixingAllowed = true; } } } // Serializes the RTP header extension properties and isRtpHeaderExtensionMixingAllowed. static void jingleRtpHeaderExtensionsNegotiationElementsToXml(QXmlStreamWriter *writer, const QVector &properties, bool isRtpHeaderExtensionMixingAllowed) { for (const auto &property : properties) { property.toXml(writer); } if (isRtpHeaderExtensionMixingAllowed) { writer->writeStartElement(QStringLiteral("extmap-allow-mixed")); writer->writeDefaultNamespace(ns_jingle_rtp_header_extensions_negotiation); writer->writeEndElement(); } } class QXmppJingleIqContentPrivate : public QSharedData { public: QXmppJingleIqContentPrivate(); QString creator; QString disposition; QString name; QString senders; QString descriptionMedia; quint32 descriptionSsrc; QString descriptionType; bool isRtpMultiplexingSupported = false; QString transportType; QString transportUser; QString transportPassword; QByteArray transportFingerprint; QString transportFingerprintHash; QString transportFingerprintSetup; QList payloadTypes; QList transportCandidates; // XEP-0167: Jingle RTP Sessions std::optional rtpEncryption; // XEP-0293: Jingle RTP Feedback Negotiation QVector rtpFeedbackProperties; QVector rtpFeedbackIntervals; // XEP-0294: Jingle RTP Header Extensions Negotiation QVector rtpHeaderExtensionProperties; bool isRtpHeaderExtensionMixingAllowed = false; }; QXmppJingleIqContentPrivate::QXmppJingleIqContentPrivate() : descriptionSsrc(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; } QString QXmppJingleIq::Content::descriptionMedia() const { return d->descriptionMedia; } void QXmppJingleIq::Content::setDescriptionMedia(const QString &media) { d->descriptionMedia = media; } /// /// Returns the description's 32-bit synchronization source for the media stream as specified by /// \xep{0167, Jingle RTP Sessions} and RFC 3550. /// /// \since QXmpp 0.9 /// quint32 QXmppJingleIq::Content::descriptionSsrc() const { return d->descriptionSsrc; } /// /// Sets the description's 32-bit synchronization source for the media stream as specified by /// \xep{0167, Jingle RTP Sessions} and RFC 3550. /// /// \since QXmpp 0.9 /// void QXmppJingleIq::Content::setDescriptionSsrc(quint32 ssrc) { d->descriptionSsrc = ssrc; } /// /// Returns whether multiplexing of RTP data and control packets on a single port is supported as /// specified by \xep{0167, Jingle RTP Sessions} and RFC 5761. /// /// \return whether multiplexing of RTP data and control packets is supported /// /// \since QXmpp 1.5 /// bool QXmppJingleIq::Content::isRtpMultiplexingSupported() const { return d->isRtpMultiplexingSupported; } /// /// Sets whether multiplexing of RTP data and control packets on a single port is supported as /// specified by \xep{0167, Jingle RTP Sessions} and RFC 5761. /// /// \param isRtpMultiplexingSupported whether multiplexing of RTP data and control packets is /// supported /// /// \since QXmpp 1.5 /// void QXmppJingleIq::Content::setRtpMultiplexingSupported(bool isRtpMultiplexingSupported) { d->isRtpMultiplexingSupported = isRtpMultiplexingSupported; } /// /// Returns the encryption used for SRTP negotiation as specified by /// \xep{0167, Jingle RTP Sessions}. /// /// \return the RTP encryption via SRTP /// /// \since QXmpp 1.5 /// std::optional QXmppJingleIq::Content::rtpEncryption() const { return d->rtpEncryption; } /// /// Sets the encryption used for SRTP negotiation as specified by \xep{0167, Jingle RTP Sessions}. /// /// \param rtpEncryption RTP encryption via SRTP /// /// \since QXmpp 1.5 /// void QXmppJingleIq::Content::setRtpEncryption(const std::optional &rtpEncryption) { d->rtpEncryption = rtpEncryption; } void QXmppJingleIq::Content::addPayloadType(const QXmppJinglePayloadType &payload) { d->descriptionType = ns_jingle_rtp; d->payloadTypes << payload; } QList QXmppJingleIq::Content::payloadTypes() const { return d->payloadTypes; } void QXmppJingleIq::Content::setPayloadTypes(const QList &payloadTypes) { d->descriptionType = payloadTypes.isEmpty() ? QString() : ns_jingle_rtp; d->payloadTypes = payloadTypes; } void QXmppJingleIq::Content::addTransportCandidate(const QXmppJingleCandidate &candidate) { d->transportType = ns_jingle_ice_udp; d->transportCandidates << candidate; } QList QXmppJingleIq::Content::transportCandidates() const { return d->transportCandidates; } /// /// Sets a list of transport candidates. /// /// \since QXmpp 0.9.2 /// void QXmppJingleIq::Content::setTransportCandidates(const QList &candidates) { d->transportType = candidates.isEmpty() ? QString() : ns_jingle_ice_udp; d->transportCandidates = candidates; } QString QXmppJingleIq::Content::transportUser() const { return d->transportUser; } void QXmppJingleIq::Content::setTransportUser(const QString &user) { d->transportUser = user; } QString QXmppJingleIq::Content::transportPassword() const { return d->transportPassword; } void QXmppJingleIq::Content::setTransportPassword(const QString &password) { d->transportPassword = password; } /// /// Returns the properties of RTP feedback. /// /// \return the RTP feedback properties /// /// \since QXmpp 1.5 /// QVector QXmppJingleIq::Content::rtpFeedbackProperties() const { return d->rtpFeedbackProperties; } /// /// Sets the properties of RTP feedback. /// /// \param rtpFeedbackProperties RTP feedback properties /// /// \since QXmpp 1.5 /// void QXmppJingleIq::Content::setRtpFeedbackProperties(const QVector &rtpFeedbackProperties) { d->rtpFeedbackProperties = rtpFeedbackProperties; } /// /// Returns the intervals of RTP feedback. /// /// \return the RTP feedback intervals /// /// \since QXmpp 1.5 /// QVector QXmppJingleIq::Content::rtpFeedbackIntervals() const { return d->rtpFeedbackIntervals; } /// /// Sets the intervals of RTP feedback. /// /// \param rtpFeedbackIntervals RTP feedback intervals /// /// \since QXmpp 1.5 /// void QXmppJingleIq::Content::setRtpFeedbackIntervals(const QVector &rtpFeedbackIntervals) { d->rtpFeedbackIntervals = rtpFeedbackIntervals; } /// /// Returns the RTP header extension properties. /// /// \return the RTP header extension properties /// /// \since QXmpp 1.5 /// QVector QXmppJingleIq::Content::rtpHeaderExtensionProperties() const { return d->rtpHeaderExtensionProperties; } /// /// Sets the RTP header extension properties. /// /// \param rtpHeaderExtensionProperties RTP header extension properties /// /// \since QXmpp 1.5 /// void QXmppJingleIq::Content::setRtpHeaderExtensionProperties(const QVector &rtpHeaderExtensionProperties) { d->rtpHeaderExtensionProperties = rtpHeaderExtensionProperties; } /// /// Returns whether mixing of RTP header extensions is allowed corresponding to the /// "extmap-allow-mixed" element as specified by /// \xep{0293, Jingle RTP Header Extensions Negotiation}. /// /// \return whether mixing of RTP header extensions is allowed /// /// \since QXmpp 1.5 /// bool QXmppJingleIq::Content::isRtpHeaderExtensionMixingAllowed() const { return d->isRtpHeaderExtensionMixingAllowed; } /// /// Sets whether mixing of RTP header extensions is allowed corresponding to the /// "extmap-allow-mixed" element as specified by /// \xep{0293, Jingle RTP Header Extensions Negotiation}. /// /// \param isAllowed whether mixing of RTP header extensions is allowed /// /// \since QXmpp 1.5 /// void QXmppJingleIq::Content::setRtpHeaderExtensionMixingAllowed(bool isRtpHeaderExtensionMixingAllowed) { d->isRtpHeaderExtensionMixingAllowed = isRtpHeaderExtensionMixingAllowed; } /// /// Returns the fingerprint hash value for the transport key. /// /// This is used for DTLS-SRTP as defined in \xep{0320}. /// /// \since QXmpp 0.9 /// QByteArray QXmppJingleIq::Content::transportFingerprint() const { return d->transportFingerprint; } /// /// Sets the fingerprint hash value for the transport key. /// /// This is used for DTLS-SRTP as defined in \xep{0320}. /// /// \since QXmpp 0.9 /// void QXmppJingleIq::Content::setTransportFingerprint(const QByteArray &fingerprint) { d->transportFingerprint = fingerprint; } /// /// Returns the fingerprint hash algorithm for the transport key. /// /// This is used for DTLS-SRTP as defined in \xep{0320}. /// /// \since QXmpp 0.9 /// QString QXmppJingleIq::Content::transportFingerprintHash() const { return d->transportFingerprintHash; } /// /// Sets the fingerprint hash algorithm for the transport key. /// /// This is used for DTLS-SRTP as defined in \xep{0320}. /// /// \since QXmpp 0.9 /// void QXmppJingleIq::Content::setTransportFingerprintHash(const QString &hash) { d->transportFingerprintHash = hash; } /// /// Returns the setup role for the encrypted transport. /// /// This is used for DTLS-SRTP as defined in \xep{0320}. /// /// \since QXmpp 0.9 /// QString QXmppJingleIq::Content::transportFingerprintSetup() const { return d->transportFingerprintSetup; } /// /// Sets the setup role for the encrypted transport. /// /// This is used for DTLS-SRTP as defined in \xep{0320}. /// /// \since QXmpp 0.9 /// void QXmppJingleIq::Content::setTransportFingerprintSetup(const QString &setup) { d->transportFingerprintSetup = setup; } /// \cond void QXmppJingleIq::Content::parse(const QDomElement &element) { d->creator = element.attribute(QStringLiteral("creator")); d->disposition = element.attribute(QStringLiteral("disposition")); d->name = element.attribute(QStringLiteral("name")); d->senders = element.attribute(QStringLiteral("senders")); // description QDomElement descriptionElement = element.firstChildElement(QStringLiteral("description")); d->descriptionType = descriptionElement.namespaceURI(); d->descriptionMedia = descriptionElement.attribute(QStringLiteral("media")); d->descriptionSsrc = 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->payloadTypes << 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->descriptionType.isEmpty() || !d->payloadTypes.isEmpty()) { 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")); } if (d->rtpEncryption) { d->rtpEncryption->toXml(writer); } jingleRtpFeedbackNegotiationElementsToXml(writer, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); jingleRtpHeaderExtensionsNegotiationElementsToXml(writer, d->rtpHeaderExtensionProperties, d->isRtpHeaderExtensionMixingAllowed); for (const auto &payload : d->payloadTypes) { payload.toXml(writer); } writer->writeEndElement(); } // transport if (!d->transportType.isEmpty() || !d->transportCandidates.isEmpty()) { writer->writeStartElement(QStringLiteral("transport")); writer->writeDefaultNamespace(d->transportType); helperToXmlAddAttribute(writer, QStringLiteral("ufrag"), d->transportUser); helperToXmlAddAttribute(writer, QStringLiteral("pwd"), d->transportPassword); for (const auto &candidate : d->transportCandidates) { candidate.toXml(writer); } // XEP-0320: Use of DTLS-SRTP in Jingle Sessions if (!d->transportFingerprint.isEmpty() && !d->transportFingerprintHash.isEmpty()) { writer->writeStartElement(QStringLiteral("fingerprint")); writer->writeDefaultNamespace(ns_jingle_dtls); writer->writeAttribute(QStringLiteral("hash"), d->transportFingerprintHash); writer->writeAttribute(QStringLiteral("setup"), d->transportFingerprintSetup); writer->writeCharacters(formatFingerprint(d->transportFingerprint)); writer->writeEndElement(); } writer->writeEndElement(); } writer->writeEndElement(); } bool QXmppJingleIq::Content::parseSdp(const QString &sdp) { QList payloads; for (auto &line : sdp.split(QChar(u'\n'))) { if (line.endsWith('\r')) { line.resize(line.size() - 1); } if (line.startsWith(QStringLiteral("a="))) { int idx = line.indexOf(':'); const QString attrName = idx != -1 ? line.mid(2, idx - 2) : line.mid(2); const QString attrValue = idx != -1 ? line.mid(idx + 1) : ""; if (attrName == QStringLiteral("candidate")) { QXmppJingleCandidate candidate; if (!candidateParseSdp(&candidate, line.mid(2))) { qWarning() << "Could not parse candidate" << line; return false; } addTransportCandidate(candidate); } else if (attrName == QStringLiteral("fingerprint")) { const QStringList bits = attrValue.split(' '); if (bits.size() > 1) { d->transportFingerprintHash = bits[0]; d->transportFingerprint = parseFingerprint(bits[1]); } } else if (attrName == QStringLiteral("fmtp")) { int spIdx = attrValue.indexOf(' '); if (spIdx == -1) { qWarning() << "Could not parse payload parameters" << line; return false; } const int id = attrValue.left(spIdx).toInt(); const QString paramStr = attrValue.mid(spIdx + 1); for (auto &payload : payloads) { if (payload.id() == id) { QMap params; if (payload.name() == QStringLiteral("telephone-event")) { params.insert(QStringLiteral("events"), paramStr); } else { thread_local static const auto regex = QRegularExpression(";\\s*"); const auto paramParts = paramStr.split(regex); for (const auto &p : paramParts) { const QStringList bits = p.split('='); if (bits.size() == 2) { params.insert(bits.at(0), bits.at(1)); } } } payload.setParameters(params); } } } else if (attrName == QStringLiteral("rtpmap")) { // payload type map const QStringList bits = attrValue.split(' '); if (bits.size() != 2) { continue; } bool ok = false; const int id = bits[0].toInt(&ok); if (!ok) { continue; } const QStringList args = bits[1].split('/'); for (auto &payload : payloads) { if (payload.id() == id) { payload.setName(args[0]); if (args.size() > 1) { payload.setClockrate(args[1].toInt()); } if (args.size() > 2) { payload.setChannels(args[2].toInt()); } } } } else if (attrName == QStringLiteral("ice-ufrag")) { d->transportUser = attrValue; } else if (attrName == QStringLiteral("ice-pwd")) { d->transportPassword = attrValue; } else if (attrName == QStringLiteral("setup")) { d->transportFingerprintSetup = attrValue; } else if (attrName == QStringLiteral("ssrc")) { const QStringList bits = attrValue.split(' '); if (bits.isEmpty()) { qWarning() << "Could not parse ssrc" << line; return false; } d->descriptionSsrc = 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->descriptionMedia = 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; } } } setPayloadTypes(payloads); return true; } static bool candidateLessThan(const QXmppJingleCandidate &c1, const QXmppJingleCandidate &c2) { if (c1.type() == c2.type()) { return c1.priority() > c2.priority(); } else { return c1.type() == QXmppJingleCandidate::ServerReflexiveType; } } QString QXmppJingleIq::Content::toSdp() const { // get default candidate QHostAddress localRtpAddress = QHostAddress::Any; quint16 localRtpPort = 0; QList sortedCandidates = d->transportCandidates; std::sort(sortedCandidates.begin(), sortedCandidates.end(), candidateLessThan); for (const auto &candidate : sortedCandidates) { if (candidate.component() == RTP_COMPONENT) { localRtpAddress = candidate.host(); localRtpPort = candidate.port(); break; } } QStringList sdp; // media QString payloads; QStringList attrs; for (const QXmppJinglePayloadType &payload : d->payloadTypes) { payloads += " " + QString::number(payload.id()); QString rtpmap = QString::number(payload.id()) + " " + payload.name() + "/" + QString::number(payload.clockrate()); if (payload.channels() > 1) { rtpmap += "/" + QString::number(payload.channels()); } attrs << "a=rtpmap:" + rtpmap; // payload parameters QStringList paramList; const QMap params = payload.parameters(); if (payload.name() == QStringLiteral("telephone-event")) { if (params.contains(QStringLiteral("events"))) { paramList << params.value(QStringLiteral("events")); } } else { QMap::const_iterator i; for (i = params.begin(); i != params.end(); ++i) { paramList << i.key() + QStringLiteral("=") + i.value(); } } if (!paramList.isEmpty()) { attrs << QStringLiteral("a=fmtp:") + QByteArray::number(payload.id()) + QStringLiteral(" ") + paramList.join("; "); } } sdp << QStringLiteral("m=%1 %2 RTP/AVP%3").arg(d->descriptionMedia, 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 /// /// \enum QXmppJingleIq::Reason::RtpErrorCondition /// /// Condition of an RTP-specific error /// /// \since QXmpp 1.5 /// QXmppJingleIq::Reason::Reason() : m_type(None) { } /// Returns the reason's textual description. QString QXmppJingleIq::Reason::text() const { return m_text; } /// Sets the reason's textual description. void QXmppJingleIq::Reason::setText(const QString &text) { m_text = text; } /// Gets the reason's type. QXmppJingleIq::Reason::Type QXmppJingleIq::Reason::type() const { return m_type; } /// Sets the reason's type. void QXmppJingleIq::Reason::setType(QXmppJingleIq::Reason::Type type) { 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 /// QXmppJingleIq::Reason::RtpErrorCondition QXmppJingleIq::Reason::rtpErrorCondition() const { return 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 QXmppJingleIq::Reason::setRtpErrorCondition(RtpErrorCondition rtpErrorCondition) { m_rtpErrorCondition = rtpErrorCondition; } /// \cond void QXmppJingleIq::Reason::parse(const QDomElement &element) { m_text = element.firstChildElement(QStringLiteral("text")).text(); for (int i = AlternativeSession; i <= UnsupportedTransports; i++) { if (!element.firstChildElement(jingle_reasons[i]).isNull()) { m_type = static_cast(i); 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) { m_rtpErrorCondition = RtpErrorCondition(index); } break; } } } void QXmppJingleIq::Reason::toXml(QXmlStreamWriter *writer) const { if (m_type < AlternativeSession || m_type > UnsupportedTransports) { return; } writer->writeStartElement(QStringLiteral("reason")); if (!m_text.isEmpty()) { helperToXmlAddTextElement(writer, QStringLiteral("text"), m_text); } writer->writeEmptyElement(jingle_reasons[m_type]); if (m_rtpErrorCondition != NoErrorCondition) { writer->writeStartElement(JINGLE_RTP_ERROR_CONDITIONS.at(m_rtpErrorCondition)); writer->writeDefaultNamespace(ns_jingle_rtp_errors); writer->writeEndElement(); } writer->writeEndElement(); } /// \endcond class QXmppJingleIqPrivate : public QSharedData { public: QXmppJingleIqPrivate(); QXmppJingleIq::Action action; QString initiator; QString responder; QString sid; QString mujiGroupChatJid; QList contents; QXmppJingleIq::Reason reason; std::optional rtpSessionState; }; QXmppJingleIqPrivate::QXmppJingleIqPrivate() : action(QXmppJingleIq::ContentAccept) { } /// Constructs a QXmppJingleIq. QXmppJingleIq::QXmppJingleIq() : d(new QXmppJingleIqPrivate()) { } /// Copy-constructor. QXmppJingleIq::QXmppJingleIq(const QXmppJingleIq &) = default; /// Move-constructor. QXmppJingleIq::QXmppJingleIq(QXmppJingleIq &&) = default; QXmppJingleIq::~QXmppJingleIq() = default; /// Assignment operator. QXmppJingleIq &QXmppJingleIq::operator=(const QXmppJingleIq &) = default; /// Move-assignment operator. QXmppJingleIq &QXmppJingleIq::operator=(QXmppJingleIq &&) = default; /// /// Returns the Jingle IQ's action. /// QXmppJingleIq::Action QXmppJingleIq::action() const { return d->action; } /// Sets the Jingle IQ's action. /// /// \param action void QXmppJingleIq::setAction(QXmppJingleIq::Action action) { d->action = action; } /// /// Adds an element to the IQ's content elements. /// /// \since QXmpp 0.9.2 /// void QXmppJingleIq::addContent(const QXmppJingleIq::Content &content) { d->contents << content; } /// /// Returns the IQ's content elements. /// /// \since QXmpp 0.9.2 /// QList QXmppJingleIq::contents() const { return d->contents; } /// /// Sets the IQ's content elements. /// /// \since QXmpp 0.9.2 /// void QXmppJingleIq::setContents(const QList &contents) { d->contents = contents; } /// Returns the session initiator. QString QXmppJingleIq::initiator() const { return d->initiator; } /// Sets the session initiator. /// /// \param initiator void QXmppJingleIq::setInitiator(const QString &initiator) { d->initiator = initiator; } /// Returns a reference to the IQ's reason element. QXmppJingleIq::Reason &QXmppJingleIq::reason() { return d->reason; } /// Returns a const reference to the IQ's reason element. const QXmppJingleIq::Reason &QXmppJingleIq::reason() const { return d->reason; } /// Returns the session responder. QString QXmppJingleIq::responder() const { return d->responder; } /// Sets the session responder. /// /// \param responder void QXmppJingleIq::setResponder(const QString &responder) { d->responder = responder; } /// /// Returns true if the call is ringing. /// /// \deprecated This method is deprecated since QXmpp 1.5. Use \c QXmppJingleIq::rtpSessionState() /// instead. /// bool QXmppJingleIq::ringing() const { if (d->rtpSessionState) { return std::holds_alternative(*d->rtpSessionState); } return false; } /// /// Set to true if the call is ringing. /// /// \param ringing /// /// \deprecated This method is deprecated since QXmpp 1.5. Use /// \c QXmppJingleIq::setRtpSessionState() instead. /// void QXmppJingleIq::setRinging(bool ringing) { if (ringing) { d->rtpSessionState = RtpSessionStateRinging(); } else { d->rtpSessionState = std::nullopt; } } /// Returns the session ID. QString QXmppJingleIq::sid() const { return d->sid; } /// Sets the session ID. /// /// \param sid void QXmppJingleIq::setSid(const QString &sid) { d->sid = sid; } /// /// Returns the JID of the \xep{0272, Multiparty Jingle (Muji)} group chat. /// /// \return the Muji group chat JID /// /// \since QXmpp 1.5 /// QString QXmppJingleIq::mujiGroupChatJid() const { return d->mujiGroupChatJid; } /// /// Sets the JID of the \xep{0272, Multiparty Jingle (Muji)} group chat. /// /// \param mujiGroupChatJid Muji group chat JID /// /// \since QXmpp 1.5 /// void QXmppJingleIq::setMujiGroupChatJid(const QString &mujiGroupChatJid) { d->mujiGroupChatJid = mujiGroupChatJid; } /// /// Returns the state of an RTP session as specified by \xep{0167, Jingle RTP Sessions} /// Informational Messages. /// /// \return the session's state /// /// \since QXmpp 1.5 /// std::optional QXmppJingleIq::rtpSessionState() const { return d->rtpSessionState; } /// /// Sets the state of an RTP session as specified by \xep{0167, Jingle RTP Sessions} Informational /// Messages. /// /// The appropriate action is set as well. /// Thus, it is not needed to set it manually. /// /// \param rtpSessionState session's state /// /// \since QXmpp 1.5 /// void QXmppJingleIq::setRtpSessionState(const std::optional &rtpSessionState) { d->rtpSessionState = rtpSessionState; d->action = Action::SessionInfo; } /// \cond bool QXmppJingleIq::isJingleIq(const QDomElement &element) { QDomElement jingleElement = element.firstChildElement(QStringLiteral("jingle")); return (jingleElement.namespaceURI() == ns_jingle); } void QXmppJingleIq::parseElementFromChild(const QDomElement &element) { QDomElement jingleElement = element.firstChildElement(QStringLiteral("jingle")); const QString action = jingleElement.attribute(QStringLiteral("action")); for (int i = ContentAccept; i <= TransportReplace; i++) { if (action == jingle_actions[i]) { d->action = static_cast(i); break; } } d->initiator = jingleElement.attribute(QStringLiteral("initiator")); d->responder = jingleElement.attribute(QStringLiteral("responder")); d->sid = jingleElement.attribute(QStringLiteral("sid")); // XEP-0272: Multiparty Jingle (Muji) if (const auto mujiGroupChatElement = jingleElement.firstChildElement(QStringLiteral("muji")); mujiGroupChatElement.namespaceURI() == ns_muji) { d->mujiGroupChatJid = mujiGroupChatElement.attribute(QStringLiteral("room")); } // content d->contents.clear(); QDomElement contentElement = jingleElement.firstChildElement(QStringLiteral("content")); while (!contentElement.isNull()) { Content content; content.parse(contentElement); addContent(content); contentElement = contentElement.nextSiblingElement(QStringLiteral("content")); } QDomElement reasonElement = jingleElement.firstChildElement(QStringLiteral("reason")); d->reason.parse(reasonElement); for (auto childElement = jingleElement.firstChildElement(); !childElement.isNull(); childElement = childElement.nextSiblingElement()) { if (childElement.namespaceURI() == ns_jingle_rtp_info) { const auto elementTag = childElement.tagName(); if (elementTag == QStringLiteral("active")) { d->rtpSessionState = RtpSessionStateActive(); } else if (elementTag == QStringLiteral("hold")) { d->rtpSessionState = RtpSessionStateHold(); } else if (elementTag == QStringLiteral("unhold")) { d->rtpSessionState = RtpSessionStateUnhold(); } else if (const auto isMute = elementTag == QStringLiteral("mute"); isMute || elementTag == QStringLiteral("unmute")) { RtpSessionStateMuting muting; muting.isMute = isMute; if (const auto creator = childElement.attribute(QStringLiteral("creator")); creator == QStringLiteral("initiator")) { muting.creator = Initiator; } else if (creator == QStringLiteral("responder")) { muting.creator = Responder; } muting.name = childElement.attribute(QStringLiteral("name")); d->rtpSessionState = muting; } else if (elementTag == QStringLiteral("ringing")) { d->rtpSessionState = RtpSessionStateRinging(); } } } } void QXmppJingleIq::toXmlElementFromChild(QXmlStreamWriter *writer) const { writer->writeStartElement(QStringLiteral("jingle")); writer->writeDefaultNamespace(ns_jingle); helperToXmlAddAttribute(writer, QStringLiteral("action"), jingle_actions[d->action]); helperToXmlAddAttribute(writer, QStringLiteral("initiator"), d->initiator); helperToXmlAddAttribute(writer, QStringLiteral("responder"), d->responder); helperToXmlAddAttribute(writer, QStringLiteral("sid"), d->sid); // XEP-0272: Multiparty Jingle (Muji) if (!d->mujiGroupChatJid.isEmpty()) { writer->writeStartElement(QStringLiteral("muji")); writer->writeDefaultNamespace(ns_muji); helperToXmlAddAttribute(writer, QStringLiteral("room"), d->mujiGroupChatJid); writer->writeEndElement(); } for (const auto &content : d->contents) { content.toXml(writer); } d->reason.toXml(writer); const auto writeStartElementWithNamespace = [=](const QString &tagName) { writer->writeStartElement(tagName); writer->writeDefaultNamespace(ns_jingle_rtp_info); }; if (d->rtpSessionState) { if (std::holds_alternative(*d->rtpSessionState)) { writeStartElementWithNamespace(QStringLiteral("active")); } else if (std::holds_alternative(*d->rtpSessionState)) { writeStartElementWithNamespace(QStringLiteral("hold")); } else if (std::holds_alternative(*d->rtpSessionState)) { writeStartElementWithNamespace(QStringLiteral("unhold")); } else if (auto rtpSessionStateMuting = std::get_if(&(*d->rtpSessionState))) { if (rtpSessionStateMuting->isMute) { writeStartElementWithNamespace(QStringLiteral("mute")); } else { writeStartElementWithNamespace(QStringLiteral("unmute")); } if (rtpSessionStateMuting->creator == Initiator) { helperToXmlAddAttribute(writer, QStringLiteral("creator"), QStringLiteral("initiator")); } else if (rtpSessionStateMuting->creator == Responder) { helperToXmlAddAttribute(writer, QStringLiteral("creator"), QStringLiteral("responder")); } helperToXmlAddAttribute(writer, QStringLiteral("name"), rtpSessionStateMuting->name); } else { writeStartElementWithNamespace(QStringLiteral("ringing")); } writer->writeEndElement(); } writer->writeEndElement(); } /// \endcond class QXmppJingleCandidatePrivate : public QSharedData { public: QXmppJingleCandidatePrivate(); int component; QString foundation; int generation; QHostAddress host; QString id; int network; quint16 port; QString protocol; int priority; QXmppJingleCandidate::Type type; }; QXmppJingleCandidatePrivate::QXmppJingleCandidatePrivate() : component(0), generation(0), network(0), port(0), priority(0), type(QXmppJingleCandidate::HostType) { } /// /// Constructs an empty candidate. /// QXmppJingleCandidate::QXmppJingleCandidate() : d(new QXmppJingleCandidatePrivate()) { } /// Copy-constructor. QXmppJingleCandidate::QXmppJingleCandidate(const QXmppJingleCandidate &other) = default; /// Move-constructor. QXmppJingleCandidate::QXmppJingleCandidate(QXmppJingleCandidate &&) = default; QXmppJingleCandidate::~QXmppJingleCandidate() = default; /// Assignment operator. QXmppJingleCandidate &QXmppJingleCandidate::operator=(const QXmppJingleCandidate &other) = default; /// Move-assignment operator. QXmppJingleCandidate &QXmppJingleCandidate::operator=(QXmppJingleCandidate &&) = default; /// Returns the candidate's component ID. int QXmppJingleCandidate::component() const { return d->component; } /// Sets the candidates's component ID. /// /// \param component void QXmppJingleCandidate::setComponent(int component) { d->component = component; } /// /// Returns the candidate's foundation. /// /// \since QXmpp 0.9 /// QString QXmppJingleCandidate::foundation() const { return d->foundation; } /// /// Sets the candidate's foundation. /// /// \param foundation /// /// \since QXmpp 0.9 /// void QXmppJingleCandidate::setFoundation(const QString &foundation) { d->foundation = foundation; } /// /// Returns the candidate's generation. /// /// \since QXmpp 0.9 /// int QXmppJingleCandidate::generation() const { return d->generation; } /// /// Sets the candidate's generation. /// /// \param generation /// /// \since QXmpp 0.9 /// void QXmppJingleCandidate::setGeneration(int generation) { d->generation = generation; } /// Returns the candidate's host address. /// QHostAddress QXmppJingleCandidate::host() const { return d->host; } /// Sets the candidate's host address. /// /// \param host void QXmppJingleCandidate::setHost(const QHostAddress &host) { d->host = host; } /// Returns the candidate's unique identifier. /// QString QXmppJingleCandidate::id() const { return d->id; } /// Sets the candidate's unique identifier. /// /// \param id void QXmppJingleCandidate::setId(const QString &id) { d->id = id; } /// Returns the network index (starting at 0) the candidate is on. /// int QXmppJingleCandidate::network() const { return d->network; } /// Sets the network index (starting at 0) the candidate is on. /// /// \param network void QXmppJingleCandidate::setNetwork(int network) { d->network = network; } /// Returns the candidate's port number. /// quint16 QXmppJingleCandidate::port() const { return d->port; } /// Sets the candidate's port number. /// /// \param port void QXmppJingleCandidate::setPort(quint16 port) { d->port = port; } /// Returns the candidate's priority. /// int QXmppJingleCandidate::priority() const { return d->priority; } /// Sets the candidate's priority. /// /// \param priority void QXmppJingleCandidate::setPriority(int priority) { d->priority = priority; } /// Returns the candidate's protocol (e.g. "udp"). /// QString QXmppJingleCandidate::protocol() const { return d->protocol; } /// Sets the candidate's protocol (e.g. "udp"). /// /// \param protocol void QXmppJingleCandidate::setProtocol(const QString &protocol) { d->protocol = protocol; } /// Returns the candidate type (e.g. "host"). /// QXmppJingleCandidate::Type QXmppJingleCandidate::type() const { return d->type; } /// Sets the candidate type (e.g. "host"). /// /// \param type void QXmppJingleCandidate::setType(QXmppJingleCandidate::Type type) { d->type = type; } /// Returns true if the host address or port are empty. /// bool QXmppJingleCandidate::isNull() const { return d->host.isNull() || !d->port; } /// \cond void QXmppJingleCandidate::parse(const QDomElement &element) { d->component = element.attribute(QStringLiteral("component")).toInt(); d->foundation = element.attribute(QStringLiteral("foundation")); d->generation = element.attribute(QStringLiteral("generation")).toInt(); d->host = QHostAddress(element.attribute(QStringLiteral("ip"))); d->id = element.attribute(QStringLiteral("id")); d->network = element.attribute(QStringLiteral("network")).toInt(); d->port = element.attribute(QStringLiteral("port")).toInt(); d->priority = element.attribute(QStringLiteral("priority")).toInt(); d->protocol = element.attribute(QStringLiteral("protocol")); d->type = typeFromString(element.attribute(QStringLiteral("type"))); } void QXmppJingleCandidate::toXml(QXmlStreamWriter *writer) const { writer->writeStartElement(QStringLiteral("candidate")); helperToXmlAddAttribute(writer, QStringLiteral("component"), QString::number(d->component)); helperToXmlAddAttribute(writer, QStringLiteral("foundation"), d->foundation); helperToXmlAddAttribute(writer, QStringLiteral("generation"), QString::number(d->generation)); helperToXmlAddAttribute(writer, QStringLiteral("id"), d->id); helperToXmlAddAttribute(writer, QStringLiteral("ip"), d->host.toString()); helperToXmlAddAttribute(writer, QStringLiteral("network"), QString::number(d->network)); helperToXmlAddAttribute(writer, QStringLiteral("port"), QString::number(d->port)); helperToXmlAddAttribute(writer, QStringLiteral("priority"), QString::number(d->priority)); helperToXmlAddAttribute(writer, QStringLiteral("protocol"), d->protocol); helperToXmlAddAttribute(writer, QStringLiteral("type"), typeToString(d->type)); writer->writeEndElement(); } QXmppJingleCandidate::Type QXmppJingleCandidate::typeFromString(const QString &typeStr, bool *ok) { QXmppJingleCandidate::Type type; if (typeStr == QStringLiteral("host")) { type = HostType; } else if (typeStr == QStringLiteral("prflx")) { type = PeerReflexiveType; } else if (typeStr == QStringLiteral("srflx")) { type = ServerReflexiveType; } else if (typeStr == QStringLiteral("relay")) { type = RelayedType; } else { qWarning() << "Unknown candidate type" << typeStr; if (ok) { *ok = false; } return HostType; } if (ok) { *ok = true; } return type; } QString QXmppJingleCandidate::typeToString(QXmppJingleCandidate::Type type) { QString typeStr; switch (type) { case HostType: typeStr = QStringLiteral("host"); break; case PeerReflexiveType: typeStr = QStringLiteral("prflx"); break; case ServerReflexiveType: typeStr = QStringLiteral("srflx"); break; case RelayedType: typeStr = QStringLiteral("relay"); break; } return typeStr; } /// \endcond class QXmppJinglePayloadTypePrivate : public QSharedData { public: QXmppJinglePayloadTypePrivate(); unsigned char channels; unsigned int clockrate; unsigned char id; unsigned int maxptime; QString name; QMap parameters; unsigned int ptime; // XEP-0293: Jingle RTP Feedback Negotiation QVector rtpFeedbackProperties; QVector rtpFeedbackIntervals; }; QXmppJinglePayloadTypePrivate::QXmppJinglePayloadTypePrivate() : channels(1), clockrate(0), id(0), maxptime(0), ptime(0) { } QXmppJinglePayloadType::QXmppJinglePayloadType() : d(new QXmppJinglePayloadTypePrivate()) { } /// Constructs a copy of other. /// /// \param other QXmppJinglePayloadType::QXmppJinglePayloadType(const QXmppJinglePayloadType &other) : d(other.d) { } QXmppJinglePayloadType::~QXmppJinglePayloadType() { } /// Returns the number of channels (e.g. 1 for mono, 2 for stereo). /// unsigned char QXmppJinglePayloadType::channels() const { return d->channels; } /// Sets the number of channels (e.g. 1 for mono, 2 for stereo). /// /// \param channels void QXmppJinglePayloadType::setChannels(unsigned char channels) { d->channels = channels; } /// Returns the clockrate in Hz, i.e. the number of samples per second. /// unsigned int QXmppJinglePayloadType::clockrate() const { return d->clockrate; } /// Sets the clockrate in Hz, i.e. the number of samples per second. /// /// \param clockrate void QXmppJinglePayloadType::setClockrate(unsigned int clockrate) { d->clockrate = clockrate; } /// Returns the payload type identifier. /// unsigned char QXmppJinglePayloadType::id() const { return d->id; } /// Sets the payload type identifier. /// void QXmppJinglePayloadType::setId(unsigned char id) { Q_ASSERT(id <= 127); d->id = id; } /// Returns the maximum packet time in milliseconds. /// unsigned int QXmppJinglePayloadType::maxptime() const { return d->maxptime; } /// Sets the maximum packet type in milliseconds. /// /// \param maxptime void QXmppJinglePayloadType::setMaxptime(unsigned int maxptime) { d->maxptime = maxptime; } /// Returns the payload type name. /// QString QXmppJinglePayloadType::name() const { return d->name; } /// Sets the payload type name. /// /// \param name void QXmppJinglePayloadType::setName(const QString &name) { d->name = name; } /// Returns the payload parameters. QMap QXmppJinglePayloadType::parameters() const { return d->parameters; } /// Sets the payload parameters. void QXmppJinglePayloadType::setParameters(const QMap ¶meters) { d->parameters = parameters; } /// Returns the packet time in milliseconds (20 by default). /// unsigned int QXmppJinglePayloadType::ptime() const { return d->ptime ? d->ptime : 20; } /// Sets the packet time in milliseconds (20 by default). /// /// \param ptime void QXmppJinglePayloadType::setPtime(unsigned int ptime) { d->ptime = ptime; } /// /// Returns the properties of RTP feedback. /// /// \return the RTP feedback properties /// /// \since QXmpp 1.5 /// QVector QXmppJinglePayloadType::rtpFeedbackProperties() const { return d->rtpFeedbackProperties; } /// /// Sets the properties of RTP feedback. /// /// \param rtpFeedbackProperties RTP feedback properties /// /// \since QXmpp 1.5 /// void QXmppJinglePayloadType::setRtpFeedbackProperties(const QVector &rtpFeedbackProperties) { d->rtpFeedbackProperties = rtpFeedbackProperties; } /// /// Returns the intervals of RTP feedback. /// /// \return the RTP feedback intervals /// QVector QXmppJinglePayloadType::rtpFeedbackIntervals() const { return d->rtpFeedbackIntervals; } /// /// Sets the intervals of RTP feedback. /// /// \param rtpFeedbackIntervals RTP feedback intervals /// void QXmppJinglePayloadType::setRtpFeedbackIntervals(const QVector &rtpFeedbackIntervals) { d->rtpFeedbackIntervals = rtpFeedbackIntervals; } /// \cond void QXmppJinglePayloadType::parse(const QDomElement &element) { d->id = element.attribute(QStringLiteral("id")).toInt(); d->name = element.attribute(QStringLiteral("name")); d->channels = element.attribute(QStringLiteral("channels")).toInt(); if (!d->channels) { d->channels = 1; } d->clockrate = element.attribute(QStringLiteral("clockrate")).toInt(); d->maxptime = element.attribute(QStringLiteral("maxptime")).toInt(); d->ptime = element.attribute(QStringLiteral("ptime")).toInt(); QDomElement child = element.firstChildElement(QStringLiteral("parameter")); while (!child.isNull()) { d->parameters.insert(child.attribute(QStringLiteral("name")), child.attribute(QStringLiteral("value"))); child = child.nextSiblingElement(QStringLiteral("parameter")); } parseJingleRtpFeedbackNegotiationElements(element, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); } void QXmppJinglePayloadType::toXml(QXmlStreamWriter *writer) const { writer->writeStartElement(QStringLiteral("payload-type")); helperToXmlAddAttribute(writer, QStringLiteral("id"), QString::number(d->id)); helperToXmlAddAttribute(writer, QStringLiteral("name"), d->name); if (d->channels > 1) { helperToXmlAddAttribute(writer, QStringLiteral("channels"), QString::number(d->channels)); } if (d->clockrate > 0) { helperToXmlAddAttribute(writer, QStringLiteral("clockrate"), QString::number(d->clockrate)); } if (d->maxptime > 0) { helperToXmlAddAttribute(writer, QStringLiteral("maxptime"), QString::number(d->maxptime)); } if (d->ptime > 0) { helperToXmlAddAttribute(writer, QStringLiteral("ptime"), QString::number(d->ptime)); } for (auto itr = d->parameters.begin(); itr != d->parameters.end(); itr++) { writer->writeStartElement(QStringLiteral("parameter")); writer->writeAttribute(QStringLiteral("name"), itr.key()); writer->writeAttribute(QStringLiteral("value"), itr.value()); writer->writeEndElement(); } jingleRtpFeedbackNegotiationElementsToXml(writer, d->rtpFeedbackProperties, d->rtpFeedbackIntervals); writer->writeEndElement(); } /// \endcond /// Assigns the other payload type to this one. /// /// \param other QXmppJinglePayloadType &QXmppJinglePayloadType::operator=(const QXmppJinglePayloadType &other) { d = other.d; return *this; } /// Returns true if this QXmppJinglePayloadType and \a other refer to the same payload type. /// /// \param other bool QXmppJinglePayloadType::operator==(const QXmppJinglePayloadType &other) const { // FIXME : what to do with m_ptime and m_maxptime? if (d->id <= 95) { return other.d->id == d->id && other.d->clockrate == d->clockrate; } else { return other.d->channels == d->channels && other.d->clockrate == d->clockrate && other.d->name.toLower() == d->name.toLower(); } } class QXmppSdpParameterPrivate : public QSharedData { public: QString name; QString value; }; /// /// \class QXmppSdpParameter /// /// \brief The QXmppSdpParameter class represents a Session Description Protocol (SDP) parameter /// specified by RFC 4566 and used by several XEPs based on \xep{0166, Jingle}. /// /// \since QXmpp 1.5 /// /// /// Constructs a Session Description Protocol parameter. /// QXmppSdpParameter::QXmppSdpParameter() : d(new QXmppSdpParameterPrivate()) { } QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppSdpParameter) /// /// Returns the name of the parameter. /// /// \return the parameter's name /// QString QXmppSdpParameter::name() const { return d->name; } /// /// Sets the name of the parameter. /// /// \param name parameter's name /// void QXmppSdpParameter::setName(const QString &name) { d->name = name; } /// /// Returns the value of the parameter. /// /// \return the parameter's value /// QString QXmppSdpParameter::value() const { return d->value; } /// /// Sets the value of the parameter. /// /// A parameter in the form "a=b" can be created by this method. /// Any other form of parameters can be created by not using this method. /// The value stays a default-constructed QString then. /// /// \param value parameter's value /// void QXmppSdpParameter::setValue(const QString &value) { d->value = value; } /// \cond void QXmppSdpParameter::parse(const QDomElement &element) { d->name = element.attribute(QStringLiteral("name")); d->value = element.attribute(QStringLiteral("value")); } void QXmppSdpParameter::toXml(QXmlStreamWriter *writer) const { writer->writeStartElement(QStringLiteral("parameter")); helperToXmlAddAttribute(writer, QStringLiteral("name"), d->name); if (!d->value.isEmpty()) { helperToXmlAddAttribute(writer, QStringLiteral("value"), d->value); } writer->writeEndElement(); } /// \endcond /// /// Determines whether the given DOM element is a Session Description Protocol parameter element. /// /// \param element DOM element being checked /// /// \return whether element is a Session Description Protocol parameter element /// bool QXmppSdpParameter::isSdpParameter(const QDomElement &element) { return element.tagName() == QStringLiteral("parameter"); } class QXmppJingleRtpCryptoElementPrivate : public QSharedData { public: uint32_t tag = 0; QString cryptoSuite; QString keyParams; QString sessionParams; }; /// /// \class QXmppJingleRtpCryptoElement /// /// \brief The QXmppJingleRtpCryptoElement class represents the \xep{0167: Jingle RTP Sessions} /// "crypto" element used for SRTP negotiation. /// /// \since QXmpp 1.5 /// /// /// Constructs a Jingle RTP crypto element. /// QXmppJingleRtpCryptoElement::QXmppJingleRtpCryptoElement() : d(new QXmppJingleRtpCryptoElementPrivate()) { } QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpCryptoElement) /// /// Returns the tag used as an identifier for the crypto element. /// /// \return the identifying tag /// uint32_t QXmppJingleRtpCryptoElement::tag() const { return d->tag; } /// /// Sets the tag used as an identifier for the crypto element. /// /// \param tag identifying tag /// void QXmppJingleRtpCryptoElement::setTag(uint32_t tag) { d->tag = tag; } /// /// Returns the crypto suite used as an identifier for describing the encryption and authentication /// algorithms. /// /// \return the identifying crypto suite /// QString QXmppJingleRtpCryptoElement::cryptoSuite() const { return d->cryptoSuite; } /// /// Sets the crypto suite used as an identifier for describing the encryption and authentication /// algorithms. /// /// \param cryptoSuite identifying crypto suite /// void QXmppJingleRtpCryptoElement::setCryptoSuite(const QString &cryptoSuite) { d->cryptoSuite = cryptoSuite; } /// /// Returns the key parameters providing one or more sets of keying material for the crypto suite. /// /// \return the key parameters providing one or more sets of keying material /// QString QXmppJingleRtpCryptoElement::keyParams() const { return d->keyParams; } /// /// Sets the key parameters providing one or more sets of keying material for the crypto suite. /// /// \param keyParams key parameters providing one or more sets of keying material /// void QXmppJingleRtpCryptoElement::setKeyParams(const QString &keyParams) { d->keyParams = keyParams; } /// /// Returns the session parameters providing transport-specific data. /// /// \return the session parameters providing transport-specific data /// QString QXmppJingleRtpCryptoElement::sessionParams() const { return d->sessionParams; } /// /// Sets the session parameters providing transport-specific data. /// /// \param sessionParams session parameters providing transport-specific data /// void QXmppJingleRtpCryptoElement::setSessionParams(const QString &sessionParams) { d->sessionParams = sessionParams; } /// \cond void QXmppJingleRtpCryptoElement::parse(const QDomElement &element) { d->tag = element.attribute(QStringLiteral("tag")).toUInt(); d->cryptoSuite = element.attribute(QStringLiteral("crypto-suite")); d->keyParams = element.attribute(QStringLiteral("key-params")); d->sessionParams = element.attribute(QStringLiteral("session-params")); } void QXmppJingleRtpCryptoElement::toXml(QXmlStreamWriter *writer) const { if (!d->cryptoSuite.isEmpty() && !d->keyParams.isEmpty()) { writer->writeStartElement(QStringLiteral("crypto")); writer->writeAttribute(QStringLiteral("tag"), QString::number(d->tag)); writer->writeAttribute(QStringLiteral("crypto-suite"), d->cryptoSuite); writer->writeAttribute(QStringLiteral("key-params"), d->keyParams); helperToXmlAddAttribute(writer, QStringLiteral("session-params"), d->sessionParams); writer->writeEndElement(); } } /// \endcond /// /// Determines whether the given DOM element is an RTP crypto element. /// /// \param element DOM element being checked /// /// \return whether element is an RTP crypto element /// bool QXmppJingleRtpCryptoElement::isJingleRtpCryptoElement(const QDomElement &element) { return element.tagName() == QStringLiteral("crypto"); } class QXmppJingleRtpEncryptionPrivate : public QSharedData { public: bool isRequired = false; QVector cryptoElements; }; /// /// \class QXmppJingleRtpEncryption /// /// \brief The QXmppJingleRtpEncryption class represents the \xep{0167: Jingle RTP Sessions} /// "encryption" element used for SRTP negotiation. /// /// \since QXmpp 1.5 /// /// /// Constructs a Jingle RTP encryption. /// QXmppJingleRtpEncryption::QXmppJingleRtpEncryption() : d(new QXmppJingleRtpEncryptionPrivate()) { } QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpEncryption) /// /// Returns whether encryption via SRTP is required. /// /// \return whether encryption is required /// bool QXmppJingleRtpEncryption::isRequired() const { return d->isRequired; } /// /// Sets whether encryption via SRTP is required. /// /// \param isRequired whether encryption is required /// void QXmppJingleRtpEncryption::setRequired(bool isRequired) { d->isRequired = isRequired; } /// /// Returns the crypto elements used for encryption via SRTP. /// /// \return the crypto elements /// QVector QXmppJingleRtpEncryption::cryptoElements() const { return d->cryptoElements; } /// /// Sets the crypto elements used for encryption via SRTP. /// /// \param cryptoElements the crypto elements /// void QXmppJingleRtpEncryption::setCryptoElements(const QVector &cryptoElements) { d->cryptoElements = cryptoElements; } /// \cond void QXmppJingleRtpEncryption::parse(const QDomElement &element) { d->isRequired = element.attribute(QStringLiteral("required")) == QStringLiteral("true") || element.attribute(QStringLiteral("required")) == QStringLiteral("1"); for (auto childElement = element.firstChildElement(); !childElement.isNull(); childElement = childElement.nextSiblingElement()) { if (QXmppJingleRtpCryptoElement::isJingleRtpCryptoElement(childElement)) { QXmppJingleRtpCryptoElement cryptoElement; cryptoElement.parse(childElement); d->cryptoElements.append(std::move(cryptoElement)); } } } void QXmppJingleRtpEncryption::toXml(QXmlStreamWriter *writer) const { if (!d->cryptoElements.isEmpty()) { writer->writeStartElement(QStringLiteral("encryption")); writer->writeDefaultNamespace(ns_jingle_rtp); if (d->isRequired) { writer->writeAttribute(QStringLiteral("required"), QStringLiteral("1")); } for (const auto &cryptoElement : std::as_const(d->cryptoElements)) { cryptoElement.toXml(writer); } writer->writeEndElement(); } } /// \endcond /// /// Determines whether the given DOM element is an RTP encryption element. /// /// \param element DOM element being checked /// /// \return whether element is an RTP encryption element /// bool QXmppJingleRtpEncryption::isJingleRtpEncryption(const QDomElement &element) { return element.tagName() == QStringLiteral("encryption") && element.namespaceURI() == ns_jingle_rtp; } class QXmppJingleRtpFeedbackPropertyPrivate : public QSharedData { public: QString type; QString subtype; QVector parameters; }; /// /// \class QXmppJingleRtpFeedbackProperty /// /// \brief The QXmppJingleRtpFeedbackProperty class represents the /// \xep{0293, Jingle RTP Feedback Negotiation} "rtcp-fb" element. /// /// \since QXmpp 1.5 /// /// /// Constructs a Jingle RTP feedback property. /// QXmppJingleRtpFeedbackProperty::QXmppJingleRtpFeedbackProperty() : d(new QXmppJingleRtpFeedbackPropertyPrivate()) { } QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpFeedbackProperty) /// /// Returns the type of RTP feedback. /// /// \return the RTP feedback type /// QString QXmppJingleRtpFeedbackProperty::type() const { return d->type; } /// /// Sets the type of RTP feedback. /// /// \param type RTP feedback type /// void QXmppJingleRtpFeedbackProperty::setType(const QString &type) { d->type = type; } /// /// Returns the subtype for RTP feedback. /// /// \return the RTP feedback subtype /// QString QXmppJingleRtpFeedbackProperty::subtype() const { return d->subtype; } /// /// Sets the subtype of RTP feedback. /// /// If there is more than one parameter, use QXmppJingleRtpFeedbackProperty::setParameters() /// instead of this method. /// /// \param subtype RTP feedback subtype /// void QXmppJingleRtpFeedbackProperty::setSubtype(const QString &subtype) { d->subtype = subtype; } /// /// Returns the parameters of RTP feedback. /// /// \return the RTP feedback parameters /// QVector QXmppJingleRtpFeedbackProperty::parameters() const { return d->parameters; } /// /// Sets the parameters of RTP feedback. /// /// Additional parameters can be set by this method. /// If there is only one parameter, use QXmppJingleRtpFeedbackProperty::setSubtype() /// instead of this method. /// /// \param parameters RTP feedback parameters /// void QXmppJingleRtpFeedbackProperty::setParameters(const QVector ¶meters) { d->parameters = parameters; } /// \cond void QXmppJingleRtpFeedbackProperty::parse(const QDomElement &element) { d->type = element.attribute(QStringLiteral("type")); d->subtype = element.attribute(QStringLiteral("subtype")); parseSdpParameters(element, d->parameters); } void QXmppJingleRtpFeedbackProperty::toXml(QXmlStreamWriter *writer) const { writer->writeStartElement(QStringLiteral("rtcp-fb")); writer->writeDefaultNamespace(ns_jingle_rtp_feedback_negotiation); helperToXmlAddAttribute(writer, QStringLiteral("type"), d->type); // If there are parameters, they must be used instead of the subtype. if (d->subtype.isEmpty()) { sdpParametersToXml(writer, d->parameters); } else { helperToXmlAddAttribute(writer, QStringLiteral("subtype"), d->subtype); } writer->writeEndElement(); } /// \endcond /// /// Determines whether the given DOM element is an RTP feedback property element. /// /// \param element DOM element being checked /// /// \return whether element is an RTP feedback property element /// bool QXmppJingleRtpFeedbackProperty::isJingleRtpFeedbackProperty(const QDomElement &element) { return element.tagName() == QStringLiteral("rtcp-fb") && element.namespaceURI() == ns_jingle_rtp_feedback_negotiation; } /// /// \class QXmppJingleRtpFeedbackInterval /// /// \brief The QXmppJingleRtpFeedbackInterval class represents the /// \xep{0293, Jingle RTP Feedback Negotiation} "rtcp-fb-trr-int" element. /// /// \since QXmpp 1.5 /// /// /// Constructs a Jingle RTP feedback interval. /// QXmppJingleRtpFeedbackInterval::QXmppJingleRtpFeedbackInterval() { } QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpFeedbackInterval) /// /// Returns the value of the RTP feedback interval. /// /// \return the RTP feedback interval value /// uint64_t QXmppJingleRtpFeedbackInterval::value() const { return m_value; } /// /// Sets the value of the RTP feedback interval. /// /// \param value RTP feedback interval value /// void QXmppJingleRtpFeedbackInterval::setValue(uint64_t value) { m_value = value; } /// \cond void QXmppJingleRtpFeedbackInterval::parse(const QDomElement &element) { m_value = element.attribute(QStringLiteral("value")).toUInt(); } void QXmppJingleRtpFeedbackInterval::toXml(QXmlStreamWriter *writer) const { writer->writeStartElement(QStringLiteral("rtcp-fb-trr-int")); writer->writeDefaultNamespace(ns_jingle_rtp_feedback_negotiation); helperToXmlAddAttribute(writer, QStringLiteral("value"), QString::number(m_value)); writer->writeEndElement(); } /// \endcond /// /// Determines whether the given DOM element is an RTP feedback interval element. /// /// \param element DOM element being checked /// /// \return whether element is an RTP feedback interval element /// bool QXmppJingleRtpFeedbackInterval::isJingleRtpFeedbackInterval(const QDomElement &element) { return element.tagName() == QStringLiteral("rtcp-fb-trr-int") && element.namespaceURI() == ns_jingle_rtp_feedback_negotiation; } class QXmppJingleRtpHeaderExtensionPropertyPrivate : public QSharedData { public: uint32_t id = 0; QString uri; QXmppJingleRtpHeaderExtensionProperty::Senders senders = QXmppJingleRtpHeaderExtensionProperty::Both; QVector parameters; }; /// /// \enum QXmppJingleRtpHeaderExtensionProperty::Senders /// /// Parties that are allowed to send the negotiated RTP header extension /// /// /// \class QXmppJingleRtpHeaderExtensionProperty /// /// \brief The QXmppJingleRtpHeaderExtensionProperty class represents the /// \xep{0294, Jingle RTP Header Extensions Negotiation} "rtp-hdrext" element. /// /// \since QXmpp 1.5 /// /// /// Constructs a Jingle RTP header extension property. /// QXmppJingleRtpHeaderExtensionProperty::QXmppJingleRtpHeaderExtensionProperty() : d(new QXmppJingleRtpHeaderExtensionPropertyPrivate()) { } QXMPP_PRIVATE_DEFINE_RULE_OF_SIX(QXmppJingleRtpHeaderExtensionProperty) /// /// Returns the ID of the RTP header extension. /// /// The ID is 0 if it is unset. /// /// \return the RTP header extension's ID /// uint32_t QXmppJingleRtpHeaderExtensionProperty::id() const { return d->id; } /// /// Sets the ID of the RTP header extension. /// /// The ID must either be at least 1 and at most 256 or at least 4096 and at most 4351. /// /// \param id RTP header extension's ID /// void QXmppJingleRtpHeaderExtensionProperty::setId(uint32_t id) { d->id = id; } /// /// Returns the URI defning the RTP header extension. /// /// \return the RTP header extension's URI /// QString QXmppJingleRtpHeaderExtensionProperty::uri() const { return d->uri; } /// /// Sets the URI defning the RTP header extension. /// /// \param uri RTP header extension's URI /// void QXmppJingleRtpHeaderExtensionProperty::setUri(const QString &uri) { d->uri = uri; } /// /// Returns the parties that are allowed to send the negotiated RTP header extensions. /// /// \return the parties that are allowed to send the RTP header extensions /// QXmppJingleRtpHeaderExtensionProperty::Senders QXmppJingleRtpHeaderExtensionProperty::senders() const { return d->senders; } /// /// Sets the parties that are allowed to send the negotiated RTP header extensions. /// /// \param senders parties that are allowed to send the RTP header extensions /// void QXmppJingleRtpHeaderExtensionProperty::setSenders(Senders senders) { d->senders = senders; } /// /// Returns the parameters of the RTP header extension. /// /// \return the RTP header extension's parameters /// QVector QXmppJingleRtpHeaderExtensionProperty::parameters() const { return d->parameters; } /// /// Sets the parameters of the RTP header extension. /// /// Additional parameters can be set by this method. /// /// \param parameters RTP header extension's parameters /// void QXmppJingleRtpHeaderExtensionProperty::setParameters(const QVector ¶meters) { d->parameters = parameters; } /// \cond void QXmppJingleRtpHeaderExtensionProperty::parse(const QDomElement &element) { if (element.tagName() == QStringLiteral("rtp-hdrext") && element.namespaceURI() == ns_jingle_rtp_header_extensions_negotiation) { d->id = element.attribute(QStringLiteral("id")).toUInt(); d->uri = element.attribute(QStringLiteral("uri")); if (const auto senders = JINGLE_RTP_HEADER_EXTENSIONS_SENDERS.indexOf(element.attribute(QStringLiteral("senders"))); senders > QXmppJingleRtpHeaderExtensionProperty::Both) { d->senders = static_cast(senders); } parseSdpParameters(element, d->parameters); } } void QXmppJingleRtpHeaderExtensionProperty::toXml(QXmlStreamWriter *writer) const { writer->writeStartElement(QStringLiteral("rtp-hdrext")); writer->writeDefaultNamespace(ns_jingle_rtp_header_extensions_negotiation); helperToXmlAddAttribute(writer, QStringLiteral("id"), QString::number(d->id)); helperToXmlAddAttribute(writer, QStringLiteral("uri"), d->uri); if (d->senders != QXmppJingleRtpHeaderExtensionProperty::Both) { helperToXmlAddAttribute(writer, QStringLiteral("senders"), JINGLE_RTP_HEADER_EXTENSIONS_SENDERS.at(d->senders)); } sdpParametersToXml(writer, d->parameters); writer->writeEndElement(); } /// \endcond /// /// Determines whether the given DOM element is an RTP header extensions property element. /// /// \param element DOM element being checked /// /// \return whether element is an RTP header extension property element /// bool QXmppJingleRtpHeaderExtensionProperty::isJingleRtpHeaderExtensionProperty(const QDomElement &element) { return element.tagName() == QStringLiteral("rtp-hdrext") && element.namespaceURI() == ns_jingle_rtp_header_extensions_negotiation; }