1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-11-21 22:02:06 +01:00

Introduce Smack's Modular Connection Architecture

This is a complete redesign of what was previously
XmppNioTcpConnection. The new architecture allows to extend an XMPP
client to server (c2s) connection with new transport bindings and
other extensions.
This commit is contained in:
Florian Schmaus 2020-04-04 13:03:31 +02:00
parent cec312fe64
commit cc636fff21
142 changed files with 6819 additions and 4068 deletions

View file

@ -14,6 +14,8 @@ buildscript {
plugins {
id 'ru.vyarus.animalsniffer' version '1.5.0'
id 'net.ltgt.errorprone' version '1.1.1'
// Use e.g. "gradle <task> taskTree" to show its dependency tree.
id 'com.dorongold.task-tree' version '1.5'
}
apply plugin: 'org.kordamp.gradle.markdown'
@ -288,14 +290,7 @@ configure (junit4Projects) {
}
}
task copyAllJavadocDocFiles(type: Copy) {
from javadocAllProjects.collect { project ->
"${project.projectDir}/src/javadoc" }
into javadocAllDir
include '**/doc-files/*.*'
}
task javadocAll(type: Javadoc, dependsOn: copyAllJavadocDocFiles) {
task javadocAll(type: Javadoc) {
source javadocAllProjects.collect {project ->
project.sourceSets.main.allJava.findAll {
// Filter out symbolic links to avoid
@ -325,6 +320,25 @@ task javadocAll(type: Javadoc, dependsOn: copyAllJavadocDocFiles) {
] as String[]
overview = "$projectDir/resources/javadoc-overview.html"
}
// Finally copy the javadoc doc-files from the subprojects, which
// are potentially generated, to the javadocAll directory. Note
// that we use a copy *method* and not a *task* because the inputs
// of copy tasks is determined within the configuration phase. And
// since some of the inputs are generated, they will not get
// picked up if we used a copy method. See also
// https://stackoverflow.com/a/40518516/194894
doLast {
copy {
javadocAllProjects.each {
from ("${it.projectDir}/src/javadoc") {
include '**/doc-files/*.*'
}
}
into javadocAllDir
}
}
}
import org.apache.tools.ant.filters.ReplaceTokens
@ -494,31 +508,25 @@ subprojects {
}
// Work around https://github.com/gradle/gradle/issues/4046
javadoc.dependsOn('copyJavadocDocFiles')
task copyJavadocDocFiles(type: Copy) {
from('src/javadoc')
into 'build/docs/javadoc'
include '**/doc-files/*.*'
}
javadoc.dependsOn copyJavadocDocFiles
// If this subproject has a Makefile then make copyJavadocDocFiles
// and the root project's javadocAll task dependend on
// generateFiles.
if (file("$projectDir/Makefile").exists()) {
copyJavadocDocFiles.dependsOn('generateFiles')
rootProject.copyAllJavadocDocFiles.dependsOn("${project.name}:generateFiles")
task generateFiles(type: Exec) {
workingDir projectDir
commandLine 'make'
}
// Make sure root projects 'javadocAll' depends on the
// subproject's javadoc, to ensure that all all doc-files/ are
// generated and up-to-date. Obviously this means that the
// javadocAll task will also create the individual javadoc's of the
// subprojects.
javadocAll.dependsOn javadoc
}
clean.dependsOn('cleanGeneratedFiles')
rootProject.clean.dependsOn("${project.name}:cleanGeneratedFiles")
task cleanGeneratedFiles(type: Exec) {
workingDir projectDir
commandLine 'make', 'clean'
}
}
// The smack-java8-full project generates the dot and png files of the
// current state graph. Ensure they are generated before copied.
configure (project(':smack-java8-full')) {
copyJavadocDocFiles.dependsOn convertModularXmppClientToServerConnectionStateGraphDotToPng
}
configure (androidProjects + androidBootClasspathProjects) {

View file

@ -0,0 +1,35 @@
Smack's Modular Connection Architecture
======================================
[Back](index.md)
**Note: Everything related to the modular connection architecture is currently considered experimental and should not be used in production. Use the mature `XMPPTCPConnection` if you do not feel adventurous.
Smack's modular connection architecture allows to extend a XMPP c2s (client-to-server) connection with additional functionalty by adding modules.
Those modules extend the Finite State Machine (FSM) within the `ModularXmppClientToServerConnection` with new states.
Connection modules can either be
- Transports
- Extensions
Transports bind the XMPP XML stream to an underlying transport like TCP, WebSockets, BOSH, and allow for the different particularities of transports like DirectTLS ([XEP-0368](https://xmpp.org/extensions/xep-0368.html)).
This eventually means that a single transport module can implement multiple transport mechanisms.
For example the TCP transport module implements the RFC6120 TCP and the XEP-0368 direct TLS TCP transport bindings.
Extensions allow for a richer functionality of the connection. Those include
- Compression
- zlib ([XEP-0138](https://xmpp.org/extensions/xep-0138.html))
- [Efficient XML Interchange (EXI)](https://www.w3.org/TR/exi/)
- Instant Stream Resumption ([XEP-0397](https://xmpp.org/extensions/xep-0397.html)
- Bind2
- Stream Management
Note that not all extensions work with every transport.
For example compression only works with TCP-based transport bindings.
Connection modules are plugged into the the modular connection via their constructor. and they usually declare backwards edges to some common, generic connection state of the FSM.
Modules and states always have an accompanying *descriptor* type.
`ModuleDescriptor` and `StateDescriptor` exist without an connection instance.
They describe the module and state metadata, while their modules are states are instanciated once a modular connection is instanciated.

View file

@ -58,8 +58,8 @@ debugger=console
### Framework properties
| Name | |
|----------------------|-------------------------------------------|
| Name | Description |
|----------------------|-----------------------------------------------------------------------------|
| service | XMPP service to run the tests on |
| serviceTlsPin | TLS Pin (used by [java-pinning](https://github.com/Flowdalic/java-pinning)) |
| securityMode | Either 'required' or 'disabled' |
@ -75,7 +75,11 @@ debugger=console
| debugger | 'console' for console debugger, 'enhanced' for the enhanced debugger |
| enabledTests | List of enabled tests |
| disabledTests | List of disabled tests |
| defaultConnection | Nickname of the default connection |
| enabledConnections | List of enabled connection's nicknames |
| disabledConnections | List of disabled connection's nicknames |
| testPackages | List of packages with tests |
| verbose | If `true` set output to verbose |
### Where to place the properties file
@ -99,6 +103,10 @@ The base class that integration tests need to subclass.
Allows low level integration test, i.e. ever test method will have its on exclusive XMPPTCPConnection instances.
### `AbstractSmackSpecificLowLevelIntegrationTest`
Operates, like `AbstractSmackLowLevelIntegrationTest` on its own `XMPPConnection` instances, but is limited to a particular type of `XMPPConnection`.
### `IntegrationTestEnvironment`
The environment, e.g. the `XMPPConnections` provided to the integration tests by the framework. Note that for convenience `AbstractSmackIntegrationTest` contains some of those as protected members.

View file

@ -13,3 +13,4 @@
* [Debugging with Smack](debugging.md)
* [Smack Extensions Manual](extensions/index.md)
* [Smack's Modular Connection Architecture](connection-modules.md)

View file

@ -23,6 +23,7 @@ include 'smack-core',
'smack-android',
'smack-android-extensions',
'smack-java7',
'smack-java8-full',
'smack-integration-test',
'smack-omemo',
'smack-omemo-signal',

View file

@ -27,7 +27,7 @@ import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.ConnectionException;
import org.jivesoftware.smack.SmackException.GenericConnectionException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.SmackWrappedException;
import org.jivesoftware.smack.XMPPConnection;
@ -136,6 +136,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
this.config = config;
}
@SuppressWarnings("deprecation")
@Override
protected void connectInternal() throws SmackException, InterruptedException {
done = false;
@ -177,7 +178,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
.setAttribute(BodyQName.createWithPrefix(XMPP_BOSH_NS, "version", "xmpp"), "1.0")
.build());
} catch (Exception e) {
throw new ConnectionException(e);
throw new GenericConnectionException(e);
}
// Wait for the response from the server

View file

@ -25,6 +25,8 @@ dependencies {
testCompile "org.xmlunit:xmlunit-assertj:$xmlUnitVersion"
testCompile 'com.jamesmurty.utils:java-xmlbuilder:1.2'
testCompile 'org.bouncycastle:bcprov-jdk15on:1.64'
testCompile 'com.google.guava:guava:28.2-jre'
testCompile 'org.jgrapht:jgrapht-io:1.3.1'
}
class CreateFileTask extends DefaultTask {

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2009 Jive Software, 2018-2019 Florian Schmaus.
* Copyright 2009 Jive Software, 2018-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -34,7 +34,6 @@ import java.security.SecureRandom;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
@ -87,6 +86,7 @@ import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.compress.packet.Compress;
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.datatypes.UInt16;
import org.jivesoftware.smack.debugger.SmackDebugger;
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
import org.jivesoftware.smack.filter.IQReplyFilter;
@ -136,7 +136,6 @@ import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.util.Predicate;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.TLSUtils;
import org.jivesoftware.smack.util.dns.HostAddress;
import org.jivesoftware.smack.util.dns.SmackDaneProvider;
import org.jivesoftware.smack.util.dns.SmackDaneVerifier;
import org.jivesoftware.smack.xml.XmlPullParser;
@ -150,8 +149,6 @@ import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart;
import org.jxmpp.stringprep.XmppStringprepException;
import org.jxmpp.util.XmppStringUtils;
import org.minidns.dnsname.DnsName;
/**
* This abstract class is commonly used as super class for XMPP connection mechanisms like TCP and BOSH. Hence it
@ -394,7 +391,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
/**
* The used port to establish the connection to
*/
protected int port;
protected UInt16 port;
/**
* Flag that indicates if the user is currently authenticated with the server.
@ -484,7 +481,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
@Override
public int getPort() {
return port;
final UInt16 port = this.port;
if (port == null) {
return -1;
}
return port.intValue();
}
@Override
@ -525,6 +527,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
saslFeatureReceived.init();
lastFeaturesReceived.init();
tlsHandled.init();
closingStreamReceived.init();
}
/**
@ -778,38 +781,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
private DomainBareJid xmppServiceDomain;
protected List<HostAddress> hostAddresses;
/**
* Populates {@link #hostAddresses} with the resolved addresses or with the configured host address. If no host
* address was configured and all lookups failed, for example with NX_DOMAIN, then {@link #hostAddresses} will be
* populated with the empty list.
*
* @return a list of host addresses where DNS (SRV) RR resolution failed.
*/
protected List<HostAddress> populateHostAddresses() {
List<HostAddress> failedAddresses = new LinkedList<>();
if (config.hostAddress != null) {
hostAddresses = new ArrayList<>(1);
HostAddress hostAddress = new HostAddress(config.port, config.hostAddress);
hostAddresses.add(hostAddress);
}
else if (config.host != null) {
hostAddresses = new ArrayList<>(1);
HostAddress hostAddress = DNSUtil.getDNSResolver().lookupHostAddress(config.host, config.port, failedAddresses, config.getDnssecMode());
if (hostAddress != null) {
hostAddresses.add(hostAddress);
}
} else {
// N.B.: Important to use config.serviceName and not AbstractXMPPConnection.serviceName
DnsName dnsName = DnsName.from(config.getXMPPServiceDomain());
hostAddresses = DNSUtil.resolveXMPPServiceDomain(dnsName, failedAddresses, config.getDnssecMode());
}
// Either the populated host addresses are not empty *or* there must be at least one failed address.
assert !hostAddresses.isEmpty() || !failedAddresses.isEmpty();
return failedAddresses;
}
protected Lock getConnectionLock() {
return connectionLock;
}
@ -980,6 +951,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
tlsHandled.reportGenericFailure(smackWrappedException);
saslFeatureReceived.reportGenericFailure(smackWrappedException);
lastFeaturesReceived.reportGenericFailure(smackWrappedException);
closingStreamReceived.reportFailure(smackWrappedException);
// TODO From XMPPTCPConnection. Was called in Smack 4.3 where notifyConnectionError() was part of
// XMPPTCPConnection. Create delegation method?
// maybeCompressFeaturesReceived.reportGenericFailure(smackWrappedException);
@ -2182,6 +2154,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
CACHED_EXECUTOR_SERVICE.execute(runnable);
}
protected final SmackReactor getReactor() {
return SMACK_REACTOR;
}
protected static ScheduledAction schedule(Runnable runnable, long delay, TimeUnit unit) {
return SMACK_REACTOR.schedule(runnable, delay, unit);
}

View file

@ -1,50 +0,0 @@
/**
*
* Copyright 2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import org.jivesoftware.smack.SmackReactor.ChannelSelectedCallback;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
public abstract class AbstractXmppNioConnection extends AbstractXmppStateMachineConnection {
protected AbstractXmppNioConnection(ConnectionConfiguration configuration, GraphVertex<StateDescriptor> initialStateDescriptorVertex) {
super(configuration, initialStateDescriptorVertex);
}
protected SelectionKey registerWithSelector(SelectableChannel channel, int ops, ChannelSelectedCallback callback)
throws ClosedChannelException {
return SMACK_REACTOR.registerWithSelector(channel, ops, callback);
}
/**
* Set the interest Ops of a SelectionKey. Since Java's NIO interestOps(int) can block at any time, we use a queue
* to perform the actual operation in the reactor where we can perform this operation non-blocking.
*
* @param selectionKey TODO javadoc me please
* @param interestOps TODO javadoc me please
*/
protected void setInterestOps(SelectionKey selectionKey, int interestOps) {
SMACK_REACTOR.setInterestOps(selectionKey, interestOps);
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2017-2019 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2017-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -229,14 +229,18 @@ public abstract class ConnectionConfiguration {
}
DnsName getHost() {
public DnsName getHost() {
return host;
}
InetAddress getHostAddress() {
public InetAddress getHostAddress() {
return hostAddress;
}
public int getPort() {
return port;
}
/**
* Returns the server name of the target server.
*

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software.
* Copyright 2003-2007 Jive Software, 2018-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,6 +28,8 @@ import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor;
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.debugger.ReflectionDebuggerFactory;
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
@ -379,4 +381,19 @@ public final class SmackConfiguration {
return defaultConcurrencyLevelLimit;
}
private static final Set<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>> KNOWN_MODULES = new HashSet<>();
public static boolean addModule(Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptor) {
synchronized (KNOWN_MODULES) {
return KNOWN_MODULES.add(moduleDescriptor);
}
}
public static void addAllKnownModulesTo(ModularXmppClientToServerConnectionConfiguration.Builder builder) {
synchronized (KNOWN_MODULES) {
for (Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptor : KNOWN_MODULES) {
builder.addModule(moduleDescriptor);
}
}
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2014-2019 Florian Schmaus
* Copyright 2014-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
*/
package org.jivesoftware.smack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.c2s.XmppClientToServerTransport.LookupConnectionEndpointsFailed;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.util.dns.HostAddress;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
import org.jivesoftware.smack.util.rce.RemoteConnectionException;
import org.jxmpp.jid.Jid;
@ -90,6 +94,7 @@ public abstract class SmackException extends Exception {
public static NoResponseException newWith(XMPPConnection connection, String waitingFor) {
final StringBuilder sb = getWaitingFor(connection);
sb.append(" While waiting for ").append(waitingFor);
sb.append(" [").append(connection).append(']');
return new NoResponseException(sb.toString());
}
@ -264,45 +269,112 @@ public abstract class SmackException extends Exception {
}
}
public abstract static class ConnectionException extends SmackException {
private static final long serialVersionUID = 1L;
protected ConnectionException(Throwable wrappedThrowable) {
super(wrappedThrowable);
}
protected ConnectionException(String message) {
super(message);
}
}
public static final class GenericConnectionException extends ConnectionException {
private static final long serialVersionUID = 1L;
/**
* ConnectionException is thrown if Smack is unable to connect to all hosts of a given XMPP
* service. The failed hosts can be retrieved with
* {@link ConnectionException#getFailedAddresses()}, which will have the exception causing the
* connection failure set and retrievable with {@link HostAddress#getExceptions()}.
* Deprecated, do not use.
*
* @param wrappedThrowable the wrapped throwable.
*/
public static class ConnectionException extends SmackException {
@Deprecated
public GenericConnectionException(Throwable wrappedThrowable) {
super(wrappedThrowable);
}
}
/**
* This exception is thrown if Smack is unable to connect to all hosts of a given XMPP
* service. The connection exceptions can be retrieved with
* {@link EndpointConnectionException#getConnectionExceptions()}, which will have the exception causing the
* connection failure set and retrievable with {@link RemoteConnectionException#getException()}.
*/
public static final class EndpointConnectionException extends ConnectionException {
/**
*
*/
private static final long serialVersionUID = 1686944201672697996L;
private static final long serialVersionUID = 1;
private final List<HostAddress> failedAddresses;
private final List<RemoteConnectionEndpointLookupFailure> lookupFailures;
private final List<? extends RemoteConnectionException<?>> connectionExceptions;
public ConnectionException(Throwable wrappedThrowable) {
super(wrappedThrowable);
failedAddresses = new ArrayList<>(0);
}
private ConnectionException(String message, List<HostAddress> failedAddresses) {
private EndpointConnectionException(String message, List<RemoteConnectionEndpointLookupFailure> lookupFailures,
List<? extends RemoteConnectionException<?>> connectionExceptions) {
super(message);
this.failedAddresses = failedAddresses;
// At least one list must contain an entry.
assert !lookupFailures.isEmpty() || !connectionExceptions.isEmpty();
this.lookupFailures = lookupFailures;
this.connectionExceptions = connectionExceptions;
}
public static ConnectionException from(List<HostAddress> failedAddresses) {
final String DELIMITER = ", ";
StringBuilder sb = new StringBuilder("The following addresses failed: ");
for (HostAddress hostAddress : failedAddresses) {
sb.append(hostAddress.getErrorMessage());
sb.append(DELIMITER);
}
// Remove the last delimiter
sb.setLength(sb.length() - DELIMITER.length());
return new ConnectionException(sb.toString(), failedAddresses);
public static EndpointConnectionException from(List<RemoteConnectionEndpointLookupFailure> lookupFailures,
List<? extends RemoteConnectionException<?>> connectionExceptions) {
StringBuilder sb = new StringBuilder(256);
if (!lookupFailures.isEmpty()) {
sb.append("Could not lookup the following endpoints: ");
StringUtils.appendTo(lookupFailures, sb);
}
public List<HostAddress> getFailedAddresses() {
return failedAddresses;
if (!connectionExceptions.isEmpty()) {
sb.append("The following addresses failed: ");
StringUtils.appendTo(connectionExceptions, sb, rce -> sb.append(rce.getErrorMessage()));
}
return new EndpointConnectionException(sb.toString(), lookupFailures, connectionExceptions);
}
public List<RemoteConnectionEndpointLookupFailure> getLookupFailures() {
return lookupFailures;
}
public List<? extends RemoteConnectionException<? extends RemoteConnectionEndpoint>> getConnectionExceptions() {
return connectionExceptions;
}
}
public static final class NoEndpointsDiscoveredException extends ConnectionException {
private static final long serialVersionUID = 1L;
private final List<LookupConnectionEndpointsFailed> lookupFailures;
private NoEndpointsDiscoveredException(String message, List<LookupConnectionEndpointsFailed> lookupFailures) {
super(message);
this.lookupFailures = Collections.unmodifiableList(lookupFailures);
}
public List<LookupConnectionEndpointsFailed> getLookupFailures() {
return lookupFailures;
}
public static NoEndpointsDiscoveredException from(List<LookupConnectionEndpointsFailed> lookupFailures) {
StringBuilder sb = new StringBuilder();
if (lookupFailures.isEmpty()) {
sb.append("No endpoint lookup finished within the timeout");
} else {
sb.append("Not endpoints could be discovered due the following lookup failures: ");
StringUtils.appendTo(lookupFailures, sb);
}
return new NoEndpointsDiscoveredException(sb.toString(), lookupFailures);
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017-2018 Florian Schmaus
* Copyright 2017-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,7 +19,9 @@ package org.jivesoftware.smack;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@ -31,6 +33,7 @@ import javax.net.SocketFactory;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.CallbackRecipient;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.ExceptionCallback;
import org.jivesoftware.smack.util.SuccessCallback;
@ -48,6 +51,8 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
private ExceptionCallback<E> exceptionCallback;
private Consumer<SmackFuture<V, E>> completionCallback;
@Override
public final synchronized boolean cancel(boolean mayInterruptIfRunning) {
if (isDone()) {
@ -87,6 +92,11 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
return this;
}
public void onCompletion(Consumer<SmackFuture<V, E>> completionCallback) {
this.completionCallback = completionCallback;
maybeInvokeCallbacks();
}
private V getOrThrowExecutionException() throws ExecutionException {
assert result != null || exception != null || cancelled;
if (result != null) {
@ -148,11 +158,19 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
return getOrThrowExecutionException();
}
public V getIfAvailable() {
return result;
}
protected final synchronized void maybeInvokeCallbacks() {
if (cancelled) {
return;
}
if ((result != null || exception != null) && completionCallback != null) {
completionCallback.accept(this);
}
if (result != null && successCallback != null) {
AbstractXMPPConnection.asyncGo(new Runnable() {
@Override
@ -308,4 +326,12 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
return future;
}
public static boolean await(Collection<? extends SmackFuture<?, ?>> futures, long timeout, TimeUnit unit) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(futures.size());
for (SmackFuture<?, ?> future : futures) {
future.onCompletion(f -> latch.countDown());
}
return latch.await(timeout, unit);
}
}

View file

@ -26,12 +26,15 @@ import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.bind2.Bind2ModuleDescriptor;
import org.jivesoftware.smack.compress.provider.CompressedProvider;
import org.jivesoftware.smack.compress.provider.FailureProvider;
import org.jivesoftware.smack.compression.CompressionModuleDescriptor;
import org.jivesoftware.smack.compression.Java7ZlibInputOutputStream;
import org.jivesoftware.smack.compression.XmppCompressionManager;
import org.jivesoftware.smack.compression.zlib.ZlibXmppCompressionFactory;
import org.jivesoftware.smack.initializer.SmackInitializer;
import org.jivesoftware.smack.isr.InstantStreamResumptionModuleDescriptor;
import org.jivesoftware.smack.packet.Bind;
import org.jivesoftware.smack.packet.Message.Body;
import org.jivesoftware.smack.provider.BindIQProvider;
@ -136,6 +139,10 @@ public final class SmackInitialization {
ProviderManager.addNonzaProvider(CompressedProvider.INSTANCE);
ProviderManager.addNonzaProvider(FailureProvider.INSTANCE);
SmackConfiguration.addModule(Bind2ModuleDescriptor.class);
SmackConfiguration.addModule(CompressionModuleDescriptor.class);
SmackConfiguration.addModule(InstantStreamResumptionModuleDescriptor.class);
SmackConfiguration.smackInitialized = true;
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -116,7 +116,7 @@ public class SmackReactor {
setReactorThreadCount(DEFAULT_REACTOR_THREAD_COUNT);
}
SelectionKey registerWithSelector(SelectableChannel channel, int ops, ChannelSelectedCallback callback)
public SelectionKey registerWithSelector(SelectableChannel channel, int ops, ChannelSelectedCallback callback)
throws ClosedChannelException {
SelectionKeyAttachment selectionKeyAttachment = new SelectionKeyAttachment(callback);
@ -129,7 +129,7 @@ public class SmackReactor {
}
}
void setInterestOps(SelectionKey selectionKey, int interestOps) {
public void setInterestOps(SelectionKey selectionKey, int interestOps) {
SetInterestOps setInterestOps = new SetInterestOps(selectionKey, interestOps);
pendingSetInterestOps.add(setInterestOps);
selector.wakeup();

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -70,7 +70,7 @@ public interface XmppInputOutputFilter {
default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException, InterruptedException, SmackException {
}
default Object getStats() {
return null;
}
Object getStats();
String getFilterName();
}

View file

@ -0,0 +1,77 @@
/**
*
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.bind2;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedAndResourceBoundStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.SaslAuthenticationStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
import org.jivesoftware.smack.fsm.State;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.fsm.StateTransitionResult;
public class Bind2Module extends ModularXmppClientToServerConnectionModule<Bind2ModuleDescriptor> {
protected Bind2Module(Bind2ModuleDescriptor moduleDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(moduleDescriptor, connectionInternal);
}
public static final class Bind2StateDescriptor extends StateDescriptor {
private Bind2StateDescriptor() {
super(Bind2State.class, 386, StateDescriptor.Property.notImplemented);
addPredeccessor(ConnectedButUnauthenticatedStateDescriptor.class);
addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
declarePrecedenceOver(SaslAuthenticationStateDescriptor.class);
}
@Override
protected Bind2Module.Bind2State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
// This is the trick: the module is constructed prior the states, so we get the actual state out of the module by fetching the module from the connection.
Bind2Module bind2Module = connectionInternal.connection.getConnectionModuleFor(Bind2ModuleDescriptor.class);
return bind2Module.constructBind2State(this, connectionInternal);
}
}
private static final class Bind2State extends State {
private Bind2State(Bind2StateDescriptor bind2StateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(bind2StateDescriptor, connectionInternal);
}
@Override
public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
return new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(stateDescriptor);
}
@Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
throw new IllegalStateException("Bind2 not implemented");
}
}
public Bind2State constructBind2State(Bind2StateDescriptor bind2StateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
return new Bind2State(bind2StateDescriptor, connectionInternal);
}
}

View file

@ -0,0 +1,53 @@
/**
*
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.bind2;
import java.util.Collections;
import java.util.Set;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.fsm.StateDescriptor;
public class Bind2ModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
private static final Bind2ModuleDescriptor INSTANCE = new Bind2ModuleDescriptor();
@Override
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
return Collections.singleton(Bind2Module.Bind2StateDescriptor.class);
}
@Override
protected Bind2Module constructXmppConnectionModule(
ModularXmppClientToServerConnectionInternal connectionInternal) {
return new Bind2Module(this, connectionInternal);
}
public static class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
protected Builder(ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
super(connectionConfigurationBuilder);
}
@Override
protected Bind2ModuleDescriptor build() {
return INSTANCE;
}
}
}

View file

@ -0,0 +1,21 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Classes and interfaces for Bind 2.0 (XEP-0386).
*/
package org.jivesoftware.smack.bind2;

View file

@ -0,0 +1,167 @@
/**
*
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.c2s;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.fsm.StateDescriptorGraph;
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
import org.jivesoftware.smack.util.CollectionUtil;
public final class ModularXmppClientToServerConnectionConfiguration extends ConnectionConfiguration {
final Set<ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptors;
final GraphVertex<StateDescriptor> initialStateDescriptorVertex;
private ModularXmppClientToServerConnectionConfiguration(Builder builder) {
super(builder);
moduleDescriptors = Collections.unmodifiableSet(CollectionUtil.newSetWith(builder.modulesDescriptors.values()));
Set<Class<? extends StateDescriptor>> backwardEdgeStateDescriptors = new HashSet<>();
// Add backward edges from configured connection modules. Note that all state descriptors from module
// descriptors are backwards edges.
for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : moduleDescriptors) {
Set<Class<? extends StateDescriptor>> moduleStateDescriptors = moduleDescriptor.getStateDescriptors();
backwardEdgeStateDescriptors.addAll(moduleStateDescriptors);
}
try {
initialStateDescriptorVertex = StateDescriptorGraph.constructStateDescriptorGraph(backwardEdgeStateDescriptors);
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
// TODO: Depending on the exact exception thrown, this potentially indicates an invalid connection
// configuration, e.g. there is no edge from disconnected to connected.
throw new IllegalStateException(e);
}
}
public void printStateGraphInDotFormat(PrintWriter pw, boolean breakStateName) {
StateDescriptorGraph.stateDescriptorGraphToDot(Collections.singleton(initialStateDescriptorVertex), pw,
breakStateName);
}
public String getStateGraphInDotFormat() {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
printStateGraphInDotFormat(pw, true);
return sw.toString();
}
public static Builder builder() {
return new Builder();
}
public static final class Builder
extends ConnectionConfiguration.Builder<Builder, ModularXmppClientToServerConnectionConfiguration> {
private final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, ModularXmppClientToServerConnectionModuleDescriptor> modulesDescriptors = new HashMap<>();
private Builder() {
SmackConfiguration.addAllKnownModulesTo(this);
}
@Override
public ModularXmppClientToServerConnectionConfiguration build() {
return new ModularXmppClientToServerConnectionConfiguration(this);
}
void addModule(ModularXmppClientToServerConnectionModuleDescriptor connectionModule) {
Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptorClass = connectionModule.getClass();
if (modulesDescriptors.containsKey(moduleDescriptorClass)) {
throw new IllegalArgumentException("A connection module for " + moduleDescriptorClass + " is already configured");
}
modulesDescriptors.put(moduleDescriptorClass, connectionModule);
}
@SuppressWarnings("unchecked")
public Builder addModule(Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleClass) {
Class<?>[] declaredClasses = moduleClass.getDeclaredClasses();
Class<? extends ModularXmppClientToServerConnectionModuleDescriptor.Builder> builderClass = null;
for (Class<?> declaredClass : declaredClasses) {
if (!ModularXmppClientToServerConnectionModuleDescriptor.Builder.class.isAssignableFrom(declaredClass)) {
continue;
}
builderClass = (Class<? extends ModularXmppClientToServerConnectionModuleDescriptor.Builder>) declaredClass;
break;
}
if (builderClass == null) {
throw new IllegalArgumentException(
"Found no builder for " + moduleClass + ". Delcared classes: " + Arrays.toString(declaredClasses));
}
return with(builderClass).buildModule();
}
public <B extends ModularXmppClientToServerConnectionModuleDescriptor.Builder> B with(
Class<? extends B> moduleDescriptorBuilderClass) {
Constructor<? extends B> moduleDescriptorBuilderCosntructor;
try {
moduleDescriptorBuilderCosntructor = moduleDescriptorBuilderClass.getDeclaredConstructor(
ModularXmppClientToServerConnectionConfiguration.Builder.class);
} catch (NoSuchMethodException | SecurityException e) {
throw new IllegalArgumentException(e);
}
moduleDescriptorBuilderCosntructor.setAccessible(true);
B moduleDescriptorBuilder;
try {
moduleDescriptorBuilder = moduleDescriptorBuilderCosntructor.newInstance(this);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new IllegalArgumentException(e);
}
return moduleDescriptorBuilder;
}
public Builder removeModule(Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleClass) {
modulesDescriptors.remove(moduleClass);
return getThis();
}
public Builder removeAllModules() {
modulesDescriptors.clear();
return getThis();
}
@Override
protected Builder getThis() {
return this;
}
}
}

View file

@ -0,0 +1,40 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.c2s;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
public abstract class ModularXmppClientToServerConnectionModule<MD extends ModularXmppClientToServerConnectionModuleDescriptor> {
protected final MD moduleDescriptor;
protected final ModularXmppClientToServerConnectionInternal connectionInternal;
protected ModularXmppClientToServerConnectionModule(MD moduleDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
this.moduleDescriptor = moduleDescriptor;
this.connectionInternal = connectionInternal;
}
public MD getModuleDescriptor() {
return moduleDescriptor;
}
protected XmppClientToServerTransport getTransport() {
return null;
}
}

View file

@ -0,0 +1,48 @@
/**
*
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.c2s;
import java.util.Set;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.fsm.StateDescriptor;
public abstract class ModularXmppClientToServerConnectionModuleDescriptor {
protected abstract Set<Class<? extends StateDescriptor>> getStateDescriptors();
protected abstract ModularXmppClientToServerConnectionModule<? extends ModularXmppClientToServerConnectionModuleDescriptor> constructXmppConnectionModule(
ModularXmppClientToServerConnectionInternal connectionInternal);
public abstract static class Builder {
private final ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder;
protected Builder(ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
this.connectionConfigurationBuilder = connectionConfigurationBuilder;
}
protected abstract ModularXmppClientToServerConnectionModuleDescriptor build();
public ModularXmppClientToServerConnectionConfiguration.Builder buildModule() {
ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor = build();
connectionConfigurationBuilder.addModule(moduleDescriptor);
return connectionConfigurationBuilder;
}
}
}

View file

@ -0,0 +1,76 @@
/**
*
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.c2s;
import java.util.List;
import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
public abstract class XmppClientToServerTransport {
protected final ModularXmppClientToServerConnectionInternal connectionInternal;
protected XmppClientToServerTransport(ModularXmppClientToServerConnectionInternal connectionInternal) {
this.connectionInternal = connectionInternal;
}
protected abstract void resetDiscoveredConnectionEndpoints();
protected abstract List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints();
protected abstract void loadConnectionEndpoints(LookupConnectionEndpointsSuccess lookupConnectionEndpointsSuccess);
/**
* Notify the transport that new outgoing data is available. Usually this method does not need to be called
* explicitly, only if the filters are modified so that they potentially produced new data.
*/
protected abstract void afterFiltersClosed();
/**
* Called by the CloseConnection state.
*/
protected abstract void disconnect();
protected abstract void notifyAboutNewOutgoingElements();
public abstract SSLSession getSslSession();
public abstract boolean isConnected();
public boolean isTransportSecured() {
return getSslSession() != null;
}
public abstract Stats getStats();
public abstract static class Stats {
}
protected interface LookupConnectionEndpointsResult {
}
protected interface LookupConnectionEndpointsSuccess extends LookupConnectionEndpointsResult {
}
public interface LookupConnectionEndpointsFailed extends LookupConnectionEndpointsResult {
// TODO: Add something like getExceptions() or getConnectionExceptions()?
}
}

View file

@ -0,0 +1,125 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.c2s.internal;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ListIterator;
import java.util.Queue;
import org.jivesoftware.smack.AbstractXMPPConnection.SmackTlsContext;
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackReactor;
import org.jivesoftware.smack.SmackReactor.ChannelSelectedCallback;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.XmppInputOutputFilter;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.XmppClientToServerTransport;
import org.jivesoftware.smack.debugger.SmackDebugger;
import org.jivesoftware.smack.fsm.ConnectionStateEvent;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.xml.XmlPullParser;
public abstract class ModularXmppClientToServerConnectionInternal {
private final SmackReactor reactor;
public final ModularXmppClientToServerConnection connection;
public final SmackDebugger smackDebugger;
public final Queue<TopLevelStreamElement> outgoingElementsQueue;
public ModularXmppClientToServerConnectionInternal(ModularXmppClientToServerConnection connection, SmackReactor reactor,
SmackDebugger smackDebugger, Queue<TopLevelStreamElement> outgoingElementsQueue) {
this.connection = connection;
this.reactor = reactor;
this.smackDebugger = smackDebugger;
this.outgoingElementsQueue = outgoingElementsQueue;
}
public SelectionKey registerWithSelector(SelectableChannel channel, int ops, ChannelSelectedCallback callback)
throws ClosedChannelException {
return reactor.registerWithSelector(channel, ops, callback);
}
public void setInterestOps(SelectionKey selectionKey, int interestOps) {
reactor.setInterestOps(selectionKey, interestOps);
}
public final void withSmackDebugger(Consumer<SmackDebugger> smackDebuggerConsumer) {
if (smackDebugger == null) {
return;
}
smackDebuggerConsumer.accept(smackDebugger);
}
public abstract XmlEnvironment getOutgoingStreamXmlEnvironment();
// TODO: The incomingElement parameter was previously of type TopLevelStreamElement, but I believe it has to be
// of type string. But would this also work for BOSH or WebSocket?
public abstract void parseAndProcessElement(String wrappedCompleteIncomingElement);
public abstract void notifyConnectionError(Exception e);
public abstract void onStreamOpen(XmlPullParser parser);
public abstract void onStreamClosed();
public abstract void fireFirstLevelElementSendListeners(TopLevelStreamElement element);
public abstract void invokeConnectionStateMachineListener(ConnectionStateEvent connectionStateEvent);
public abstract void addXmppInputOutputFilter(XmppInputOutputFilter xmppInputOutputFilter);
public abstract ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterBeginIterator();
public abstract ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterEndIterator();
public abstract void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException;
public abstract SmackTlsContext getSmackTlsContext()
throws KeyManagementException, NoSuchAlgorithmException, CertificateException, IOException,
UnrecoverableKeyException, KeyStoreException, NoSuchProviderException;
public abstract <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(Nonza nonza,
Class<SN> successNonzaClass, Class<FN> failedNonzaClass)
throws NoResponseException, NotConnectedException, FailedNonzaException, InterruptedException;
public abstract void asyncGo(Runnable runnable);
public abstract Exception getCurrentConnectionException();
public abstract void setCompressionEnabled(boolean compressionEnabled);
public abstract void setTransport(XmppClientToServerTransport xmppTransport);
}

View file

@ -0,0 +1,179 @@
/**
*
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.c2s.internal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedAndResourceBoundStateDescriptor;
import org.jivesoftware.smack.fsm.LoginContext;
import org.jivesoftware.smack.fsm.State;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
import org.jivesoftware.smack.fsm.StateTransitionResult;
import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.Objects;
import org.jxmpp.jid.parts.Resourcepart;
public final class WalkStateGraphContext {
private final Class<? extends StateDescriptor> initialStateClass;
private final Class<? extends StateDescriptor> finalStateClass;
private final Class<? extends StateDescriptor> mandatoryIntermediateState;
private final LoginContext loginContext;
private final List<State> walkedStateGraphPath = new ArrayList<>();
/**
* A linked Map of failed States with their reason as value.
*/
final Map<State, StateTransitionResult> failedStates = new LinkedHashMap<>();
boolean mandatoryIntermediateStateHandled;
WalkStateGraphContext(Builder builder) {
initialStateClass = builder.initialStateClass;
finalStateClass = builder.finalStateClass;
mandatoryIntermediateState = builder.mandatoryIntermediateState;
loginContext = builder.loginContext;
}
public void recordWalkTo(State state) {
walkedStateGraphPath.add(state);
}
public boolean isWalksFinalState(StateDescriptor stateDescriptor) {
return stateDescriptor.getClass() == finalStateClass;
}
public boolean isFinalStateAuthenticatedAndResourceBound() {
return finalStateClass == AuthenticatedAndResourceBoundStateDescriptor.class;
}
public GraphVertex<State> maybeReturnMandatoryImmediateState(List<GraphVertex<State>> outgoingStateEdges) {
for (GraphVertex<State> outgoingStateVertex : outgoingStateEdges) {
if (outgoingStateVertex.getElement().getStateDescriptor().getClass() == mandatoryIntermediateState) {
mandatoryIntermediateStateHandled = true;
return outgoingStateVertex;
}
}
return null;
}
public List<State> getWalk() {
return CollectionUtil.newListWith(walkedStateGraphPath);
}
public int getWalkLength() {
return walkedStateGraphPath.size();
}
public void appendWalkTo(List<State> walk) {
walk.addAll(walkedStateGraphPath);
}
public LoginContext getLoginContext() {
return loginContext;
}
public boolean stateAlreadyVisited(State state) {
return walkedStateGraphPath.contains(state);
}
public void recordFailedState(State state, StateTransitionResult stateTransitionResult) {
failedStates.put(state, stateTransitionResult);
}
public Map<State, StateTransitionResult> getFailedStates() {
return new HashMap<>(failedStates);
}
/**
* Check if the way to the final state via the given successor state that would loop, i.e., lead over the initial state and
* thus from a cycle.
*
* @param successorStateVertex the successor state to use on the way.
* @return <code>true</code> if it would loop, <code>false</code> otherwise.
*/
public boolean wouldCauseCycle(GraphVertex<State> successorStateVertex) {
Set<Class<? extends StateDescriptor>> visited = new HashSet<>();
return wouldCycleRecursive(successorStateVertex, visited);
}
private boolean wouldCycleRecursive(GraphVertex<State> stateVertex, Set<Class<? extends StateDescriptor>> visited) {
Class<? extends StateDescriptor> stateVertexClass = stateVertex.getElement().getStateDescriptor().getClass();
if (stateVertexClass == initialStateClass) {
return true;
}
if (finalStateClass == stateVertexClass || visited.contains(stateVertexClass)) {
return false;
}
visited.add(stateVertexClass);
for (GraphVertex<State> successorStateVertex : stateVertex.getOutgoingEdges()) {
boolean cycle = wouldCycleRecursive(successorStateVertex, visited);
if (cycle) {
return true;
}
}
return false;
}
public static Builder builder(Class<? extends StateDescriptor> initialStateClass, Class<? extends StateDescriptor> finalStateClass) {
return new Builder(initialStateClass, finalStateClass);
}
public static final class Builder {
private final Class<? extends StateDescriptor> initialStateClass;
private final Class<? extends StateDescriptor> finalStateClass;
private Class<? extends StateDescriptor> mandatoryIntermediateState;
private LoginContext loginContext;
private Builder(Class<? extends StateDescriptor> initialStateClass, Class<? extends StateDescriptor> finalStateClass) {
this.initialStateClass = Objects.requireNonNull(initialStateClass);
this.finalStateClass = Objects.requireNonNull(finalStateClass);
}
public Builder withMandatoryIntermediateState(Class<? extends StateDescriptor> mandatoryIntermedidateState) {
this.mandatoryIntermediateState = mandatoryIntermedidateState;
return this;
}
public Builder withLoginContext(String username, String password, Resourcepart resource) {
LoginContext loginContext = new LoginContext(username, password, resource);
return withLoginContext(loginContext);
}
public Builder withLoginContext(LoginContext loginContext) {
this.loginContext = loginContext;
return this;
}
public WalkStateGraphContext build() {
return new WalkStateGraphContext(this);
}
}
}

View file

@ -0,0 +1,21 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Smack's internal API for client-to-server (c2s) connections.
*/
package org.jivesoftware.smack.c2s.internal;

View file

@ -0,0 +1,21 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Smack's (new) API for client-to-server (c2s) connections.
*/
package org.jivesoftware.smack.c2s;

View file

@ -0,0 +1,132 @@
/**
*
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.compression;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.XmppInputOutputFilter;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedButUnboundStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ResourceBindingStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
import org.jivesoftware.smack.compress.packet.Compress;
import org.jivesoftware.smack.compress.packet.Compressed;
import org.jivesoftware.smack.compress.packet.Failure;
import org.jivesoftware.smack.fsm.State;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.fsm.StateTransitionResult;
public class CompressionModule extends ModularXmppClientToServerConnectionModule<CompressionModuleDescriptor> {
protected CompressionModule(CompressionModuleDescriptor moduleDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(moduleDescriptor, connectionInternal);
}
public static final class CompressionStateDescriptor extends StateDescriptor {
private CompressionStateDescriptor() {
super(CompressionModule.CompressionState.class, 138);
addPredeccessor(AuthenticatedButUnboundStateDescriptor.class);
addSuccessor(AuthenticatedButUnboundStateDescriptor.class);
declarePrecedenceOver(ResourceBindingStateDescriptor.class);
}
@Override
protected CompressionModule.CompressionState constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
CompressionModule compressionModule = connectionInternal.connection.getConnectionModuleFor(CompressionModuleDescriptor.class);
return compressionModule.constructCompressionState(this, connectionInternal);
}
}
private static final class CompressionState extends State {
private XmppCompressionFactory selectedCompressionFactory;
private XmppInputOutputFilter usedXmppInputOutputCompressionFitler;
private CompressionState(StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
super(stateDescriptor, connectionInternal);
}
@Override
public StateTransitionResult.TransitionImpossible isTransitionToPossible(
WalkStateGraphContext walkStateGraphContext) {
final ConnectionConfiguration config = connectionInternal.connection.getConfiguration();
if (!config.isCompressionEnabled()) {
return new StateTransitionResult.TransitionImpossibleReason("Stream compression disabled by connection configuration");
}
Compress.Feature compressFeature = connectionInternal.connection.getFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE);
if (compressFeature == null) {
return new StateTransitionResult.TransitionImpossibleReason("Stream compression not supported or enabled by service");
}
selectedCompressionFactory = XmppCompressionManager.getBestFactory(compressFeature);
if (selectedCompressionFactory == null) {
return new StateTransitionResult.TransitionImpossibleReason(
"No matching compression factory for " + compressFeature.getMethods());
}
usedXmppInputOutputCompressionFitler = selectedCompressionFactory.fabricate(config);
return null;
}
@Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws NoResponseException, NotConnectedException, FailedNonzaException, InterruptedException,
ConnectionUnexpectedTerminatedException {
final String compressionMethod = selectedCompressionFactory.getCompressionMethod();
connectionInternal.sendAndWaitForResponse(new Compress(compressionMethod), Compressed.class, Failure.class);
connectionInternal.addXmppInputOutputFilter(usedXmppInputOutputCompressionFitler);
connectionInternal.newStreamOpenWaitForFeaturesSequence("server stream features after compression enabled");
connectionInternal.setCompressionEnabled(true);
return new CompressionTransitionSuccessResult(compressionMethod);
}
@Override
public void resetState() {
selectedCompressionFactory = null;
usedXmppInputOutputCompressionFitler = null;
connectionInternal.setCompressionEnabled(false);
}
}
public static final class CompressionTransitionSuccessResult extends StateTransitionResult.Success {
private final String compressionMethod;
private CompressionTransitionSuccessResult(String compressionMethod) {
super(compressionMethod + " compression enabled");
this.compressionMethod = compressionMethod;
}
public String getCompressionMethod() {
return compressionMethod;
}
}
public CompressionState constructCompressionState(CompressionStateDescriptor compressionStateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
return new CompressionState(compressionStateDescriptor, connectionInternal);
}
}

View file

@ -0,0 +1,54 @@
/**
*
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.compression;
import java.util.Collections;
import java.util.Set;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.fsm.StateDescriptor;
public class CompressionModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
private static final CompressionModuleDescriptor INSTANCE = new CompressionModuleDescriptor();
@Override
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
return Collections.singleton(CompressionModule.CompressionStateDescriptor.class);
}
@Override
protected CompressionModule constructXmppConnectionModule(
ModularXmppClientToServerConnectionInternal connectionInternal) {
return new CompressionModule(this, connectionInternal);
}
public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
private Builder(ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
super(connectionConfigurationBuilder);
}
@Override
protected ModularXmppClientToServerConnectionModuleDescriptor build() {
return INSTANCE;
}
}
}

View file

@ -230,6 +230,11 @@ public final class ZlibXmppCompressionFactory extends XmppCompressionFactory {
public Stats getStats() {
return new Stats(this);
}
@Override
public String getFilterName() {
return "Compression (zlib)";
}
}
public static final class Stats {

View file

@ -1,806 +0,0 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.fsm;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.XmppInputOutputFilter;
import org.jivesoftware.smack.compress.packet.Compress;
import org.jivesoftware.smack.compress.packet.Compressed;
import org.jivesoftware.smack.compress.packet.Failure;
import org.jivesoftware.smack.compression.XmppCompressionFactory;
import org.jivesoftware.smack.compression.XmppCompressionManager;
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.sasl.SASLErrorException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jxmpp.jid.parts.Resourcepart;
public abstract class AbstractXmppStateMachineConnection extends AbstractXMPPConnection {
private final List<ConnectionStateMachineListener> connectionStateMachineListeners = new CopyOnWriteArrayList<>();
private boolean featuresReceived;
protected boolean streamResumed;
private GraphVertex<State> currentStateVertex;
private List<State> walkFromDisconnectToAuthenticated;
private final List<XmppInputOutputFilter> inputOutputFilters = new CopyOnWriteArrayList<>();
private List<XmppInputOutputFilter> previousInputOutputFilters;
protected AbstractXmppStateMachineConnection(ConnectionConfiguration configuration, GraphVertex<StateDescriptor> initialStateDescriptorVertex) {
super(configuration);
currentStateVertex = StateDescriptorGraph.convertToStateGraph(initialStateDescriptorVertex, this);
}
@Override
protected void loginInternal(String username, String password, Resourcepart resource)
throws XMPPException, SmackException, IOException, InterruptedException {
WalkStateGraphContext walkStateGraphContext = buildNewWalkTo(AuthenticatedAndResourceBoundStateDescriptor.class)
.withLoginContext(username, password, resource)
.build();
walkStateGraph(walkStateGraphContext);
}
protected static WalkStateGraphContextBuilder buildNewWalkTo(Class<? extends StateDescriptor> finalStateClass) {
return new WalkStateGraphContextBuilder(finalStateClass);
}
protected static final class WalkStateGraphContext {
private final Class<? extends StateDescriptor> finalStateClass;
private final Class<? extends StateDescriptor> mandatoryIntermediateState;
private final LoginContext loginContext;
private final List<State> walkedStateGraphPath = new ArrayList<>();
/**
* A linked Map of failed States with their reason as value.
*/
private final Map<State, TransitionReason> failedStates = new LinkedHashMap<>();
private boolean mandatoryIntermediateStateHandled;
private WalkStateGraphContext(Class<? extends StateDescriptor> finalStateClass, Class<? extends StateDescriptor> mandatoryIntermedidateState, LoginContext loginContext) {
this.finalStateClass = Objects.requireNonNull(finalStateClass);
this.mandatoryIntermediateState = mandatoryIntermedidateState;
this.loginContext = loginContext;
}
public boolean isFinalStateAuthenticatedAndResourceBound() {
return finalStateClass == AuthenticatedAndResourceBoundStateDescriptor.class;
}
}
protected static final class WalkStateGraphContextBuilder {
private final Class<? extends StateDescriptor> finalStateClass;
private Class<? extends StateDescriptor> mandatoryIntermedidateState;
private LoginContext loginContext;
private WalkStateGraphContextBuilder(Class<? extends StateDescriptor> finalStateClass) {
this.finalStateClass = finalStateClass;
}
public WalkStateGraphContextBuilder withMandatoryIntermediateState(Class<? extends StateDescriptor> mandatoryIntermedidateState) {
this.mandatoryIntermedidateState = mandatoryIntermedidateState;
return this;
}
public WalkStateGraphContextBuilder withLoginContext(String username, String password, Resourcepart resource) {
LoginContext loginContext = new LoginContext(username, password, resource);
return withLoginContext(loginContext);
}
public WalkStateGraphContextBuilder withLoginContext(LoginContext loginContext) {
this.loginContext = loginContext;
return this;
}
public WalkStateGraphContext build() {
return new WalkStateGraphContext(finalStateClass, mandatoryIntermedidateState, loginContext);
}
}
protected final void walkStateGraph(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException, SASLErrorException,
FailedNonzaException, IOException, SmackException, InterruptedException {
// Save a copy of the current state
GraphVertex<State> previousStateVertex = currentStateVertex;
try {
walkStateGraphInternal(walkStateGraphContext);
}
catch (XMPPErrorException | SASLErrorException | FailedNonzaException | IOException | SmackException
| InterruptedException e) {
currentStateVertex = previousStateVertex;
// Reset that state.
State revertedState = currentStateVertex.getElement();
invokeConnectionStateMachineListener(new ConnectionStateEvent.StateRevertBackwardsWalk(revertedState));
revertedState.resetState();
throw e;
}
}
private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext)
throws XMPPErrorException, SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
// Save a copy of the current state
final GraphVertex<State> initialStateVertex = currentStateVertex;
final State initialState = initialStateVertex.getElement();
final StateDescriptor initialStateDescriptor = initialState.getStateDescriptor();
walkStateGraphContext.walkedStateGraphPath.add(initialState);
if (initialStateDescriptor.getClass() == walkStateGraphContext.finalStateClass) {
// If this is used as final state, then it should be marked as such.
assert initialStateDescriptor.isFinalState();
// We reached the final state.
invokeConnectionStateMachineListener(new ConnectionStateEvent.FinalStateReached(initialState));
return;
}
List<GraphVertex<State>> outgoingStateEdges = currentStateVertex.getOutgoingEdges();
// See if we need to handle mandatory intermediate states.
if (walkStateGraphContext.mandatoryIntermediateState != null && !walkStateGraphContext.mandatoryIntermediateStateHandled) {
// Check if outgoingStateEdges contains the mandatory intermediate state.
GraphVertex<State> mandatoryIntermediateStateVertex = null;
for (GraphVertex<State> outgoingStateVertex : outgoingStateEdges) {
if (outgoingStateVertex.getElement().getStateDescriptor().getClass() == walkStateGraphContext.mandatoryIntermediateState) {
mandatoryIntermediateStateVertex = outgoingStateVertex;
break;
}
}
if (mandatoryIntermediateStateVertex != null) {
walkStateGraphContext.mandatoryIntermediateStateHandled = true;
TransitionReason reason = attemptEnterState(mandatoryIntermediateStateVertex, walkStateGraphContext);
if (reason instanceof TransitionSuccessResult) {
walkStateGraph(walkStateGraphContext);
return;
}
// We could not enter a mandatory intermediate state. Throw here.
throw new StateMachineException.SmackMandatoryStateFailedException(
mandatoryIntermediateStateVertex.getElement(), reason);
}
}
for (Iterator<GraphVertex<State>> it = outgoingStateEdges.iterator(); it.hasNext();) {
GraphVertex<State> successorStateVertex = it.next();
State successorState = successorStateVertex.getElement();
TransitionReason reason = attemptEnterState(successorStateVertex, walkStateGraphContext);
if (reason instanceof TransitionSuccessResult) {
break;
}
// If attemptEnterState did not throw and did not return a value of type TransitionSuccessResult, then we
// just record this value and go on from there. Note that reason may be null, which is returned by
// attemptEnterState in case the state was already successfully handled. If this is the case, then we don't
// record it.
if (reason != null) {
walkStateGraphContext.failedStates.put(successorState, reason);
}
if (!it.hasNext()) {
throw new StateMachineException.SmackStateGraphDeadEndException(walkStateGraphContext.walkedStateGraphPath, walkStateGraphContext.failedStates);
}
}
// Walk the state graph by recursion.
walkStateGraph(walkStateGraphContext);
}
private TransitionReason attemptEnterState(GraphVertex<State> successorStateVertex,
WalkStateGraphContext walkStateGraphContext)
throws SmackException, XMPPErrorException, SASLErrorException, IOException, InterruptedException, FailedNonzaException {
final State successorState = successorStateVertex.getElement();
final StateDescriptor successorStateDescriptor = successorState.getStateDescriptor();
if (!successorStateDescriptor.isMultiVisitState() && walkStateGraphContext.walkedStateGraphPath.contains(successorState)) {
// This can happen if a state leads back to the state where it originated from. See for example the
// 'Compression' state. We return 'null' here to signal that the state can safely be ignored.
return null;
}
if (successorStateDescriptor.isNotImplemented()) {
TransitionImpossibleBecauseNotImplemented transtionImpossibleBecauseNotImplemented = new TransitionImpossibleBecauseNotImplemented(
successorStateDescriptor);
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(successorState,
transtionImpossibleBecauseNotImplemented));
return transtionImpossibleBecauseNotImplemented;
}
final TransitionIntoResult transitionIntoResult;
try {
TransitionImpossibleReason transitionImpossibleReason = successorState.isTransitionToPossible(walkStateGraphContext);
if (transitionImpossibleReason != null) {
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(successorState,
transitionImpossibleReason));
return transitionImpossibleReason;
}
invokeConnectionStateMachineListener(new ConnectionStateEvent.AboutToTransitionInto(successorState));
transitionIntoResult = successorState.transitionInto(walkStateGraphContext);
} catch (SmackException | XMPPErrorException | SASLErrorException | IOException | InterruptedException
| FailedNonzaException e) {
// TODO Document why this is required given that there is another call site of resetState().
invokeConnectionStateMachineListener(new ConnectionStateEvent.StateRevertBackwardsWalk(successorState));
successorState.resetState();
throw e;
}
if (transitionIntoResult instanceof TransitionFailureResult) {
TransitionFailureResult transitionFailureResult = (TransitionFailureResult) transitionIntoResult;
invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionFailed(successorState, transitionFailureResult));
return transitionIntoResult;
}
// If transitionIntoResult is not an instance of TransitionFailureResult, then it has to be of type
// TransitionSuccessResult.
TransitionSuccessResult transitionSuccessResult = (TransitionSuccessResult) transitionIntoResult;
currentStateVertex = successorStateVertex;
invokeConnectionStateMachineListener(new ConnectionStateEvent.SuccessfullyTransitionedInto(successorState,
transitionSuccessResult));
return transitionSuccessResult;
}
protected abstract SSLSession getSSLSession();
@Override
protected void afterFeaturesReceived() {
featuresReceived = true;
synchronized (this) {
notifyAll();
}
}
protected final void parseAndProcessElement(String element) throws XmlPullParserException, IOException,
InterruptedException, StreamErrorException, SmackException, SmackParsingException {
XmlPullParser parser = PacketParserUtils.getParserFor(element);
// Skip the enclosing stream open what is guaranteed to be there.
parser.next();
XmlPullParser.Event event = parser.getEventType();
outerloop: while (true) {
switch (event) {
case START_ELEMENT:
final String name = parser.getName();
// Note that we don't handle "stream" here as it's done in the splitter.
switch (name) {
case Message.ELEMENT:
case IQ.IQ_ELEMENT:
case Presence.ELEMENT:
try {
parseAndProcessStanza(parser);
} finally {
// TODO: Here would be the following stream management code.
// clientHandledStanzasCount = SMUtils.incrementHeight(clientHandledStanzasCount);
}
break;
case "error":
StreamError streamError = PacketParserUtils.parseStreamError(parser, null);
saslFeatureReceived.reportFailure(new StreamErrorException(streamError));
throw new StreamErrorException(streamError);
case "features":
parseFeatures(parser);
afterFeaturesReceived();
break;
default:
parseAndProcessNonza(parser);
break;
}
break;
case END_DOCUMENT:
break outerloop;
default: // fall out
}
event = parser.next();
}
}
protected synchronized void prepareToWaitForFeaturesReceived() {
featuresReceived = false;
}
protected void waitForFeaturesReceived(String waitFor)
throws InterruptedException, ConnectionUnexpectedTerminatedException, NoResponseException {
long waitStartMs = System.currentTimeMillis();
long timeoutMs = getReplyTimeout();
synchronized (this) {
while (!featuresReceived && currentConnectionException == null) {
long remainingWaitMs = timeoutMs - (System.currentTimeMillis() - waitStartMs);
if (remainingWaitMs <= 0) {
throw NoResponseException.newWith(this, waitFor);
}
wait(remainingWaitMs);
}
if (currentConnectionException != null) {
throw new SmackException.ConnectionUnexpectedTerminatedException(currentConnectionException);
}
}
}
protected void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
prepareToWaitForFeaturesReceived();
sendStreamOpen();
waitForFeaturesReceived(waitFor);
}
protected final void addXmppInputOutputFilter(XmppInputOutputFilter xmppInputOutputFilter) {
inputOutputFilters.add(0, xmppInputOutputFilter);
}
protected final ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterBeginIterator() {
return inputOutputFilters.listIterator();
}
protected final ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterEndIterator() {
return inputOutputFilters.listIterator(inputOutputFilters.size());
}
protected final synchronized List<Object> getFilterStats() {
Collection<XmppInputOutputFilter> filters;
if (inputOutputFilters.isEmpty() && previousInputOutputFilters != null) {
filters = previousInputOutputFilters;
} else {
filters = inputOutputFilters;
}
List<Object> filterStats = new ArrayList<>(filters.size());
for (XmppInputOutputFilter xmppInputOutputFilter : filters) {
Object stats = xmppInputOutputFilter.getStats();
if (stats != null) {
filterStats.add(stats);
}
}
return Collections.unmodifiableList(filterStats);
}
protected abstract class State {
private final StateDescriptor stateDescriptor;
protected State(StateDescriptor stateDescriptor) {
this.stateDescriptor = stateDescriptor;
}
/**
* Check if the state should be activated.
*
* @param walkStateGraphContext the context of the current state graph walk.
* @return <code>null</code> if the state should be activated.
* @throws SmackException in case a Smack exception occurs.
*/
protected TransitionImpossibleReason isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) throws SmackException {
return null;
}
protected abstract TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws XMPPErrorException, SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException;
StateDescriptor getStateDescriptor() {
return stateDescriptor;
}
protected void resetState() {
}
@Override
public String toString() {
return "State " + stateDescriptor + ' ' + AbstractXmppStateMachineConnection.this;
}
protected final void ensureNotOnOurWayToAuthenticatedAndResourceBound(WalkStateGraphContext walkStateGraphContext) {
if (walkStateGraphContext.isFinalStateAuthenticatedAndResourceBound()) {
throw new IllegalStateException(
"Smack should never attempt to reach the authenticated and resource bound state over " + this
+ ". This is probably a programming error within Smack, please report it to the develoeprs.");
}
}
}
abstract static class TransitionReason {
public final String reason;
private TransitionReason(String reason) {
this.reason = reason;
}
@Override
public final String toString() {
return reason;
}
}
protected static class TransitionImpossibleReason extends TransitionReason {
public TransitionImpossibleReason(String reason) {
super(reason);
}
}
protected static class TransitionImpossibleBecauseNotImplemented extends TransitionImpossibleReason {
public TransitionImpossibleBecauseNotImplemented(StateDescriptor stateDescriptor) {
super(stateDescriptor.getFullStateName(false) + " is not implemented (yet)");
}
}
protected abstract static class TransitionIntoResult extends TransitionReason {
public TransitionIntoResult(String reason) {
super(reason);
}
}
public static class TransitionSuccessResult extends TransitionIntoResult {
public static final TransitionSuccessResult EMPTY_INSTANCE = new TransitionSuccessResult();
private TransitionSuccessResult() {
super("");
}
public TransitionSuccessResult(String reason) {
super(reason);
}
}
public static final class TransitionFailureResult extends TransitionIntoResult {
private TransitionFailureResult(String reason) {
super(reason);
}
}
protected final class NoOpState extends State {
private NoOpState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionImpossibleReason isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
// Transition into a NoOpState is always possible.
return null;
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
// Transition into a NoOpState always succeeds.
return TransitionSuccessResult.EMPTY_INSTANCE;
}
}
protected static class DisconnectedStateDescriptor extends StateDescriptor {
protected DisconnectedStateDescriptor() {
super(DisconnectedState.class, StateDescriptor.Property.finalState);
}
}
private final class DisconnectedState extends State {
private DisconnectedState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
if (inputOutputFilters.isEmpty()) {
previousInputOutputFilters = null;
} else {
previousInputOutputFilters = new ArrayList<>(inputOutputFilters.size());
previousInputOutputFilters.addAll(inputOutputFilters);
inputOutputFilters.clear();
}
ListIterator<State> it = walkFromDisconnectToAuthenticated.listIterator(
walkFromDisconnectToAuthenticated.size());
while (it.hasPrevious()) {
State stateToReset = it.previous();
stateToReset.resetState();
}
walkFromDisconnectToAuthenticated = null;
return TransitionSuccessResult.EMPTY_INSTANCE;
}
}
protected static final class ConnectedButUnauthenticatedStateDescriptor extends StateDescriptor {
private ConnectedButUnauthenticatedStateDescriptor() {
super(ConnectedButUnauthenticatedState.class, StateDescriptor.Property.finalState);
addSuccessor(SaslAuthenticationStateDescriptor.class);
}
}
private final class ConnectedButUnauthenticatedState extends State {
private ConnectedButUnauthenticatedState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
assert walkFromDisconnectToAuthenticated == null;
if (getStateDescriptor().getClass() == walkStateGraphContext.finalStateClass) {
// If this is the final state, then record the walk so far.
walkFromDisconnectToAuthenticated = new ArrayList<>(walkStateGraphContext.walkedStateGraphPath);
}
connected = true;
return TransitionSuccessResult.EMPTY_INSTANCE;
}
@Override
protected void resetState() {
connected = false;
}
}
protected static final class SaslAuthenticationStateDescriptor extends StateDescriptor {
private SaslAuthenticationStateDescriptor() {
super(SaslAuthenticationState.class, "RFC 6120 § 6");
addSuccessor(AuthenticatedButUnboundStateDescriptor.class);
}
}
private final class SaslAuthenticationState extends State {
private SaslAuthenticationState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
SASLErrorException, IOException, SmackException, InterruptedException {
prepareToWaitForFeaturesReceived();
LoginContext loginContext = walkStateGraphContext.loginContext;
SASLMechanism usedSaslMechanism = authenticate(loginContext.username, loginContext.password, config.getAuthzid(), getSSLSession());
// authenticate() will only return if the SASL authentication was successful, but we also need to wait for the next round of stream features.
waitForFeaturesReceived("server stream features after SASL authentication");
return new SaslAuthenticationSuccessResult(usedSaslMechanism);
}
}
public static final class SaslAuthenticationSuccessResult extends TransitionSuccessResult {
private final String saslMechanismName;
private SaslAuthenticationSuccessResult(SASLMechanism usedSaslMechanism) {
super("SASL authentication successfull using " + usedSaslMechanism.getName());
this.saslMechanismName = usedSaslMechanism.getName();
}
public String getSaslMechanismName() {
return saslMechanismName;
}
}
protected static final class AuthenticatedButUnboundStateDescriptor extends StateDescriptor {
private AuthenticatedButUnboundStateDescriptor() {
super(StateDescriptor.Property.multiVisitState);
addSuccessor(ResourceBindingStateDescriptor.class);
addSuccessor(CompressionStateDescriptor.class);
}
}
protected static final class ResourceBindingStateDescriptor extends StateDescriptor {
private ResourceBindingStateDescriptor() {
super(ResourceBindingState.class, "RFC 6120 § 7");
addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
}
}
private final class ResourceBindingState extends State {
private ResourceBindingState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
SASLErrorException, IOException, SmackException, InterruptedException {
// TODO: The reportSuccess() is just a quick fix until there is a variant of the
// bindResourceAndEstablishSession() method which does not require this.
lastFeaturesReceived.reportSuccess();
LoginContext loginContext = walkStateGraphContext.loginContext;
Resourcepart resource = bindResourceAndEstablishSession(loginContext.resource);
streamResumed = false;
return new ResourceBoundResult(resource, loginContext.resource);
}
}
public static final class ResourceBoundResult extends TransitionSuccessResult {
private final Resourcepart resource;
private ResourceBoundResult(Resourcepart boundResource, Resourcepart requestedResource) {
super("Resource '" + boundResource + "' bound (requested: '" + requestedResource + "'");
this.resource = boundResource;
}
public Resourcepart getResource() {
return resource;
}
}
protected static final class CompressionStateDescriptor extends StateDescriptor {
private CompressionStateDescriptor() {
super(CompressionState.class, 138);
addSuccessor(AuthenticatedButUnboundStateDescriptor.class);
declarePrecedenceOver(ResourceBindingStateDescriptor.class);
}
}
private boolean compressionEnabled;
private class CompressionState extends State {
private XmppCompressionFactory selectedCompressionFactory;
private XmppInputOutputFilter usedXmppInputOutputCompressionFitler;
protected CompressionState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionImpossibleReason isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
if (!config.isCompressionEnabled()) {
return new TransitionImpossibleReason("Stream compression disabled");
}
Compress.Feature compressFeature = getFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE);
if (compressFeature == null) {
return new TransitionImpossibleReason("Stream compression not supported");
}
selectedCompressionFactory = XmppCompressionManager.getBestFactory(compressFeature);
if (selectedCompressionFactory == null) {
return new TransitionImpossibleReason("No matching compression factory");
}
usedXmppInputOutputCompressionFitler = selectedCompressionFactory.fabricate(config);
return null;
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws NoResponseException, NotConnectedException, FailedNonzaException, InterruptedException,
ConnectionUnexpectedTerminatedException {
final String compressionMethod = selectedCompressionFactory.getCompressionMethod();
sendAndWaitForResponse(new Compress(compressionMethod), Compressed.class, Failure.class);
addXmppInputOutputFilter(usedXmppInputOutputCompressionFitler);
newStreamOpenWaitForFeaturesSequence("server stream features after compression enabled");
compressionEnabled = true;
return new CompressionTransitionSuccessResult(compressionMethod);
}
@Override
protected void resetState() {
selectedCompressionFactory = null;
usedXmppInputOutputCompressionFitler = null;
compressionEnabled = false;
}
}
public static final class CompressionTransitionSuccessResult extends TransitionSuccessResult {
private final String compressionMethod;
private CompressionTransitionSuccessResult(String compressionMethod) {
super(compressionMethod + " compression enabled");
this.compressionMethod = compressionMethod;
}
public String getCompressionMethod() {
return compressionMethod;
}
}
@Override
public final boolean isUsingCompression() {
return compressionEnabled;
}
protected static final class AuthenticatedAndResourceBoundStateDescriptor extends StateDescriptor {
private AuthenticatedAndResourceBoundStateDescriptor() {
super(AuthenticatedAndResourceBoundState.class, StateDescriptor.Property.finalState);
}
}
private final class AuthenticatedAndResourceBoundState extends State {
private AuthenticatedAndResourceBoundState(StateDescriptor stateDescriptor) {
super(stateDescriptor);
}
@Override
protected TransitionIntoResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws NotConnectedException, InterruptedException {
if (walkFromDisconnectToAuthenticated != null) {
// If there was already a previous walk to ConnectedButUnauthenticated, then the context of the current
// walk must not start from the 'Disconnected' state.
assert walkStateGraphContext.walkedStateGraphPath.get(0).stateDescriptor.getClass() != DisconnectedStateDescriptor.class;
walkFromDisconnectToAuthenticated.addAll(walkStateGraphContext.walkedStateGraphPath);
} else {
walkFromDisconnectToAuthenticated = new ArrayList<>(walkStateGraphContext.walkedStateGraphPath.size() + 1);
walkFromDisconnectToAuthenticated.addAll(walkStateGraphContext.walkedStateGraphPath);
}
walkFromDisconnectToAuthenticated.add(this);
afterSuccessfulLogin(streamResumed);
return TransitionSuccessResult.EMPTY_INSTANCE;
}
@Override
protected void resetState() {
authenticated = false;
}
}
public void addConnectionStateMachineListener(ConnectionStateMachineListener connectionStateMachineListener) {
connectionStateMachineListeners.add(connectionStateMachineListener);
}
public boolean removeConnectionStateMachineListener(ConnectionStateMachineListener connectionStateMachineListener) {
return connectionStateMachineListeners.remove(connectionStateMachineListener);
}
protected void invokeConnectionStateMachineListener(ConnectionStateEvent connectionStateEvent) {
if (connectionStateMachineListeners.isEmpty()) {
return;
}
ASYNC_BUT_ORDERED.performAsyncButOrdered(this, () -> {
for (ConnectionStateMachineListener connectionStateMachineListener : connectionStateMachineListeners) {
connectionStateMachineListener.onConnectionStateEvent(connectionStateEvent, this);
}
});
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,29 +16,37 @@
*/
package org.jivesoftware.smack.fsm;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.State;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.TransitionFailureResult;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.TransitionImpossibleReason;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.TransitionSuccessResult;
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
public class ConnectionStateEvent {
private final StateDescriptor stateDescriptor;
private final StateDescriptor currentStateDescriptor;
private final StateDescriptor successorStateDescriptor;
private final long timestamp;
protected ConnectionStateEvent(StateDescriptor stateDescriptor) {
this.stateDescriptor = stateDescriptor;
public ConnectionStateEvent(StateDescriptor currentStateDescriptor) {
this(currentStateDescriptor, null);
}
public ConnectionStateEvent(StateDescriptor currentStateDescriptor, StateDescriptor successorStateDescriptor) {
this.currentStateDescriptor = currentStateDescriptor;
this.successorStateDescriptor = successorStateDescriptor;
this.timestamp = System.currentTimeMillis();
}
public StateDescriptor getStateDescriptor() {
return stateDescriptor;
return currentStateDescriptor;
}
@Override
public String toString() {
return stateDescriptor.getStateName() + ' ' + getClass().getSimpleName();
if (successorStateDescriptor == null) {
return getClass().getSimpleName() + ": " + currentStateDescriptor.getStateName();
} else {
return currentStateDescriptor.getStateName() + ' ' + getClass().getSimpleName() + ' '
+ successorStateDescriptor.getStateName();
}
}
public long getTimestamp() {
@ -46,22 +54,22 @@ public class ConnectionStateEvent {
}
public static class StateRevertBackwardsWalk extends ConnectionStateEvent {
StateRevertBackwardsWalk(State state) {
public StateRevertBackwardsWalk(State state) {
super(state.getStateDescriptor());
}
}
public static class FinalStateReached extends ConnectionStateEvent {
FinalStateReached(State state) {
public FinalStateReached(State state) {
super(state.getStateDescriptor());
}
}
public static class TransitionNotPossible extends ConnectionStateEvent {
private final TransitionImpossibleReason transitionImpossibleReason;
private final StateTransitionResult.TransitionImpossible transitionImpossibleReason;
TransitionNotPossible(State state, TransitionImpossibleReason reason) {
super(state.getStateDescriptor());
public TransitionNotPossible(State currentState, State successorState, StateTransitionResult.TransitionImpossible reason) {
super(currentState.getStateDescriptor(), successorState.getStateDescriptor());
this.transitionImpossibleReason = reason;
}
@ -72,16 +80,16 @@ public class ConnectionStateEvent {
}
public static class AboutToTransitionInto extends ConnectionStateEvent {
AboutToTransitionInto(State state) {
super(state.getStateDescriptor());
public AboutToTransitionInto(State currentState, State successorState) {
super(currentState.getStateDescriptor(), successorState.getStateDescriptor());
}
}
public static class TransitionFailed extends ConnectionStateEvent {
private final TransitionFailureResult transitionFailedReason;
private final StateTransitionResult.Failure transitionFailedReason;
TransitionFailed(State state, TransitionFailureResult transitionFailedReason) {
super(state.getStateDescriptor());
public TransitionFailed(State currentState, State failedSuccessorState, StateTransitionResult.Failure transitionFailedReason) {
super(currentState.getStateDescriptor(), failedSuccessorState.getStateDescriptor());
this.transitionFailedReason = transitionFailedReason;
}
@ -91,10 +99,16 @@ public class ConnectionStateEvent {
}
}
public static class SuccessfullyTransitionedInto extends ConnectionStateEvent {
private final TransitionSuccessResult transitionSuccessResult;
public static class TransitionIgnoredDueCycle extends ConnectionStateEvent {
public TransitionIgnoredDueCycle(GraphVertex<State> currentStateVertex, GraphVertex<State> successorStateVertexCausingCycle) {
super(currentStateVertex.getElement().getStateDescriptor(), successorStateVertexCausingCycle.getElement().getStateDescriptor());
}
}
SuccessfullyTransitionedInto(State state, TransitionSuccessResult transitionSuccessResult) {
public static class SuccessfullyTransitionedInto extends ConnectionStateEvent {
private final StateTransitionResult.Success transitionSuccessResult;
public SuccessfullyTransitionedInto(State state, StateTransitionResult.Success transitionSuccessResult) {
super(state.getStateDescriptor());
this.transitionSuccessResult = transitionSuccessResult;
}

View file

@ -16,9 +16,11 @@
*/
package org.jivesoftware.smack.fsm;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
// TODO: Mark as java.lang.FunctionalInterface once Smack's minimum Android API level is 24 or higher.
public interface ConnectionStateMachineListener {
void onConnectionStateEvent(ConnectionStateEvent connectionStateEvent, AbstractXmppStateMachineConnection connection);
void onConnectionStateEvent(ConnectionStateEvent connectionStateEvent, ModularXmppClientToServerConnection connection);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,11 +20,11 @@ import org.jxmpp.jid.parts.Resourcepart;
// TODO: At one point SASL authzid should be part of this.
public class LoginContext {
final String username;
final String password;
final Resourcepart resource;
public final String username;
public final String password;
public final Resourcepart resource;
LoginContext(String username, String password, Resourcepart resource) {
public LoginContext(String username, String password, Resourcepart resource) {
this.username = username;
this.password = password;
this.resource = resource;

View file

@ -0,0 +1,43 @@
/**
*
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.fsm;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
public class NoOpState extends State {
/**
* Constructs a NoOpState. Note that the signature of this constructor is designed so that it mimics States which
* are non-static inner classes of ModularXmppClientToServerConnection. That is why the first argument is not used.
*
* @param connection the connection.
* @param stateDescriptor the related state descriptor
* @param connectionInternal the internal connection API.
*/
@SuppressWarnings("UnusedVariable")
protected NoOpState(ModularXmppClientToServerConnection connection, StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
super(stateDescriptor, connectionInternal);
}
@Override
public StateTransitionResult.Success transitionInto(WalkStateGraphContext walkStateGraphContext) {
// Transition into a NoOpState always succeeds.
return StateTransitionResult.Success.EMPTY_INSTANCE;
}
}

View file

@ -0,0 +1,81 @@
/**
*
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.fsm;
import java.io.IOException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
import org.jivesoftware.smack.sasl.SASLErrorException;
/**
* Note that this is an non-static inner class of XmppClientToServerConnection so that states can inspect and modify
* the connection.
*/
public abstract class State {
protected final StateDescriptor stateDescriptor;
protected final ModularXmppClientToServerConnectionInternal connectionInternal;
protected State(StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
this.stateDescriptor = stateDescriptor;
this.connectionInternal = connectionInternal;
}
/**
* Check if the state should be activated.
*
* @param walkStateGraphContext the context of the current state graph walk.
* @return <code>null</code> if the state should be activated.
* @throws SmackException in case a Smack exception occurs.
*/
public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext)
throws SmackException {
return null;
}
public abstract StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws XMPPErrorException, SASLErrorException, IOException, SmackException,
InterruptedException, FailedNonzaException;
public StateDescriptor getStateDescriptor() {
return stateDescriptor;
}
public void resetState() {
}
@Override
public String toString() {
return "State " + stateDescriptor + ' ' + connectionInternal.connection;
}
protected final void ensureNotOnOurWayToAuthenticatedAndResourceBound(
WalkStateGraphContext walkStateGraphContext) {
if (walkStateGraphContext.isFinalStateAuthenticatedAndResourceBound()) {
throw new IllegalStateException(
"Smack should never attempt to reach the authenticated and resource bound state over "
+ this
+ ". This is probably a programming error within Smack, please report it to the develoeprs.");
}
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,9 +22,9 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.State;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
public abstract class StateDescriptor {
@ -34,15 +34,13 @@ public abstract class StateDescriptor {
notImplemented,
}
private static final Logger LOGGER = Logger.getLogger(StateDescriptor.class.getName());
private final String stateName;
private final int xepNum;
private final String rfcSection;
private final Set<Property> properties;
private final Class<? extends AbstractXmppStateMachineConnection.State> stateClass;
private final Constructor<? extends AbstractXmppStateMachineConnection.State> stateClassConstructor;
private final Class<? extends State> stateClass;
private final Constructor<? extends State> stateClassConstructor;
private final Set<Class<? extends StateDescriptor>> successors = new HashSet<>();
@ -53,36 +51,36 @@ public abstract class StateDescriptor {
private final Set<Class<? extends StateDescriptor>> inferiorTo = new HashSet<>();
protected StateDescriptor() {
this(AbstractXmppStateMachineConnection.NoOpState.class, (Property) null);
this(NoOpState.class, (Property) null);
}
protected StateDescriptor(Property... properties) {
this(AbstractXmppStateMachineConnection.NoOpState.class, properties);
this(NoOpState.class, properties);
}
protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass) {
protected StateDescriptor(Class<? extends State> stateClass) {
this(stateClass, -1, null, Collections.emptySet());
}
protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, Property... properties) {
protected StateDescriptor(Class<? extends State> stateClass, Property... properties) {
this(stateClass, -1, null, new HashSet<>(Arrays.asList(properties)));
}
protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, int xepNum) {
protected StateDescriptor(Class<? extends State> stateClass, int xepNum) {
this(stateClass, xepNum, null, Collections.emptySet());
}
protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, int xepNum,
protected StateDescriptor(Class<? extends State> stateClass, int xepNum,
Property... properties) {
this(stateClass, xepNum, null, new HashSet<>(Arrays.asList(properties)));
}
protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, String rfcSection) {
protected StateDescriptor(Class<? extends State> stateClass, String rfcSection) {
this(stateClass, -1, rfcSection, Collections.emptySet());
}
@SuppressWarnings("unchecked")
private StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, int xepNum,
private StateDescriptor(Class<? extends State> stateClass, int xepNum,
String rfcSection, Set<Property> properties) {
this.stateClass = stateClass;
if (rfcSection != null && xepNum > 0) {
@ -92,26 +90,32 @@ public abstract class StateDescriptor {
this.rfcSection = rfcSection;
this.properties = properties;
Constructor<? extends AbstractXmppStateMachineConnection.State> selectedConstructor = null;
Constructor<? extends State> selectedConstructor = null;
Constructor<?>[] constructors = stateClass.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
Class<?>[] parameterTypes = constructor.getParameterTypes();
if (parameterTypes.length != 2) {
LOGGER.warning("Invalid State class constructor: " + constructor);
if (parameterTypes.length != 3) {
continue;
}
if (!AbstractXmppStateMachineConnection.class.isAssignableFrom(parameterTypes[0])) {
if (!ModularXmppClientToServerConnection.class.isAssignableFrom(parameterTypes[0])) {
continue;
}
if (!StateDescriptor.class.isAssignableFrom(parameterTypes[1])) {
continue;
}
if (!ModularXmppClientToServerConnectionInternal.class.isAssignableFrom(parameterTypes[2])) {
continue;
}
selectedConstructor = (Constructor<? extends State>) constructor;
break;
}
if (selectedConstructor == null) {
throw new IllegalArgumentException();
}
stateClassConstructor = selectedConstructor;
if (stateClassConstructor != null) {
stateClassConstructor.setAccessible(true);
} else {
// TODO: Add validation check that if stateClassConstructor is 'null' the cosntructState() method is overriden.
}
String className = getClass().getSimpleName();
stateName = className.replaceFirst("StateDescriptor", "");
@ -121,7 +125,7 @@ public abstract class StateDescriptor {
addAndCheckNonExistent(successors, successor);
}
protected void addPredeccessor(Class<? extends StateDescriptor> predeccessor) {
public void addPredeccessor(Class<? extends StateDescriptor> predeccessor) {
addAndCheckNonExistent(predecessors, predeccessor);
}
@ -189,7 +193,7 @@ public abstract class StateDescriptor {
return referenceCache;
}
public Class<? extends AbstractXmppStateMachineConnection.State> getStateClass() {
public Class<? extends State> getStateClass() {
return stateClass;
}
@ -205,9 +209,12 @@ public abstract class StateDescriptor {
return properties.contains(Property.finalState);
}
protected final AbstractXmppStateMachineConnection.State constructState(AbstractXmppStateMachineConnection connection) {
protected State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
ModularXmppClientToServerConnection connection = connectionInternal.connection;
try {
return stateClassConstructor.newInstance(connection, this);
// If stateClassConstructor is null here, then you probably forgot to override the the
// StateDescriptor.constructState() method?
return stateClassConstructor.newInstance(connection, this, connectionInternal);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new IllegalStateException(e);

View file

@ -30,7 +30,8 @@ import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.DisconnectedStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.DisconnectedStateDescriptor;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.MultiMap;
@ -134,7 +135,7 @@ public class StateDescriptorGraph {
// The preference graph is the graph where the precedence information of all successors is stored, which we will
// topologically sort to find out which successor we should try first. It is a further new graph we use solely in
// this step for every node. The graph is representent as map. There is no special marker for the initial node
// this step for every node. The graph is represented as map. There is no special marker for the initial node
// as it is not required for the topological sort performed later.
Map<Class<? extends StateDescriptor>, GraphVertex<Class<? extends StateDescriptor>>> preferenceGraph = new HashMap<>(numSuccessors);
@ -171,7 +172,8 @@ public class StateDescriptorGraph {
}
}
// Perform a topological sort which returns the state descriptor classes in their priority.
// Perform a topological sort which returns the state descriptor classes sorted by their priority. Highest
// priority state descriptors first.
List<GraphVertex<Class<? extends StateDescriptor>>> sortedSuccessors = topologicalSort(preferenceGraph.values());
// Handle the successor nodes which have not preference information available. Simply append them to the end of
@ -222,19 +224,19 @@ public class StateDescriptorGraph {
return initialNode;
}
private static GraphVertex<AbstractXmppStateMachineConnection.State> convertToStateGraph(GraphVertex<StateDescriptor> stateDescriptorVertex,
AbstractXmppStateMachineConnection connection, Map<StateDescriptor, GraphVertex<AbstractXmppStateMachineConnection.State>> handledStateDescriptors) {
private static GraphVertex<State> convertToStateGraph(GraphVertex<StateDescriptor> stateDescriptorVertex,
ModularXmppClientToServerConnectionInternal connectionInternal, Map<StateDescriptor, GraphVertex<State>> handledStateDescriptors) {
StateDescriptor stateDescriptor = stateDescriptorVertex.getElement();
GraphVertex<AbstractXmppStateMachineConnection.State> stateVertex = handledStateDescriptors.get(stateDescriptor);
GraphVertex<State> stateVertex = handledStateDescriptors.get(stateDescriptor);
if (stateVertex != null) {
return stateVertex;
}
AbstractXmppStateMachineConnection.State state = stateDescriptor.constructState(connection);
State state = stateDescriptor.constructState(connectionInternal);
stateVertex = new GraphVertex<>(state);
handledStateDescriptors.put(stateDescriptor, stateVertex);
for (GraphVertex<StateDescriptor> successorStateDescriptorVertex : stateDescriptorVertex.getOutgoingEdges()) {
GraphVertex<AbstractXmppStateMachineConnection.State> successorStateVertex = convertToStateGraph(successorStateDescriptorVertex, connection, handledStateDescriptors);
GraphVertex<State> successorStateVertex = convertToStateGraph(successorStateDescriptorVertex, connectionInternal, handledStateDescriptors);
// It is important that we keep the order of the edges. This should do it.
stateVertex.addOutgoingEdge(successorStateVertex);
}
@ -242,10 +244,10 @@ public class StateDescriptorGraph {
return stateVertex;
}
static GraphVertex<AbstractXmppStateMachineConnection.State> convertToStateGraph(GraphVertex<StateDescriptor> initialStateDescriptor,
AbstractXmppStateMachineConnection connection) {
Map<StateDescriptor, GraphVertex<AbstractXmppStateMachineConnection.State>> handledStateDescriptors = new HashMap<>();
GraphVertex<AbstractXmppStateMachineConnection.State> initialState = convertToStateGraph(initialStateDescriptor, connection,
public static GraphVertex<State> convertToStateGraph(GraphVertex<StateDescriptor> initialStateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
Map<StateDescriptor, GraphVertex<State>> handledStateDescriptors = new HashMap<>();
GraphVertex<State> initialState = convertToStateGraph(initialStateDescriptor, connectionInternal,
handledStateDescriptors);
return initialState;
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,18 +21,26 @@ import java.util.List;
import java.util.Map;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.State;
import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.TransitionReason;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
public abstract class StateMachineException extends SmackException {
private static final long serialVersionUID = 1L;
protected StateMachineException(String message) {
super(message);
}
protected StateMachineException() {
super();
}
public static class SmackMandatoryStateFailedException extends StateMachineException {
private static final long serialVersionUID = 1L;
SmackMandatoryStateFailedException(State state, TransitionReason failureReason) {
public SmackMandatoryStateFailedException(State state, StateTransitionResult failureReason) {
}
}
@ -40,21 +48,36 @@ public abstract class StateMachineException extends SmackException {
private final List<State> walkedStateGraphPath;
private final Map<State, TransitionReason> failedStates;
private final Map<State, StateTransitionResult> failedStates;
private final StateDescriptor deadEndState;
private static final long serialVersionUID = 1L;
SmackStateGraphDeadEndException(List<State> walkedStateGraphPath, Map<State, TransitionReason> failedStates) {
this.walkedStateGraphPath = Collections.unmodifiableList(walkedStateGraphPath);
this.failedStates = Collections.unmodifiableMap(failedStates);
private SmackStateGraphDeadEndException(String message, WalkStateGraphContext walkStateGraphContext, GraphVertex<State> stateVertex) {
super(message);
this.walkedStateGraphPath = Collections.unmodifiableList(walkStateGraphContext.getWalk());
this.failedStates = Collections.unmodifiableMap(walkStateGraphContext.getFailedStates());
deadEndState = stateVertex.getElement().getStateDescriptor();
}
public List<State> getWalkedStateGraph() {
return walkedStateGraphPath;
}
public Map<State, TransitionReason> getFailedStates() {
public Map<State, StateTransitionResult> getFailedStates() {
return failedStates;
}
public StateDescriptor getDeadEndState() {
return deadEndState;
}
public static SmackStateGraphDeadEndException from(WalkStateGraphContext walkStateGraphContext, GraphVertex<State> stateVertex) {
String message = stateVertex + " has no successor vertexes";
return new SmackStateGraphDeadEndException(message, walkStateGraphContext, stateVertex);
}
}
}

View file

@ -0,0 +1,87 @@
/**
*
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.fsm;
public abstract class StateTransitionResult {
private final String message;
protected StateTransitionResult(String message) {
this.message = message;
}
@Override
public String toString() {
return message;
}
public abstract static class AttemptResult extends StateTransitionResult {
protected AttemptResult(String message) {
super(message);
}
}
public static class Success extends AttemptResult {
public static final Success EMPTY_INSTANCE = new Success();
private Success() {
super("");
}
public Success(String successMessage) {
super(successMessage);
}
}
public static class Failure extends AttemptResult {
public Failure(String failureMessage) {
super(failureMessage);
}
}
public static final class FailureCausedByException<E extends Exception> extends Failure {
private final E exception;
public FailureCausedByException(E exception) {
super(exception.getMessage());
this.exception = exception;
}
public E getException() {
return exception;
}
}
public abstract static class TransitionImpossible extends StateTransitionResult {
protected TransitionImpossible(String message) {
super(message);
}
}
public static class TransitionImpossibleReason extends TransitionImpossible {
public TransitionImpossibleReason(String reason) {
super(reason);
}
}
public static class TransitionImpossibleBecauseNotImplemented extends TransitionImpossibleReason {
public TransitionImpossibleBecauseNotImplemented(StateDescriptor stateDescriptor) {
super(stateDescriptor.getFullStateName(false) + " is not implemented (yet)");
}
}
}

View file

@ -0,0 +1,85 @@
/**
*
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.isr;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedAndResourceBoundStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.SaslAuthenticationStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
import org.jivesoftware.smack.fsm.State;
import org.jivesoftware.smack.fsm.StateDescriptor;
import org.jivesoftware.smack.fsm.StateTransitionResult;
public class InstantStreamResumptionModule extends ModularXmppClientToServerConnectionModule<InstantStreamResumptionModuleDescriptor> {
protected InstantStreamResumptionModule(InstantStreamResumptionModuleDescriptor instantStreamResumptionModuleDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(instantStreamResumptionModuleDescriptor, connectionInternal);
}
public static final class InstantStreamResumptionStateDescriptor extends StateDescriptor {
private InstantStreamResumptionStateDescriptor() {
super(InstantStreamResumptionState.class, 397, StateDescriptor.Property.notImplemented);
addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
addPredeccessor(ConnectedButUnauthenticatedStateDescriptor.class);
declarePrecedenceOver(SaslAuthenticationStateDescriptor.class);
}
@Override
protected InstantStreamResumptionModule.InstantStreamResumptionState constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
// This is the trick: the module is constructed prior the states, so we get the actual state out of the module by fetching the module from the connection.
InstantStreamResumptionModule isrModule = connectionInternal.connection.getConnectionModuleFor(InstantStreamResumptionModuleDescriptor.class);
return isrModule.constructInstantStreamResumptionState(this, connectionInternal);
}
}
private boolean useIsr = true;
private final class InstantStreamResumptionState extends State {
private InstantStreamResumptionState(InstantStreamResumptionStateDescriptor instantStreamResumptionStateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
super(instantStreamResumptionStateDescriptor, connectionInternal);
}
@Override
public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
if (!useIsr) {
return new StateTransitionResult.TransitionImpossibleReason("Instant stream resumption not enabled nor implemented");
}
return new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(stateDescriptor);
}
@Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
throw new IllegalStateException("Instant stream resumption not implemented");
}
}
public void setInstantStreamResumptionEnabled(boolean useIsr) {
this.useIsr = useIsr;
}
public InstantStreamResumptionState constructInstantStreamResumptionState(
InstantStreamResumptionStateDescriptor instantStreamResumptionStateDescriptor,
ModularXmppClientToServerConnectionInternal connectionInternal) {
return new InstantStreamResumptionState(instantStreamResumptionStateDescriptor, connectionInternal);
}
}

View file

@ -0,0 +1,54 @@
/**
*
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.isr;
import java.util.Collections;
import java.util.Set;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModuleDescriptor;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.fsm.StateDescriptor;
public class InstantStreamResumptionModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
private static final InstantStreamResumptionModuleDescriptor INSTANCE = new InstantStreamResumptionModuleDescriptor();
@Override
protected Set<Class<? extends StateDescriptor>> getStateDescriptors() {
return Collections.singleton(InstantStreamResumptionModule.InstantStreamResumptionStateDescriptor.class);
}
@Override
protected InstantStreamResumptionModule constructXmppConnectionModule(
ModularXmppClientToServerConnectionInternal connectionInternal) {
return new InstantStreamResumptionModule(this, connectionInternal);
}
public static final class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
private Builder(ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
super(connectionConfigurationBuilder);
}
@Override
protected ModularXmppClientToServerConnectionModuleDescriptor build() {
return INSTANCE;
}
}
}

View file

@ -0,0 +1,23 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Classes and interfaces for Instant Stream Resumption (ISR) (XEP-0397).
*
* @see <a href="https://xmpp.org/extensions/xep-0397.html">XEP-0397: Instant Stream Resumption</a>
*/
package org.jivesoftware.smack.isr;

View file

@ -163,15 +163,20 @@ public class ArrayBlockingQueueWithShutdown<E> extends AbstractQueue<E> implemen
/**
* Start the queue. Newly created instances will be started automatically, thus this only needs
* to be called after {@link #shutdown()}.
*
* @return <code>true</code> if the queues was shutdown before, <code>false</code> if not.
*/
public void start() {
public boolean start() {
boolean previousIsShutdown;
lock.lock();
try {
previousIsShutdown = isShutdown;
isShutdown = false;
}
finally {
lock.unlock();
}
return previousIsShutdown;
}
/**

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,8 +18,10 @@ package org.jivesoftware.smack.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class CollectionUtil {
@ -56,4 +58,11 @@ public class CollectionUtil {
}
return new ArrayList<>(collection);
}
public static <T> Set<T> newSetWith(Collection<? extends T> collection) {
if (collection == null) {
return null;
}
return new HashSet<>(collection);
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2005 Jive Software, 2016-2018 Florian Schmaus.
* Copyright 2003-2005 Jive Software, 2016-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,23 +16,9 @@
*/
package org.jivesoftware.smack.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
import org.jivesoftware.smack.util.dns.DNSResolver;
import org.jivesoftware.smack.util.dns.HostAddress;
import org.jivesoftware.smack.util.dns.SRVRecord;
import org.jivesoftware.smack.util.dns.SmackDaneProvider;
import org.minidns.dnsname.DnsName;
/**
* Utility class to perform DNS lookups for XMPP services.
*
@ -41,10 +27,6 @@ import org.minidns.dnsname.DnsName;
*/
public class DNSUtil {
public static final String XMPP_CLIENT_DNS_SRV_PREFIX = "_xmpp-client._tcp";
public static final String XMPP_SERVER_DNS_SRV_PREFIX = "_xmpp-server._tcp";
private static final Logger LOGGER = Logger.getLogger(DNSUtil.class.getName());
private static DNSResolver dnsResolver = null;
private static SmackDaneProvider daneProvider;
@ -84,188 +66,4 @@ public class DNSUtil {
return daneProvider;
}
@SuppressWarnings("ImmutableEnumChecker")
enum DomainType {
server(XMPP_SERVER_DNS_SRV_PREFIX),
client(XMPP_CLIENT_DNS_SRV_PREFIX),
;
public final DnsName srvPrefix;
DomainType(String srvPrefixString) {
srvPrefix = DnsName.from(srvPrefixString);
}
}
/**
* Returns a list of HostAddresses under which the specified XMPP server can be reached at for client-to-server
* communication. A DNS lookup for a SRV record in the form "_xmpp-client._tcp.example.com" is attempted, according
* to section 3.2.1 of RFC 6120. If that lookup fails, it's assumed that the XMPP server lives at the host resolved
* by a DNS lookup at the specified domain on the default port of 5222.
* <p>
* As an example, a lookup for "example.com" may return "im.example.com:5269".
* </p>
*
* @param domain the domain.
* @param failedAddresses on optional list that will be populated with host addresses that failed to resolve.
* @param dnssecMode DNSSec mode.
* @return List of HostAddress, which encompasses the hostname and port that the
* XMPP server can be reached at for the specified domain.
*/
public static List<HostAddress> resolveXMPPServiceDomain(DnsName domain, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
return resolveDomain(domain, DomainType.client, failedAddresses, dnssecMode);
}
/**
* Returns a list of HostAddresses under which the specified XMPP server can be reached at for server-to-server
* communication. A DNS lookup for a SRV record in the form "_xmpp-server._tcp.example.com" is attempted, according
* to section 3.2.1 of RFC 6120. If that lookup fails , it's assumed that the XMPP server lives at the host resolved
* by a DNS lookup at the specified domain on the default port of 5269.
* <p>
* As an example, a lookup for "example.com" may return "im.example.com:5269".
* </p>
*
* @param domain the domain.
* @param failedAddresses on optional list that will be populated with host addresses that failed to resolve.
* @param dnssecMode DNSSec mode.
* @return List of HostAddress, which encompasses the hostname and port that the
* XMPP server can be reached at for the specified domain.
*/
public static List<HostAddress> resolveXMPPServerDomain(DnsName domain, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
return resolveDomain(domain, DomainType.server, failedAddresses, dnssecMode);
}
/**
*
* @param domain the domain.
* @param domainType the XMPP domain type, server or client.
* @param failedAddresses a list that will be populated with host addresses that failed to resolve.
* @return a list of resolver host addresses for this domain.
*/
private static List<HostAddress> resolveDomain(DnsName domain, DomainType domainType,
List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
if (dnsResolver == null) {
throw new IllegalStateException("No DNS Resolver active in Smack");
}
List<HostAddress> addresses = new ArrayList<HostAddress>();
// Step one: Do SRV lookups
DnsName srvDomain = DnsName.from(domainType.srvPrefix, domain);
List<SRVRecord> srvRecords = dnsResolver.lookupSRVRecords(srvDomain, failedAddresses, dnssecMode);
if (srvRecords != null && !srvRecords.isEmpty()) {
if (LOGGER.isLoggable(Level.FINE)) {
String logMessage = "Resolved SRV RR for " + srvDomain + ":";
for (SRVRecord r : srvRecords)
logMessage += " " + r;
LOGGER.fine(logMessage);
}
List<HostAddress> sortedRecords = sortSRVRecords(srvRecords);
addresses.addAll(sortedRecords);
} else {
LOGGER.info("Could not resolve DNS SRV resource records for " + srvDomain + ". Consider adding those.");
}
int defaultPort = -1;
switch (domainType) {
case client:
defaultPort = 5222;
break;
case server:
defaultPort = 5269;
break;
}
// Step two: Add the hostname to the end of the list
HostAddress hostAddress = dnsResolver.lookupHostAddress(domain, defaultPort, failedAddresses, dnssecMode);
if (hostAddress != null) {
addresses.add(hostAddress);
}
return addresses;
}
/**
* Sort a given list of SRVRecords as described in RFC 2782
* Note that we follow the RFC with one exception. In a group of the same priority, only the first entry
* is calculated by random. The others are ore simply ordered by their priority.
*
* @param records TODO javadoc me please
* @return the list of resolved HostAddresses
*/
private static List<HostAddress> sortSRVRecords(List<SRVRecord> records) {
// RFC 2782, Usage rules: "If there is precisely one SRV RR, and its Target is "."
// (the root domain), abort."
if (records.size() == 1 && records.get(0).getFQDN().isRootLabel())
return Collections.emptyList();
// sorting the records improves the performance of the bisection later
Collections.sort(records);
// create the priority buckets
SortedMap<Integer, List<SRVRecord>> buckets = new TreeMap<Integer, List<SRVRecord>>();
for (SRVRecord r : records) {
Integer priority = r.getPriority();
List<SRVRecord> bucket = buckets.get(priority);
// create the list of SRVRecords if it doesn't exist
if (bucket == null) {
bucket = new LinkedList<SRVRecord>();
buckets.put(priority, bucket);
}
bucket.add(r);
}
List<HostAddress> res = new ArrayList<HostAddress>(records.size());
for (Integer priority : buckets.keySet()) {
List<SRVRecord> bucket = buckets.get(priority);
int bucketSize;
while ((bucketSize = bucket.size()) > 0) {
int[] totals = new int[bucketSize];
int running_total = 0;
int count = 0;
int zeroWeight = 1;
for (SRVRecord r : bucket) {
if (r.getWeight() > 0) {
zeroWeight = 0;
break;
}
}
for (SRVRecord r : bucket) {
running_total += r.getWeight() + zeroWeight;
totals[count] = running_total;
count++;
}
int selectedPos;
if (running_total == 0) {
// If running total is 0, then all weights in this priority
// group are 0. So we simply select one of the weights randomly
// as the other 'normal' algorithm is unable to handle this case
selectedPos = (int) (Math.random() * bucketSize);
} else {
double rnd = Math.random() * running_total;
selectedPos = bisect(totals, rnd);
}
// add the SRVRecord that was randomly chosen on it's weight
// to the start of the result list
SRVRecord chosenSRVRecord = bucket.remove(selectedPos);
res.add(chosenSRVRecord);
}
}
return res;
}
// TODO this is not yet really bisection just a stupid linear search
private static int bisect(int[] array, double value) {
int pos = 0;
for (int element : array) {
if (value < element)
break;
pos++;
}
return pos;
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2019 Florian Schmaus
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,4 +20,7 @@ public interface Function<R, T> {
R apply(T t);
static <T> Function<T, T> identity() {
return t -> t;
}
}

View file

@ -123,17 +123,26 @@ public class MultiMap<K, V> implements TypedCloneable<MultiMap<K, V>> {
}
public boolean put(K key, V value) {
return putInternal(key, list -> list.add(value));
}
public boolean putFirst(K key, V value) {
return putInternal(key, list -> list.add(0, value));
}
private boolean putInternal(K key, Consumer<List<V>> valueListConsumer) {
boolean keyExisted;
List<V> list = map.get(key);
if (list == null) {
list = new ArrayList<>(ENTRY_LIST_SIZE);
list.add(value);
map.put(key, list);
keyExisted = false;
} else {
list.add(value);
keyExisted = true;
}
valueListConsumer.accept(list);
return keyExisted;
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2016-2019 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2016-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -467,10 +467,24 @@ public class StringUtils {
return sb;
}
public static void appendTo(Collection<? extends Object> collection, StringBuilder sb) {
appendTo(collection, ", ", sb);
}
public static <O extends Object> void appendTo(Collection<O> collection, StringBuilder sb,
Consumer<O> appendFunction) {
appendTo(collection, ", ", sb, appendFunction);
}
public static void appendTo(Collection<? extends Object> collection, String delimiter, StringBuilder sb) {
for (Iterator<? extends Object> it = collection.iterator(); it.hasNext();) {
Object cs = it.next();
sb.append(cs);
appendTo(collection, delimiter, sb, o -> sb.append(o));
}
public static <O extends Object> void appendTo(Collection<O> collection, String delimiter, StringBuilder sb,
Consumer<O> appendFunction) {
for (Iterator<O> it = collection.iterator(); it.hasNext();) {
O cs = it.next();
appendFunction.accept(cs);
if (it.hasNext()) {
sb.append(delimiter);
}
@ -565,4 +579,16 @@ public class StringUtils {
public static String deleteXmlWhitespace(String string) {
return XML_WHITESPACE.matcher(string).replaceAll("");
}
public static Appendable appendHeading(Appendable appendable, String heading) throws IOException {
return appendHeading(appendable, heading, '-');
}
public static Appendable appendHeading(Appendable appendable, String heading, char underlineChar) throws IOException {
appendable.append(heading).append('\n');
for (int i = 0; i < heading.length(); i++) {
appendable.append(underlineChar);
}
return appendable.append('\n');
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2013-2018 Florian Schmaus
* Copyright 2013-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,13 +19,16 @@ package org.jivesoftware.smack.util.dns;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpointLookupFailure;
import org.minidns.dnsname.DnsName;
import org.minidns.record.SRV;
/**
* Implementations of this interface define a class that is capable of resolving DNS addresses.
@ -43,25 +46,25 @@ public abstract class DNSResolver {
/**
* Gets a list of service records for the specified service.
*
* @param name The symbolic name of the service.
* @param failedAddresses list of failed addresses.
* @param lookupFailures list of exceptions that occurred during lookup.
* @param dnssecMode security mode.
* @return The list of SRV records mapped to the service name.
*/
public final List<SRVRecord> lookupSRVRecords(DnsName name, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
public final Collection<SRV> lookupSrvRecords(DnsName name,
List<RemoteConnectionEndpointLookupFailure> lookupFailures, DnssecMode dnssecMode) {
checkIfDnssecRequestedAndSupported(dnssecMode);
return lookupSRVRecords0(name, failedAddresses, dnssecMode);
return lookupSrvRecords0(name, lookupFailures, dnssecMode);
}
protected abstract List<SRVRecord> lookupSRVRecords0(DnsName name, List<HostAddress> failedAddresses, DnssecMode dnssecMode);
protected abstract Collection<SRV> lookupSrvRecords0(DnsName name,
List<RemoteConnectionEndpointLookupFailure> lookupFailures, DnssecMode dnssecMode);
public final HostAddress lookupHostAddress(DnsName name, int port, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
public final List<InetAddress> lookupHostAddress(DnsName name,
List<RemoteConnectionEndpointLookupFailure> lookupFailures, DnssecMode dnssecMode) {
checkIfDnssecRequestedAndSupported(dnssecMode);
List<InetAddress> inetAddresses = lookupHostAddress0(name, failedAddresses, dnssecMode);
if (inetAddresses == null || inetAddresses.isEmpty()) {
return null;
}
return new HostAddress(name, port, inetAddresses);
return lookupHostAddress0(name, lookupFailures, dnssecMode);
}
/**
@ -74,11 +77,11 @@ public abstract class DNSResolver {
* </p>
*
* @param name the DNS name to lookup
* @param failedAddresses a list with the failed addresses
* @param lookupFailures list of exceptions that occurred during lookup.
* @param dnssecMode the selected DNSSEC mode
* @return A list, either empty or non-empty, or <code>null</code>
*/
protected List<InetAddress> lookupHostAddress0(DnsName name, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
protected List<InetAddress> lookupHostAddress0(DnsName name, List<RemoteConnectionEndpointLookupFailure> lookupFailures, DnssecMode dnssecMode) {
// Default implementation of a DNS name lookup for A/AAAA records. It is assumed that this method does never
// support DNSSEC. Subclasses are free to override this method.
if (dnssecMode != DnssecMode.disabled) {
@ -89,14 +92,14 @@ public abstract class DNSResolver {
try {
inetAddressArray = InetAddress.getAllByName(name.toString());
} catch (UnknownHostException e) {
failedAddresses.add(new HostAddress(name, e));
lookupFailures.add(new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(name, e));
return null;
}
return Arrays.asList(inetAddressArray);
}
protected final boolean shouldContinue(CharSequence name, CharSequence hostname, List<InetAddress> hostAddresses) {
protected static boolean shouldContinue(CharSequence name, CharSequence hostname, List<InetAddress> hostAddresses) {
if (hostAddresses == null) {
return true;
}

View file

@ -1,182 +0,0 @@
/**
*
* Copyright © 2013-2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util.dns;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.jivesoftware.smack.SmackException.ConnectionException;
import org.minidns.dnsname.DnsName;
public class HostAddress {
private final DnsName fqdn;
private final int port;
private final Map<InetAddress, Exception> exceptions = new LinkedHashMap<>();
private final List<InetAddress> inetAddresses;
/**
* Creates a new HostAddress with the given FQDN.
*
* @param fqdn the optional fully qualified domain name (FQDN).
* @param port The port to connect on.
* @param inetAddresses list of addresses.
* @throws IllegalArgumentException If the port is out of valid range (0 - 65535).
*/
public HostAddress(DnsName fqdn, int port, List<InetAddress> inetAddresses) {
if (port < 0 || port > 65535)
throw new IllegalArgumentException(
"Port must be a 16-bit unsigned integer (i.e. between 0-65535. Port was: " + port);
this.fqdn = fqdn;
this.port = port;
if (inetAddresses.isEmpty()) {
throw new IllegalArgumentException("Must provide at least one InetAddress");
}
this.inetAddresses = inetAddresses;
}
public HostAddress(int port, InetAddress hostAddress) {
this(null, port, Collections.singletonList(hostAddress));
}
/**
* Constructs a new failed HostAddress. This constructor is usually used when the DNS resolution of the domain name
* failed for some reason.
*
* @param fqdn the domain name of the host.
* @param e the exception causing the failure.
*/
public HostAddress(DnsName fqdn, Exception e) {
this.fqdn = fqdn;
this.port = 5222;
inetAddresses = Collections.emptyList();
setException(e);
}
public HostAddress(InetSocketAddress inetSocketAddress, Exception exception) {
String hostString = inetSocketAddress.getHostString();
this.fqdn = DnsName.from(hostString);
this.port = inetSocketAddress.getPort();
inetAddresses = Collections.emptyList();
setException(exception);
}
public String getHost() {
if (fqdn != null) {
return fqdn.toString();
}
// In this case, the HostAddress(int, InetAddress) constructor must been used. We have no FQDN. And
// inetAddresses.size() must be exactly one.
assert inetAddresses.size() == 1;
return inetAddresses.get(0).getHostAddress();
}
/**
* Return the fully qualified domain name. This may return <code>null</code> in case there host address is only numeric, i.e. an IP address.
*
* @return the fully qualified domain name or <code>null</code>
*/
public DnsName getFQDN() {
return fqdn;
}
public int getPort() {
return port;
}
public void setException(Exception exception) {
setException(null, exception);
}
public void setException(InetAddress inetAddress, Exception exception) {
Exception old = exceptions.put(inetAddress, exception);
assert old == null;
}
/**
* Retrieve the Exception that caused a connection failure to this HostAddress. Every
* HostAddress found in {@link ConnectionException} will have an Exception set,
* which can be retrieved with this method.
*
* @return the Exception causing this HostAddress to fail
*/
public Map<InetAddress, Exception> getExceptions() {
return Collections.unmodifiableMap(exceptions);
}
public List<InetAddress> getInetAddresses() {
return Collections.unmodifiableList(inetAddresses);
}
@Override
public String toString() {
return getHost() + ":" + port;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof HostAddress)) {
return false;
}
final HostAddress address = (HostAddress) o;
if (!getHost().equals(address.getHost())) {
return false;
}
return port == address.port;
}
@Override
public int hashCode() {
int result = 1;
result = 37 * result + getHost().hashCode();
return result * 37 + port;
}
public String getErrorMessage() {
if (exceptions.isEmpty()) {
return "No error logged";
}
StringBuilder sb = new StringBuilder();
sb.append('\'').append(toString()).append("' failed because: ");
Iterator<Entry<InetAddress, Exception>> iterator = exceptions.entrySet().iterator();
while (iterator.hasNext()) {
Entry<InetAddress, Exception> entry = iterator.next();
InetAddress inetAddress = entry.getKey();
if (inetAddress != null) {
sb.append(entry.getKey()).append(" exception: ");
}
sb.append(entry.getValue());
if (iterator.hasNext()) {
sb.append(", ");
}
}
return sb.toString();
}
}

View file

@ -1,91 +0,0 @@
/**
*
* Copyright 2013-2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util.dns;
import java.net.InetAddress;
import java.util.List;
import org.jivesoftware.smack.util.StringUtils;
import org.minidns.dnsname.DnsName;
/**
* A DNS SRV RR.
*
* @see <a href="http://tools.ietf.org/html/rfc2782">RFC 2782: A DNS RR for specifying the location of services (DNS
* SRV)</a>
* @author Florian Schmaus
*
*/
public class SRVRecord extends HostAddress implements Comparable<SRVRecord> {
private int weight;
private int priority;
/**
* SRV Record constructor.
*
* @param fqdn Fully qualified domain name
* @param port The connection port
* @param priority Priority of the target host
* @param weight Relative weight for records with same priority
* @param inetAddresses list of addresses.
* @throws IllegalArgumentException fqdn is null or any other field is not in valid range (0-65535).
*/
public SRVRecord(DnsName fqdn, int port, int priority, int weight, List<InetAddress> inetAddresses) {
super(fqdn, port, inetAddresses);
StringUtils.requireNotNullNorEmpty(fqdn, "The FQDN must not be null");
if (weight < 0 || weight > 65535)
throw new IllegalArgumentException(
"DNS SRV records weight must be a 16-bit unsigned integer (i.e. between 0-65535. Weight was: "
+ weight);
if (priority < 0 || priority > 65535)
throw new IllegalArgumentException(
"DNS SRV records priority must be a 16-bit unsigned integer (i.e. between 0-65535. Priority was: "
+ priority);
this.priority = priority;
this.weight = weight;
}
public int getPriority() {
return priority;
}
public int getWeight() {
return weight;
}
@Override
public int compareTo(SRVRecord other) {
// According to RFC2782,
// "[a] client MUST attempt to contact the target host with the lowest-numbered priority it can reach".
// This means that a SRV record with a higher priority is 'less' then one with a lower.
int res = other.priority - this.priority;
if (res == 0) {
res = this.weight - other.weight;
}
return res;
}
@Override
public String toString() {
return super.toString() + " prio:" + priority + ":w:" + weight;
}
}

View file

@ -0,0 +1,59 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util.rce;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Collection;
import org.jivesoftware.smack.datatypes.UInt16;
public interface RemoteConnectionEndpoint {
CharSequence getHost();
UInt16 getPort();
Collection<? extends InetAddress> getInetAddresses();
String getDescription();
class InetSocketAddressCoupling<RCE extends RemoteConnectionEndpoint> {
private final RCE connectionEndpoint;
private final InetSocketAddress inetSocketAddress;
public InetSocketAddressCoupling(RCE connectionEndpoint, InetAddress inetAddress) {
this.connectionEndpoint = connectionEndpoint;
UInt16 port = connectionEndpoint.getPort();
inetSocketAddress = new InetSocketAddress(inetAddress, port.intValue());
}
public RCE getRemoteConnectionEndpoint() {
return connectionEndpoint;
}
public InetSocketAddress getInetSocketAddress() {
return inetSocketAddress;
}
@Override
public String toString() {
return connectionEndpoint.getDescription() + " (" + inetSocketAddress + ')';
}
}
}

View file

@ -0,0 +1,70 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util.rce;
import org.jivesoftware.smack.util.ToStringUtil;
import org.minidns.dnsname.DnsName;
public abstract class RemoteConnectionEndpointLookupFailure {
private final String description;
private final Exception exception;
public RemoteConnectionEndpointLookupFailure(String description, Exception exception) {
this.description = description;
this.exception = exception;
}
public final String getDescription() {
return description;
}
public final Exception getException() {
return exception;
}
public String getErrorMessage() {
return description + " because: " + exception;
}
private transient String toStringCache;
@Override
public String toString() {
if (toStringCache == null) {
toStringCache = ToStringUtil.builderFor(RemoteConnectionEndpointLookupFailure.class)
.addValue("description", description)
.addValue("exception", exception)
.build();
}
return toStringCache;
}
public static class DnsLookupFailure extends RemoteConnectionEndpointLookupFailure {
private final DnsName dnsName;
public DnsLookupFailure(DnsName dnsName, Exception exception) {
super("DNS lookup exception for " + dnsName, exception);
this.dnsName = dnsName;
}
public DnsName getDnsName() {
return dnsName;
}
}
}

View file

@ -0,0 +1,66 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util.rce;
import java.net.InetAddress;
import org.jivesoftware.smack.util.ToStringUtil;
public final class RemoteConnectionException<RCE extends RemoteConnectionEndpoint> {
private final RemoteConnectionEndpoint.InetSocketAddressCoupling<RCE> address;
private final Exception exception;
public RemoteConnectionException(RCE remoteConnectionEndpoint, InetAddress inetAddress,
Exception exception) {
this(new RemoteConnectionEndpoint.InetSocketAddressCoupling<>(remoteConnectionEndpoint, inetAddress), exception);
}
public RemoteConnectionException(RemoteConnectionEndpoint.InetSocketAddressCoupling<RCE> address, Exception exception) {
this.address = address;
this.exception = exception;
}
public RemoteConnectionEndpoint.InetSocketAddressCoupling<RCE> getAddress() {
return address;
}
public Exception getException() {
return exception;
}
public String getErrorMessage() {
return "\'" + address + "' failed because: " + exception;
}
private transient String toStringCache;
@Override
public String toString() {
if (toStringCache == null) {
toStringCache = ToStringUtil.builderFor(RemoteConnectionException.class)
.addValue("address", address)
.addValue("exception", exception)
.build();
}
return toStringCache;
}
public static <SARCE extends SingleAddressRemoteConnectionEndpoint> RemoteConnectionException<SARCE> from(SARCE remoteConnectionEndpoint, Exception exception) {
return new RemoteConnectionException<SARCE>(remoteConnectionEndpoint, remoteConnectionEndpoint.getInetAddress(), exception);
}
}

View file

@ -0,0 +1,31 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util.rce;
import java.net.InetAddress;
import java.util.Collection;
import java.util.Collections;
public interface SingleAddressRemoteConnectionEndpoint extends RemoteConnectionEndpoint {
InetAddress getInetAddress();
@Override
default Collection<? extends InetAddress> getInetAddresses() {
return Collections.singletonList(getInetAddress());
}
}

View file

@ -0,0 +1,21 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Utility classes for Remote Connection Endpoints (RCE).
*/
package org.jivesoftware.smack.util.rce;

View file

@ -1,59 +0,0 @@
/**
*
* Copyright © 2014-2018 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import static org.junit.Assert.assertEquals;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.jivesoftware.smack.SmackException.ConnectionException;
import org.jivesoftware.smack.util.dns.HostAddress;
import org.junit.Test;
import org.minidns.dnsname.DnsName;
public class SmackExceptionTest {
@Test
public void testConnectionException() throws UnknownHostException {
List<HostAddress> failedAddresses = new LinkedList<HostAddress>();
DnsName host = DnsName.from("foo.bar.example");
InetAddress inetAddress = InetAddress.getByAddress(host.toString(), new byte[] { 0, 0, 0, 0 });
List<InetAddress> inetAddresses = Collections.singletonList(inetAddress);
HostAddress hostAddress = new HostAddress(host, 1234, inetAddresses);
hostAddress.setException(new Exception("Failed for some reason"));
failedAddresses.add(hostAddress);
host = DnsName.from("barz.example");
inetAddress = InetAddress.getByAddress(host.toString(), new byte[] { 0, 0, 0, 0 });
inetAddresses = Collections.singletonList(inetAddress);
hostAddress = new HostAddress(host, 5678, inetAddresses);
hostAddress.setException(new Exception("Failed for some other reason"));
failedAddresses.add(hostAddress);
ConnectionException connectionException = ConnectionException.from(failedAddresses);
String message = connectionException.getMessage();
assertEquals("The following addresses failed: 'foo.bar.example:1234' failed because: java.lang.Exception: Failed for some reason, 'barz.example:5678' failed because: java.lang.Exception: Failed for some other reason",
message);
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus.
* Copyright 2018-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,7 +18,6 @@ package org.jivesoftware.smack.util;
import static org.junit.Assert.assertEquals;
import org.jivesoftware.smack.util.DNSUtil.DomainType;
import org.jivesoftware.smack.util.dns.SmackDaneProvider;
import org.jivesoftware.smack.util.dns.SmackDaneVerifier;
@ -26,15 +25,6 @@ import org.junit.Test;
public class DnsUtilTest {
@Test
public void simpleDomainTypeTest() {
DomainType client = DomainType.client;
assertEquals(DNSUtil.XMPP_CLIENT_DNS_SRV_PREFIX, client.srvPrefix.ace);
DomainType server = DomainType.server;
assertEquals(DNSUtil.XMPP_SERVER_DNS_SRV_PREFIX, server.srvPrefix.ace);
}
private static final SmackDaneProvider DNS_UTIL_TEST_DANE_PROVIDER = new SmackDaneProvider() {
@Override
public SmackDaneVerifier newInstance() {

View file

@ -7,13 +7,7 @@ mainClassName = 'org.igniterealtime.smack.inttest.SmackIntegrationTestFramework'
applicationDefaultJvmArgs = ["-enableassertions"]
dependencies {
compile project(':smack-java7')
compile project(':smack-tcp')
compile project(':smack-extensions')
compile project(':smack-experimental')
compile project(':smack-omemo')
compile project(':smack-openpgp')
compile project(':smack-debug')
api project(':smack-java8-full')
compile 'org.reflections:reflections:0.9.11'
compile 'eu.geekplace.javapinning:java-pinning-java7:1.1.0-alpha1'
compile group: 'commons-io', name: 'commons-io', version: "$commonsIoVersion"

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -45,7 +45,7 @@ public abstract class AbstractSmackIntTest {
protected final Configuration sinttestConfiguration;
protected AbstractSmackIntTest(SmackIntegrationTestEnvironment<?> environment) {
protected AbstractSmackIntTest(SmackIntegrationTestEnvironment environment) {
this.testRunId = environment.testRunId;
this.sinttestConfiguration = environment.configuration;
this.timeout = environment.configuration.replyTimeout;

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2018 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -46,7 +46,7 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest
protected final List<XMPPConnection> connections;
public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
this.connection = this.conOne = environment.conOne;
this.conTwo = environment.conTwo;

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,7 +31,7 @@ import org.jxmpp.jid.DomainBareJid;
public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmackIntTest {
private final SmackIntegrationTestEnvironment<?> environment;
private final SmackIntegrationTestEnvironment environment;
/**
* The configuration
@ -40,7 +40,7 @@ public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmack
protected final DomainBareJid service;
protected AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
protected AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
this.environment = environment;
this.configuration = environment.configuration;

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,19 +28,22 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException;
public abstract class AbstractSmackSpecificLowLevelIntegrationTest<C extends AbstractXMPPConnection>
extends AbstractSmackLowLevelIntegrationTest {
private final SmackIntegrationTestEnvironment<?> environment;
private final SmackIntegrationTestEnvironment environment;
protected final Class<C> connectionClass;
private final XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor;
public AbstractSmackSpecificLowLevelIntegrationTest(SmackIntegrationTestEnvironment<?> environment,
public AbstractSmackSpecificLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment,
Class<C> connectionClass) {
super(environment);
this.environment = environment;
this.connectionClass = connectionClass;
connectionDescriptor = environment.connectionManager.getConnectionDescriptorFor(connectionClass);
if (connectionDescriptor == null) {
throw new IllegalStateException("No connection descriptor for " + connectionClass + " known");
}
}
public Class<C> getConnectionClass() {

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2018 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -33,7 +33,9 @@ import javax.net.ssl.SSLContext;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.util.Function;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.debugger.EnhancedDebugger;
@ -94,80 +96,90 @@ public final class Configuration {
public final Set<String> disabledTests;
public final String defaultConnectionNickname;
public final Set<String> enabledConnections;
public final Set<String> disabledConnections;
public final Set<String> testPackages;
public final ConnectionConfigurationBuilderApplier configurationApplier;
private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout,
Debugger debugger, String accountOneUsername, String accountOnePassword, String accountTwoUsername,
String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set<String> enabledTests, Set<String> disabledTests,
Set<String> testPackages, String adminAccountUsername, String adminAccountPassword)
throws KeyManagementException, NoSuchAlgorithmException {
this.service = Objects.requireNonNull(service,
public final boolean verbose;
private Configuration(Configuration.Builder builder) throws KeyManagementException, NoSuchAlgorithmException {
service = Objects.requireNonNull(builder.service,
"'service' must be set. Either via 'properties' files or via system property 'sinttest.service'.");
this.serviceTlsPin = serviceTlsPin;
serviceTlsPin = builder.serviceTlsPin;
if (serviceTlsPin != null) {
tlsContext = Java7Pinning.forPin(serviceTlsPin);
} else {
tlsContext = null;
}
this.securityMode = securityMode;
if (replyTimeout > 0) {
this.replyTimeout = replyTimeout;
securityMode = builder.securityMode;
if (builder.replyTimeout > 0) {
replyTimeout = builder.replyTimeout;
} else {
this.replyTimeout = 60000;
replyTimeout = 60000;
}
this.debugger = debugger;
if (StringUtils.isNotEmpty(adminAccountUsername, adminAccountPassword)) {
debugger = builder.debugger;
if (StringUtils.isNotEmpty(builder.adminAccountUsername, builder.adminAccountPassword)) {
accountRegistration = AccountRegistration.serviceAdministration;
}
else if (StringUtils.isNotEmpty(accountOneUsername, accountOnePassword, accountTwoUsername, accountTwoPassword,
accountThreeUsername, accountThreePassword)) {
else if (StringUtils.isNotEmpty(builder.accountOneUsername, builder.accountOnePassword,
builder.accountTwoUsername, builder.accountTwoPassword, builder.accountThreeUsername,
builder.accountThreePassword)) {
accountRegistration = AccountRegistration.disabled;
}
else {
accountRegistration = AccountRegistration.inBandRegistration;
}
this.adminAccountUsername = adminAccountUsername;
this.adminAccountPassword = adminAccountPassword;
this.adminAccountUsername = builder.adminAccountUsername;
this.adminAccountPassword = builder.adminAccountPassword;
boolean accountOnePasswordSet = StringUtils.isNotEmpty(accountOnePassword);
if (accountOnePasswordSet != StringUtils.isNotEmpty(accountTwoPassword) ||
accountOnePasswordSet != StringUtils.isNotEmpty(accountThreePassword)) {
boolean accountOnePasswordSet = StringUtils.isNotEmpty(builder.accountOnePassword);
if (accountOnePasswordSet != StringUtils.isNotEmpty(builder.accountTwoPassword) ||
accountOnePasswordSet != StringUtils.isNotEmpty(builder.accountThreePassword)) {
// Ensure the invariant that either all main accounts have a password set, or none.
throw new IllegalArgumentException();
}
this.accountOneUsername = accountOneUsername;
this.accountOnePassword = accountOnePassword;
this.accountTwoUsername = accountTwoUsername;
this.accountTwoPassword = accountTwoPassword;
this.accountThreeUsername = accountThreeUsername;
this.accountThreePassword = accountThreePassword;
this.enabledTests = enabledTests;
this.disabledTests = disabledTests;
this.testPackages = testPackages;
this.accountOneUsername = builder.accountOneUsername;
this.accountOnePassword = builder.accountOnePassword;
this.accountTwoUsername = builder.accountTwoUsername;
this.accountTwoPassword = builder.accountTwoPassword;
this.accountThreeUsername = builder.accountThreeUsername;
this.accountThreePassword = builder.accountThreePassword;
this.enabledTests = builder.enabledTests;
this.disabledTests = builder.disabledTests;
this.defaultConnectionNickname = builder.defaultConnectionNickname;
this.enabledConnections = builder.enabledConnections;
this.disabledConnections = builder.disabledConnections;
this.testPackages = builder.testPackages;
this.configurationApplier = builder -> {
this.configurationApplier = b -> {
if (tlsContext != null) {
builder.setCustomSSLContext(tlsContext);
b.setCustomSSLContext(tlsContext);
}
builder.setSecurityMode(securityMode);
builder.setXmppDomain(service);
b.setSecurityMode(securityMode);
b.setXmppDomain(service);
switch (debugger) {
case enhanced:
builder.setDebuggerFactory(EnhancedDebugger.Factory.INSTANCE);
b.setDebuggerFactory(EnhancedDebugger.Factory.INSTANCE);
break;
case console:
builder.setDebuggerFactory(ConsoleDebugger.Factory.INSTANCE);
b.setDebuggerFactory(ConsoleDebugger.Factory.INSTANCE);
break;
case none:
// Nothing to do :).
break;
}
};
this.verbose = builder.verbose;
}
public boolean isAccountRegistrationPossible() {
@ -210,8 +222,16 @@ public final class Configuration {
private Set<String> disabledTests;
private String defaultConnectionNickname;
private Set<String> enabledConnections;
private Set<String> disabledConnections;
private Set<String> testPackages;
private boolean verbose;
private Builder() {
}
@ -324,6 +344,21 @@ public final class Configuration {
return this;
}
public Builder setDefaultConnection(String defaultConnectionNickname) {
this.defaultConnectionNickname = defaultConnectionNickname;
return this;
}
public Builder setEnabledConnections(String enabledConnectionsString) {
enabledConnections = split(enabledConnectionsString);
return this;
}
public Builder setDisabledConnections(String disabledConnectionsString) {
disabledConnections = split(disabledConnectionsString);
return this;
}
public Builder addTestPackages(String testPackagesString) {
if (testPackagesString != null) {
String[] testPackagesArray = testPackagesString.split(",");
@ -350,10 +385,22 @@ public final class Configuration {
return this;
}
public Builder setVerbose(boolean verbose) {
this.verbose = verbose;
return this;
}
public Builder setVerbose(String verboseBooleanString) {
if (verboseBooleanString == null) {
return this;
}
boolean verbose = ParserUtils.parseXmlBoolean(verboseBooleanString);
return setVerbose(verbose);
}
public Configuration build() throws KeyManagementException, NoSuchAlgorithmException {
return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debugger, accountOneUsername,
accountOnePassword, accountTwoUsername, accountTwoPassword, accountThreeUsername, accountThreePassword, enabledTests, disabledTests,
testPackages, adminAccountUsername, adminAccountPassword);
return new Configuration(this);
}
}
@ -414,10 +461,15 @@ public final class Configuration {
builder.setDebugger(properties.getProperty("debugger"));
builder.setEnabledTests(properties.getProperty("enabledTests"));
builder.setDisabledTests(properties.getProperty("disabledTests"));
builder.setDefaultConnection(properties.getProperty("defaultConnection"));
builder.setEnabledConnections(properties.getProperty("enabledConnections"));
builder.setDisabledConnections(properties.getProperty("disabledConnections"));
builder.addTestPackages(properties.getProperty("testPackages"));
builder.addTestPackages(testPackages);
builder.setVerbose(properties.getProperty("verbose"));
return builder.build();
}
@ -437,23 +489,36 @@ public final class Configuration {
return null;
}
private static Set<String> getTestSetFrom(String string) {
if (string == null) {
private static Set<String> split(String input) {
return split(input, Function.identity());
}
private static Set<String> split(String input, Function<String, String> transformer) {
if (input == null) {
return null;
}
String[] stringArray = string.split(",");
Set<String> res = new HashSet<>(stringArray.length);
for (String s : stringArray) {
res.add(getFullTestStringFrom(s));
String[] inputArray = input.split(",");
Set<String> res = new HashSet<>(inputArray.length);
for (String s : inputArray) {
s = transformer.apply(s);
boolean newElement = res.add(s);
if (!newElement) {
throw new IllegalArgumentException("The argument '" + s + "' was already provided.");
}
}
return res;
}
private static String getFullTestStringFrom(String string) {
string = string.trim();
if (string.startsWith("smackx.") || string.startsWith("smack.")) {
string = "org.jivesoftware." + string;
private static Set<String> getTestSetFrom(String input) {
return split(input, s -> {
s = s.trim();
if (s.startsWith("smackx.") || s.startsWith("smack.")) {
s = "org.jivesoftware." + s;
}
return string;
return s;
});
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,18 +18,18 @@ package org.igniterealtime.smack.inttest;
import org.jivesoftware.smack.AbstractXMPPConnection;
public class SmackIntegrationTestEnvironment<C extends AbstractXMPPConnection> {
public class SmackIntegrationTestEnvironment {
public final C conOne, conTwo, conThree;
public final AbstractXMPPConnection conOne, conTwo, conThree;
public final String testRunId;
public final Configuration configuration;
public final XmppConnectionManager<C> connectionManager;
public final XmppConnectionManager connectionManager;
SmackIntegrationTestEnvironment(C conOne, C conTwo, C conThree, String testRunId,
Configuration configuration, XmppConnectionManager<C> connectionManager) {
SmackIntegrationTestEnvironment(AbstractXMPPConnection conOne, AbstractXMPPConnection conTwo, AbstractXMPPConnection conThree, String testRunId,
Configuration configuration, XmppConnectionManager connectionManager) {
this.conOne = conOne;
this.conTwo = conTwo;
this.conThree = conThree;

View file

@ -23,6 +23,7 @@ import static org.reflections.ReflectionUtils.withParametersCount;
import static org.reflections.ReflectionUtils.withReturnType;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@ -43,7 +44,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -53,7 +53,6 @@ import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.TLSUtils;
@ -70,7 +69,7 @@ import org.reflections.scanners.MethodParameterScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
public class SmackIntegrationTestFramework {
static {
TLSUtils.setDefaultTrustStoreTypeToJksIfRequired();
@ -80,14 +79,12 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
public static boolean SINTTEST_UNIT_TEST = false;
private final Class<DC> defaultConnectionClass;
protected final Configuration config;
protected TestRunResult testRunResult;
private SmackIntegrationTestEnvironment<DC> environment;
protected XmppConnectionManager<DC> connectionManager;
private SmackIntegrationTestEnvironment environment;
protected XmppConnectionManager connectionManager;
public enum TestType {
Normal,
@ -100,7 +97,7 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Configuration config = Configuration.newConfiguration(args);
SmackIntegrationTestFramework<XMPPTCPConnection> sinttest = new SmackIntegrationTestFramework<>(config, XMPPTCPConnection.class);
SmackIntegrationTestFramework sinttest = new SmackIntegrationTestFramework(config);
TestRunResult testRunResult = sinttest.run();
for (Entry<Class<? extends AbstractSmackIntTest>, Throwable> entry : testRunResult.impossibleTestClasses.entrySet()) {
@ -116,11 +113,9 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
}
final int successfulTests = testRunResult.successfulIntegrationTests.size();
final int failedTests = testRunResult.failedIntegrationTests.size();
final int totalIntegrationTests = successfulTests + failedTests;
final int availableTests = testRunResult.getNumberOfAvailableTests();
final int possibleTests = testRunResult.getNumberOfPossibleTests();
LOGGER.info("SmackIntegrationTestFramework[" + testRunResult.testRunId + ']' + ": Finished ["
+ successfulTests + '/' + totalIntegrationTests + "] (" + possibleTests + " test methods of " + availableTests + " where possible)");
LOGGER.info("SmackIntegrationTestFramework[" + testRunResult.testRunId + ']' + " finished: "
+ successfulTests + '/' + availableTests + " [" + failedTests + " failed]");
final int exitStatus;
if (failedTests > 0) {
@ -146,12 +141,8 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
System.exit(exitStatus);
}
public SmackIntegrationTestFramework(Configuration configuration, Class<DC> defaultConnectionClass)
throws KeyManagementException, InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchAlgorithmException, SmackException, IOException, XMPPException,
InterruptedException {
public SmackIntegrationTestFramework(Configuration configuration) {
this.config = configuration;
this.defaultConnectionClass = defaultConnectionClass;
}
public synchronized TestRunResult run()
@ -160,7 +151,7 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
testRunResult = new TestRunResult();
// Create a connection manager *after* we created the testRunId (in testRunResult).
this.connectionManager = new XmppConnectionManager<>(this, defaultConnectionClass);
this.connectionManager = new XmppConnectionManager(this);
LOGGER.info("SmackIntegrationTestFramework [" + testRunResult.testRunId + ']' + ": Starting");
if (config.debugger != Configuration.Debugger.none) {
@ -226,10 +217,13 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
return testRunResult;
}
@SuppressWarnings({"unchecked", "Finally"})
@SuppressWarnings({"Finally"})
private void runTests(Set<Class<? extends AbstractSmackIntTest>> classes)
throws InterruptedException, InstantiationException, IllegalAccessException,
IllegalArgumentException, SmackException, IOException, XMPPException {
List<PreparedTest> tests = new ArrayList<>(classes.size());
int numberOfAvailableTests = 0;
for (Class<? extends AbstractSmackIntTest> testClass : classes) {
final String testClassName = testClass.getName();
@ -260,17 +254,19 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
// - https://discuss.gradle.org/t/main-vs-test-compile-vs-runtime-classpaths-in-eclipse-once-and-for-all-how/17403
// - https://bugs.eclipse.org/bugs/show_bug.cgi?id=376616 (Scope of dependencies has no effect on Eclipse compilation)
if (!SINTTEST_UNIT_TEST && testClassName.startsWith("org.igniterealtime.smack.inttest.unittest")) {
LOGGER.finer("Skipping integration test '" + testClassName + "' from src/test classpath");
LOGGER.warning("Skipping integration test '" + testClassName + "' from src/test classpath (should not be in classpath)");
continue;
}
if (config.enabledTests != null && !isInSet(testClass, config.enabledTests)) {
LOGGER.info("Skipping test class " + testClassName + " because it is not enabled");
DisabledTestClass disabledTestClass = new DisabledTestClass(testClass, "Skipping test class " + testClassName + " because it is not enabled");
testRunResult.disabledTestClasses.add(disabledTestClass);
continue;
}
if (isInSet(testClass, config.disabledTests)) {
LOGGER.info("Skipping test class " + testClassName + " because it is disalbed");
DisabledTestClass disabledTestClass = new DisabledTestClass(testClass, "Skipping test class " + testClassName + " because it is disalbed");
testRunResult.disabledTestClasses.add(disabledTestClass);
continue;
}
@ -301,8 +297,6 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
continue;
}
testRunResult.numberOfAvailableTestMethods.addAndGet(smackIntegrationTestMethods.size());
final AbstractSmackIntTest test;
try {
test = cons.newInstance(environment);
@ -360,12 +354,14 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
final String methodName = method.getName();
if (config.enabledTests != null && !(config.enabledTests.contains(methodName)
|| isInSet(testClass, config.enabledTests))) {
LOGGER.fine("Skipping test method " + methodName + " because it is not enabled");
DisabledTest disabledTest = new DisabledTest(method, "Skipping test method " + methodName + " because it is not enabled");
testRunResult.disabledTests.add(disabledTest);
it.remove();
continue;
}
if (config.disabledTests != null && config.disabledTests.contains(methodName)) {
LOGGER.info("Skipping test method " + methodName + " because it is disabled");
DisabledTest disabledTest = new DisabledTest(method, "Skipping test method " + methodName + " because it is disabled");
testRunResult.disabledTests.add(disabledTest);
it.remove();
continue;
}
@ -376,47 +372,14 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
continue;
}
final int detectedTestMethodsCount = smackIntegrationTestMethods.size();
testRunResult.numberOfPossibleTestMethods.addAndGet(detectedTestMethodsCount);
try {
// Run the @BeforeClass methods (if any)
Set<Method> beforeClassMethods = getAllMethods(testClass,
withAnnotation(BeforeClass.class), withReturnType(Void.TYPE),
withParametersCount(0), withModifier(Modifier.PUBLIC
));
// See if there are any methods that have the @BeforeClassAnnotation but a wrong signature
Set<Method> allBeforeClassMethods = getAllMethods(testClass, withAnnotation(BeforeClass.class));
allBeforeClassMethods.removeAll(beforeClassMethods);
if (!allBeforeClassMethods.isEmpty()) {
throw new IllegalArgumentException("@BeforeClass methods with wrong signature found");
}
if (beforeClassMethods.size() == 1) {
Method beforeClassMethod = beforeClassMethods.iterator().next();
LOGGER.info("Executing @BeforeClass method of " + testClass);
try {
beforeClassMethod.invoke(test);
}
catch (InvocationTargetException | IllegalAccessException e) {
LOGGER.log(Level.SEVERE, "Exception executing @BeforeClass method", e);
}
catch (IllegalArgumentException e) {
throw new AssertionError(e);
}
}
else if (beforeClassMethods.size() > 1) {
throw new IllegalArgumentException("Only one @BeforeClass method allowed");
}
List<ConcreteTest> concreteTests = new ArrayList<>(smackIntegrationTestMethods.size());
for (Method testMethod : smackIntegrationTestMethods) {
List<ConcreteTest> concreteTests = null;
switch (testType) {
case Normal: {
ConcreteTest.Executor concreteTestExecutor = () -> testMethod.invoke(test);
ConcreteTest concreteTest = new ConcreteTest(testType, testMethod, concreteTestExecutor);
concreteTests = Collections.singletonList(concreteTest);
concreteTests.add(concreteTest);
}
break;
case LowLevel:
@ -424,13 +387,14 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
LowLevelTestMethod lowLevelTestMethod = new LowLevelTestMethod(testMethod);
switch (testType) {
case LowLevel:
concreteTests = invokeLowLevel(lowLevelTestMethod, (AbstractSmackLowLevelIntegrationTest) test);
List<ConcreteTest> concreteLowLevelTests = invokeLowLevel(lowLevelTestMethod, (AbstractSmackLowLevelIntegrationTest) test);
concreteTests.addAll(concreteLowLevelTests);
break;
case SpecificLowLevel: {
ConcreteTest.Executor concreteTestExecutor = () -> invokeSpecificLowLevel(
lowLevelTestMethod, (AbstractSmackSpecificLowLevelIntegrationTest<?>) test);
ConcreteTest concreteTest = new ConcreteTest(testType, testMethod, concreteTestExecutor);
concreteTests = Collections.singletonList(concreteTest);
concreteTests.add(concreteTest);
break;
}
default:
@ -438,44 +402,47 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
}
break;
}
for (ConcreteTest concreteTest : concreteTests) {
runConcreteTest(concreteTest);
}
}
}
finally {
// Run the @AfterClass method (if any)
Set<Method> afterClassMethods = getAllMethods(testClass,
withAnnotation(AfterClass.class), withReturnType(Void.TYPE),
withParametersCount(0), withModifier(Modifier.PUBLIC
));
// See if there are any methods that have the @AfterClassAnnotation but a wrong signature
Set<Method> allAfterClassMethods = getAllMethods(testClass, withAnnotation(AfterClass.class));
allAfterClassMethods.removeAll(afterClassMethods);
if (!allAfterClassMethods.isEmpty()) {
throw new IllegalArgumentException("@AfterClass methods with wrong signature found");
}
if (afterClassMethods.size() == 1) {
Method afterClassMethod = afterClassMethods.iterator().next();
LOGGER.info("Executing @AfterClass method of " + testClass);
try {
afterClassMethod.invoke(test);
// Instantiate the prepared test early as this will check the before and after class annotations.
PreparedTest preparedTest = new PreparedTest(test, concreteTests);
tests.add(preparedTest);
numberOfAvailableTests += concreteTests.size();
}
catch (InvocationTargetException | IllegalAccessException e) {
LOGGER.log(Level.SEVERE, "Exception executing @AfterClass method", e);
}
catch (IllegalArgumentException e) {
throw new AssertionError(e);
// Print status information.
StringBuilder sb = new StringBuilder(1024);
sb.append("Smack Integration Test Framework\n");
sb.append("################################\n");
if (config.verbose) {
sb.append('\n');
if (!testRunResult.disabledTestClasses.isEmpty()) {
sb.append("The following test classes are disabled:\n");
for (DisabledTestClass disabledTestClass : testRunResult.disabledTestClasses) {
disabledTestClass.appendTo(sb).append('\n');
}
}
else if (afterClassMethods.size() > 1) {
throw new IllegalArgumentException("Only one @AfterClass method allowed");
if (!testRunResult.disabledTests.isEmpty()) {
sb.append("The following tests are disabled:\n");
for (DisabledTest disabledTest : testRunResult.disabledTests) {
disabledTest.appendTo(sb).append('\n');
}
}
sb.append('\n');
}
sb.append("Available tests: ").append(numberOfAvailableTests)
.append("(#-classes: ").append(testRunResult.disabledTestClasses.size())
.append(", #-tests: ").append(testRunResult.disabledTests.size())
.append(")\n");
LOGGER.info(sb.toString());
for (PreparedTest test : tests) {
test.run();
}
// Assert that all tests in the 'tests' list produced a result.
assert numberOfAvailableTests == testRunResult.getNumberOfAvailableTests();
}
private void runConcreteTest(ConcreteTest concreteTest)
@ -524,17 +491,35 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
}
private List<ConcreteTest> invokeLowLevel(LowLevelTestMethod lowLevelTestMethod, AbstractSmackLowLevelIntegrationTest test) {
Set<Class<? extends AbstractXMPPConnection>> connectionClasses;
Collection<? extends XmppConnectionDescriptor<?, ?, ?>> connectionDescriptors;
if (lowLevelTestMethod.smackIntegrationTestAnnotation.onlyDefaultConnectionType()) {
Class<? extends AbstractXMPPConnection> defaultConnectionClass = connectionManager.getDefaultConnectionClass();
connectionClasses = Collections.singleton(defaultConnectionClass);
XmppConnectionDescriptor<?, ?, ?> defaultConnectionDescriptor = connectionManager.getDefaultConnectionDescriptor();
connectionDescriptors = Collections.singleton(defaultConnectionDescriptor);
} else {
connectionClasses = connectionManager.getConnectionClasses();
connectionDescriptors = connectionManager.getConnectionDescriptors();
}
List<ConcreteTest> resultingConcreteTests = new ArrayList<>(connectionClasses.size());
List<ConcreteTest> resultingConcreteTests = new ArrayList<>(connectionDescriptors.size());
for (XmppConnectionDescriptor<?, ?, ?> connectionDescriptor : connectionDescriptors) {
String connectionNick = connectionDescriptor.getNickname();
if (config.enabledConnections != null && !config.enabledConnections.contains(connectionNick)) {
DisabledTest disabledTest = new DisabledTest(lowLevelTestMethod.testMethod, "Not creating test for " + lowLevelTestMethod + " with connection '" + connectionNick
+ "', as this connection type is not enabled");
testRunResult.disabledTests.add(disabledTest);
continue;
}
if (config.disabledConnections != null && config.disabledConnections.contains(connectionNick)) {
DisabledTest disabledTest = new DisabledTest(lowLevelTestMethod.testMethod, "Not creating test for " + lowLevelTestMethod + " with connection '" + connectionNick
+ ", as this connection type is disabled");
testRunResult.disabledTests.add(disabledTest);
continue;
}
Class<? extends AbstractXMPPConnection> connectionClass = connectionDescriptor.getConnectionClass();
for (Class<? extends AbstractXMPPConnection> connectionClass : connectionClasses) {
ConcreteTest.Executor executor = () -> lowLevelTestMethod.invoke(test, connectionClass);
ConcreteTest concreteTest = new ConcreteTest(TestType.LowLevel, lowLevelTestMethod.testMethod, executor, connectionClass.getSimpleName());
resultingConcreteTests.add(concreteTest);
@ -543,7 +528,7 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
return resultingConcreteTests;
}
private <C extends AbstractXMPPConnection> void invokeSpecificLowLevel(LowLevelTestMethod testMethod,
private static <C extends AbstractXMPPConnection> void invokeSpecificLowLevel(LowLevelTestMethod testMethod,
AbstractSmackSpecificLowLevelIntegrationTest<C> test)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InterruptedException,
SmackException, IOException, XMPPException {
@ -554,7 +539,7 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
testMethod.invoke(test, connectionClass);
}
protected SmackIntegrationTestEnvironment<DC> prepareEnvironment() throws SmackException,
protected SmackIntegrationTestEnvironment prepareEnvironment() throws SmackException,
IOException, XMPPException, InterruptedException, KeyManagementException,
NoSuchAlgorithmException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return connectionManager.prepareEnvironment();
@ -576,9 +561,6 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
private static Exception throwFatalException(Throwable e) throws Error, NoResponseException,
InterruptedException {
if (e instanceof NoResponseException) {
throw (NoResponseException) e;
}
if (e instanceof InterruptedException) {
throw (InterruptedException) e;
}
@ -612,9 +594,13 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
private final List<SuccessfulTest> successfulIntegrationTests = Collections.synchronizedList(new LinkedList<SuccessfulTest>());
private final List<FailedTest> failedIntegrationTests = Collections.synchronizedList(new LinkedList<FailedTest>());
private final List<TestNotPossible> impossibleIntegrationTests = Collections.synchronizedList(new LinkedList<TestNotPossible>());
// TODO: Ideally three would only be a list of disabledTests, but since we do not process a disabled test class
// any further, we can not determine the concrete disabled tests.
private final List<DisabledTestClass> disabledTestClasses = Collections.synchronizedList(new ArrayList<>());
private final List<DisabledTest> disabledTests = Collections.synchronizedList(new ArrayList<>());
private final Map<Class<? extends AbstractSmackIntTest>, Throwable> impossibleTestClasses = new HashMap<>();
private final AtomicInteger numberOfAvailableTestMethods = new AtomicInteger();
private final AtomicInteger numberOfPossibleTestMethods = new AtomicInteger();
TestRunResult() {
}
@ -624,11 +610,7 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
}
public int getNumberOfAvailableTests() {
return numberOfAvailableTestMethods.get();
}
public int getNumberOfPossibleTests() {
return numberOfPossibleTestMethods.get();
return successfulIntegrationTests.size() + failedIntegrationTests.size() + impossibleIntegrationTests.size();
}
public List<SuccessfulTest> getSuccessfulTests() {
@ -648,6 +630,77 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
}
}
final class PreparedTest {
private final AbstractSmackIntTest test;
private final List<ConcreteTest> concreteTests;
private final Method beforeClassMethod;
private final Method afterClassMethod;
private PreparedTest(AbstractSmackIntTest test, List<ConcreteTest> concreteTests) {
this.test = test;
this.concreteTests = concreteTests;
Class<? extends AbstractSmackIntTest> testClass = test.getClass();
beforeClassMethod = getSinttestSpecialMethod(testClass, BeforeClass.class);
afterClassMethod = getSinttestSpecialMethod(testClass, AfterClass.class);
}
public void run() throws InterruptedException, XMPPException, IOException, SmackException {
try {
// Run the @BeforeClass methods (if any)
executeSinttestSpecialMethod(beforeClassMethod);
for (ConcreteTest concreteTest : concreteTests) {
runConcreteTest(concreteTest);
}
}
finally {
executeSinttestSpecialMethod(afterClassMethod);
}
}
private void executeSinttestSpecialMethod(Method method) {
if (method == null) {
return;
}
try {
method.invoke(test);
}
catch (InvocationTargetException | IllegalAccessException e) {
LOGGER.log(Level.SEVERE, "Exception executing " + method, e);
}
catch (IllegalArgumentException e) {
throw new AssertionError(e);
}
}
}
@SuppressWarnings("unchecked")
private static Method getSinttestSpecialMethod(Class<? extends AbstractSmackIntTest> testClass, Class<? extends Annotation> annotation) {
Set<Method> specialClassMethods = getAllMethods(testClass,
withAnnotation(annotation), withReturnType(Void.TYPE),
withParametersCount(0), withModifier(Modifier.PUBLIC
));
// See if there are any methods that have a special but a wrong signature
Set<Method> allSpecialClassMethods = getAllMethods(testClass, withAnnotation(annotation));
allSpecialClassMethods.removeAll(specialClassMethods);
if (!allSpecialClassMethods.isEmpty()) {
throw new IllegalArgumentException(annotation + " methods with wrong signature found");
}
if (specialClassMethods.size() == 1) {
return specialClassMethods.iterator().next();
}
else if (specialClassMethods.size() > 1) {
throw new IllegalArgumentException("Only one @BeforeClass method allowed");
}
return null;
}
static final class ConcreteTest {
private final TestType testType;
private final Method method;
@ -705,6 +758,50 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
}
}
public static final class DisabledTestClass {
private final Class<? extends AbstractSmackIntTest> testClass;
private final String reason;
private DisabledTestClass(Class<? extends AbstractSmackIntTest> testClass, String reason) {
this.testClass = testClass;
this.reason = reason;
}
public Class<? extends AbstractSmackIntTest> getTestClass() {
return testClass;
}
public String getReason() {
return reason;
}
public StringBuilder appendTo(StringBuilder sb) {
return sb.append("Disabled ").append(testClass).append(" because ").append(reason);
}
}
public static final class DisabledTest {
private final Method method;
private final String reason;
private DisabledTest(Method method, String reason) {
this.method = method;
this.reason = reason;
}
public Method getMethod() {
return method;
}
public String getReason() {
return reason;
}
public StringBuilder appendTo(StringBuilder sb) {
return sb.append("Disabled ").append(method).append(" because ").append(reason);
}
}
private final class LowLevelTestMethod {
private final Method testMethod;
private final SmackIntegrationTest smackIntegrationTestAnnotation;
@ -718,6 +815,7 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
parameterListOfConnections = testMethodParametersIsListOfConnections(testMethod);
}
// TODO: The second parameter should probably be a connection descriptor?
private void invoke(AbstractSmackLowLevelIntegrationTest test,
Class<? extends AbstractXMPPConnection> connectionClass)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
@ -746,6 +844,11 @@ public class SmackIntegrationTestFramework<DC extends AbstractXMPPConnection> {
testMethod.invoke(test, connectionsArray);
}
}
@Override
public String toString() {
return testMethod.toString();
}
}
private static boolean testMethodParametersIsListOfConnections(Method testMethod) {

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -29,8 +29,13 @@ import java.util.List;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.util.Consumer;
public class XmppConnectionDescriptor<C extends AbstractXMPPConnection, CC extends ConnectionConfiguration, CCB extends ConnectionConfiguration.Builder<?, CC>> {
public final class XmppConnectionDescriptor<
C extends AbstractXMPPConnection,
CC extends ConnectionConfiguration,
CCB extends ConnectionConfiguration.Builder<?, CC>
> {
private final Class<C> connectionClass;
private final Class<CC> connectionConfigurationClass;
@ -38,13 +43,18 @@ public class XmppConnectionDescriptor<C extends AbstractXMPPConnection, CC exten
private final Constructor<C> connectionConstructor;
private final Method builderMethod;
public XmppConnectionDescriptor(Class<C> connectionClass, Class<CC> connectionConfigurationClass)
throws ClassNotFoundException, NoSuchMethodException, SecurityException {
this.connectionClass = connectionClass;
this.connectionConfigurationClass = connectionConfigurationClass;
private final Consumer<CCB> extraBuilder;
this.connectionConstructor = getConstructor(connectionClass, connectionConfigurationClass);
this.builderMethod = getBuilderMethod(connectionConfigurationClass);
private final String nickname;
private XmppConnectionDescriptor(Builder<C, CC, CCB> builder) throws NoSuchMethodException, SecurityException {
connectionClass = builder.connectionClass;
connectionConfigurationClass = builder.connectionConfigurationClass;
extraBuilder = builder.extraBuilder;
nickname = builder.nickname;
connectionConstructor = getConstructor(connectionClass, connectionConfigurationClass);
builderMethod = getBuilderMethod(connectionConfigurationClass);
}
public C construct(Configuration sinttestConfiguration)
@ -65,6 +75,9 @@ public class XmppConnectionDescriptor<C extends AbstractXMPPConnection, CC exten
Collection<ConnectionConfigurationBuilderApplier> customConnectionConfigurationAppliers)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
CCB connectionConfigurationBuilder = getNewBuilder();
if (extraBuilder != null) {
extraBuilder.accept(connectionConfigurationBuilder);
}
for (ConnectionConfigurationBuilderApplier customConnectionConfigurationApplier : customConnectionConfigurationAppliers) {
customConnectionConfigurationApplier.applyConfigurationTo(connectionConfigurationBuilder);
}
@ -88,6 +101,10 @@ public class XmppConnectionDescriptor<C extends AbstractXMPPConnection, CC exten
return connectionClass;
}
public String getNickname() {
return nickname;
}
private static <C extends XMPPConnection> Constructor<C> getConstructor(Class<C> connectionClass,
Class<? extends ConnectionConfiguration> connectionConfigurationClass)
throws NoSuchMethodException, SecurityException {
@ -106,4 +123,47 @@ public class XmppConnectionDescriptor<C extends AbstractXMPPConnection, CC exten
}
return builderMethod;
}
public static <C extends AbstractXMPPConnection, CC extends ConnectionConfiguration, CCB extends ConnectionConfiguration.Builder<?, CC>>
Builder<C, CC, CCB> buildWith(Class<C> connectionClass, Class<CC> connectionConfigurationClass) {
return buildWith(connectionClass, connectionConfigurationClass, null);
}
public static <C extends AbstractXMPPConnection, CC extends ConnectionConfiguration, CCB extends ConnectionConfiguration.Builder<?, CC>>
Builder<C, CC, CCB> buildWith(Class<C> connectionClass, Class<CC> connectionConfigurationClass, Class<CCB> connectionConfigurationBuilderClass) {
return new Builder<>(connectionClass, connectionConfigurationClass, connectionConfigurationBuilderClass);
}
public static final class Builder<C extends AbstractXMPPConnection, CC extends ConnectionConfiguration, CCB extends ConnectionConfiguration.Builder<?, CC>> {
private final Class<C> connectionClass;
private final Class<CC> connectionConfigurationClass;
private Consumer<CCB> extraBuilder;
private String nickname;
// The connectionConfigurationBuilderClass merely exists for type-checking purposes.
@SuppressWarnings("UnusedVariable")
private Builder(Class<C> connectionClass, Class<CC> connectionConfigurationClass,
Class<CCB> connectionConfigurationBuilderClass) {
this.connectionClass = connectionClass;
this.connectionConfigurationClass = connectionConfigurationClass;
nickname = connectionClass.getSimpleName();
}
public Builder<C, CC, CCB> applyExtraConfguration(Consumer<CCB> extraBuilder) {
this.extraBuilder = extraBuilder;
return this;
}
public Builder<C, CC, CCB> withNickname(String nickname) {
this.nickname = nickname;
return this;
}
public XmppConnectionDescriptor<C, CC, CCB> build() throws NoSuchMethodException, SecurityException {
return new XmppConnectionDescriptor<>(this);
}
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -27,7 +27,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -38,9 +37,11 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
import org.jivesoftware.smack.compression.CompressionModuleDescriptor;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.tcp.XmppNioTcpConnection;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.StringUtils;
@ -54,55 +55,92 @@ import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.stringprep.XmppStringprepException;
public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
public class XmppConnectionManager {
private static final Logger LOGGER = Logger.getLogger(XmppConnectionManager.class.getName());
private static final Map<Class<? extends AbstractXMPPConnection>, XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>> CONNECTION_DESCRIPTORS = new ConcurrentHashMap<>();
private static final XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> DEFAULT_CONNECTION_DESCRIPTOR;
private static final Map<String, XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>> NICKNAME_CONNECTION_DESCRIPTORS = new HashMap<>();
private static final MultiMap<
Class<? extends AbstractXMPPConnection>,
XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>
> CONNECTION_DESCRIPTORS = new MultiMap<>();
static {
try {
addConnectionDescriptor(XmppNioTcpConnection.class, XMPPTCPConnectionConfiguration.class);
addConnectionDescriptor(XMPPTCPConnection.class, XMPPTCPConnectionConfiguration.class);
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
DEFAULT_CONNECTION_DESCRIPTOR = XmppConnectionDescriptor.buildWith(XMPPTCPConnection.class, XMPPTCPConnectionConfiguration.class)
.withNickname("tcp")
.build();
addConnectionDescriptor(DEFAULT_CONNECTION_DESCRIPTOR);
addConnectionDescriptor(
XmppConnectionDescriptor.buildWith(ModularXmppClientToServerConnection.class, ModularXmppClientToServerConnectionConfiguration.class)
.withNickname("modular")
.build()
);
addConnectionDescriptor(
XmppConnectionDescriptor.buildWith(ModularXmppClientToServerConnection.class, ModularXmppClientToServerConnectionConfiguration.class, ModularXmppClientToServerConnectionConfiguration.Builder.class)
.withNickname("modular-nocompress")
.applyExtraConfguration(cb -> cb.removeModule(CompressionModuleDescriptor.class))
.build()
);
} catch (NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
}
public static void addConnectionDescriptor(Class<? extends AbstractXMPPConnection> connectionClass,
Class<? extends ConnectionConfiguration> connectionConfigurationClass) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor = new XmppConnectionDescriptor<>(
connectionClass, connectionConfigurationClass);
addConnectionDescriptor(connectionDescriptor);
}
public static void addConnectionDescriptor(
public static boolean addConnectionDescriptor(
XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor) {
String nickname = connectionDescriptor.getNickname();
Class<? extends AbstractXMPPConnection> connectionClass = connectionDescriptor.getConnectionClass();
boolean alreadyExisted;
synchronized (CONNECTION_DESCRIPTORS) {
alreadyExisted = removeConnectionDescriptor(nickname);
CONNECTION_DESCRIPTORS.put(connectionClass, connectionDescriptor);
NICKNAME_CONNECTION_DESCRIPTORS.put(connectionDescriptor.getNickname(), connectionDescriptor);
}
return alreadyExisted;
}
public static void removeConnectionDescriptor(Class<? extends AbstractXMPPConnection> connectionClass) {
CONNECTION_DESCRIPTORS.remove(connectionClass);
public static boolean removeConnectionDescriptor(String nickname) {
synchronized (CONNECTION_DESCRIPTORS) {
XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor = NICKNAME_CONNECTION_DESCRIPTORS.remove(nickname);
if (connectionDescriptor == null) {
return false;
}
private final XmppConnectionDescriptor<DC, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> defaultConnectionDescriptor;
boolean removed = CONNECTION_DESCRIPTORS.removeOne(connectionDescriptor.getConnectionClass(), connectionDescriptor);
assert removed;
}
private final Map<Class<? extends AbstractXMPPConnection>, XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>> connectionDescriptors = new HashMap<>(
CONNECTION_DESCRIPTORS.size());
return true;
}
private final SmackIntegrationTestFramework<?> sinttestFramework;
private final XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> defaultConnectionDescriptor;
private final Map<String, XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>> nicknameConnectionDescriptors;
private final MultiMap<
Class<? extends AbstractXMPPConnection>,
XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>
> connectionDescriptors;
private final SmackIntegrationTestFramework sinttestFramework;
private final Configuration sinttestConfiguration;
private final String testRunId;
private final DC accountRegistrationConnection;
private final AbstractXMPPConnection accountRegistrationConnection;
private final ServiceAdministrationManager adminManager;
private final AccountManager accountManager;
/**
* One of the three main connections. The type of the main connections is the default connection type.
*/
DC conOne, conTwo, conThree;
AbstractXMPPConnection conOne, conTwo, conThree;
/**
* A pool of authenticated and free to use connections.
@ -114,20 +152,25 @@ public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
*/
private final List<AbstractXMPPConnection> connections = new ArrayList<>();
@SuppressWarnings("unchecked")
XmppConnectionManager(SmackIntegrationTestFramework<?> sinttestFramework,
Class<? extends DC> defaultConnectionClass)
XmppConnectionManager(SmackIntegrationTestFramework sinttestFramework)
throws SmackException, IOException, XMPPException, InterruptedException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
synchronized (CONNECTION_DESCRIPTORS) {
connectionDescriptors = CONNECTION_DESCRIPTORS.clone();
nicknameConnectionDescriptors = new HashMap<>(NICKNAME_CONNECTION_DESCRIPTORS);
}
this.sinttestFramework = sinttestFramework;
this.sinttestConfiguration = sinttestFramework.config;
this.testRunId = sinttestFramework.testRunResult.testRunId;
connectionDescriptors.putAll(CONNECTION_DESCRIPTORS);
defaultConnectionDescriptor = (XmppConnectionDescriptor<DC, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>) connectionDescriptors.get(
defaultConnectionClass);
String configuredDefaultConnectionNickname = sinttestConfiguration.defaultConnectionNickname;
if (configuredDefaultConnectionNickname != null) {
defaultConnectionDescriptor = nicknameConnectionDescriptors.get(configuredDefaultConnectionNickname);
if (defaultConnectionDescriptor == null) {
throw new IllegalArgumentException("Could not find a connection descriptor for " + defaultConnectionClass);
throw new IllegalArgumentException("Could not find a connection descriptor for connection nickname '" + configuredDefaultConnectionNickname + "'");
}
} else {
defaultConnectionDescriptor = DEFAULT_CONNECTION_DESCRIPTOR;
}
switch (sinttestConfiguration.accountRegistration) {
@ -157,11 +200,11 @@ public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
}
}
SmackIntegrationTestEnvironment<DC> prepareEnvironment() throws KeyManagementException, NoSuchAlgorithmException,
SmackIntegrationTestEnvironment prepareEnvironment() throws KeyManagementException, NoSuchAlgorithmException,
InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
SmackException, IOException, XMPPException, InterruptedException {
prepareMainConnections();
return new SmackIntegrationTestEnvironment<DC>(conOne, conTwo, conThree,
return new SmackIntegrationTestEnvironment(conOne, conTwo, conThree,
sinttestFramework.testRunResult.testRunId, sinttestConfiguration, this);
}
@ -169,9 +212,9 @@ public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
IllegalAccessException, IllegalArgumentException, InvocationTargetException, SmackException, IOException,
XMPPException, InterruptedException {
final int mainAccountCount = AccountNum.values().length;
List<DC> connections = new ArrayList<>(mainAccountCount);
List<AbstractXMPPConnection> connections = new ArrayList<>(mainAccountCount);
for (AccountNum mainAccountNum : AccountNum.values()) {
DC mainConnection = getConnectedMainConnectionFor(mainAccountNum);
AbstractXMPPConnection mainConnection = getConnectedMainConnectionFor(mainAccountNum);
connections.add(mainConnection);
}
conOne = connections.get(0);
@ -179,18 +222,18 @@ public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
conThree = connections.get(2);
}
public Class<? extends AbstractXMPPConnection> getDefaultConnectionClass() {
return defaultConnectionDescriptor.getConnectionClass();
public XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> getDefaultConnectionDescriptor() {
return defaultConnectionDescriptor;
}
public Set<Class<? extends AbstractXMPPConnection>> getConnectionClasses() {
return Collections.unmodifiableSet(connectionDescriptors.keySet());
public Collection<XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>> getConnectionDescriptors() {
return Collections.unmodifiableCollection(nicknameConnectionDescriptors.values());
}
@SuppressWarnings("unchecked")
public <C extends AbstractXMPPConnection> XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> getConnectionDescriptorFor(
Class<C> connectionClass) {
return (XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>) connectionDescriptors.get(
return (XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>) connectionDescriptors.getFirst(
connectionClass);
}
@ -249,7 +292,7 @@ public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
private static final String USERNAME_PREFIX = "smack-inttest";
private DC getConnectedMainConnectionFor(AccountNum accountNum) throws SmackException, IOException, XMPPException,
private AbstractXMPPConnection getConnectedMainConnectionFor(AccountNum accountNum) throws SmackException, IOException, XMPPException,
InterruptedException, KeyManagementException, NoSuchAlgorithmException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
String middlefix;
@ -283,7 +326,7 @@ public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
registerAccount(finalAccountUsername, finalAccountPassword);
}
DC mainConnection = defaultConnectionDescriptor.construct(sinttestConfiguration, builder -> {
AbstractXMPPConnection mainConnection = defaultConnectionDescriptor.construct(sinttestConfiguration, builder -> {
try {
builder.setUsernameAndPassword(finalAccountUsername, finalAccountPassword)
.setResource(middlefix + '-' + testRunId);
@ -347,7 +390,7 @@ public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
@SuppressWarnings("unchecked")
XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor = (XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>) connectionDescriptors
.get(connectionClass);
.getFirst(connectionClass);
for (int i = 0; i < count; i++) {
C connection = constructConnectedConnection(connectionDescriptor);
connections.add(connection);
@ -367,7 +410,7 @@ public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
return connection;
}
DC constructConnection()
AbstractXMPPConnection constructConnection()
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return constructConnection(defaultConnectionDescriptor);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -51,7 +51,7 @@ public class ChatTest extends AbstractSmackIntegrationTest {
private boolean invoked;
@SuppressWarnings("deprecation")
public ChatTest(SmackIntegrationTestEnvironment<?> environment) {
public ChatTest(SmackIntegrationTestEnvironment environment) {
super(environment);
chatManagerOne = org.jivesoftware.smack.chat.ChatManager.getInstanceFor(conOne);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -33,7 +33,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class LoginIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
public LoginIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public LoginIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -35,7 +35,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
public class StreamManagementTest extends AbstractSmackSpecificLowLevelIntegrationTest<XMPPTCPConnection> {
public StreamManagementTest(SmackIntegrationTestEnvironment<?> environment) throws Exception {
public StreamManagementTest(SmackIntegrationTestEnvironment environment) throws Exception {
super(environment, XMPPTCPConnection.class);
XMPPTCPConnection connection = getSpecificUnconnectedConnection();
connection.connect().login();

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,7 +26,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class WaitForClosingStreamElementTest extends AbstractSmackLowLevelIntegrationTest {
public WaitForClosingStreamElementTest(SmackIntegrationTestEnvironment<?> environment) {
public WaitForClosingStreamElementTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,7 +19,7 @@ package org.jivesoftware.smack;
import java.util.List;
import java.util.logging.Level;
import org.jivesoftware.smack.tcp.XmppNioTcpConnection;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.igniterealtime.smack.XmppConnectionStressTest;
import org.igniterealtime.smack.XmppConnectionStressTest.StressTestFailedException.ErrorsWhileSendingOrReceivingException;
@ -30,7 +30,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class XmppConnectionIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
public XmppConnectionIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public XmppConnectionIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
@ -52,12 +52,12 @@ public class XmppConnectionIntegrationTest extends AbstractSmackLowLevelIntegrat
final Level connectionStatsLogLevel = Level.FINE;
if (LOGGER.isLoggable(connectionStatsLogLevel)) {
if (connections.get(0) instanceof XmppNioTcpConnection) {
if (connections.get(0) instanceof ModularXmppClientToServerConnection) {
for (XMPPConnection connection : connections) {
XmppNioTcpConnection xmppNioTcpConnection = (XmppNioTcpConnection) connection;
XmppNioTcpConnection.Stats stats = xmppNioTcpConnection.getStats();
ModularXmppClientToServerConnection xmppC2sConnection = (ModularXmppClientToServerConnection) connection;
ModularXmppClientToServerConnection.Stats stats = xmppC2sConnection.getStats();
LOGGER.log(connectionStatsLogLevel,
"Connections stats for " + xmppNioTcpConnection + ":\n{}",
"Connections stats for " + xmppC2sConnection + ":\n{}",
stats);
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -25,7 +25,7 @@ public abstract class AbstractChatIntegrationTest extends AbstractSmackIntegrati
protected final ChatManager chatManagerTwo;
protected final ChatManager chatManagerThree;
protected AbstractChatIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
protected AbstractChatIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
chatManagerOne = ChatManager.getInstanceFor(conOne);
chatManagerTwo = ChatManager.getInstanceFor(conTwo);

View file

@ -26,7 +26,7 @@ import org.jxmpp.jid.EntityBareJid;
public class IncomingMessageListenerIntegrationTest extends AbstractChatIntegrationTest {
public IncomingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public IncomingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}

View file

@ -27,7 +27,7 @@ import org.jxmpp.jid.EntityBareJid;
public class OutgoingMessageListenerIntegrationTest extends AbstractChatIntegrationTest {
public OutgoingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public OutgoingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2019 Florian Schmaus
* Copyright 2016-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -30,7 +30,7 @@ import org.jxmpp.jid.FullJid;
public class LowLevelRosterIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
public LowLevelRosterIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public LowLevelRosterIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -38,7 +38,7 @@ public class RosterIntegrationTest extends AbstractSmackIntegrationTest {
private final Roster rosterOne;
private final Roster rosterTwo;
public RosterIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public RosterIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
rosterOne = Roster.getInstanceFor(conOne);
rosterTwo = Roster.getInstanceFor(conTwo);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018-2019 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,21 +22,22 @@ import java.security.NoSuchAlgorithmException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.igniterealtime.smack.inttest.AbstractSmackSpecificLowLevelIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class XmppNioTcpConnectionLowLevelIntegrationTest extends AbstractSmackSpecificLowLevelIntegrationTest<XmppNioTcpConnection> {
public class XmppNioTcpConnectionLowLevelIntegrationTest extends AbstractSmackSpecificLowLevelIntegrationTest<ModularXmppClientToServerConnection> {
public XmppNioTcpConnectionLowLevelIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
super(environment, XmppNioTcpConnection.class);
public XmppNioTcpConnectionLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment, ModularXmppClientToServerConnection.class);
}
@SmackIntegrationTest
public void testDisconnectAfterConnect() throws KeyManagementException, NoSuchAlgorithmException, SmackException,
IOException, XMPPException, InterruptedException {
XmppNioTcpConnection connection = getSpecificUnconnectedConnection();
ModularXmppClientToServerConnection connection = getSpecificUnconnectedConnection();
connection.connect();

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2013-2019 Florian Schmaus
* Copyright 2013-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -57,7 +57,7 @@ public class EntityCapsTest extends AbstractSmackIntegrationTest {
private final ServiceDiscoveryManager sdmOne;
private final ServiceDiscoveryManager sdmTwo;
public EntityCapsTest(SmackIntegrationTestEnvironment<?> environment) {
public EntityCapsTest(SmackIntegrationTestEnvironment environment) {
super(environment);
ecmTwo = EntityCapsManager.getInstanceFor(environment.conTwo);
sdmOne = ServiceDiscoveryManager.getInstanceFor(environment.conOne);

View file

@ -55,7 +55,7 @@ public class ChatStateIntegrationTest extends AbstractSmackIntegrationTest {
};
public ChatStateIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public ChatStateIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -39,7 +39,7 @@ public class FileTransferIntegrationTest extends AbstractSmackIntegrationTest {
private final FileTransferManager ftManagerOne;
private final FileTransferManager ftManagerTwo;
public FileTransferIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public FileTransferIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
ftManagerOne = FileTransferManager.getInstanceFor(conOne);
ftManagerTwo = FileTransferManager.getInstanceFor(conTwo);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017-2019 Florian Schmaus
* Copyright 2017-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -43,7 +43,7 @@ public class HttpFileUploadIntegrationTest extends AbstractSmackIntegrationTest
private final HttpFileUploadManager hfumOne;
public HttpFileUploadIntegrationTest(SmackIntegrationTestEnvironment<?> environment) throws XMPPErrorException,
public HttpFileUploadIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPErrorException,
NotConnectedException, NoResponseException, InterruptedException, TestNotPossibleException {
super(environment);
hfumOne = HttpFileUploadManager.getInstanceFor(conOne);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2019 Florian Schmaus
* Copyright 2016-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -43,7 +43,7 @@ public class IoTControlIntegrationTest extends AbstractSmackIntegrationTest {
private final IoTControlManager IoTControlManagerTwo;
public IoTControlIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public IoTControlIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
IoTControlManagerOne = IoTControlManager.getInstanceFor(conOne);
IoTControlManagerTwo = IoTControlManager.getInstanceFor(conTwo);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2019 Florian Schmaus
* Copyright 2016-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -45,7 +45,7 @@ public class IoTDataIntegrationTest extends AbstractSmackIntegrationTest {
private final IoTDataManager iotDataManagerTwo;
public IoTDataIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public IoTDataIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
iotDataManagerOne = IoTDataManager.getInstanceFor(conOne);
iotDataManagerTwo = IoTDataManager.getInstanceFor(conTwo);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2019 Florian Schmaus
* Copyright 2016-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -41,7 +41,7 @@ public class IoTDiscoveryIntegrationTest extends AbstractSmackIntegrationTest {
private final IoTDiscoveryManager discoveryManagerOne;
private final IoTDiscoveryManager discoveryManagerTwo;
public IoTDiscoveryIntegrationTest(SmackIntegrationTestEnvironment<?> environment) throws NoResponseException,
public IoTDiscoveryIntegrationTest(SmackIntegrationTestEnvironment environment) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException {
super(environment);
discoveryManagerOne = IoTDiscoveryManager.getInstanceFor(conOne);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,7 +31,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class VersionIntegrationTest extends AbstractSmackIntegrationTest {
public VersionIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public VersionIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2016 Fernando Ramirez, 2018-2019 Florian Schmaus
* Copyright 2016 Fernando Ramirez, 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -48,7 +48,7 @@ public class MamIntegrationTest extends AbstractSmackIntegrationTest {
private final MamManager mamManagerConTwo;
public MamIntegrationTest(SmackIntegrationTestEnvironment<?> environment) throws NoResponseException,
public MamIntegrationTest(SmackIntegrationTestEnvironment environment) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException, NotLoggedInException {
super(environment);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Paul Schaub, 2019 Florian Schmaus.
* Copyright 2018 Paul Schaub, 2019-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,7 +31,7 @@ public class MoodIntegrationTest extends AbstractSmackIntegrationTest {
private final MoodManager mm1;
private final MoodManager mm2;
public MoodIntegrationTest(SmackIntegrationTestEnvironment<?> environment) {
public MoodIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
mm1 = MoodManager.getInstanceFor(conOne);
mm2 = MoodManager.getInstanceFor(conTwo);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -54,7 +54,7 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
private final MultiUserChatManager mucManagerTwo;
private final DomainBareJid mucService;
public MultiUserChatIntegrationTest(SmackIntegrationTestEnvironment<?> environment)
public MultiUserChatIntegrationTest(SmackIntegrationTestEnvironment environment)
throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException, TestNotPossibleException {
super(environment);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2019 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -40,7 +40,7 @@ import org.jxmpp.jid.parts.Resourcepart;
public class MultiUserChatLowLevelIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
public MultiUserChatLowLevelIntegrationTest(SmackIntegrationTestEnvironment<?> environment) throws Exception {
public MultiUserChatLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) throws Exception {
super(environment);
AbstractXMPPConnection connection = getConnectedConnection();
try {

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017-2018 Florian Schmaus, Paul Schaub
* Copyright 2017-2020 Florian Schmaus, Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,7 +28,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
*/
public abstract class AbstractOmemoIntegrationTest extends AbstractSmackIntegrationTest {
public AbstractOmemoIntegrationTest(SmackIntegrationTestEnvironment<?> environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
public AbstractOmemoIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
super(environment);
// Test for server support

View file

@ -40,7 +40,7 @@ public abstract class AbstractTwoUsersOmemoIntegrationTest extends AbstractOmemo
protected OmemoManager alice, bob;
public AbstractTwoUsersOmemoIntegrationTest(SmackIntegrationTestEnvironment<?> environment)
public AbstractTwoUsersOmemoIntegrationTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, TestNotPossibleException {
super(environment);

View file

@ -37,7 +37,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
*/
public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoIntegrationTest {
public MessageEncryptionIntegrationTest(SmackIntegrationTestEnvironment<?> environment)
public MessageEncryptionIntegrationTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, TestNotPossibleException {
super(environment);

View file

@ -41,7 +41,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
* Then Bob fetches his Mam archive and decrypts the result.
*/
public class OmemoMamDecryptionTest extends AbstractTwoUsersOmemoIntegrationTest {
public OmemoMamDecryptionTest(SmackIntegrationTestEnvironment<?> environment)
public OmemoMamDecryptionTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, TestNotPossibleException {
super(environment);

View file

@ -35,7 +35,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
public class ReadOnlyDeviceIntegrationTest extends AbstractTwoUsersOmemoIntegrationTest {
public ReadOnlyDeviceIntegrationTest(SmackIntegrationTestEnvironment<?> environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
public ReadOnlyDeviceIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
super(environment);
}

View file

@ -27,7 +27,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIntegrationTest {
public SessionRenegotiationIntegrationTest(SmackIntegrationTestEnvironment<?> environment)
public SessionRenegotiationIntegrationTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, TestNotPossibleException {
super(environment);

View file

@ -42,7 +42,7 @@ public abstract class AbstractOpenPgpIntegrationTest extends AbstractSmackIntegr
protected final PepManager bobPepManager;
protected final PepManager chloePepManager;
protected AbstractOpenPgpIntegrationTest(SmackIntegrationTestEnvironment<?> environment)
protected AbstractOpenPgpIntegrationTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, TestNotPossibleException, SmackException.NotConnectedException,
InterruptedException, SmackException.NoResponseException {
super(environment);

Some files were not shown because too many files have changed in this diff Show more