diff options
| author | Niels Ole Salscheider <niels_ole@salscheider-online.de> | 2017-02-12 17:18:06 +0100 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2017-02-12 17:18:06 +0100 |
| commit | 084eb01fb395488a0a3aee799be2b72ce11aa220 (patch) | |
| tree | 6fec32386c116b0560b9c5d81412993361a2f453 /src/base | |
| parent | 9deb86b248fee6bb9bcee14d595a933c8fdc4aa2 (diff) | |
| download | qxmpp-084eb01fb395488a0a3aee799be2b72ce11aa220.tar.gz | |
Implement XEP-0198: Stream Management (client only) (#99)
* Some features can be available with different namespaces (e.g. SM)
* Provide static functions to convert between strings and stream errors
Stream management will reuse this for <failed />.
* [travis] test builds using clang
* Implement XEP-0198: Stream Management (client only)
* QXmppOutgoingClient: Move private methods to QXmppOutgoingClientPrivate
Diffstat (limited to 'src/base')
| -rw-r--r-- | src/base/QXmppConstants.cpp | 2 | ||||
| -rw-r--r-- | src/base/QXmppConstants_p.h | 2 | ||||
| -rw-r--r-- | src/base/QXmppIq.cpp | 8 | ||||
| -rw-r--r-- | src/base/QXmppIq.h | 2 | ||||
| -rw-r--r-- | src/base/QXmppMessage.cpp | 8 | ||||
| -rw-r--r-- | src/base/QXmppMessage.h | 2 | ||||
| -rw-r--r-- | src/base/QXmppPresence.cpp | 8 | ||||
| -rw-r--r-- | src/base/QXmppPresence.h | 2 | ||||
| -rw-r--r-- | src/base/QXmppStanza.cpp | 106 | ||||
| -rw-r--r-- | src/base/QXmppStanza.h | 2 | ||||
| -rw-r--r-- | src/base/QXmppStanza_p.h | 145 | ||||
| -rw-r--r-- | src/base/QXmppStream.cpp | 122 | ||||
| -rw-r--r-- | src/base/QXmppStream.h | 24 | ||||
| -rw-r--r-- | src/base/QXmppStreamFeatures.cpp | 34 | ||||
| -rw-r--r-- | src/base/QXmppStreamFeatures.h | 10 | ||||
| -rw-r--r-- | src/base/QXmppStreamManagement.cpp | 325 | ||||
| -rw-r--r-- | src/base/QXmppStreamManagement_p.h | 194 | ||||
| -rw-r--r-- | src/base/base.pri | 2 |
18 files changed, 891 insertions, 107 deletions
diff --git a/src/base/QXmppConstants.cpp b/src/base/QXmppConstants.cpp index a1a62de0..323b77f5 100644 --- a/src/base/QXmppConstants.cpp +++ b/src/base/QXmppConstants.cpp @@ -98,6 +98,8 @@ const char* ns_jingle_rtp_audio = "urn:xmpp:jingle:apps:rtp:audio"; const char* ns_jingle_rtp_video = "urn:xmpp:jingle:apps:rtp:video"; // XEP-0184: Message Receipts const char* ns_message_receipts = "urn:xmpp:receipts"; +// XEP-0198: Stream Management +const char* ns_stream_management = "urn:xmpp:sm:3"; // XEP-0199: XMPP Ping const char* ns_ping = "urn:xmpp:ping"; // XEP-0202: Entity Time diff --git a/src/base/QXmppConstants_p.h b/src/base/QXmppConstants_p.h index 8b78c9ab..c4cddeb8 100644 --- a/src/base/QXmppConstants_p.h +++ b/src/base/QXmppConstants_p.h @@ -110,6 +110,8 @@ extern const char* ns_jingle_rtp_audio; extern const char* ns_jingle_rtp_video; // XEP-0184: Message Receipts extern const char* ns_message_receipts; +// XEP-0198: Stream Management +extern const char* ns_stream_management; // XEP-0199: XMPP Ping extern const char* ns_ping; // XEP-0202: Entity Time diff --git a/src/base/QXmppIq.cpp b/src/base/QXmppIq.cpp index dee0cb83..3e579726 100644 --- a/src/base/QXmppIq.cpp +++ b/src/base/QXmppIq.cpp @@ -91,6 +91,14 @@ void QXmppIq::setType(QXmppIq::Type type) d->type = type; } +/// Indicates if the QXmppStanza is a stanza in the XMPP sence (i. e. a message, +/// iq or presence) + +bool QXmppIq::isXmppStanza() const +{ + return true; +} + /// \cond void QXmppIq::parse(const QDomElement &element) { diff --git a/src/base/QXmppIq.h b/src/base/QXmppIq.h index 5cfe11c4..9d75908b 100644 --- a/src/base/QXmppIq.h +++ b/src/base/QXmppIq.h @@ -60,6 +60,8 @@ public: QXmppIq::Type type() const; void setType(QXmppIq::Type); + bool isXmppStanza() const; + /// \cond void parse(const QDomElement &element); void toXml(QXmlStreamWriter *writer) const; diff --git a/src/base/QXmppMessage.cpp b/src/base/QXmppMessage.cpp index cdccba73..df6e6bd1 100644 --- a/src/base/QXmppMessage.cpp +++ b/src/base/QXmppMessage.cpp @@ -467,6 +467,14 @@ void QXmppMessage::setPrivate(const bool priv) d->privatemsg = priv; } +/// Indicates if the QXmppStanza is a stanza in the XMPP sence (i. e. a message, +/// iq or presence) + +bool QXmppMessage::isXmppStanza() const +{ + return true; +} + /// \cond void QXmppMessage::parse(const QDomElement &element) { diff --git a/src/base/QXmppMessage.h b/src/base/QXmppMessage.h index cff04250..fbed97e0 100644 --- a/src/base/QXmppMessage.h +++ b/src/base/QXmppMessage.h @@ -133,6 +133,8 @@ public: bool isPrivate() const; void setPrivate(const bool); + bool isXmppStanza() const; + /// \cond void parse(const QDomElement &element); void toXml(QXmlStreamWriter *writer) const; diff --git a/src/base/QXmppPresence.cpp b/src/base/QXmppPresence.cpp index 07d0aa9e..259a2ea5 100644 --- a/src/base/QXmppPresence.cpp +++ b/src/base/QXmppPresence.cpp @@ -496,3 +496,11 @@ void QXmppPresence::setMucSupported(bool supported) { d->mucSupported = supported; } + +/// Indicates if the QXmppStanza is a stanza in the XMPP sence (i. e. a message, +/// iq or presence) + +bool QXmppPresence::isXmppStanza() const +{ + return true; +} diff --git a/src/base/QXmppPresence.h b/src/base/QXmppPresence.h index 011549a9..f94c7c2a 100644 --- a/src/base/QXmppPresence.h +++ b/src/base/QXmppPresence.h @@ -128,6 +128,8 @@ public: QStringList capabilityExt() const; + bool isXmppStanza() const; + private: QSharedDataPointer<QXmppPresencePrivate> d; }; diff --git a/src/base/QXmppStanza.cpp b/src/base/QXmppStanza.cpp index 99b0692e..a2bb43be 100644 --- a/src/base/QXmppStanza.cpp +++ b/src/base/QXmppStanza.cpp @@ -25,6 +25,7 @@ #include "QXmppStanza.h" +#include "QXmppStanza_p.h" #include "QXmppUtils.h" #include "QXmppConstants_p.h" @@ -245,55 +246,7 @@ QString QXmppStanza::Error::getTypeStr() const QString QXmppStanza::Error::getConditionStr() const { - switch(m_condition) - { - case BadRequest: - return "bad-request"; - case Conflict: - return "conflict"; - case FeatureNotImplemented: - return "feature-not-implemented"; - case Forbidden: - return "forbidden"; - case Gone: - return "gone"; - case InternalServerError: - return "internal-server-error"; - case ItemNotFound: - return "item-not-found"; - case JidMalformed: - return "jid-malformed"; - case NotAcceptable: - return "not-acceptable"; - case NotAllowed: - return "not-allowed"; - case NotAuthorized: - return "not-authorized"; - case PaymentRequired: - return "payment-required"; - case RecipientUnavailable: - return "recipient-unavailable"; - case Redirect: - return "redirect"; - case RegistrationRequired: - return "registration-required"; - case RemoteServerNotFound: - return "remote-server-not-found"; - case RemoteServerTimeout: - return "remote-server-timeout"; - case ResourceConstraint: - return "resource-constraint"; - case ServiceUnavailable: - return "service-unavailable"; - case SubscriptionRequired: - return "subscription-required"; - case UndefinedCondition: - return "undefined-condition"; - case UnexpectedRequest: - return "unexpected-request"; - default: - return ""; - } + return strFromCondition(m_condition); } void QXmppStanza::Error::setTypeFromStr(const QString& type) @@ -314,52 +267,7 @@ void QXmppStanza::Error::setTypeFromStr(const QString& type) void QXmppStanza::Error::setConditionFromStr(const QString& type) { - if(type == "bad-request") - setCondition(BadRequest); - else if(type == "conflict") - setCondition(Conflict); - else if(type == "feature-not-implemented") - setCondition(FeatureNotImplemented); - else if(type == "forbidden") - setCondition(Forbidden); - else if(type == "gone") - setCondition(Gone); - else if(type == "internal-server-error") - setCondition(InternalServerError); - else if(type == "item-not-found") - setCondition(ItemNotFound); - else if(type == "jid-malformed") - setCondition(JidMalformed); - else if(type == "not-acceptable") - setCondition(NotAcceptable); - else if(type == "not-allowed") - setCondition(NotAllowed); - else if(type == "not-authorized") - setCondition(NotAuthorized); - else if(type == "payment-required") - setCondition(PaymentRequired); - else if(type == "recipient-unavailable") - setCondition(RecipientUnavailable); - else if(type == "redirect") - setCondition(Redirect); - else if(type == "registration-required") - setCondition(RegistrationRequired); - else if(type == "remote-server-not-found") - setCondition(RemoteServerNotFound); - else if(type == "remote-server-timeout") - setCondition(RemoteServerTimeout); - else if(type == "resource-constraint") - setCondition(ResourceConstraint); - else if(type == "service-unavailable") - setCondition(ServiceUnavailable); - else if(type == "subscription-required") - setCondition(SubscriptionRequired); - else if(type == "undefined-condition") - setCondition(UndefinedCondition); - else if(type == "unexpected-request") - setCondition(UnexpectedRequest); - else - setCondition(static_cast<QXmppStanza::Error::Condition>(-1)); + setCondition(conditionFromStr(type)); } void QXmppStanza::Error::parse(const QDomElement &errorElement) @@ -578,6 +486,14 @@ void QXmppStanza::setExtendedAddresses(const QList<QXmppExtendedAddress> &addres d->extendedAddresses = addresses; } +/// Indicates if the QXmppStanza is a stanza in the XMPP sence (i. e. a message, +/// iq or presence) + +bool QXmppStanza::isXmppStanza() const +{ + return false; +} + /// \cond void QXmppStanza::generateAndSetNextId() { diff --git a/src/base/QXmppStanza.h b/src/base/QXmppStanza.h index 4583bcd3..1d378956 100644 --- a/src/base/QXmppStanza.h +++ b/src/base/QXmppStanza.h @@ -189,6 +189,8 @@ public: QList<QXmppExtendedAddress> extendedAddresses() const; void setExtendedAddresses(const QList<QXmppExtendedAddress> &extendedAddresses); + virtual bool isXmppStanza() const; + /// \cond virtual void parse(const QDomElement &element); virtual void toXml(QXmlStreamWriter *writer) const = 0; diff --git a/src/base/QXmppStanza_p.h b/src/base/QXmppStanza_p.h new file mode 100644 index 00000000..a35a44f2 --- /dev/null +++ b/src/base/QXmppStanza_p.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2017 The QXmpp developers + * + * Author: + * Niels Ole Salscheider + * + * Source: + * https://github.com/qxmpp-project/qxmpp + * + * This file is a part of QXmpp library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifndef QXMPPSTANZA_P_H +#define QXMPPSTANZA_P_H + +#include "QXmppStanza.h" + +// W A R N I N G +// ------------- +// +// This file is not part of the QXmpp API. It exists for the convenience +// of the QXmppStanza class. +// +// This header file may change from version to version without notice, +// or even be removed. +// +// We mean it. +// + +static QString strFromCondition(const QXmppStanza::Error::Condition& condition) +{ + switch(condition) + { + case QXmppStanza::Error::BadRequest: + return "bad-request"; + case QXmppStanza::Error::Conflict: + return "conflict"; + case QXmppStanza::Error::FeatureNotImplemented: + return "feature-not-implemented"; + case QXmppStanza::Error::Forbidden: + return "forbidden"; + case QXmppStanza::Error::Gone: + return "gone"; + case QXmppStanza::Error::InternalServerError: + return "internal-server-error"; + case QXmppStanza::Error::ItemNotFound: + return "item-not-found"; + case QXmppStanza::Error::JidMalformed: + return "jid-malformed"; + case QXmppStanza::Error::NotAcceptable: + return "not-acceptable"; + case QXmppStanza::Error::NotAllowed: + return "not-allowed"; + case QXmppStanza::Error::NotAuthorized: + return "not-authorized"; + case QXmppStanza::Error::PaymentRequired: + return "payment-required"; + case QXmppStanza::Error::RecipientUnavailable: + return "recipient-unavailable"; + case QXmppStanza::Error::Redirect: + return "redirect"; + case QXmppStanza::Error::RegistrationRequired: + return "registration-required"; + case QXmppStanza::Error::RemoteServerNotFound: + return "remote-server-not-found"; + case QXmppStanza::Error::RemoteServerTimeout: + return "remote-server-timeout"; + case QXmppStanza::Error::ResourceConstraint: + return "resource-constraint"; + case QXmppStanza::Error::ServiceUnavailable: + return "service-unavailable"; + case QXmppStanza::Error::SubscriptionRequired: + return "subscription-required"; + case QXmppStanza::Error::UndefinedCondition: + return "undefined-condition"; + case QXmppStanza::Error::UnexpectedRequest: + return "unexpected-request"; + default: + return ""; + } +} + +static QXmppStanza::Error::Condition conditionFromStr(const QString& string) +{ + if(string == "bad-request") + return QXmppStanza::Error::BadRequest; + else if(string == "conflict") + return QXmppStanza::Error::Conflict; + else if(string == "feature-not-implemented") + return QXmppStanza::Error::FeatureNotImplemented; + else if(string == "forbidden") + return QXmppStanza::Error::Forbidden; + else if(string == "gone") + return QXmppStanza::Error::Gone; + else if(string == "internal-server-error") + return QXmppStanza::Error::InternalServerError; + else if(string == "item-not-found") + return QXmppStanza::Error::ItemNotFound; + else if(string == "jid-malformed") + return QXmppStanza::Error::JidMalformed; + else if(string == "not-acceptable") + return QXmppStanza::Error::NotAcceptable; + else if(string == "not-allowed") + return QXmppStanza::Error::NotAllowed; + else if(string == "not-authorized") + return QXmppStanza::Error::NotAuthorized; + else if(string == "payment-required") + return QXmppStanza::Error::PaymentRequired; + else if(string == "recipient-unavailable") + return QXmppStanza::Error::RecipientUnavailable; + else if(string == "redirect") + return QXmppStanza::Error::Redirect; + else if(string == "registration-required") + return QXmppStanza::Error::RegistrationRequired; + else if(string == "remote-server-not-found") + return QXmppStanza::Error::RemoteServerNotFound; + else if(string == "remote-server-timeout") + return QXmppStanza::Error::RemoteServerTimeout; + else if(string == "resource-constraint") + return QXmppStanza::Error::ResourceConstraint; + else if(string == "service-unavailable") + return QXmppStanza::Error::ServiceUnavailable; + else if(string == "subscription-required") + return QXmppStanza::Error::SubscriptionRequired; + else if(string == "undefined-condition") + return QXmppStanza::Error::UndefinedCondition; + else if(string == "unexpected-request") + return QXmppStanza::Error::UnexpectedRequest; + else + return static_cast<QXmppStanza::Error::Condition>(-1); +} + +#endif + diff --git a/src/base/QXmppStream.cpp b/src/base/QXmppStream.cpp index 9ea20856..215bf86b 100644 --- a/src/base/QXmppStream.cpp +++ b/src/base/QXmppStream.cpp @@ -27,11 +27,13 @@ #include "QXmppLogger.h" #include "QXmppStanza.h" #include "QXmppStream.h" +#include "QXmppStreamManagement_p.h" #include "QXmppUtils.h" #include <QBuffer> #include <QDomDocument> #include <QHostAddress> +#include <QMap> #include <QRegExp> #include <QSslSocket> #include <QStringList> @@ -51,10 +53,15 @@ public: // incoming stream state QByteArray streamStart; + + bool streamManagementEnabled; + QMap<unsigned, QByteArray> unacknowledgedStanzas; + unsigned lastOutgoingSequenceNumber; + unsigned lastIncomingSequenceNumber; }; QXmppStreamPrivate::QXmppStreamPrivate() - : socket(0) + : socket(0), streamManagementEnabled(false), lastOutgoingSequenceNumber(0), lastIncomingSequenceNumber(0) { } @@ -86,6 +93,7 @@ QXmppStream::~QXmppStream() void QXmppStream::disconnectFromHost() { + d->streamManagementEnabled = false; if (d->socket) { if (d->socket->state() == QAbstractSocket::ConnectedState) { sendData(streamRootElementEnd); @@ -140,8 +148,15 @@ bool QXmppStream::sendPacket(const QXmppStanza &packet) QXmlStreamWriter xmlStream(&data); packet.toXml(&xmlStream); + bool isXmppStanza = packet.isXmppStanza(); + if (isXmppStanza && d->streamManagementEnabled) + d->unacknowledgedStanzas[++d->lastOutgoingSequenceNumber] = data; + // send packet - return sendData(data); + bool success = sendData(data); + if (isXmppStanza) + sendAcknowledgementRequest(); + return success; } /// Returns the QSslSocket used for this stream. @@ -253,7 +268,17 @@ void QXmppStream::_q_socketReadyRead() // process stanzas QDomElement nodeRecv = doc.documentElement().firstChildElement(); while (!nodeRecv.isNull()) { - handleStanza(nodeRecv); + if (QXmppStreamManagementAck::isStreamManagementAck(nodeRecv)) + handleAcknowledgement(nodeRecv); + else if (QXmppStreamManagementReq::isStreamManagementReq(nodeRecv)) + sendAcknowledgement(); + else { + handleStanza(nodeRecv); + if(nodeRecv.tagName() == QLatin1String("message") || + nodeRecv.tagName() == QLatin1String("presence") || + nodeRecv.tagName() == QLatin1String("iq")) + ++d->lastIncomingSequenceNumber; + } nodeRecv = nodeRecv.nextSiblingElement(); } @@ -262,4 +287,95 @@ void QXmppStream::_q_socketReadyRead() disconnectFromHost(); } +/// Enables Stream Management acks / reqs (XEP-0198). +/// +/// \param resetSequenceNumber Indicates if the sequence numbers should be resetted. +/// This must be done iff the stream is not resumed. +void QXmppStream::enableStreamManagement(bool resetSequenceNumber) +{ + d->streamManagementEnabled = true; + + if (resetSequenceNumber) { + d->lastOutgoingSequenceNumber = 0; + d->lastIncomingSequenceNumber = 0; + + // resend unacked stanzas + if (!d->unacknowledgedStanzas.empty()) { + QMap<unsigned, QByteArray> oldUnackedStanzas = d->unacknowledgedStanzas; + d->unacknowledgedStanzas.clear(); + for (QMap<unsigned, QByteArray>::iterator it = oldUnackedStanzas.begin(); it != oldUnackedStanzas.end(); ++it) { + d->unacknowledgedStanzas[++d->lastOutgoingSequenceNumber] = it.value(); + sendData(it.value()); + } + sendAcknowledgementRequest(); + } + } else { + // resend unacked stanzas + if (!d->unacknowledgedStanzas.empty()) { + for (QMap<unsigned, QByteArray>::iterator it = d->unacknowledgedStanzas.begin(); it != d->unacknowledgedStanzas.end(); ++it) + sendData(it.value()); + sendAcknowledgementRequest(); + } + } +} +/// Returns the sequence number of the last incoming stanza (XEP-0198). +unsigned QXmppStream::lastIncomingSequenceNumber() const +{ + return d->lastIncomingSequenceNumber; +} + +/// Sets the last acknowledged sequence number for outgoing stanzas (XEP-0198). +void QXmppStream::setAcknowledgedSequenceNumber(unsigned sequenceNumber) +{ + for (QMap<unsigned, QByteArray>::iterator it = d->unacknowledgedStanzas.begin(); it != d->unacknowledgedStanzas.end(); ) { + if (it.key() <= sequenceNumber) + it = d->unacknowledgedStanzas.erase(it); + else + ++it; + } +} + +/// Handles an incoming acknowledgement from XEP-0198. +/// +/// \param element +void QXmppStream::handleAcknowledgement(QDomElement &element) +{ + if (!d->streamManagementEnabled) + return; + + QXmppStreamManagementAck ack; + ack.parse(element); + setAcknowledgedSequenceNumber(ack.seqNo()); +} + +/// Sends an acknowledgement as defined in XEP-0198. +void QXmppStream::sendAcknowledgement() +{ + if (!d->streamManagementEnabled) + return; + + // prepare packet + QByteArray data; + QXmlStreamWriter xmlStream(&data); + QXmppStreamManagementAck ack(d->lastIncomingSequenceNumber); + ack.toXml(&xmlStream); + + // send packet + sendData(data); +} + +/// Sends an acknowledgement request as defined in XEP-0198. +void QXmppStream::sendAcknowledgementRequest() +{ + if (!d->streamManagementEnabled) + return; + + // prepare packet + QByteArray data; + QXmlStreamWriter xmlStream(&data); + QXmppStreamManagementReq::toXml(&xmlStream); + + // send packet + sendData(data); +} diff --git a/src/base/QXmppStream.h b/src/base/QXmppStream.h index 3411e2e9..c0ac1807 100644 --- a/src/base/QXmppStream.h +++ b/src/base/QXmppStream.h @@ -74,6 +74,30 @@ protected: /// \param element virtual void handleStream(const QDomElement &element) = 0; + /// Enables Stream Management acks / reqs (XEP-0198). + /// + /// \param resetSeqno Indicates if the sequence numbers should be resetted. + /// This must be done iff the stream is not resumed. + void enableStreamManagement(bool resetSequenceNumber); + + /// Returns the sequence number of the last incoming stanza (XEP-0198). + unsigned lastIncomingSequenceNumber() const; + + /// Sets the last acknowledged sequence number for outgoing stanzas (XEP-0198). + void setAcknowledgedSequenceNumber(unsigned sequenceNumber); + +private: + /// Handles an incoming acknowledgement from XEP-0198. + /// + /// \param element + void handleAcknowledgement(QDomElement &element); + + /// Sends an acknowledgement as defined in XEP-0198. + void sendAcknowledgement(); + + /// Sends an acknowledgement request as defined in XEP-0198. + void sendAcknowledgementRequest(); + public slots: virtual void disconnectFromHost(); virtual bool sendData(const QByteArray&); diff --git a/src/base/QXmppStreamFeatures.cpp b/src/base/QXmppStreamFeatures.cpp index 322530b5..fd2832dc 100644 --- a/src/base/QXmppStreamFeatures.cpp +++ b/src/base/QXmppStreamFeatures.cpp @@ -30,7 +30,8 @@ QXmppStreamFeatures::QXmppStreamFeatures() : m_bindMode(Disabled), m_sessionMode(Disabled), m_nonSaslAuthMode(Disabled), - m_tlsMode(Disabled) + m_tlsMode(Disabled), + m_streamManagementMode(Disabled) { } @@ -94,6 +95,16 @@ void QXmppStreamFeatures::setTlsMode(QXmppStreamFeatures::Mode mode) m_tlsMode = mode; } +QXmppStreamFeatures::Mode QXmppStreamFeatures::streamManagementMode() const +{ + return m_streamManagementMode; +} + +void QXmppStreamFeatures::setStreamManagementMode(QXmppStreamFeatures::Mode mode) +{ + m_streamManagementMode = mode; +} + /// \cond bool QXmppStreamFeatures::isStreamFeatures(const QDomElement &element) { @@ -104,15 +115,18 @@ bool QXmppStreamFeatures::isStreamFeatures(const QDomElement &element) static QXmppStreamFeatures::Mode readFeature(const QDomElement &element, const char *tagName, const char *tagNs) { QDomElement subElement = element.firstChildElement(tagName); - if (subElement.namespaceURI() == tagNs) - { - if (!subElement.firstChildElement("required").isNull()) - return QXmppStreamFeatures::Required; - else - return QXmppStreamFeatures::Enabled; - } else { - return QXmppStreamFeatures::Disabled; + QXmppStreamFeatures::Mode mode = QXmppStreamFeatures::Disabled; + while (!subElement.isNull()) { + if (subElement.namespaceURI() == tagNs) + { + if (!subElement.firstChildElement("required").isNull()) + mode = QXmppStreamFeatures::Required; + else if (mode != QXmppStreamFeatures::Required) + mode = QXmppStreamFeatures::Enabled; + } + subElement = subElement.nextSiblingElement(tagName); } + return mode; } void QXmppStreamFeatures::parse(const QDomElement &element) @@ -121,6 +135,7 @@ void QXmppStreamFeatures::parse(const QDomElement &element) m_sessionMode = readFeature(element, "session", ns_session); m_nonSaslAuthMode = readFeature(element, "auth", ns_authFeature); m_tlsMode = readFeature(element, "starttls", ns_tls); + m_streamManagementMode = readFeature(element, "sm", ns_stream_management); // parse advertised compression methods QDomElement compression = element.firstChildElement("compression"); @@ -165,6 +180,7 @@ void QXmppStreamFeatures::toXml(QXmlStreamWriter *writer) const writeFeature(writer, "session", ns_session, m_sessionMode); writeFeature(writer, "auth", ns_authFeature, m_nonSaslAuthMode); writeFeature(writer, "starttls", ns_tls, m_tlsMode); + writeFeature(writer, "sm", ns_stream_management, m_streamManagementMode); if (!m_compressionMethods.isEmpty()) { diff --git a/src/base/QXmppStreamFeatures.h b/src/base/QXmppStreamFeatures.h index 46a9fd7d..242f748c 100644 --- a/src/base/QXmppStreamFeatures.h +++ b/src/base/QXmppStreamFeatures.h @@ -56,6 +56,15 @@ public: Mode tlsMode() const; void setTlsMode(Mode mode); + /// Returns the mode (disabled, enabled or required) for XEP-0198: Stream + /// Management + Mode streamManagementMode() const; + + /// Sets the mode for XEP-0198: Stream Management + /// + /// \pa mode The mode to set. + void setStreamManagementMode(Mode mode); + /// \cond void parse(const QDomElement &element); void toXml(QXmlStreamWriter *writer) const; @@ -68,6 +77,7 @@ private: Mode m_sessionMode; Mode m_nonSaslAuthMode; Mode m_tlsMode; + Mode m_streamManagementMode; QStringList m_authMechanisms; QStringList m_compressionMethods; }; diff --git a/src/base/QXmppStreamManagement.cpp b/src/base/QXmppStreamManagement.cpp new file mode 100644 index 00000000..02c70c5a --- /dev/null +++ b/src/base/QXmppStreamManagement.cpp @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2008-2013 The QXmpp developers + * + * Authors: + * Niels Ole Salscheider + * + * Source: + * https://github.com/qxmpp-project/qxmpp + * + * This file is a part of QXmpp library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#include "QXmppStreamManagement_p.h" +#include "QXmppStanza_p.h" + +#include "QXmppConstants_p.h" + +QXmppStreamManagementEnable::QXmppStreamManagementEnable(const bool resume, const unsigned max) + : m_resume(resume), m_max(max) +{ +} + +bool QXmppStreamManagementEnable::resume() const +{ + return m_resume; +} + +void QXmppStreamManagementEnable::setResume(bool resume) +{ + m_resume = resume; +} + +unsigned QXmppStreamManagementEnable::max() const +{ + return m_max; +} + +void QXmppStreamManagementEnable::setMax(const unsigned max) +{ + m_max = max; +} + +bool QXmppStreamManagementEnable::isStreamManagementEnable(const QDomElement &element) +{ + return element.tagName() == QLatin1String("enable") && + element.namespaceURI() == ns_stream_management; +} + +void QXmppStreamManagementEnable::parse(const QDomElement &element) +{ + QString resume = element.attribute("resume"); + m_resume = resume == QString("true") || resume == QString("1"); + m_max = element.attribute("max").toUInt(); +} + +void QXmppStreamManagementEnable::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("enable"); + writer->writeAttribute("xmlns", ns_stream_management); + if (m_resume) + writer->writeAttribute("resume", "true"); + if (m_max > 0) + writer->writeAttribute("max", QString::number(m_max)); + writer->writeEndElement(); +} + +QXmppStreamManagementEnabled::QXmppStreamManagementEnabled(const bool resume, const QString id, const unsigned max, const QString location) + : m_resume(resume), m_id(id), m_max(max), m_location(location) +{ +} + +bool QXmppStreamManagementEnabled::resume() const +{ + return m_resume; +} + +void QXmppStreamManagementEnabled::setResume(const bool resume) +{ + m_resume = resume; +} + +QString QXmppStreamManagementEnabled::id() const +{ + return m_id; +} + +void QXmppStreamManagementEnabled::setId(const QString id) +{ + m_id = id; +} + +unsigned QXmppStreamManagementEnabled::max() const +{ + return m_max; +} + +void QXmppStreamManagementEnabled::setMax(const unsigned max) +{ + m_max = max; +} + +QString QXmppStreamManagementEnabled::location() const +{ + return m_location; +} + +void QXmppStreamManagementEnabled::setLocation(const QString location) +{ + m_location = location; +} + +bool QXmppStreamManagementEnabled::isStreamManagementEnabled(const QDomElement &element) +{ + return element.tagName() == QLatin1String("enabled") && + element.namespaceURI() == ns_stream_management; +} + +void QXmppStreamManagementEnabled::parse(const QDomElement &element) +{ + QString resume = element.attribute("resume"); + m_resume = resume == QString("true") || resume == QString("1"); + m_max = element.attribute("max").toUInt(); + m_location = element.attribute("location"); +} + +void QXmppStreamManagementEnabled::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("enable"); + writer->writeAttribute("xmlns", ns_stream_management); + if (m_resume) + writer->writeAttribute("resume", "true"); + if (m_max > 0) + writer->writeAttribute("max", QString::number(m_max)); + if (!m_location.isEmpty()) + writer->writeAttribute("location", m_location); + writer->writeEndElement(); +} + +QXmppStreamManagementResume::QXmppStreamManagementResume(const unsigned h, const QString& previd) + : m_h(h), m_previd(previd) +{ +} + +unsigned QXmppStreamManagementResume::h() const +{ + return m_h; +} + +void QXmppStreamManagementResume::setH(const unsigned h) +{ + m_h = h; +} + +QString QXmppStreamManagementResume::prevId() const +{ + return m_previd; +} + +void QXmppStreamManagementResume::setPrevId(const QString& previd) +{ + m_previd = previd; +} + +bool QXmppStreamManagementResume::isStreamManagementResume(const QDomElement &element) +{ + return element.tagName() == QLatin1String("resume") && + element.namespaceURI() == ns_stream_management; +} + +void QXmppStreamManagementResume::parse(const QDomElement &element) +{ + m_h = element.attribute("h").toUInt(); + m_previd = element.attribute("previd"); +} + +void QXmppStreamManagementResume::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("resume"); + writer->writeAttribute("h", QString::number(m_h)); + writer->writeAttribute("previd", m_previd); + writer->writeEndElement(); +} + +QXmppStreamManagementResumed::QXmppStreamManagementResumed(const unsigned h, const QString& previd) + : m_h(h), m_previd(previd) +{ +} + +unsigned QXmppStreamManagementResumed::h() const +{ + return m_h; +} + +void QXmppStreamManagementResumed::setH(const unsigned h) +{ + m_h = h; +} + +QString QXmppStreamManagementResumed::prevId() const +{ + return m_previd; +} + +void QXmppStreamManagementResumed::setPrevId(const QString& previd) +{ + m_previd = previd; +} + +bool QXmppStreamManagementResumed::isStreamManagementResumed(const QDomElement &element) +{ + return element.tagName() == QLatin1String("resumed") && + element.namespaceURI() == ns_stream_management; +} + +void QXmppStreamManagementResumed::parse(const QDomElement &element) +{ + m_h = element.attribute("h").toUInt(); + m_previd = element.attribute("previd"); +} + +void QXmppStreamManagementResumed::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("resumed"); + writer->writeAttribute("h", QString::number(m_h)); + writer->writeAttribute("previd", m_previd); + writer->writeEndElement(); +} + +QXmppStreamManagementFailed::QXmppStreamManagementFailed(const QXmppStanza::Error::Condition error) + : m_error(error) +{ +} + +QXmppStanza::Error::Condition QXmppStreamManagementFailed::error() const +{ + return m_error; +} + +void QXmppStreamManagementFailed::setError(const QXmppStanza::Error::Condition error) +{ + m_error = error; +} + +bool QXmppStreamManagementFailed::isStreamManagementFailed(const QDomElement &element) +{ + return element.tagName() == QLatin1String("failed") && + element.namespaceURI() == ns_stream_management; +} + +void QXmppStreamManagementFailed::parse(const QDomElement &element) +{ + QDomElement childElement = element.firstChildElement(); + if(!childElement.isNull() && childElement.namespaceURI() == ns_stanza) { + m_error = conditionFromStr(childElement.tagName()); + } +} + +void QXmppStreamManagementFailed::toXml(QXmlStreamWriter *writer) const +{ + QString errorString = strFromCondition(m_error); + + writer->writeStartElement("failed"); + writer->writeAttribute("xmlns", ns_stream_management); + writer->writeStartElement(errorString, ns_stanza); + writer->writeEndElement(); + writer->writeEndElement(); +} + +QXmppStreamManagementAck::QXmppStreamManagementAck(const unsigned seqNo) + : m_seqNo(seqNo) +{ +} + +unsigned QXmppStreamManagementAck::seqNo() const +{ + return m_seqNo; +} + +void QXmppStreamManagementAck::setSeqNo(const unsigned seqNo) +{ + m_seqNo = seqNo; +} + +void QXmppStreamManagementAck::parse(const QDomElement &element) +{ + m_seqNo = element.attribute("h").toUInt(); +} + +void QXmppStreamManagementAck::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("a"); + writer->writeAttribute("xmlns", ns_stream_management); + writer->writeAttribute("h", QString::number(m_seqNo)); + writer->writeEndElement(); +} + +bool QXmppStreamManagementAck::isStreamManagementAck(const QDomElement &element) +{ + return element.tagName() == QLatin1String("a") && + element.namespaceURI() == ns_stream_management; +} + +bool QXmppStreamManagementReq::isStreamManagementReq(const QDomElement &element) +{ + return element.tagName() == QLatin1String("r") && + element.namespaceURI() == ns_stream_management; +} + +void QXmppStreamManagementReq::toXml(QXmlStreamWriter *writer) +{ + writer->writeStartElement("r"); + writer->writeAttribute("xmlns", ns_stream_management); + writer->writeEndElement(); +} diff --git a/src/base/QXmppStreamManagement_p.h b/src/base/QXmppStreamManagement_p.h new file mode 100644 index 00000000..2cf53c81 --- /dev/null +++ b/src/base/QXmppStreamManagement_p.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2008-2014 The QXmpp developers + * + * Authors: + * Niels Ole Salscheider + * + * Source: + * https://github.com/qxmpp-project/qxmpp + * + * This file is a part of QXmpp library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifndef QXMPPSTREAMMANAGEMENT_P_H +#define QXMPPSTREAMMANAGEMENT_P_H + +#include "QXmppGlobal.h" +#include "QXmppStanza.h" + +#include <QDomDocument> +#include <QXmlStreamWriter> + +// W A R N I N G +// ------------- +// +// This file is not part of the QXmpp API. It exists for the convenience +// of the QXmppIncomingClient and QXmppOutgoingClient classes. +// +// This header file may change from version to version without notice, +// or even be removed. +// +// We mean it. +// + +class QXMPP_AUTOTEST_EXPORT QXmppStreamManagementEnable +{ +public: + QXmppStreamManagementEnable(const bool resume = false, const unsigned max = 0); + + bool resume() const; + void setResume(const bool resume); + + unsigned max() const; + void setMax(const unsigned max); + + static bool isStreamManagementEnable(const QDomElement &element); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + bool m_resume; + unsigned m_max; +}; + +class QXMPP_AUTOTEST_EXPORT QXmppStreamManagementEnabled +{ +public: + QXmppStreamManagementEnabled(const bool resume = false, const QString id = QString(), + const unsigned max = 0, const QString location = QString()); + + bool resume() const; + void setResume(const bool resume); + + QString id() const; + void setId(const QString id); + + unsigned max() const; + void setMax(const unsigned max); + + QString location() const; + void setLocation(const QString location); + + static bool isStreamManagementEnabled(const QDomElement &element); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + bool m_resume; + QString m_id; + unsigned m_max; + QString m_location; +}; + +class QXMPP_AUTOTEST_EXPORT QXmppStreamManagementResume +{ +public: + QXmppStreamManagementResume(const unsigned h = 0, const QString& previd = QString()); + + unsigned h() const; + void setH(const unsigned h); + + QString prevId() const; + void setPrevId(const QString& id); + + static bool isStreamManagementResume(const QDomElement &element); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + unsigned m_h; + QString m_previd; +}; + +class QXMPP_AUTOTEST_EXPORT QXmppStreamManagementResumed +{ +public: + QXmppStreamManagementResumed(const unsigned h = 0, const QString& previd = QString()); + + unsigned h() const; + void setH(const unsigned h); + + QString prevId() const; + void setPrevId(const QString& id); + + static bool isStreamManagementResumed(const QDomElement &element); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + unsigned m_h; + QString m_previd; +}; + +class QXMPP_AUTOTEST_EXPORT QXmppStreamManagementFailed +{ +public: + QXmppStreamManagementFailed(const QXmppStanza::Error::Condition error = QXmppStanza::Error::UndefinedCondition); + + QXmppStanza::Error::Condition error() const; + void setError(const QXmppStanza::Error::Condition error); + + static bool isStreamManagementFailed(const QDomElement &element); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QXmppStanza::Error::Condition m_error; +}; + +class QXMPP_AUTOTEST_EXPORT QXmppStreamManagementAck +{ +public: + QXmppStreamManagementAck(const unsigned seqNo = 0); + + unsigned seqNo() const; + void setSeqNo(const unsigned seqNo); + + static bool isStreamManagementAck(const QDomElement &element); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + unsigned m_seqNo; +}; + +class QXMPP_AUTOTEST_EXPORT QXmppStreamManagementReq +{ +public: + static bool isStreamManagementReq(const QDomElement &element); + + /// \cond + static void toXml(QXmlStreamWriter *writer); + /// \endcond +}; + +#endif diff --git a/src/base/base.pri b/src/base/base.pri index 3326d07d..670de9d6 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -40,6 +40,7 @@ HEADERS += \ base/QXmppCodec_p.h \ base/QXmppConstants_p.h \ base/QXmppSasl_p.h \ + base/QXmppStanza_p.h \ base/QXmppStreamInitiationIq_p.h \ base/QXmppStun_p.h @@ -80,6 +81,7 @@ SOURCES += \ base/QXmppStream.cpp \ base/QXmppStreamFeatures.cpp \ base/QXmppStreamInitiationIq.cpp \ + base/QXmppStreamManagement.cpp \ base/QXmppStun.cpp \ base/QXmppUtils.cpp \ base/QXmppVCardIq.cpp \ |
