1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-29 23:42:06 +01:00

Compare commits

..

22 commits

Author SHA1 Message Date
Florian Schmaus
daab6039a1 Smack 4.3.3
-----BEGIN PGP SIGNATURE-----
 
 iQGTBAABCgB9FiEEl3UFnzoh3OFr5PuuIjmn6PWFIFIFAlyKV9tfFIAAAAAALgAo
 aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDk3
 NzUwNTlGM0EyMURDRTE2QkU0RkJBRTIyMzlBN0U4RjU4NTIwNTIACgkQIjmn6PWF
 IFL4vQf/Qfg3VzNEnmk0+KjOtuvfAbhMfzE92gfo15vE0PPEIe9VA0Pzkvqhva4k
 Efw7BhD2zx8hWvo0d5FfkdII89hSYnOCiSmhiX1Ln9q/gUqFW0TDAKpsMfAl7jAK
 Fap8M7uUStP9T6fF/gq01djYCYoWA/4v1lGKv4J4b9gWCqzGIF2sK0M7of7VERnr
 pEXJSUM228rk6EYjVmX/9Ujo1Y+xaNMFFZZSbKYrirAqjP540v0OPBCRQyB8qFaI
 NTcNJ+qESe6Q80mw5V+y/kD6kX0LERSDB+pigzOOOlmfMtD1uADR84tAynuTKNAU
 7/5K/YSQxceRY6RTgD17Al5lHIH5nw==
 =ToR+
 -----END PGP SIGNATURE-----

Merge tag '4.3.3'

Smack 4.3.3
2019-03-14 18:23:33 +01:00
Florian Schmaus
b054c4fe77 Smack 4.3.3 2019-03-14 14:31:09 +01:00
Florian Schmaus
0de0873abb version.gradle: Add link to SMACK-858 2019-03-14 14:29:23 +01:00
Georg Lukas
e5bbd19ef1 StanzaDroppedListener for XEP-0198 resumption failures
If a stream resume fails, smack will re-send all queued stanzas after a
reconnect. However, it does not make sense to re-send them:

* IQs / IQ responses have probably timed out
* MUC messages and PMs will be rejected as you haven't rejoined yet
* regular messages should be amended with a <delay> element

This patch adds a StanzaDroppedListener interface to the
XMPPTCPConnection. If at least one StanzaDroppedListener is provided,
all queued messages will be drained into the StanzaDroppedListener(s).
Otherwise, the original behavior of attempting to transmit them will be
followed.

Discussion: https://discourse.igniterealtime.org/t/xep-0198-resume-failure-reconnect-resending-of-muc-messages/83510/3

Signed-off-by: Georg Lukas <georg@op-co.de>
2019-03-10 21:24:37 +01:00
Florian Schmaus
569f7417a8 Add AuthenticatedConnectionInitiallyEstablished timestamp 2019-03-10 21:24:37 +01:00
Florian Schmaus
c4289b2c18 Add AbstractXMPPConnection.initState()
and init/reset the sychronization points there.

This method is called right at the beginning of connect() and at the
end of shutdown().
2019-03-10 21:24:37 +01:00
Florian Schmaus
7518bf9a25 Add descriptive text to StanzaError.toString() 2019-03-10 21:24:37 +01:00
Florian Schmaus
5da6dea138 Throw exception to reduce call sites of notifyConnectionError()
in XMPPTCPConnection.
2019-03-10 21:24:37 +01:00
Florian Schmaus
7d2c3ac9f9 Do not call synchronized methods in reader/writer thread
This may cause deadlocks with a call to acquire(2) on the introduced
readerWriterSemaphore in initConnection(), which is also synchronized.
2019-03-10 21:24:37 +01:00
Florian Schmaus
3d1a781a22 Show correct reply timeout value in StanzaCollector's NoResponseException 2019-03-05 08:21:59 +01:00
Florian Schmaus
7f0932a481 Reset the MUC self-presence collector on presence stanzas on join
To prevent timeouts when joining very large MUCs we now reset the
self-presence collector's timeout for every other (occupant) presence
we receive.

Fixes SMACK-859.
2019-03-04 23:01:52 +01:00
Florian Schmaus
f602de8771 Call shutdown() in connect() on exception
to clean up the state build up by connect().

