1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-02-16 05:46:25 +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,24 +58,28 @@ debugger=console
### Framework properties
| Name | |
|----------------------|-------------------------------------------|
| 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' |
| replyTimeout | In milliseconds |
| adminAccountUsername | Username of the XEP-0133 Admin account |
| adminAccountPassword | Password of the XEP-0133 Admin account |
| accountOneUsername | Username of the first XMPP account |
| accountOnePassword | Password of the first XMPP account |
| accountTwoUsername | Username of the second XMPP account |
| accountTwoPassword | Password of the second XMPP account |
| accountThreeUsername | Username of the third XMPP account |
| accountThreePassword | Password of the third XMPP account |
| debugger | 'console' for console debugger, 'enhanced' for the enhanced debugger |
| enabledTests | List of enabled tests |
| disabledTests | List of disabled tests |
| testPackages | List of packages with tests |
| 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' |
| replyTimeout | In milliseconds |
| adminAccountUsername | Username of the XEP-0133 Admin account |
| adminAccountPassword | Password of the XEP-0133 Admin account |
| accountOneUsername | Username of the first XMPP account |
| accountOnePassword | Password of the first XMPP account |
| accountTwoUsername | Username of the second XMPP account |
| accountTwoPassword | Password of the second XMPP account |
| accountThreeUsername | Username of the third XMPP account |
| accountThreePassword | Password of the third XMPP account |
| 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;
/**
* Deprecated, do not use.
*
* @param wrappedThrowable the wrapped throwable.
*/
@Deprecated
public GenericConnectionException(Throwable wrappedThrowable) {
super(wrappedThrowable);
}
}
/**
* 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()}.
* 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 class ConnectionException extends SmackException {
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);
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);
}
// Remove the last delimiter
sb.setLength(sb.length() - DELIMITER.length());
return new ConnectionException(sb.toString(), 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<HostAddress> getFailedAddresses() {
return failedAddresses;
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;