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/client/QXmppOutgoingClient.cpp | |
| parent | 9deb86b248fee6bb9bcee14d595a933c8fdc4aa2 (diff) | |
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/client/QXmppOutgoingClient.cpp')
| -rw-r--r-- | src/client/QXmppOutgoingClient.cpp | 229 |
1 files changed, 180 insertions, 49 deletions
diff --git a/src/client/QXmppOutgoingClient.cpp b/src/client/QXmppOutgoingClient.cpp index 133d61ee..552f6f07 100644 --- a/src/client/QXmppOutgoingClient.cpp +++ b/src/client/QXmppOutgoingClient.cpp @@ -40,6 +40,7 @@ #include "QXmppPresence.h" #include "QXmppOutgoingClient.h" #include "QXmppStreamFeatures.h" +#include "QXmppStreamManagement_p.h" #include "QXmppNonSASLAuth.h" #include "QXmppSasl_p.h" #include "QXmppUtils.h" @@ -64,6 +65,12 @@ public: QXmppOutgoingClientPrivate(QXmppOutgoingClient *q); void connectToHost(const QString &host, quint16 port); + void sendNonSASLAuth(bool plaintext); + void sendNonSASLAuthQuery(); + void sendBind(); + void sendSessionStart(); + void sendStreamManagementEnable(); + // This object provides the configuration // required for connecting to the XMPP server. QXmppConfiguration config; @@ -84,6 +91,7 @@ public: // Session QString bindId; QString sessionId; + bool bindModeAvailable; bool sessionAvailable; bool sessionStarted; @@ -92,6 +100,14 @@ public: QString nonSASLAuthId; QXmppSaslClient *saslClient; + // Stream Management + bool streamManagementAvailable; + QString smId; + bool canResume; + bool isResuming; + QString resumeHost; + quint16 resumePort; + // Timers QTimer *pingTimer; QTimer *timeoutTimer; @@ -102,10 +118,15 @@ private: QXmppOutgoingClientPrivate::QXmppOutgoingClientPrivate(QXmppOutgoingClient *qq) : redirectPort(0) + , bindModeAvailable(false) , sessionAvailable(false) , sessionStarted(false) , isAuthenticated(false) , saslClient(0) + , streamManagementAvailable(false) + , canResume(false) + , isResuming(false) + , resumePort(0) , pingTimer(0) , timeoutTimer(0) , q(qq) @@ -212,6 +233,12 @@ QXmppConfiguration& QXmppOutgoingClient::configuration() void QXmppOutgoingClient::connectToHost() { + // if a host for resumption is available, connect to it + if (d->canResume && !d->resumeHost.isEmpty() && d->resumePort) { + d->connectToHost(d->resumeHost, d->resumePort); + return; + } + // if an explicit host was provided, connect to it if (!d->config.host().isEmpty() && d->config.port()) { d->connectToHost(d->config.host(), d->config.port()); @@ -226,6 +253,12 @@ void QXmppOutgoingClient::connectToHost() d->dns.lookup(); } +void QXmppOutgoingClient::disconnectFromHost() +{ + d->canResume = false; + QXmppStream::disconnectFromHost(); +} + void QXmppOutgoingClient::_q_dnsLookupFinished() { if (d->dns.error() == QDnsLookup::NoError && @@ -332,7 +365,7 @@ void QXmppOutgoingClient::handleStream(const QDomElement &streamElement) // no version specified, signals XMPP Version < 1.0. // switch to old auth mechanism if enabled if(d->streamVersion.isEmpty() && configuration().useNonSASLAuthentication()) { - sendNonSASLAuthQuery(); + d->sendNonSASLAuthQuery(); } } } @@ -451,38 +484,41 @@ void QXmppOutgoingClient::handleStanza(const QDomElement &nodeRecv) sendPacket(QXmppSaslAuth(d->saslClient->mechanism(), response)); return; } else if(nonSaslAvailable && configuration().useNonSASLAuthentication()) { - sendNonSASLAuthQuery(); + d->sendNonSASLAuthQuery(); return; } - // store whether session is available + // store which features are available d->sessionAvailable = (features.sessionMode() != QXmppStreamFeatures::Disabled); + d->bindModeAvailable = (features.bindMode() != QXmppStreamFeatures::Disabled); + d->streamManagementAvailable = (features.streamManagementMode() != QXmppStreamFeatures::Disabled); + + // chech whether the stream can be resumed + if (d->streamManagementAvailable && d->canResume) { + d->isResuming = true; + QXmppStreamManagementResume streamManagementResume(lastIncomingSequenceNumber(), d->smId); + QByteArray data; + QXmlStreamWriter xmlStream(&data); + streamManagementResume.toXml(&xmlStream); + sendData(data); + return; + } // check whether bind is available - if (features.bindMode() != QXmppStreamFeatures::Disabled) - { - QXmppBindIq bind; - bind.setType(QXmppIq::Set); - bind.setResource(configuration().resource()); - d->bindId = bind.id(); - sendPacket(bind); + if (d->bindModeAvailable) { + d->sendBind(); return; } // check whether session is available - if (d->sessionAvailable) - { - // start session if it is available - QXmppSessionIq session; - session.setType(QXmppIq::Set); - session.setTo(configuration().domain()); - d->sessionId = session.id(); - sendPacket(session); - } else { - // otherwise we are done - d->sessionStarted = true; - emit connected(); + if (d->sessionAvailable) { + d->sendSessionStart(); + return; } + + // otherwise we are done + d->sessionStarted = true; + emit connected(); } else if(ns == ns_stream && nodeRecv.tagName() == "error") { @@ -494,7 +530,7 @@ void QXmppOutgoingClient::handleStanza(const QDomElement &nodeRecv) d->redirectPort = redirectRegex.cap(2).mid(1).toUShort(); else d->redirectPort = 5222; - disconnectFromHost(); + QXmppStream::disconnectFromHost(); return; } @@ -571,10 +607,17 @@ void QXmppOutgoingClient::handleStanza(const QDomElement &nodeRecv) { QXmppSessionIq session; session.parse(nodeRecv); - - // xmpp connection made d->sessionStarted = true; - emit connected(); + + if(d->streamManagementAvailable) + { + d->sendStreamManagementEnable(); + } + else + { + // we are connected now + emit connected(); + } } else if(QXmppBindIq::isBindIq(nodeRecv) && id == d->bindId) { @@ -597,18 +640,17 @@ void QXmppOutgoingClient::handleStanza(const QDomElement &nodeRecv) } } - if (d->sessionAvailable) - { - // start session if it is available - QXmppSessionIq session; - session.setType(QXmppIq::Set); - session.setTo(configuration().domain()); - d->sessionId = session.id(); - sendPacket(session); + if (d->sessionAvailable) { + d->sendSessionStart(); } else { - // otherwise we are done d->sessionStarted = true; - emit connected(); + + if (d->streamManagementAvailable) { + d->sendStreamManagementEnable(); + } else { + // we are connected now + emit connected(); + } } } } @@ -653,7 +695,7 @@ void QXmppOutgoingClient::handleStanza(const QDomElement &nodeRecv) disconnectFromHost(); return; } - sendNonSASLAuth(plainText); + d->sendNonSASLAuth(plainText); } } // XEP-0199: XMPP Ping @@ -705,6 +747,68 @@ void QXmppOutgoingClient::handleStanza(const QDomElement &nodeRecv) emit messageReceived(message); } } + else if(QXmppStreamManagementEnabled::isStreamManagementEnabled(nodeRecv)) + { + QXmppStreamManagementEnabled streamManagementEnabled; + streamManagementEnabled.parse(nodeRecv); + d->smId = streamManagementEnabled.id(); + d->canResume = streamManagementEnabled.resume(); + if (streamManagementEnabled.resume() && !streamManagementEnabled.location().isEmpty()) { + QRegExp locationRegex("([^:]+)(:[0-9]+)?"); + if (locationRegex.exactMatch(streamManagementEnabled.location())) { + d->resumeHost = locationRegex.cap(0); + if (!locationRegex.cap(2).isEmpty()) + d->resumePort = locationRegex.cap(2).mid(1).toUShort(); + else + d->resumePort = 5222; + } else { + d->resumeHost = QString(); + d->resumePort = 0; + } + } + + enableStreamManagement(true); + // we are connected now + emit connected(); + } + else if(QXmppStreamManagementResumed::isStreamManagementResumed(nodeRecv)) + { + QXmppStreamManagementResumed streamManagementResumed; + streamManagementResumed.parse(nodeRecv); + setAcknowledgedSequenceNumber(streamManagementResumed.h()); + d->isResuming = false; + + enableStreamManagement(false); + // we are connected now + // TODO: The stream was resumed. Therefore, we should not send presence information or request the roster. + emit connected(); + } + else if(QXmppStreamManagementFailed::isStreamManagementFailed(nodeRecv)) + { + if (d->isResuming) { + // resuming failed. We can try to bind a resource now. + d->isResuming = false; + + // check whether bind is available + if (d->bindModeAvailable) { + d->sendBind(); + return; + } + + // check whether session is available + if (d->sessionAvailable) { + d->sendSessionStart(); + return; + } + + // otherwise we are done + d->sessionStarted = true; + emit connected(); + } else { + // we are connected now, but stream management is disabled + emit connected(); + } + } } /// \endcond @@ -745,33 +849,60 @@ void QXmppOutgoingClient::pingSend() void QXmppOutgoingClient::pingTimeout() { warning("Ping timeout"); - disconnectFromHost(); + QXmppStream::disconnectFromHost(); emit error(QXmppClient::KeepAliveError); } -void QXmppOutgoingClient::sendNonSASLAuth(bool plainText) +void QXmppOutgoingClientPrivate::sendNonSASLAuth(bool plainText) { QXmppNonSASLAuthIq authQuery; authQuery.setType(QXmppIq::Set); - authQuery.setUsername(configuration().user()); + authQuery.setUsername(q->configuration().user()); if (plainText) - authQuery.setPassword(configuration().password()); + authQuery.setPassword(q->configuration().password()); else - authQuery.setDigest(d->streamId, configuration().password()); - authQuery.setResource(configuration().resource()); - d->nonSASLAuthId = authQuery.id(); - sendPacket(authQuery); + authQuery.setDigest(streamId, q->configuration().password()); + authQuery.setResource(q->configuration().resource()); + nonSASLAuthId = authQuery.id(); + q->sendPacket(authQuery); } -void QXmppOutgoingClient::sendNonSASLAuthQuery() +void QXmppOutgoingClientPrivate::sendNonSASLAuthQuery() { QXmppNonSASLAuthIq authQuery; authQuery.setType(QXmppIq::Get); - authQuery.setTo(d->streamFrom); + authQuery.setTo(streamFrom); // FIXME : why are we setting the username, XEP-0078 states we should // not attempt to guess the required fields? - authQuery.setUsername(configuration().user()); - sendPacket(authQuery); + authQuery.setUsername(q->configuration().user()); + q->sendPacket(authQuery); +} + +void QXmppOutgoingClientPrivate::sendBind() +{ + QXmppBindIq bind; + bind.setType(QXmppIq::Set); + bind.setResource(q->configuration().resource()); + bindId = bind.id(); + q->sendPacket(bind); +} + +void QXmppOutgoingClientPrivate::sendSessionStart() +{ + QXmppSessionIq session; + session.setType(QXmppIq::Set); + session.setTo(q->configuration().domain()); + sessionId = session.id(); + q->sendPacket(session); +} + +void QXmppOutgoingClientPrivate::sendStreamManagementEnable() +{ + QXmppStreamManagementEnable streamManagementEnable(true); + QByteArray data; + QXmlStreamWriter xmlStream(&data); + streamManagementEnable.toXml(&xmlStream); + q->sendData(data); } /// Returns the type of the last XMPP stream error that occured. |