Related to SMACK-855 there is the possiblitiy of a stray (writer)
thread if, for example, tlsHandled.checkifSuccessOrWaitorThrow() in
XMPPTCPConnection.connectInternal() throws. This commit should prevent
that.
2019-03-04 20:14:12 +01:00
Florian Schmaus
f4ebd530e6 Add note about module dependencies to JavaxResolver
Related to SMACK-856.
2019-03-02 14:47:28 +01:00
Florian Schmaus
78ee22c261 Revert "Do not set com.sun.jndi.dns.DnsContextFactory in JavaxResolver"
This reverts commit ac9641f091.

Reverted because now an NoInitialContextException is now thrown.

Related to SMACK-856.
2019-03-02 14:42:19 +01:00
Florian Schmaus
456d645e27 Use different version specifier for jxmpp and MiniDNS
Fixes SMACK-858.
2019-02-24 22:09:47 +01:00
Florian Schmaus
3bdc1d30b1 Correctly name provider INSTANCE fields
and make them 'final' where possible.
2019-02-23 23:59:17 +01:00
Florian Schmaus
5f7cfd04bd Add further unit test to StableUniqueStanzaIdTest 2019-02-23 23:59:17 +01:00
Florian Schmaus
5a2109e73f Move cast in extra line in ADD_ORIGIN_ID_INTERCEPTOR 2019-02-23 23:40:27 +01:00
Florian Schmaus
4da4558b29 Make origin-id interceptor static and rename it 2019-02-23 23:39:58 +01:00
Florian Schmaus
27749b2137 Remove unused filter in StableUniqueStanzaIdManager 2019-02-23 23:39:41 +01:00
Florian Schmaus
2b9cbb978e Enable full stacktraces of failed tests in console 2019-02-22 23:02:40 +01:00
Florian Schmaus
47f76952e3 Smack 4.3.3-SNAPSHOT 2019-02-22 10:57:48 +01:00
16 changed files with 259 additions and 63 deletions

View file

@ -113,6 +113,15 @@ allprojects {
version += '-SNAPSHOT' version += '-SNAPSHOT'
} }
test {
// Enable full stacktraces of failed tests. Especially handy
// for environments like Travis.
testLogging {
events "failed"
exceptionFormat "full"
}
}
ext.sharedManifest = manifest { ext.sharedManifest = manifest {
attributes('Implementation-Version': version, attributes('Implementation-Version': version,
'Implementation-GitRevision': ext.gitCommit, 'Implementation-GitRevision': ext.gitCommit,

View file

@ -141,6 +141,24 @@ hr {
<div id="pageBody"> <div id="pageBody">
<h2>4.3.3 -- <span style="font-weight: normal;">2019-03-14</span></h2>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-856'>SMACK-856</a>] - Smack fails under JDK 11 because com.sun.jndi.dns.DnsContextFactory is not inaccessible
</li>
</ul>
<h2> Improvement
</h2>
<ul>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-858'>SMACK-858</a>] - Dependency version specifier of jxmpp and MiniDNS include alpha/beta/... versions of the follow up version when Maven is used
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-859'>SMACK-859</a>] - MultiUserChat enter() should reset the timeout of the collector waiting for the final self presence to prevent timeouts for large MUCs
</li>
</ul>
<h2>4.3.2 -- <span style="font-weight: normal;">2019-02-22</span></h2> <h2>4.3.2 -- <span style="font-weight: normal;">2019-02-22</span></h2>
<h2> Bug <h2> Bug

View file

@ -266,6 +266,11 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
client = null; client = null;
} }
instantShutdown();
}
@Override
public void instantShutdown() {
setWasAuthenticated(); setWasAuthenticated();
sessionID = null; sessionID = null;
done = true; done = true;

View file

@ -111,6 +111,7 @@ import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smack.provider.NonzaProvider; import org.jivesoftware.smack.provider.NonzaProvider;
import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.sasl.core.SASLAnonymous; import org.jivesoftware.smack.sasl.core.SASLAnonymous;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.DNSUtil; import org.jivesoftware.smack.util.DNSUtil;
import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.PacketParserUtils;
@ -357,6 +358,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
*/ */
protected boolean authenticated = false; protected boolean authenticated = false;
// TODO: Migrate to ZonedDateTime once Smack's minimum required Android SDK level is 26 (8.0, Oreo) or higher.
protected long authenticatedConnectionInitiallyEstablishedTimestamp;
/** /**
* Flag that indicates if the user was authenticated with the server when the connection * Flag that indicates if the user was authenticated with the server when the connection
* to the server was closed (abruptly or not). * to the server was closed (abruptly or not).
@ -449,6 +453,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
@Override @Override
public abstract boolean isUsingCompression(); public abstract boolean isUsingCompression();
protected void initState() {
saslFeatureReceived.init();
lastFeaturesReceived.init();
tlsHandled.init();
}
/** /**
* Establishes a connection to the XMPP server. It basically * Establishes a connection to the XMPP server. It basically
* creates and maintains a connection to the server. * creates and maintains a connection to the server.
@ -467,12 +477,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
throwAlreadyConnectedExceptionIfAppropriate(); throwAlreadyConnectedExceptionIfAppropriate();
// Reset the connection state // Reset the connection state
initState();
saslAuthentication.init(); saslAuthentication.init();
saslFeatureReceived.init();
lastFeaturesReceived.init();
tlsHandled.init();
streamId = null; streamId = null;
try {
// Perform the actual connection to the XMPP service // Perform the actual connection to the XMPP service
connectInternal(); connectInternal();
@ -480,9 +489,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// from the server and throw an error. First check if we've already negotiated TLS // from the server and throw an error. First check if we've already negotiated TLS
// and are secure, however (features get parsed a second time after TLS is established). // and are secure, however (features get parsed a second time after TLS is established).
if (!isSecureConnection() && getConfiguration().getSecurityMode() == SecurityMode.required) { if (!isSecureConnection() && getConfiguration().getSecurityMode() == SecurityMode.required) {
shutdown();
throw new SecurityRequiredByClientException(); throw new SecurityRequiredByClientException();
} }
} catch (SmackException | IOException | XMPPException | InterruptedException e) {
instantShutdown();
throw e;
}
// Make note of the fact that we're now connected. // Make note of the fact that we're now connected.
connected = true; connected = true;
@ -652,6 +664,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
} }
protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException, InterruptedException { protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException, InterruptedException {
if (!resumed) {
authenticatedConnectionInitiallyEstablishedTimestamp = System.currentTimeMillis();
}
// Indicate that we're now authenticated. // Indicate that we're now authenticated.
this.authenticated = true; this.authenticated = true;
@ -834,15 +849,18 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* *
* @param exception the exception that causes the connection close event. * @param exception the exception that causes the connection close event.
*/ */
protected final synchronized void notifyConnectionError(Exception exception) { protected final void notifyConnectionError(final Exception exception) {
if (!isConnected()) { if (!isConnected()) {
LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception, LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception,
exception); exception);
return; return;
} }
ASYNC_BUT_ORDERED.performAsyncButOrdered(this, () -> {
currentConnectionException = exception; currentConnectionException = exception;
synchronized (AbstractXMPPConnection.this) {
notifyAll(); notifyAll();
}
for (StanzaCollector collector : collectors) { for (StanzaCollector collector : collectors) {
collector.notifyConnectionError(exception); collector.notifyConnectionError(exception);
@ -851,19 +869,26 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
tlsHandled.reportGenericFailure(smackWrappedException); tlsHandled.reportGenericFailure(smackWrappedException);
saslFeatureReceived.reportGenericFailure(smackWrappedException); saslFeatureReceived.reportGenericFailure(smackWrappedException);
lastFeaturesReceived.reportGenericFailure(smackWrappedException); lastFeaturesReceived.reportGenericFailure(smackWrappedException);
// TODO From XMPPTCPConnection. Was called in Smack 4.3 where notifyConnectionError() was part of
// XMPPTCPConnection. Create delegation method?
// maybeCompressFeaturesReceived.reportGenericFailure(smackWrappedException);
// Closes the connection temporary. A if the connection supports stream management, then a reconnection is // Closes the connection temporary. A if the connection supports stream management, then a reconnection is
// possible. Note that a connection listener of e.g. XMPPTCPConnection will drop the SM state in // possible. Note that a connection listener of e.g. XMPPTCPConnection will drop the SM state in
// case the Exception is a StreamErrorException. // case the Exception is a StreamErrorException.
instantShutdown(); instantShutdown();
Async.go(() -> {
// Notify connection listeners of the error.
callConnectionClosedOnErrorListener(exception); callConnectionClosedOnErrorListener(exception);
}, AbstractXMPPConnection.this + " callConnectionClosedOnErrorListener()");
});
} }
protected void instantShutdown() { /**
// Default implementation simply calls shutdown(), subclasses may override this. * Performs an unclean disconnect and shutdown of the connection. Does not send a closing stream stanza.
shutdown(); */
} public abstract void instantShutdown();
/** /**
* Shuts the current connection down. * Shuts the current connection down.
@ -1811,6 +1836,18 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return lastStanzaReceived; return lastStanzaReceived;
} }
/**
* Get the timestamp when the connection was the first time authenticated, i.e., when the first successful login was
* performed. Note that this value is not reset on disconnect, so it represents the timestamp from the last
* authenticated connection. The value is also not reset on stream resumption.
*
* @return the timestamp or {@code null}.
* @since 4.3.3
*/
public final long getAuthenticatedConnectionInitiallyEstablishedTimestamp() {
return authenticatedConnectionInitiallyEstablishedTimestamp;
}
/** /**
* Install a parsing exception callback, which will be invoked once an exception is encountered while parsing a * Install a parsing exception callback, which will be invoked once an exception is encountered while parsing a
* stanza. * stanza.

View file

@ -93,17 +93,24 @@ public abstract class SmackException extends Exception {
return new NoResponseException(sb.toString()); return new NoResponseException(sb.toString());
} }
public static NoResponseException newWith(XMPPConnection connection, @Deprecated
// TODO: Remove in Smack 4.4.
public static NoResponseException newWith(long timeout,
StanzaCollector collector) {
return newWith(timeout, collector.getStanzaFilter(), false);
}
public static NoResponseException newWith(long timeout,
StanzaCollector collector, boolean stanzaCollectorCancelled) { StanzaCollector collector, boolean stanzaCollectorCancelled) {
return newWith(connection, collector.getStanzaFilter(), stanzaCollectorCancelled); return newWith(timeout, collector.getStanzaFilter(), stanzaCollectorCancelled);
} }
public static NoResponseException newWith(XMPPConnection connection, StanzaFilter filter) { public static NoResponseException newWith(XMPPConnection connection, StanzaFilter filter) {
return newWith(connection, filter, false); return newWith(connection.getReplyTimeout(), filter, false);
} }
public static NoResponseException newWith(XMPPConnection connection, StanzaFilter filter, boolean stanzaCollectorCancelled) { public static NoResponseException newWith(long timeout, StanzaFilter filter, boolean stanzaCollectorCancelled) {
final StringBuilder sb = getWaitingFor(connection); final StringBuilder sb = getWaitingFor(timeout);
if (stanzaCollectorCancelled) { if (stanzaCollectorCancelled) {
sb.append(" StanzaCollector has been cancelled."); sb.append(" StanzaCollector has been cancelled.");
} }
@ -119,7 +126,10 @@ public abstract class SmackException extends Exception {
} }
private static StringBuilder getWaitingFor(XMPPConnection connection) { private static StringBuilder getWaitingFor(XMPPConnection connection) {
final long replyTimeout = connection.getReplyTimeout(); return getWaitingFor(connection.getReplyTimeout());
}
private static StringBuilder getWaitingFor(final long replyTimeout) {
final StringBuilder sb = new StringBuilder(256); final StringBuilder sb = new StringBuilder(256);
sb.append("No response received within reply timeout. Timeout was " sb.append("No response received within reply timeout. Timeout was "
+ replyTimeout + "ms (~" + replyTimeout + "ms (~"

View file

@ -290,7 +290,7 @@ public class StanzaCollector implements AutoCloseable {
if (!connection.isConnected()) { if (!connection.isConnected()) {
throw new NotConnectedException(connection, packetFilter); throw new NotConnectedException(connection, packetFilter);
} }
throw NoResponseException.newWith(connection, this, cancelled); throw NoResponseException.newWith(timeout, this, cancelled);
} }
XMPPErrorException.ifHasErrorThenThrow(result); XMPPErrorException.ifHasErrorThenThrow(result);

View file

@ -198,6 +198,12 @@ public class StanzaError extends AbstractError implements ExtensionElement {
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder("XMPPError: "); StringBuilder sb = new StringBuilder("XMPPError: ");
sb.append(condition.toString()).append(" - ").append(type.toString()); sb.append(condition.toString()).append(" - ").append(type.toString());
String descriptiveText = getDescriptiveText();
if (descriptiveText != null) {
sb.append(" [").append(descriptiveText).append(']');
}
if (errorGenerator != null) { if (errorGenerator != null) {
sb.append(". Generated by ").append(errorGenerator); sb.append(". Generated by ").append(errorGenerator);
} }

View file

@ -100,6 +100,11 @@ public class DummyConnection extends AbstractXMPPConnection {
callConnectionClosedListener(); callConnectionClosedListener();
} }
@Override
public void instantShutdown() {
shutdown();
}
@Override @Override
public boolean isSecureConnection() { public boolean isSecureConnection() {
return false; return false;
@ -226,4 +231,5 @@ public class DummyConnection extends AbstractXMPPConnection {
} }
} }
} }
} }

View file

@ -27,7 +27,6 @@ import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter; import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.NotFilter; import org.jivesoftware.smack.filter.NotFilter;
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.filter.ToTypeFilter; import org.jivesoftware.smack.filter.ToTypeFilter;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
@ -46,13 +45,12 @@ public final class StableUniqueStanzaIdManager extends Manager {
MessageTypeFilter.NORMAL_OR_CHAT_OR_HEADLINE, MessageTypeFilter.NORMAL_OR_CHAT_OR_HEADLINE,
ToTypeFilter.ENTITY_FULL_OR_BARE_JID); ToTypeFilter.ENTITY_FULL_OR_BARE_JID);
private static final StanzaFilter ORIGIN_ID_FILTER = new StanzaExtensionFilter(OriginIdElement.ELEMENT, NAMESPACE);
// Listener for outgoing stanzas that adds origin-ids to outgoing stanzas. // Listener for outgoing stanzas that adds origin-ids to outgoing stanzas.
private final StanzaListener stanzaListener = new StanzaListener() { private static final StanzaListener ADD_ORIGIN_ID_INTERCEPTOR = new StanzaListener() {
@Override @Override
public void processStanza(Stanza stanza) { public void processStanza(Stanza stanza) {
OriginIdElement.addOriginId((Message) stanza); Message message = (Message) stanza;
OriginIdElement.addOriginId(message);
} }
}; };
@ -95,7 +93,7 @@ public final class StableUniqueStanzaIdManager extends Manager {
public synchronized void enable() { public synchronized void enable() {
ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE); ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE);
StanzaFilter filter = new AndFilter(OUTGOING_FILTER, new NotFilter(OUTGOING_FILTER)); StanzaFilter filter = new AndFilter(OUTGOING_FILTER, new NotFilter(OUTGOING_FILTER));
connection().addStanzaInterceptor(stanzaListener, filter); connection().addStanzaInterceptor(ADD_ORIGIN_ID_INTERCEPTOR, filter);
} }
/** /**
@ -103,7 +101,7 @@ public final class StableUniqueStanzaIdManager extends Manager {
*/ */
public synchronized void disable() { public synchronized void disable() {
ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE); ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE);
connection().removeStanzaInterceptor(stanzaListener); connection().removeStanzaInterceptor(ADD_ORIGIN_ID_INTERCEPTOR);
} }
/** /**

View file

@ -24,7 +24,11 @@ import org.xmlpull.v1.XmlPullParser;
public class OriginIdProvider extends ExtensionElementProvider<OriginIdElement> { public class OriginIdProvider extends ExtensionElementProvider<OriginIdElement> {
public static final OriginIdProvider TEST_INSTANCE = new OriginIdProvider(); public static final OriginIdProvider INSTANCE = new OriginIdProvider();
// TODO: Remove in Smack 4.4.
@Deprecated
public static final OriginIdProvider TEST_INSTANCE = INSTANCE;
@Override @Override
public OriginIdElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) { public OriginIdElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) {

View file

@ -24,7 +24,11 @@ import org.xmlpull.v1.XmlPullParser;
public class StanzaIdProvider extends ExtensionElementProvider<StanzaIdElement> { public class StanzaIdProvider extends ExtensionElementProvider<StanzaIdElement> {
public static StanzaIdProvider TEST_INSTANCE = new StanzaIdProvider(); public static final StanzaIdProvider INSTANCE = new StanzaIdProvider();
// TODO: Remove in Smack 4.4.
@Deprecated
public static final StanzaIdProvider TEST_INSTANCE = INSTANCE;
@Override @Override
public StanzaIdElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) { public StanzaIdElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) {

View file

@ -25,6 +25,7 @@ import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.test.util.SmackTestSuite; import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.test.util.TestUtils; import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smackx.sid.element.OriginIdElement; import org.jivesoftware.smackx.sid.element.OriginIdElement;
import org.jivesoftware.smackx.sid.element.StanzaIdElement; import org.jivesoftware.smackx.sid.element.StanzaIdElement;
import org.jivesoftware.smackx.sid.provider.OriginIdProvider; import org.jivesoftware.smackx.sid.provider.OriginIdProvider;
@ -42,7 +43,7 @@ public class StableUniqueStanzaIdTest extends SmackTestSuite {
assertEquals("alice@wonderland.lit", element.getBy()); assertEquals("alice@wonderland.lit", element.getBy());
assertXMLEqual(xml, element.toXML().toString()); assertXMLEqual(xml, element.toXML().toString());
StanzaIdElement parsed = StanzaIdProvider.TEST_INSTANCE.parse(TestUtils.getParser(xml)); StanzaIdElement parsed = StanzaIdProvider.INSTANCE.parse(TestUtils.getParser(xml));
assertEquals(element.getId(), parsed.getId()); assertEquals(element.getId(), parsed.getId());
assertEquals(element.getBy(), parsed.getBy()); assertEquals(element.getBy(), parsed.getBy());
} }
@ -54,7 +55,7 @@ public class StableUniqueStanzaIdTest extends SmackTestSuite {
assertEquals("de305d54-75b4-431b-adb2-eb6b9e546013", element.getId()); assertEquals("de305d54-75b4-431b-adb2-eb6b9e546013", element.getId());
assertXMLEqual(xml, element.toXML().toString()); assertXMLEqual(xml, element.toXML().toString());
OriginIdElement parsed = OriginIdProvider.TEST_INSTANCE.parse(TestUtils.getParser(xml)); OriginIdElement parsed = OriginIdProvider.INSTANCE.parse(TestUtils.getParser(xml));
assertEquals(element.getId(), parsed.getId()); assertEquals(element.getId(), parsed.getId());
} }
@ -81,4 +82,17 @@ public class StableUniqueStanzaIdTest extends SmackTestSuite {
assertTrue(StanzaIdElement.hasStanzaId(message)); assertTrue(StanzaIdElement.hasStanzaId(message));
assertEquals(stanzaId, StanzaIdElement.getStanzaId(message)); assertEquals(stanzaId, StanzaIdElement.getStanzaId(message));
} }
@Test
public void testMultipleUssidExtensions() throws Exception {
String message = "<message xmlns='jabber:client' from='e4aec989-3e20-4846-83bf-f50df89b5d07@muclight.example.com/user1@example.com' to='user1@example.com' id='6b71fe3a-3cb2-489c-9c8e-b6879761d15e' type='groupchat'>" +
"<body>Test message</body>" +
"<markable xmlns='urn:xmpp:chat-markers:0'/>" +
"<stanza-id by='e4aec989-3e20-4846-83bf-f50df89b5d07@muclight.example.com' id='B0KK24ETVC81' xmlns='urn:xmpp:sid:0'/>" +
"<stanza-id by='user1@example.com' id='B0KK24EV89G1' xmlns='urn:xmpp:sid:0'/>" +
"</message>";
Message messageStanza = PacketParserUtils.parseStanza(message);
assertTrue(StanzaIdElement.hasStanzaId(messageStanza));
}
} }

View file

@ -341,9 +341,10 @@ public class MultiUserChat {
// Setup the messageListeners and presenceListeners *before* the join presence is send. // Setup the messageListeners and presenceListeners *before* the join presence is send.
connection.addSyncStanzaListener(messageListener, fromRoomGroupchatFilter); connection.addSyncStanzaListener(messageListener, fromRoomGroupchatFilter);
connection.addSyncStanzaListener(presenceListener, new AndFilter(fromRoomFilter, StanzaFilter presenceFromRoomFilter = new AndFilter(fromRoomFilter,
StanzaTypeFilter.PRESENCE, StanzaTypeFilter.PRESENCE,
PossibleFromTypeFilter.ENTITY_FULL_JID)); PossibleFromTypeFilter.ENTITY_FULL_JID);
connection.addSyncStanzaListener(presenceListener, presenceFromRoomFilter);
// @formatter:off // @formatter:off
connection.addSyncStanzaListener(subjectListener, connection.addSyncStanzaListener(subjectListener,
new AndFilter(fromRoomFilter, new AndFilter(fromRoomFilter,
@ -372,15 +373,27 @@ public class MultiUserChat {
) )
); );
// @formatter:on // @formatter:on
StanzaCollector presenceStanzaCollector = null;
Presence presence; Presence presence;
try { try {
presence = connection.createStanzaCollectorAndSend(responseFilter, joinPresence).nextResultOrThrow(conf.getTimeout()); // This stanza collector will collect the final self presence from the MUC, which also signals that we have successful entered the MUC.
StanzaCollector selfPresenceCollector = connection.createStanzaCollectorAndSend(responseFilter, joinPresence);
StanzaCollector.Configuration presenceStanzaCollectorConfguration = StanzaCollector.newConfiguration().setCollectorToReset(
selfPresenceCollector).setStanzaFilter(presenceFromRoomFilter);
// This stanza collector is used to reset the timeout of the selfPresenceCollector.
presenceStanzaCollector = connection.createStanzaCollector(presenceStanzaCollectorConfguration);
presence = selfPresenceCollector.nextResultOrThrow(conf.getTimeout());
} }
catch (NotConnectedException | InterruptedException | NoResponseException | XMPPErrorException e) { catch (NotConnectedException | InterruptedException | NoResponseException | XMPPErrorException e) {
// Ensure that all callbacks are removed if there is an exception // Ensure that all callbacks are removed if there is an exception
removeConnectionCallbacks(); removeConnectionCallbacks();
throw e; throw e;
} }
finally {
if (presenceStanzaCollector != null) {
presenceStanzaCollector.cancel();
}
}
// This presence must be send from a full JID. We use the resourcepart of this JID as nick, since the room may // This presence must be send from a full JID. We use the resourcepart of this JID as nick, since the room may
// performed roomnick rewriting // performed roomnick rewriting

View file

@ -18,6 +18,7 @@ package org.jivesoftware.smack.util.dns.javax;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
@ -40,6 +41,7 @@ import org.minidns.dnsname.DnsName;
/** /**
* A DNS resolver (mostly for SRV records), which makes use of the API provided in the javax.* namespace. * A DNS resolver (mostly for SRV records), which makes use of the API provided in the javax.* namespace.
* Note that using JavaxResovler requires applications using newer Java versions (at least 11) to declare a dependency on the "sun.jdk" module.
* *
* @author Florian Schmaus * @author Florian Schmaus
* *
@ -51,7 +53,9 @@ public class JavaxResolver extends DNSResolver implements SmackInitializer {
static { static {
try { try {
dirContext = new InitialDirContext(); Hashtable<String, String> env = new Hashtable<>();
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
dirContext = new InitialDirContext(env);
} catch (NamingException e) { } catch (NamingException e) {
LOGGER.log(Level.SEVERE, "Could not construct InitialDirContext", e); LOGGER.log(Level.SEVERE, "Could not construct InitialDirContext", e);
} }

View file

@ -257,6 +257,15 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
*/ */
private final Collection<StanzaListener> stanzaAcknowledgedListeners = new ConcurrentLinkedQueue<>(); private final Collection<StanzaListener> stanzaAcknowledgedListeners = new ConcurrentLinkedQueue<>();
/**
* These listeners are invoked for every stanza that got dropped.
* <p>
* We use a {@link ConcurrentLinkedQueue} here in order to allow the listeners to remove
* themselves after they have been invoked.
* </p>
*/
private final Collection<StanzaListener> stanzaDroppedListeners = new ConcurrentLinkedQueue<>();
/** /**
* This listeners are invoked for a acknowledged stanza that has the given stanza ID. They will * This listeners are invoked for a acknowledged stanza that has the given stanza ID. They will
* only be invoked once and automatically removed after that. * only be invoked once and automatically removed after that.
@ -421,10 +430,25 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
} }
} }
// (Re-)send the stanzas *after* we tried to enable SM // Inform client about failed resumption if possible, resend stanzas otherwise
// Process the stanzas synchronously so a client can re-queue them for transmission
// before it is informed about connection success
if (!stanzaDroppedListeners.isEmpty()) {
for (Stanza stanza : previouslyUnackedStanzas) {
for (StanzaListener listener : stanzaDroppedListeners) {
try {
listener.processStanza(stanza);
}
catch (InterruptedException | NotConnectedException | NotLoggedInException e) {
LOGGER.log(Level.FINER, "StanzaDroppedListener received exception", e);
}
}
}
} else {
for (Stanza stanza : previouslyUnackedStanzas) { for (Stanza stanza : previouslyUnackedStanzas) {
sendStanzaInternal(stanza); sendStanzaInternal(stanza);
} }
}
afterSuccessfulLogin(false); afterSuccessfulLogin(false);
} }
@ -452,9 +476,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
shutdown(false); shutdown(false);
} }
/**
* Performs an unclean disconnect and shutdown of the connection. Does not send a closing stream stanza.
*/
@Override @Override
public synchronized void instantShutdown() { public synchronized void instantShutdown() {
shutdown(true); shutdown(true);
@ -492,6 +513,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
// Reset the stream management session id to null, since if the stream is cleanly closed, i.e. sending a closing // Reset the stream management session id to null, since if the stream is cleanly closed, i.e. sending a closing
// stream tag, there is no longer a stream to resume. // stream tag, there is no longer a stream to resume.
smSessionId = null; smSessionId = null;
// Note that we deliberately do not reset authenticatedConnectionInitiallyEstablishedTimestamp here, so that the
// information is available in the connectionClosedOnError() listeners.
} }
authenticated = false; authenticated = false;
connected = false; connected = false;
@ -499,6 +522,16 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
reader = null; reader = null;
writer = null; writer = null;
initState();
// Wait for reader and writer threads to be terminated.
readerWriterSemaphore.acquireUninterruptibly(2);
readerWriterSemaphore.release(2);
}
@Override
protected void initState() {
super.initState();
maybeCompressFeaturesReceived.init(); maybeCompressFeaturesReceived.init();
compressSyncPoint.init(); compressSyncPoint.init();
smResumedSyncPoint.init(); smResumedSyncPoint.init();
@ -784,14 +817,13 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
@Override @Override
protected void afterFeaturesReceived() throws NotConnectedException, InterruptedException { protected void afterFeaturesReceived() throws NotConnectedException, InterruptedException, SecurityRequiredByServerException {
StartTls startTlsFeature = getFeature(StartTls.ELEMENT, StartTls.NAMESPACE); StartTls startTlsFeature = getFeature(StartTls.ELEMENT, StartTls.NAMESPACE);
if (startTlsFeature != null) { if (startTlsFeature != null) {
if (startTlsFeature.required() && config.getSecurityMode() == SecurityMode.disabled) { if (startTlsFeature.required() && config.getSecurityMode() == SecurityMode.disabled) {
SmackException smackException = new SecurityRequiredByServerException(); SecurityRequiredByServerException smackException = new SecurityRequiredByServerException();
tlsHandled.reportFailure(smackException); tlsHandled.reportFailure(smackException);
notifyConnectionError(smackException); throw smackException;
return;
} }
if (config.getSecurityMode() != ConnectionConfiguration.SecurityMode.disabled) { if (config.getSecurityMode() != ConnectionConfiguration.SecurityMode.disabled) {
@ -1062,7 +1094,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
LOGGER.info(XMPPTCPConnection.this LOGGER.info(XMPPTCPConnection.this
+ " received closing </stream> element." + " received closing </stream> element."
+ " Server wants to terminate the connection, calling disconnect()"); + " Server wants to terminate the connection, calling disconnect()");
ASYNC_BUT_ORDERED.performAsyncButOrdered(XMPPTCPConnection.this, new Runnable() {
@Override
public void run() {
disconnect(); disconnect();
}});
} }
} }
break; break;
@ -1567,6 +1603,32 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
stanzaAcknowledgedListeners.clear(); stanzaAcknowledgedListeners.clear();
} }
/**
* Add a Stanza dropped listener.
* <p>
* Those listeners will be invoked every time a Stanza has been dropped due to a failed SM resume. They will not get
* automatically removed. If at least one StanzaDroppedListener is configured, no attempt will be made to retransmit
* the Stanzas.
* </p>
*
* @param listener the listener to add.
* @since 4.3.3
*/
public void addStanzaDroppedListener(StanzaListener listener) {
stanzaDroppedListeners.add(listener);
}
/**
* Remove the given Stanza dropped listener.
*
* @param listener the listener.
* @return true if the listener was removed.
* @since 4.3.3
*/
public boolean removeStanzaDroppedListener(StanzaListener listener) {
return stanzaDroppedListeners.remove(listener);
}
/** /**
* Add a new Stanza ID acknowledged listener for the given ID. * Add a new Stanza ID acknowledged listener for the given ID.
* <p> * <p>

View file

@ -2,6 +2,12 @@ allprojects {
ext { ext {
shortVersion = '4.4.0-alpha2' shortVersion = '4.4.0-alpha2'
isSnapshot = true isSnapshot = true
// When using dynamic versions for those, do *not* use [1.0,
// 2.0), since this will also pull in 2.0-alpha1. Instead use
// [1.0, 1.0.99].
// See also:
// - https://issues.apache.org/jira/browse/MNG-6232
// - https://issues.igniterealtime.org/browse/SMACK-858
jxmppVersion = '0.7.0-alpha5' jxmppVersion = '0.7.0-alpha5'
miniDnsVersion = '0.4.0-alpha3' miniDnsVersion = '0.4.0-alpha3'
smackMinAndroidSdk = 19 smackMinAndroidSdk = 19