mirror of
https://github.com/vanitasvitae/Smack.git
synced 2025-09-09 17:19:39 +02: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:
parent
cec312fe64
commit
cc636fff21
142 changed files with 6819 additions and 4068 deletions
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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()?
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
stateClassConstructor.setAccessible(true);
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 + ')';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue