Merge branch '4.1'

Conflicts:
	smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java
	version.gradle
This commit is contained in:
Florian Schmaus 2015-05-24 22:36:11 +02:00
commit 01f08e87c2
7 changed files with 82 additions and 56 deletions

View File

@ -19,6 +19,7 @@ package org.jivesoftware.smack.packet;
import java.util.Locale; import java.util.Locale;
import org.jivesoftware.smack.packet.id.StanzaIdUtil;
import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.TypedCloneable; import org.jivesoftware.smack.util.TypedCloneable;
import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smack.util.XmlStringBuilder;
@ -259,6 +260,18 @@ public final class Presence extends Stanza implements TypedCloneable<Presence> {
return new Presence(this); return new Presence(this);
} }
/**
* Clone this presence and set a newly generated stanza ID as the clone's ID.
*
* @return a "clone" of this presence with a different stanza ID.
* @since 4.1.2
*/
public Presence cloneWithNewId() {
Presence clone = clone();
clone.setStanzaId(StanzaIdUtil.newStanzaId());
return clone;
}
/** /**
* An enum to represent the presence type. Note that presence type is often confused * An enum to represent the presence type. Note that presence type is often confused
* with presence mode. Generally, if a user is signed in to a server, they have a presence * with presence mode. Generally, if a user is signed in to a server, they have a presence

View File

@ -359,6 +359,23 @@ public abstract class Stanza implements TopLevelStreamElement {
} }
} }
/**
* Add the given extension and override eventually existing extensions with the same name and
* namespace.
*
* @param extension the extension element to add.
* @return one of the removed extensions or <code>null</code> if there are none.
* @since 4.1.2
*/
public ExtensionElement overrideExtension(ExtensionElement extension) {
if (extension == null) return null;
synchronized (packetExtensions) {
ExtensionElement removedExtension = removeExtension(extension);
addExtension(extension);
return removedExtension;
}
}
/** /**
* Adds a collection of stanza(/packet) extensions to the packet. Does nothing if extensions is null. * Adds a collection of stanza(/packet) extensions to the packet. Does nothing if extensions is null.
* *

View File

@ -201,7 +201,10 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
private final void authenticate() throws SmackException, NotConnectedException, InterruptedException { private final void authenticate() throws SmackException, NotConnectedException, InterruptedException {
byte[] authenticationBytes = getAuthenticationText(); byte[] authenticationBytes = getAuthenticationText();
String authenticationText; String authenticationText;
if (authenticationBytes != null) { // Some SASL mechanisms do return an empty array (e.g. EXTERNAL from javax), so check that
// the array is not-empty. Mechanisms are allowed to return either 'null' or an empty array
// if there is no authentication text.
if (authenticationBytes != null && authenticationBytes.length > 0) {
authenticationText = Base64.encodeToString(authenticationBytes); authenticationText = Base64.encodeToString(authenticationBytes);
} else { } else {
// RFC6120 6.4.2 "If the initiating entity needs to send a zero-length initial response, // RFC6120 6.4.2 "If the initiating entity needs to send a zero-length initial response,
@ -215,7 +218,8 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
/** /**
* Should return the initial response of the SASL mechanism. The returned byte array will be * Should return the initial response of the SASL mechanism. The returned byte array will be
* send base64 encoded to the server. SASL mechanism are free to return <code>null</code> here. * send base64 encoded to the server. SASL mechanism are free to return <code>null</code> or an
* empty array here.
* *
* @return the initial response or null * @return the initial response or null
* @throws SmackException * @throws SmackException

View File

@ -30,7 +30,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.jivesoftware.smack.AbstractConnectionClosedListener; import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.FeatureNotSupportedException; import org.jivesoftware.smack.SmackException.FeatureNotSupportedException;
@ -89,7 +89,7 @@ import org.jxmpp.jid.Jid;
* *
* @author Henning Staib * @author Henning Staib
*/ */
public final class Socks5BytestreamManager implements BytestreamManager { public final class Socks5BytestreamManager extends Manager implements BytestreamManager {
/* /*
* create a new Socks5BytestreamManager and register a shutdown listener on every established * create a new Socks5BytestreamManager and register a shutdown listener on every established
@ -101,22 +101,6 @@ public final class Socks5BytestreamManager implements BytestreamManager {
public void connectionCreated(final XMPPConnection connection) { public void connectionCreated(final XMPPConnection connection) {
// create the manager for this connection // create the manager for this connection
Socks5BytestreamManager.getBytestreamManager(connection); Socks5BytestreamManager.getBytestreamManager(connection);
// register shutdown listener
connection.addConnectionListener(new AbstractConnectionClosedListener() {
@Override
public void connectionTerminated() {
Socks5BytestreamManager.getBytestreamManager(connection).disableService();
}
@Override
public void reconnectionSuccessful() {
// re-create the manager for this connection
Socks5BytestreamManager.getBytestreamManager(connection);
}
});
} }
}); });
@ -131,9 +115,6 @@ public final class Socks5BytestreamManager implements BytestreamManager {
/* stores one Socks5BytestreamManager for each XMPP connection */ /* stores one Socks5BytestreamManager for each XMPP connection */
private final static Map<XMPPConnection, Socks5BytestreamManager> managers = new HashMap<XMPPConnection, Socks5BytestreamManager>(); private final static Map<XMPPConnection, Socks5BytestreamManager> managers = new HashMap<XMPPConnection, Socks5BytestreamManager>();
/* XMPP connection */
private final XMPPConnection connection;
/* /*
* assigns a user to a listener that is informed if a bytestream request for this user is * assigns a user to a listener that is informed if a bytestream request for this user is
* received * received
@ -188,7 +169,6 @@ public final class Socks5BytestreamManager implements BytestreamManager {
if (manager == null) { if (manager == null) {
manager = new Socks5BytestreamManager(connection); manager = new Socks5BytestreamManager(connection);
managers.put(connection, manager); managers.put(connection, manager);
manager.activate();
} }
return manager; return manager;
} }
@ -199,8 +179,9 @@ public final class Socks5BytestreamManager implements BytestreamManager {
* @param connection the XMPP connection * @param connection the XMPP connection
*/ */
private Socks5BytestreamManager(XMPPConnection connection) { private Socks5BytestreamManager(XMPPConnection connection) {
this.connection = connection; super(connection);
this.initiationListener = new InitiationListener(this); this.initiationListener = new InitiationListener(this);
activate();
} }
/** /**
@ -285,7 +266,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
* Using the file transfer API will automatically re-enable the SOCKS5 Bytestream feature. * Using the file transfer API will automatically re-enable the SOCKS5 Bytestream feature.
*/ */
public synchronized void disableService() { public synchronized void disableService() {
XMPPConnection connection = connection();
// remove initiation packet listener // remove initiation packet listener
connection.unregisterIQRequestHandler(initiationListener); connection.unregisterIQRequestHandler(initiationListener);
@ -302,7 +283,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
this.ignoredBytestreamRequests.clear(); this.ignoredBytestreamRequests.clear();
// remove manager from static managers map // remove manager from static managers map
managers.remove(this.connection); managers.remove(connection);
// shutdown local SOCKS5 proxy if there are no more managers for other connections // shutdown local SOCKS5 proxy if there are no more managers for other connections
if (managers.size() == 0) { if (managers.size() == 0) {
@ -310,7 +291,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
} }
// remove feature from service discovery // remove feature from service discovery
ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
// check if service discovery is not already disposed by connection shutdown // check if service discovery is not already disposed by connection shutdown
if (serviceDiscoveryManager != null) { if (serviceDiscoveryManager != null) {
@ -426,7 +407,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
*/ */
public Socks5BytestreamSession establishSession(Jid targetJID, String sessionID) public Socks5BytestreamSession establishSession(Jid targetJID, String sessionID)
throws IOException, InterruptedException, NoResponseException, SmackException, XMPPException{ throws IOException, InterruptedException, NoResponseException, SmackException, XMPPException{
XMPPConnection connection = connection();
XMPPErrorException discoveryException = null; XMPPErrorException discoveryException = null;
// check if target supports SOCKS5 Bytestream // check if target supports SOCKS5 Bytestream
if (!supportsSocks5(targetJID)) { if (!supportsSocks5(targetJID)) {
@ -455,7 +436,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
} }
// compute digest // compute digest
String digest = Socks5Utils.createDigest(sessionID, this.connection.getUser(), targetJID); String digest = Socks5Utils.createDigest(sessionID, connection.getUser(), targetJID);
// prioritize last working SOCKS5 proxy if exists // prioritize last working SOCKS5 proxy if exists
if (this.proxyPrioritizationEnabled && this.lastWorkingProxy != null) { if (this.proxyPrioritizationEnabled && this.lastWorkingProxy != null) {
@ -496,7 +477,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
// build SOCKS5 client // build SOCKS5 client
Socks5Client socks5Client = new Socks5ClientForInitiator(usedStreamHost, digest, Socks5Client socks5Client = new Socks5ClientForInitiator(usedStreamHost, digest,
this.connection, sessionID, targetJID); connection, sessionID, targetJID);
// establish connection to proxy // establish connection to proxy
Socket socket = socks5Client.getSocket(getProxyConnectionTimeout()); Socket socket = socks5Client.getSocket(getProxyConnectionTimeout());
@ -506,7 +487,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
// negotiation successful, return the output stream // negotiation successful, return the output stream
return new Socks5BytestreamSession(socket, usedStreamHost.getJID().equals( return new Socks5BytestreamSession(socket, usedStreamHost.getJID().equals(
this.connection.getUser())); connection.getUser()));
} }
catch (TimeoutException e) { catch (TimeoutException e) {
@ -533,7 +514,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
* @throws InterruptedException * @throws InterruptedException
*/ */
private boolean supportsSocks5(Jid targetJID) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { private boolean supportsSocks5(Jid targetJID) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return ServiceDiscoveryManager.getInstanceFor(connection).supportsFeature(targetJID, Bytestream.NAMESPACE); return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(targetJID, Bytestream.NAMESPACE);
} }
/** /**
@ -547,12 +528,13 @@ public final class Socks5BytestreamManager implements BytestreamManager {
* @throws InterruptedException * @throws InterruptedException
*/ */
private List<Jid> determineProxies() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { private List<Jid> determineProxies() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); XMPPConnection connection = connection();
ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
List<Jid> proxies = new ArrayList<>(); List<Jid> proxies = new ArrayList<>();
// get all items from XMPP server // get all items from XMPP server
DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(this.connection.getXMPPServiceDomain()); DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(connection.getXMPPServiceDomain());
// query all items if they are SOCKS5 proxies // query all items if they are SOCKS5 proxies
for (Item item : discoverItems.getItems()) { for (Item item : discoverItems.getItems()) {
@ -595,6 +577,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
* @return a list of stream hosts containing the IP address an the port * @return a list of stream hosts containing the IP address an the port
*/ */
private List<StreamHost> determineStreamHostInfos(List<Jid> proxies) { private List<StreamHost> determineStreamHostInfos(List<Jid> proxies) {
XMPPConnection connection = connection();
List<StreamHost> streamHosts = new ArrayList<StreamHost>(); List<StreamHost> streamHosts = new ArrayList<StreamHost>();
// add local proxy on first position if exists // add local proxy on first position if exists
@ -641,7 +624,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
* is not running * is not running
*/ */
private List<StreamHost> getLocalStreamHost() { private List<StreamHost> getLocalStreamHost() {
XMPPConnection connection = connection();
// get local proxy singleton // get local proxy singleton
Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy(); Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy();
@ -710,7 +693,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
protected void replyRejectPacket(IQ packet) throws NotConnectedException, InterruptedException { protected void replyRejectPacket(IQ packet) throws NotConnectedException, InterruptedException {
XMPPError xmppError = new XMPPError(XMPPError.Condition.not_acceptable); XMPPError xmppError = new XMPPError(XMPPError.Condition.not_acceptable);
IQ errorIQ = IQ.createErrorResponse(packet, xmppError); IQ errorIQ = IQ.createErrorResponse(packet, xmppError);
this.connection.sendStanza(errorIQ); connection().sendStanza(errorIQ);
} }
/** /**
@ -719,7 +702,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
*/ */
private void activate() { private void activate() {
// register bytestream initiation packet listener // register bytestream initiation packet listener
connection.registerIQRequestHandler(initiationListener); connection().registerIQRequestHandler(initiationListener);
// enable SOCKS5 feature // enable SOCKS5 feature
enableService(); enableService();
@ -729,7 +712,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
* Adds the SOCKS5 Bytestream feature to the service discovery. * Adds the SOCKS5 Bytestream feature to the service discovery.
*/ */
private void enableService() { private void enableService() {
ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(this.connection); ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(connection());
manager.addFeature(Bytestream.NAMESPACE); manager.addFeature(Bytestream.NAMESPACE);
} }
@ -751,7 +734,7 @@ public final class Socks5BytestreamManager implements BytestreamManager {
* @return the XMPP connection * @return the XMPP connection
*/ */
protected XMPPConnection getConnection() { protected XMPPConnection getConnection() {
return this.connection; return connection();
} }
/** /**

View File

@ -17,6 +17,7 @@
package org.jivesoftware.smackx.bytestreams.socks5; package org.jivesoftware.smackx.bytestreams.socks5;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.Socket; import java.net.Socket;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@ -42,7 +43,7 @@ import org.jxmpp.jid.Jid;
class Socks5ClientForInitiator extends Socks5Client { class Socks5ClientForInitiator extends Socks5Client {
/* the XMPP connection used to communicate with the SOCKS5 proxy */ /* the XMPP connection used to communicate with the SOCKS5 proxy */
private XMPPConnection connection; private WeakReference<XMPPConnection> connection;
/* the session ID used to activate SOCKS5 stream */ /* the session ID used to activate SOCKS5 stream */
private String sessionID; private String sessionID;
@ -63,7 +64,7 @@ class Socks5ClientForInitiator extends Socks5Client {
public Socks5ClientForInitiator(StreamHost streamHost, String digest, XMPPConnection connection, public Socks5ClientForInitiator(StreamHost streamHost, String digest, XMPPConnection connection,
String sessionID, Jid target) { String sessionID, Jid target) {
super(streamHost, digest); super(streamHost, digest);
this.connection = connection; this.connection = new WeakReference<>(connection);
this.sessionID = sessionID; this.sessionID = sessionID;
this.target = target; this.target = target;
} }
@ -73,7 +74,7 @@ class Socks5ClientForInitiator extends Socks5Client {
Socket socket = null; Socket socket = null;
// check if stream host is the local SOCKS5 proxy // check if stream host is the local SOCKS5 proxy
if (this.streamHost.getJID().equals(this.connection.getUser())) { if (this.streamHost.getJID().equals(this.connection.get().getUser())) {
Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy(); Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy();
socket = socks5Server.getSocket(this.digest); socket = socks5Server.getSocket(this.digest);
if (socket == null) { if (socket == null) {
@ -112,7 +113,7 @@ class Socks5ClientForInitiator extends Socks5Client {
private void activate() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { private void activate() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
Bytestream activate = createStreamHostActivation(); Bytestream activate = createStreamHostActivation();
// if activation fails #nextResultOrThrow() throws an exception // if activation fails #nextResultOrThrow() throws an exception
connection.createPacketCollectorAndSend(activate).nextResultOrThrow(); connection.get().createPacketCollectorAndSend(activate).nextResultOrThrow();
} }
/** /**

View File

@ -30,6 +30,7 @@ import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.filter.NotFilter; import org.jivesoftware.smack.filter.NotFilter;
import org.jivesoftware.smack.filter.PresenceTypeFilter;
import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.StanzaTypeFilter; import org.jivesoftware.smack.filter.StanzaTypeFilter;
@ -96,7 +97,6 @@ public final class EntityCapsManager extends Manager {
ELEMENT, NAMESPACE)); ELEMENT, NAMESPACE));
private static final StanzaFilter PRESENCES_WITHOUT_CAPS = new AndFilter(new StanzaTypeFilter(Presence.class), new NotFilter(new StanzaExtensionFilter( private static final StanzaFilter PRESENCES_WITHOUT_CAPS = new AndFilter(new StanzaTypeFilter(Presence.class), new NotFilter(new StanzaExtensionFilter(
ELEMENT, NAMESPACE))); ELEMENT, NAMESPACE)));
private static final StanzaFilter PRESENCES = StanzaTypeFilter.PRESENCE;
/** /**
* Map of "node + '#' + hash" to DiscoverInfo data * Map of "node + '#' + hash" to DiscoverInfo data
@ -264,7 +264,7 @@ public final class EntityCapsManager extends Manager {
private boolean entityCapsEnabled; private boolean entityCapsEnabled;
private CapsVersionAndHash currentCapsVersion; private CapsVersionAndHash currentCapsVersion;
private boolean presenceSend = false; private volatile Presence presenceSend;
/** /**
* The entity node String used by this EntityCapsManager instance. * The entity node String used by this EntityCapsManager instance.
@ -293,7 +293,7 @@ public final class EntityCapsManager extends Manager {
// Reset presenceSend when the connection was not resumed // Reset presenceSend when the connection was not resumed
if (!resumed) { if (!resumed) {
presenceSend = false; presenceSend = null;
} }
} }
private void processCapsStreamFeatureIfAvailable(XMPPConnection connection) { private void processCapsStreamFeatureIfAvailable(XMPPConnection connection) {
@ -341,23 +341,26 @@ public final class EntityCapsManager extends Manager {
connection.addPacketSendingListener(new StanzaListener() { connection.addPacketSendingListener(new StanzaListener() {
@Override @Override
public void processPacket(Stanza packet) { public void processPacket(Stanza packet) {
presenceSend = true; presenceSend = (Presence) packet;
} }
}, PRESENCES); }, PresenceTypeFilter.AVAILABLE);
// Intercept presence packages and add caps data when intended. // Intercept presence packages and add caps data when intended.
// XEP-0115 specifies that a client SHOULD include entity capabilities // XEP-0115 specifies that a client SHOULD include entity capabilities
// with every presence notification it sends. // with every presence notification it sends.
StanzaListener packetInterceptor = new StanzaListener() { StanzaListener packetInterceptor = new StanzaListener() {
public void processPacket(Stanza packet) { public void processPacket(Stanza packet) {
if (!entityCapsEnabled) if (!entityCapsEnabled) {
// Be sure to not send stanzas with the caps extension if it's not enabled
packet.removeExtension(CapsExtension.ELEMENT, CapsExtension.NAMESPACE);
return; return;
}
CapsVersionAndHash capsVersionAndHash = getCapsVersionAndHash(); CapsVersionAndHash capsVersionAndHash = getCapsVersionAndHash();
CapsExtension caps = new CapsExtension(entityNode, capsVersionAndHash.version, capsVersionAndHash.hash); CapsExtension caps = new CapsExtension(entityNode, capsVersionAndHash.version, capsVersionAndHash.hash);
packet.addExtension(caps); packet.overrideExtension(caps);
} }
}; };
connection.addPacketInterceptor(packetInterceptor, PRESENCES); connection.addPacketInterceptor(packetInterceptor, PresenceTypeFilter.AVAILABLE);
// It's important to do this as last action. Since it changes the // It's important to do this as last action. Since it changes the
// behavior of the SDM in some ways // behavior of the SDM in some ways
sdm.setEntityCapsManager(this); sdm.setEntityCapsManager(this);
@ -506,15 +509,14 @@ public final class EntityCapsManager extends Manager {
} }
}); });
// Send an empty presence, and let the packet interceptor // Re-send the last sent presence, and let the stanza interceptor
// add a <c/> node to it. // add a <c/> node to it.
// See http://xmpp.org/extensions/xep-0115.html#advertise // See http://xmpp.org/extensions/xep-0115.html#advertise
// We only send a presence packet if there was already one send // We only send a presence packet if there was already one send
// to respect ConnectionConfiguration.isSendPresence() // to respect ConnectionConfiguration.isSendPresence()
if (connection != null && connection.isAuthenticated() && presenceSend) { if (connection != null && connection.isAuthenticated() && presenceSend != null) {
Presence presence = new Presence(Presence.Type.available);
try { try {
connection.sendStanza(presence); connection.sendStanza(presenceSend.cloneWithNewId());
} }
catch (InterruptedException | NotConnectedException e) { catch (InterruptedException | NotConnectedException e) {
LOGGER.log(Level.WARNING, "Could could not update presence with caps info", e); LOGGER.log(Level.WARNING, "Could could not update presence with caps info", e);

View File

@ -1075,6 +1075,12 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
smResumedSyncPoint.reportSuccess(); smResumedSyncPoint.reportSuccess();
smEnabledSyncPoint.reportSuccess(); smEnabledSyncPoint.reportSuccess();
// If there where stanzas resent, then request a SM ack for them.
// Writer's sendStreamElement() won't do it automatically based on
// predicates.
if (!stanzasToResend.isEmpty()) {
requestSmAcknowledgementInternal();
}
LOGGER.fine("Stream Management (XEP-198): Stream resumed"); LOGGER.fine("Stream Management (XEP-198): Stream resumed");
break; break;
case AckAnswer.ELEMENT: case AckAnswer.ELEMENT: