From cec312fe64c6167d31a4aa77120cc08da5f1c260 Mon Sep 17 00:00:00 2001
From: Jesus Fuentes
Date: Mon, 16 Mar 2020 21:51:05 -0500
Subject: [PATCH 1/7] Help With Documentation
Add missed word for clearer instruction -Jesus Fuentes
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index b069d6c5e..dcb526f47 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ Getting started
Start with having a look at the **[Documentation]** and the **[Javadoc]**.
-Instructions how to use Smack in your Java or Android project are provided in the [Smack Readme and Upgrade Guide](https://igniterealtime.org/projects/smack/readme).
+Instructions on how to use Smack in your Java or Android project are provided in the [Smack Readme and Upgrade Guide](https://igniterealtime.org/projects/smack/readme).
Professional Services
---------------------
From cc636fff21cace34759a350dfe267fe2d1ec5a7d Mon Sep 17 00:00:00 2001
From: Florian Schmaus
Date: Sat, 4 Apr 2020 13:03:31 +0200
Subject: [PATCH 2/7] 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.
---
build.gradle | 60 +-
documentation/connection-modules.md | 35 +
documentation/developer/integrationtest.md | 44 +-
documentation/index.md | 1 +
settings.gradle | 1 +
.../smack/bosh/XMPPBOSHConnection.java | 5 +-
smack-core/build.gradle | 2 +
.../smack/AbstractXMPPConnection.java | 54 +-
.../smack/AbstractXmppNioConnection.java | 50 -
.../smack/ConnectionConfiguration.java | 10 +-
.../smack/SmackConfiguration.java | 19 +-
.../jivesoftware/smack/SmackException.java | 128 +-
.../org/jivesoftware/smack/SmackFuture.java | 28 +-
.../smack/SmackInitialization.java | 7 +
.../org/jivesoftware/smack/SmackReactor.java | 6 +-
.../smack/XmppInputOutputFilter.java | 8 +-
.../jivesoftware/smack/bind2/Bind2Module.java | 77 +
.../smack/bind2/Bind2ModuleDescriptor.java | 53 +
.../smack/bind2/package-info.java | 21 +
.../ModularXmppClientToServerConnection.java | 1114 ++++++++++
...ClientToServerConnectionConfiguration.java | 167 ++
...larXmppClientToServerConnectionModule.java | 40 +
...entToServerConnectionModuleDescriptor.java | 48 +
.../c2s/XmppClientToServerTransport.java | 76 +
...rXmppClientToServerConnectionInternal.java | 125 ++
.../c2s/internal/WalkStateGraphContext.java | 179 ++
.../smack/c2s/internal/package-info.java | 21 +
.../jivesoftware/smack/c2s/package-info.java | 21 +
.../smack/compression/CompressionModule.java | 132 ++
.../CompressionModuleDescriptor.java | 54 +
.../zlib/ZlibXmppCompressionFactory.java | 5 +
.../AbstractXmppStateMachineConnection.java | 806 -------
.../smack/fsm/ConnectionStateEvent.java | 60 +-
.../fsm/ConnectionStateMachineListener.java | 4 +-
.../jivesoftware/smack/fsm/LoginContext.java | 10 +-
.../org/jivesoftware/smack/fsm/NoOpState.java | 43 +
.../org/jivesoftware/smack/fsm/State.java | 81 +
.../smack/fsm/StateDescriptor.java | 61 +-
.../smack/fsm/StateDescriptorGraph.java | 26 +-
.../smack/fsm/StateMachineException.java | 41 +-
.../smack/fsm/StateTransitionResult.java | 87 +
.../isr/InstantStreamResumptionModule.java | 85 +
...stantStreamResumptionModuleDescriptor.java | 54 +
.../jivesoftware/smack/isr/package-info.java | 23 +
.../util/ArrayBlockingQueueWithShutdown.java | 7 +-
.../smack/util/CollectionUtil.java | 11 +-
.../org/jivesoftware/smack/util/DNSUtil.java | 204 +-
.../org/jivesoftware/smack/util/Function.java | 5 +-
.../org/jivesoftware/smack/util/MultiMap.java | 13 +-
.../jivesoftware/smack/util/StringUtils.java | 34 +-
.../smack/util/dns/DNSResolver.java | 33 +-
.../smack/util/dns/HostAddress.java | 182 --
.../smack/util/dns/SRVRecord.java | 91 -
.../util/rce/RemoteConnectionEndpoint.java | 59 +
...RemoteConnectionEndpointLookupFailure.java | 70 +
.../util/rce/RemoteConnectionException.java | 66 +
...SingleAddressRemoteConnectionEndpoint.java | 31 +
.../smack/util/rce/package-info.java | 21 +
.../smack/SmackExceptionTest.java | 59 -
.../jivesoftware/smack/util/DnsUtilTest.java | 12 +-
smack-integration-test/build.gradle | 8 +-
.../smack/inttest/AbstractSmackIntTest.java | 4 +-
.../inttest/AbstractSmackIntegrationTest.java | 4 +-
.../AbstractSmackLowLevelIntegrationTest.java | 6 +-
...tSmackSpecificLowLevelIntegrationTest.java | 9 +-
.../smack/inttest/Configuration.java | 167 +-
.../SmackIntegrationTestEnvironment.java | 12 +-
.../SmackIntegrationTestFramework.java | 367 ++--
.../inttest/XmppConnectionDescriptor.java | 76 +-
.../smack/inttest/XmppConnectionManager.java | 137 +-
.../java/org/jivesoftware/smack/ChatTest.java | 4 +-
.../smack/LoginIntegrationTest.java | 4 +-
.../smack/StreamManagementTest.java | 4 +-
.../WaitForClosingStreamElementTest.java | 4 +-
.../smack/XmppConnectionIntegrationTest.java | 14 +-
.../chat2/AbstractChatIntegrationTest.java | 4 +-
...ncomingMessageListenerIntegrationTest.java | 2 +-
...utgoingMessageListenerIntegrationTest.java | 2 +-
.../roster/LowLevelRosterIntegrationTest.java | 4 +-
.../smack/roster/RosterIntegrationTest.java | 4 +-
...oTcpConnectionLowLevelIntegrationTest.java | 11 +-
.../smackx/caps/EntityCapsTest.java | 4 +-
.../chatstate/ChatStateIntegrationTest.java | 2 +-
.../FileTransferIntegrationTest.java | 4 +-
.../HttpFileUploadIntegrationTest.java | 4 +-
.../smackx/iot/IoTControlIntegrationTest.java | 4 +-
.../smackx/iot/IoTDataIntegrationTest.java | 4 +-
.../iot/IoTDiscoveryIntegrationTest.java | 4 +-
.../iqversion/VersionIntegrationTest.java | 4 +-
.../smackx/mam/MamIntegrationTest.java | 4 +-
.../smackx/mood/MoodIntegrationTest.java | 4 +-
.../muc/MultiUserChatIntegrationTest.java | 4 +-
.../MultiUserChatLowLevelIntegrationTest.java | 4 +-
.../omemo/AbstractOmemoIntegrationTest.java | 4 +-
.../AbstractTwoUsersOmemoIntegrationTest.java | 2 +-
.../MessageEncryptionIntegrationTest.java | 2 +-
.../smackx/omemo/OmemoMamDecryptionTest.java | 2 +-
.../omemo/ReadOnlyDeviceIntegrationTest.java | 2 +-
.../SessionRenegotiationIntegrationTest.java | 2 +-
.../ox/AbstractOpenPgpIntegrationTest.java | 2 +-
.../ox/OXSecretKeyBackupIntegrationTest.java | 2 +-
.../OXInstantMessagingIntegrationTest.java | 2 +-
.../smackx/ping/PingIntegrationTest.java | 4 +-
.../smackx/pubsub/PubSubIntegrationTest.java | 4 +-
.../usertune/UserTuneIntegrationTest.java | 2 +-
.../jivesoftware/smackx/xdata/FormTest.java | 4 +-
.../DummySmackIntegrationTestFramework.java | 21 +-
.../SmackIntegrationTestUnitTestUtil.java | 6 +-
...egrationTestXmppConnectionManagerTest.java | 21 +-
...SmackIntegrationTestFrameworkUnitTest.java | 6 +-
smack-java8-full/build.gradle | 48 +
.../smack/full/doc-files/.gitignore | 2 +
...dularXmppClientToServerConnectionTool.java | 71 +
.../jivesoftware/smack/full/package-info.java | 28 +
...lientToServerConnectionStateGraphTest.java | 83 +
.../src/test/resources/state-graph.dot | 36 +
smack-repl/build.gradle | 21 +-
.../igniterealtime/smack/smackrepl/Nio.java | 40 +-
.../smack/smackrepl/StateGraph.java | 48 -
.../util/dns/dnsjava/DNSJavaResolver.java | 31 +-
.../smack/util/dns/javax/JavaxResolver.java | 30 +-
.../util/dns/minidns/MiniDnsResolver.java | 64 +-
smack-tcp/Makefile | 21 -
.../smack/tcp/doc-files/.gitignore | 2 -
.../smack/sm/StreamManagementModule.java | 141 ++
.../sm/StreamManagementModuleDescriptor.java | 58 +
.../smack/tcp/ConnectionAttemptState.java | 175 ++
.../smack/tcp/TCPInitializer.java | 9 +-
.../jivesoftware/smack/tcp/TcpHostEvent.java | 79 +
.../smack/tcp/XMPPTCPConnection.java | 44 +-
.../smack/tcp/XmppNioTcpConnection.java | 1883 -----------------
.../smack/tcp/XmppTcpTransportModule.java | 1431 +++++++++++++
.../tcp/XmppTcpTransportModuleDescriptor.java | 91 +
.../rce/IpTcpRemoteConnectionEndpoint.java | 78 +
.../rce/RemoteXmppTcpConnectionEndpoints.java | 232 ++
.../Rfc6120TcpRemoteConnectionEndpoint.java | 23 +
.../tcp/rce/SrvRemoteConnectionEndpoint.java | 57 +
.../rce/SrvXmppRemoteConnectionEndpoint.java | 35 +
.../rce/SrvXmppsRemoteConnectionEndpoint.java | 34 +
.../smack/tcp/rce/package-info.java | 21 +
.../smack/tcp/XmppNioTcpConnectionTest.java | 33 -
.../RemoteXmppTcpConnectionEndpointsTest.java | 87 +
142 files changed, 6819 insertions(+), 4068 deletions(-)
create mode 100644 documentation/connection-modules.md
delete mode 100644 smack-core/src/main/java/org/jivesoftware/smack/AbstractXmppNioConnection.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/bind2/Bind2Module.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/bind2/Bind2ModuleDescriptor.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/bind2/package-info.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModule.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModuleDescriptor.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/WalkStateGraphContext.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/package-info.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/c2s/package-info.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModuleDescriptor.java
delete mode 100644 smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/fsm/NoOpState.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/isr/InstantStreamResumptionModule.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/isr/InstantStreamResumptionModuleDescriptor.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/isr/package-info.java
delete mode 100644 smack-core/src/main/java/org/jivesoftware/smack/util/dns/HostAddress.java
delete mode 100644 smack-core/src/main/java/org/jivesoftware/smack/util/dns/SRVRecord.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpoint.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpointLookupFailure.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionException.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/util/rce/SingleAddressRemoteConnectionEndpoint.java
create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/util/rce/package-info.java
delete mode 100644 smack-core/src/test/java/org/jivesoftware/smack/SmackExceptionTest.java
create mode 100644 smack-java8-full/build.gradle
create mode 100644 smack-java8-full/src/javadoc/org/jivesoftware/smack/full/doc-files/.gitignore
create mode 100644 smack-java8-full/src/main/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionTool.java
create mode 100644 smack-java8-full/src/main/java/org/jivesoftware/smack/full/package-info.java
create mode 100644 smack-java8-full/src/test/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionStateGraphTest.java
create mode 100644 smack-java8-full/src/test/resources/state-graph.dot
delete mode 100644 smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/StateGraph.java
delete mode 100644 smack-tcp/Makefile
delete mode 100644 smack-tcp/src/javadoc/org/jivesoftware/smack/tcp/doc-files/.gitignore
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModuleDescriptor.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/ConnectionAttemptState.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/TcpHostEvent.java
delete mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnection.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModuleDescriptor.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/rce/IpTcpRemoteConnectionEndpoint.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/rce/RemoteXmppTcpConnectionEndpoints.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/rce/Rfc6120TcpRemoteConnectionEndpoint.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/rce/SrvRemoteConnectionEndpoint.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/rce/SrvXmppRemoteConnectionEndpoint.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/rce/SrvXmppsRemoteConnectionEndpoint.java
create mode 100644 smack-tcp/src/main/java/org/jivesoftware/smack/tcp/rce/package-info.java
delete mode 100644 smack-tcp/src/test/java/org/jivesoftware/smack/tcp/XmppNioTcpConnectionTest.java
create mode 100644 smack-tcp/src/test/java/org/jivesoftware/smack/tcp/rce/RemoteXmppTcpConnectionEndpointsTest.java
diff --git a/build.gradle b/build.gradle
index 2e9b23ecb..117cf8a7d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,6 +14,8 @@ buildscript {
plugins {
id 'ru.vyarus.animalsniffer' version '1.5.0'
id 'net.ltgt.errorprone' version '1.1.1'
+ // Use e.g. "gradle taskTree" to show its dependency tree.
+ id 'com.dorongold.task-tree' version '1.5'
}
apply plugin: 'org.kordamp.gradle.markdown'
@@ -288,14 +290,7 @@ configure (junit4Projects) {
}
}
-task copyAllJavadocDocFiles(type: Copy) {
- from javadocAllProjects.collect { project ->
- "${project.projectDir}/src/javadoc" }
- into javadocAllDir
- include '**/doc-files/*.*'
-}
-
-task javadocAll(type: Javadoc, dependsOn: copyAllJavadocDocFiles) {
+task javadocAll(type: Javadoc) {
source javadocAllProjects.collect {project ->
project.sourceSets.main.allJava.findAll {
// Filter out symbolic links to avoid
@@ -325,6 +320,25 @@ task javadocAll(type: Javadoc, dependsOn: copyAllJavadocDocFiles) {
] as String[]
overview = "$projectDir/resources/javadoc-overview.html"
}
+
+ // Finally copy the javadoc doc-files from the subprojects, which
+ // are potentially generated, to the javadocAll directory. Note
+ // that we use a copy *method* and not a *task* because the inputs
+ // of copy tasks is determined within the configuration phase. And
+ // since some of the inputs are generated, they will not get
+ // picked up if we used a copy method. See also
+ // https://stackoverflow.com/a/40518516/194894
+ doLast {
+ copy {
+ javadocAllProjects.each {
+ from ("${it.projectDir}/src/javadoc") {
+ include '**/doc-files/*.*'
+ }
+ }
+
+ into javadocAllDir
+ }
+ }
}
import org.apache.tools.ant.filters.ReplaceTokens
@@ -494,31 +508,25 @@ subprojects {
}
// Work around https://github.com/gradle/gradle/issues/4046
- javadoc.dependsOn('copyJavadocDocFiles')
task copyJavadocDocFiles(type: Copy) {
from('src/javadoc')
into 'build/docs/javadoc'
include '**/doc-files/*.*'
}
+ javadoc.dependsOn copyJavadocDocFiles
- // If this subproject has a Makefile then make copyJavadocDocFiles
- // and the root project's javadocAll task dependend on
- // generateFiles.
- if (file("$projectDir/Makefile").exists()) {
- copyJavadocDocFiles.dependsOn('generateFiles')
- rootProject.copyAllJavadocDocFiles.dependsOn("${project.name}:generateFiles")
- task generateFiles(type: Exec) {
- workingDir projectDir
- commandLine 'make'
- }
+ // Make sure root projects 'javadocAll' depends on the
+ // subproject's javadoc, to ensure that all all doc-files/ are
+ // generated and up-to-date. Obviously this means that the
+ // javadocAll task will also create the individual javadoc's of the
+ // subprojects.
+ javadocAll.dependsOn javadoc
+}
- clean.dependsOn('cleanGeneratedFiles')
- rootProject.clean.dependsOn("${project.name}:cleanGeneratedFiles")
- task cleanGeneratedFiles(type: Exec) {
- workingDir projectDir
- commandLine 'make', 'clean'
- }
- }
+// The smack-java8-full project generates the dot and png files of the
+// current state graph. Ensure they are generated before copied.
+configure (project(':smack-java8-full')) {
+ copyJavadocDocFiles.dependsOn convertModularXmppClientToServerConnectionStateGraphDotToPng
}
configure (androidProjects + androidBootClasspathProjects) {
diff --git a/documentation/connection-modules.md b/documentation/connection-modules.md
new file mode 100644
index 000000000..50110e6f7
--- /dev/null
+++ b/documentation/connection-modules.md
@@ -0,0 +1,35 @@
+Smack's Modular Connection Architecture
+======================================
+
+[Back](index.md)
+
+**Note: Everything related to the modular connection architecture is currently considered experimental and should not be used in production. Use the mature `XMPPTCPConnection` if you do not feel adventurous.
+
+Smack's modular connection architecture allows to extend a XMPP c2s (client-to-server) connection with additional functionalty by adding modules.
+Those modules extend the Finite State Machine (FSM) within the `ModularXmppClientToServerConnection` with new states.
+
+Connection modules can either be
+- Transports
+- Extensions
+
+Transports bind the XMPP XML stream to an underlying transport like TCP, WebSockets, BOSH, and allow for the different particularities of transports like DirectTLS ([XEP-0368](https://xmpp.org/extensions/xep-0368.html)).
+This eventually means that a single transport module can implement multiple transport mechanisms.
+For example the TCP transport module implements the RFC6120 TCP and the XEP-0368 direct TLS TCP transport bindings.
+
+Extensions allow for a richer functionality of the connection. Those include
+- Compression
+ - zlib ([XEP-0138](https://xmpp.org/extensions/xep-0138.html))
+ - [Efficient XML Interchange (EXI)](https://www.w3.org/TR/exi/)
+- Instant Stream Resumption ([XEP-0397](https://xmpp.org/extensions/xep-0397.html)
+- Bind2
+- Stream Management
+
+Note that not all extensions work with every transport.
+For example compression only works with TCP-based transport bindings.
+
+
+Connection modules are plugged into the the modular connection via their constructor. and they usually declare backwards edges to some common, generic connection state of the FSM.
+
+Modules and states always have an accompanying *descriptor* type.
+`ModuleDescriptor` and `StateDescriptor` exist without an connection instance.
+They describe the module and state metadata, while their modules are states are instanciated once a modular connection is instanciated.
diff --git a/documentation/developer/integrationtest.md b/documentation/developer/integrationtest.md
index 2bf2143db..e9caed4e3 100644
--- a/documentation/developer/integrationtest.md
+++ b/documentation/developer/integrationtest.md
@@ -58,24 +58,28 @@ debugger=console
### Framework properties
-| Name | |
-|----------------------|-------------------------------------------|
-| service | XMPP service to run the tests on |
-| serviceTlsPin | TLS Pin (used by [java-pinning](https://github.com/Flowdalic/java-pinning)) |
-| securityMode | Either 'required' or 'disabled' |
-| replyTimeout | In milliseconds |
-| adminAccountUsername | Username of the XEP-0133 Admin account |
-| adminAccountPassword | Password of the XEP-0133 Admin account |
-| accountOneUsername | Username of the first XMPP account |
-| accountOnePassword | Password of the first XMPP account |
-| accountTwoUsername | Username of the second XMPP account |
-| accountTwoPassword | Password of the second XMPP account |
-| accountThreeUsername | Username of the third XMPP account |
-| accountThreePassword | Password of the third XMPP account |
-| debugger | 'console' for console debugger, 'enhanced' for the enhanced debugger |
-| enabledTests | List of enabled tests |
-| disabledTests | List of disabled tests |
-| testPackages | List of packages with tests |
+| Name | Description |
+|----------------------|-----------------------------------------------------------------------------|
+| service | XMPP service to run the tests on |
+| serviceTlsPin | TLS Pin (used by [java-pinning](https://github.com/Flowdalic/java-pinning)) |
+| securityMode | Either 'required' or 'disabled' |
+| replyTimeout | In milliseconds |
+| adminAccountUsername | Username of the XEP-0133 Admin account |
+| adminAccountPassword | Password of the XEP-0133 Admin account |
+| accountOneUsername | Username of the first XMPP account |
+| accountOnePassword | Password of the first XMPP account |
+| accountTwoUsername | Username of the second XMPP account |
+| accountTwoPassword | Password of the second XMPP account |
+| accountThreeUsername | Username of the third XMPP account |
+| accountThreePassword | Password of the third XMPP account |
+| debugger | 'console' for console debugger, 'enhanced' for the enhanced debugger |
+| enabledTests | List of enabled tests |
+| disabledTests | List of disabled tests |
+| defaultConnection | Nickname of the default connection |
+| enabledConnections | List of enabled connection's nicknames |
+| disabledConnections | List of disabled connection's nicknames |
+| testPackages | List of packages with tests |
+| verbose | If `true` set output to verbose |
### Where to place the properties file
@@ -99,6 +103,10 @@ The base class that integration tests need to subclass.
Allows low level integration test, i.e. ever test method will have its on exclusive XMPPTCPConnection instances.
+### `AbstractSmackSpecificLowLevelIntegrationTest`
+
+Operates, like `AbstractSmackLowLevelIntegrationTest` on its own `XMPPConnection` instances, but is limited to a particular type of `XMPPConnection`.
+
### `IntegrationTestEnvironment`
The environment, e.g. the `XMPPConnections` provided to the integration tests by the framework. Note that for convenience `AbstractSmackIntegrationTest` contains some of those as protected members.
diff --git a/documentation/index.md b/documentation/index.md
index 034f18070..5b9d27e9f 100644
--- a/documentation/index.md
+++ b/documentation/index.md
@@ -13,3 +13,4 @@
* [Debugging with Smack](debugging.md)
* [Smack Extensions Manual](extensions/index.md)
+ * [Smack's Modular Connection Architecture](connection-modules.md)
diff --git a/settings.gradle b/settings.gradle
index d3b53d80e..0f84a8753 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -23,6 +23,7 @@ include 'smack-core',
'smack-android',
'smack-android-extensions',
'smack-java7',
+ 'smack-java8-full',
'smack-integration-test',
'smack-omemo',
'smack-omemo-signal',
diff --git a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java
index 824dedc44..c3d7109f8 100644
--- a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java
+++ b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java
@@ -27,7 +27,7 @@ import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.SmackException;
-import org.jivesoftware.smack.SmackException.ConnectionException;
+import org.jivesoftware.smack.SmackException.GenericConnectionException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.SmackWrappedException;
import org.jivesoftware.smack.XMPPConnection;
@@ -136,6 +136,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
this.config = config;
}
+ @SuppressWarnings("deprecation")
@Override
protected void connectInternal() throws SmackException, InterruptedException {
done = false;
@@ -177,7 +178,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
.setAttribute(BodyQName.createWithPrefix(XMPP_BOSH_NS, "version", "xmpp"), "1.0")
.build());
} catch (Exception e) {
- throw new ConnectionException(e);
+ throw new GenericConnectionException(e);
}
// Wait for the response from the server
diff --git a/smack-core/build.gradle b/smack-core/build.gradle
index 8d29cfd89..d9da388b1 100644
--- a/smack-core/build.gradle
+++ b/smack-core/build.gradle
@@ -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 {
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
index 0e13c28c7..3c20fce8b 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
@@ -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 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 populateHostAddresses() {
- List 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);
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXmppNioConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXmppNioConnection.java
deleted file mode 100644
index 8655db39f..000000000
--- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXmppNioConnection.java
+++ /dev/null
@@ -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 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);
- }
-
-}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java
index 33d1bfdd4..3d59269c7 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java
@@ -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.
*
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java
index cd49af68e..9e15ef8b0 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java
@@ -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> 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);
+ }
+ }
+ }
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java
index 41b3b417f..fbf6ebd40 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java
@@ -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 failedAddresses;
+ private final List lookupFailures;
+ private final List extends RemoteConnectionException>> connectionExceptions;
- public ConnectionException(Throwable wrappedThrowable) {
- super(wrappedThrowable);
- failedAddresses = new ArrayList<>(0);
- }
-
- private ConnectionException(String message, List failedAddresses) {
+ private EndpointConnectionException(String message, List 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 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 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 getFailedAddresses() {
- return failedAddresses;
+ public List 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 lookupFailures;
+
+ private NoEndpointsDiscoveredException(String message, List lookupFailures) {
+ super(message);
+ this.lookupFailures = Collections.unmodifiableList(lookupFailures);
+ }
+
+ public List getLookupFailures() {
+ return lookupFailures;
+ }
+
+ public static NoEndpointsDiscoveredException from(List 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);
}
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java
index b08a50603..08646728a 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java
@@ -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 implements Future,
private ExceptionCallback exceptionCallback;
+ private Consumer> completionCallback;
+
@Override
public final synchronized boolean cancel(boolean mayInterruptIfRunning) {
if (isDone()) {
@@ -87,6 +92,11 @@ public abstract class SmackFuture implements Future,
return this;
}
+ public void onCompletion(Consumer> 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 implements Future,
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 implements Future,
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);
+ }
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java
index 92c122571..2d5aeb30e 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java
@@ -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;
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java
index 1c2477789..76d028204 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java
@@ -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();
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/XmppInputOutputFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/XmppInputOutputFilter.java
index b7b14ce05..18432f564 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/XmppInputOutputFilter.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/XmppInputOutputFilter.java
@@ -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();
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/bind2/Bind2Module.java b/smack-core/src/main/java/org/jivesoftware/smack/bind2/Bind2Module.java
new file mode 100644
index 000000000..53162ddce
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/bind2/Bind2Module.java
@@ -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 {
+
+ 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);
+ }
+
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/bind2/Bind2ModuleDescriptor.java b/smack-core/src/main/java/org/jivesoftware/smack/bind2/Bind2ModuleDescriptor.java
new file mode 100644
index 000000000..bf09a4522
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/bind2/Bind2ModuleDescriptor.java
@@ -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> 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;
+ }
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/bind2/package-info.java b/smack-core/src/main/java/org/jivesoftware/smack/bind2/package-info.java
new file mode 100644
index 000000000..bc422ed02
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/bind2/package-info.java
@@ -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;
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java
new file mode 100644
index 000000000..9694b47b8
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java
@@ -0,0 +1,1114 @@
+/**
+ *
+ * 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;
+
+import java.io.IOException;
+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.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLSession;
+
+import org.jivesoftware.smack.AbstractXMPPConnection;
+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.SmackFuture;
+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.c2s.XmppClientToServerTransport.LookupConnectionEndpointsFailed;
+import org.jivesoftware.smack.c2s.XmppClientToServerTransport.LookupConnectionEndpointsResult;
+import org.jivesoftware.smack.c2s.XmppClientToServerTransport.LookupConnectionEndpointsSuccess;
+import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
+import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
+import org.jivesoftware.smack.fsm.ConnectionStateEvent;
+import org.jivesoftware.smack.fsm.ConnectionStateMachineListener;
+import org.jivesoftware.smack.fsm.LoginContext;
+import org.jivesoftware.smack.fsm.NoOpState;
+import org.jivesoftware.smack.fsm.State;
+import org.jivesoftware.smack.fsm.StateDescriptor;
+import org.jivesoftware.smack.fsm.StateDescriptorGraph;
+import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
+import org.jivesoftware.smack.fsm.StateMachineException;
+import org.jivesoftware.smack.fsm.StateTransitionResult;
+import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Nonza;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.packet.Stanza;
+import org.jivesoftware.smack.packet.StreamClose;
+import org.jivesoftware.smack.packet.StreamError;
+import org.jivesoftware.smack.packet.TopLevelStreamElement;
+import org.jivesoftware.smack.packet.XmlEnvironment;
+import org.jivesoftware.smack.parsing.SmackParsingException;
+import org.jivesoftware.smack.sasl.SASLErrorException;
+import org.jivesoftware.smack.sasl.SASLMechanism;
+import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smack.xml.XmlPullParser;
+import org.jivesoftware.smack.xml.XmlPullParserException;
+
+import org.jxmpp.jid.parts.Resourcepart;
+
+public final class ModularXmppClientToServerConnection extends AbstractXMPPConnection {
+
+ private static final Logger LOGGER = Logger.getLogger(ModularXmppClientToServerConnectionConfiguration.class.getName());
+
+ private final ArrayBlockingQueueWithShutdown outgoingElementsQueue = new ArrayBlockingQueueWithShutdown<>(
+ 100, true);
+
+ private XmppClientToServerTransport activeTransport;
+
+ private final List connectionStateMachineListeners = new CopyOnWriteArrayList<>();
+
+ private boolean featuresReceived;
+
+ protected boolean streamResumed;
+
+ private GraphVertex currentStateVertex;
+
+ private List walkFromDisconnectToAuthenticated;
+
+ private final ModularXmppClientToServerConnectionConfiguration configuration;
+
+ private final ModularXmppClientToServerConnectionInternal connectionInternal;
+
+ private final Map, ModularXmppClientToServerConnectionModule extends ModularXmppClientToServerConnectionModuleDescriptor>> connectionModules = new HashMap<>();
+
+ private final Map, XmppClientToServerTransport> transports = new HashMap<>();
+ /**
+ * This is one of those cases where the field is modified by one thread and read by another. We currently use
+ * CopyOnWriteArrayList but should potentially use a VarHandle once Smack supports them.
+ */
+ private final List inputOutputFilters = new CopyOnWriteArrayList<>();
+
+ private List previousInputOutputFilters;
+
+ public ModularXmppClientToServerConnection(ModularXmppClientToServerConnectionConfiguration configuration) {
+ super(configuration);
+
+ this.configuration = configuration;
+
+ // Construct the internal connection API.
+ connectionInternal = new ModularXmppClientToServerConnectionInternal(this, getReactor(), debugger, outgoingElementsQueue) {
+
+ @Override
+ public void parseAndProcessElement(String wrappedCompleteElement) {
+ ModularXmppClientToServerConnection.this.parseAndProcessElement(wrappedCompleteElement);
+ }
+
+ @Override
+ public void notifyConnectionError(Exception e) {
+ ModularXmppClientToServerConnection.this.notifyConnectionError(e);
+ }
+
+ @Override
+ public void onStreamOpen(XmlPullParser parser) {
+ ModularXmppClientToServerConnection.this.onStreamOpen(parser);
+ }
+
+ @Override
+ public void onStreamClosed() {
+ ModularXmppClientToServerConnection.this.closingStreamReceived.reportSuccess();
+ }
+
+ @Override
+ public void fireFirstLevelElementSendListeners(TopLevelStreamElement element) {
+ ModularXmppClientToServerConnection.this.firePacketSendingListeners(element);
+ }
+
+ @Override
+ public void invokeConnectionStateMachineListener(ConnectionStateEvent connectionStateEvent) {
+ ModularXmppClientToServerConnection.this.invokeConnectionStateMachineListener(connectionStateEvent);
+ }
+
+ @Override
+ public XmlEnvironment getOutgoingStreamXmlEnvironment() {
+ return outgoingStreamXmlEnvironment;
+ }
+
+ @Override
+ public void addXmppInputOutputFilter(XmppInputOutputFilter xmppInputOutputFilter) {
+ inputOutputFilters.add(0, xmppInputOutputFilter);
+ }
+
+ @Override
+ public ListIterator getXmppInputOutputFilterBeginIterator() {
+ return inputOutputFilters.listIterator();
+ }
+
+ @Override
+ public ListIterator getXmppInputOutputFilterEndIterator() {
+ return inputOutputFilters.listIterator(inputOutputFilters.size());
+ }
+
+ @Override
+ public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
+ ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
+ ModularXmppClientToServerConnection.this.newStreamOpenWaitForFeaturesSequence(waitFor);
+ }
+
+ @Override
+ public SmackTlsContext getSmackTlsContext()
+ throws KeyManagementException, NoSuchAlgorithmException, CertificateException, IOException,
+ UnrecoverableKeyException, KeyStoreException, NoSuchProviderException {
+ return ModularXmppClientToServerConnection.this.getSmackTlsContext();
+ }
+
+ @Override
+ public SN sendAndWaitForResponse(Nonza nonza, Class successNonzaClass,
+ Class failedNonzaClass) throws NoResponseException, NotConnectedException, FailedNonzaException, InterruptedException {
+ return ModularXmppClientToServerConnection.this.sendAndWaitForResponse(nonza, successNonzaClass, failedNonzaClass);
+ }
+
+ @Override
+ public void asyncGo(Runnable runnable) {
+ AbstractXMPPConnection.asyncGo(runnable);
+ }
+
+ @Override
+ public Exception getCurrentConnectionException() {
+ return ModularXmppClientToServerConnection.this.currentConnectionException;
+ }
+
+ @Override
+ public void setCompressionEnabled(boolean compressionEnabled) {
+ ModularXmppClientToServerConnection.this.compressionEnabled = compressionEnabled;
+ }
+
+ @Override
+ public void setTransport(XmppClientToServerTransport xmppTransport) {
+ ModularXmppClientToServerConnection.this.activeTransport = xmppTransport;
+ }
+
+ };
+
+ // Construct the modules from the module descriptor. We do this before constructing the state graph, as the
+ // modules are sometimes used to construct the states.
+ for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : configuration.moduleDescriptors) {
+ Class extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptorClass = moduleDescriptor.getClass();
+ ModularXmppClientToServerConnectionModule extends ModularXmppClientToServerConnectionModuleDescriptor> connectionModule = moduleDescriptor.constructXmppConnectionModule(connectionInternal);
+ connectionModules.put(moduleDescriptorClass, connectionModule);
+
+ XmppClientToServerTransport transport = connectionModule.getTransport();
+ // Not every module may provide a transport.
+ if (transport != null) {
+ transports.put(moduleDescriptorClass, transport);
+ }
+ }
+
+ GraphVertex initialStateDescriptorVertex = configuration.initialStateDescriptorVertex;
+ // Convert the graph of state descriptors to a graph of states, bound to this very connection.
+ currentStateVertex = StateDescriptorGraph.convertToStateGraph(initialStateDescriptorVertex, connectionInternal);
+ }
+
+ @SuppressWarnings("unchecked")
+ public > CM getConnectionModuleFor(
+ Class extends ModularXmppClientToServerConnectionModuleDescriptor> descriptorClass) {
+ return (CM) connectionModules.get(descriptorClass);
+ }
+
+ @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 WalkStateGraphContext.Builder buildNewWalkTo(Class extends StateDescriptor> finalStateClass) {
+ return WalkStateGraphContext.builder(currentStateVertex.getElement().getStateDescriptor().getClass(), finalStateClass);
+ }
+
+ /**
+ * Unwind the state. This will revert the effects of the state by calling {@link State#resetState()} prior issuing a
+ * connection state event of {@link ConnectionStateEvent#StateRevertBackwardsWalk}.
+ *
+ * @param revertedState the state which is going to get reverted.
+ */
+ private void unwindState(State revertedState) {
+ invokeConnectionStateMachineListener(new ConnectionStateEvent.StateRevertBackwardsWalk(revertedState));
+ revertedState.resetState();
+ }
+
+ protected void walkStateGraph(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
+ SASLErrorException, FailedNonzaException, IOException, SmackException, InterruptedException {
+ // Save a copy of the current state
+ GraphVertex previousStateVertex = currentStateVertex;
+ try {
+ walkStateGraphInternal(walkStateGraphContext);
+ } catch (XMPPErrorException | SASLErrorException | FailedNonzaException | IOException | SmackException
+ | InterruptedException e) {
+ currentStateVertex = previousStateVertex;
+ // Unwind the state.
+ State revertedState = currentStateVertex.getElement();
+ unwindState(revertedState);
+ throw e;
+ }
+ }
+
+ private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
+ SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
+ // Save a copy of the current state
+ final GraphVertex initialStateVertex = currentStateVertex;
+ final State initialState = initialStateVertex.getElement();
+ final StateDescriptor initialStateDescriptor = initialState.getStateDescriptor();
+
+ walkStateGraphContext.recordWalkTo(initialState);
+
+ // Check if this is the walk's final state.
+ if (walkStateGraphContext.isWalksFinalState(initialStateDescriptor)) {
+ // 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> outgoingStateEdges = initialStateVertex.getOutgoingEdges();
+
+ // See if we need to handle mandatory intermediate states.
+ GraphVertex mandatoryIntermediateStateVertex = walkStateGraphContext.maybeReturnMandatoryImmediateState(outgoingStateEdges);
+ if (mandatoryIntermediateStateVertex != null) {
+ StateTransitionResult reason = attemptEnterState(mandatoryIntermediateStateVertex, walkStateGraphContext);
+
+ if (reason instanceof StateTransitionResult.Success) {
+ walkStateGraph(walkStateGraphContext);
+ return;
+ }
+
+ // We could not enter a mandatory intermediate state. Throw here.
+ throw new StateMachineException.SmackMandatoryStateFailedException(
+ mandatoryIntermediateStateVertex.getElement(), reason);
+ }
+
+ for (Iterator> it = outgoingStateEdges.iterator(); it.hasNext();) {
+ GraphVertex successorStateVertex = it.next();
+ State successorState = successorStateVertex.getElement();
+
+ // Ignore successorStateVertex if the only way to the final state is via the initial state. This happens
+ // typically if we are in the ConnectedButUnauthenticated state on the way to ResourceboundAndAuthenticated,
+ // where we do not want to walk via InstantShutdown/Shtudown in a cycle over the initial state towards this
+ // state.
+ if (walkStateGraphContext.wouldCauseCycle(successorStateVertex)) {
+ // Ignore this successor.
+ invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionIgnoredDueCycle(initialStateVertex, successorStateVertex));
+ } else {
+ StateTransitionResult result = attemptEnterState(successorStateVertex, walkStateGraphContext);
+
+ if (result instanceof StateTransitionResult.Success) {
+ 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 (result != null) {
+ walkStateGraphContext.recordFailedState(successorState, result);
+ }
+ }
+
+ if (!it.hasNext()) {
+ throw StateMachineException.SmackStateGraphDeadEndException.from(walkStateGraphContext,
+ initialStateVertex);
+ }
+ }
+
+ // Walk the state graph by recursion.
+ walkStateGraph(walkStateGraphContext);
+ }
+
+ /**
+ * Attempt to enter a state. Note that this method may return null
if this state can be safely ignored ignored.
+ *
+ * @param successorStateVertex the successor state vertex.
+ * @param walkStateGraphContext the "walk state graph" context.
+ * @return A state transition result or null
if this state can be ignored.
+ * @throws SmackException if Smack detected an exceptional situation.
+ * @throws XMPPErrorException if an XMPP protocol error was received.
+ * @throws SASLErrorException if a SASL protocol error was returned.
+ * @throws IOException if an I/O error occurred.
+ * @throws InterruptedException if the calling thread was interrupted.
+ * @throws FailedNonzaException if an XMPP protocol failure was received.
+ */
+ private StateTransitionResult attemptEnterState(GraphVertex successorStateVertex,
+ WalkStateGraphContext walkStateGraphContext) throws SmackException, XMPPErrorException,
+ SASLErrorException, IOException, InterruptedException, FailedNonzaException {
+ final GraphVertex initialStateVertex = currentStateVertex;
+ final State initialState = initialStateVertex.getElement();
+ final State successorState = successorStateVertex.getElement();
+ final StateDescriptor successorStateDescriptor = successorState.getStateDescriptor();
+
+ if (!successorStateDescriptor.isMultiVisitState()
+ && walkStateGraphContext.stateAlreadyVisited(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()) {
+ StateTransitionResult.TransitionImpossibleBecauseNotImplemented transtionImpossibleBecauseNotImplemented = new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(
+ successorStateDescriptor);
+ invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(initialState, successorState,
+ transtionImpossibleBecauseNotImplemented));
+ return transtionImpossibleBecauseNotImplemented;
+ }
+
+ final StateTransitionResult.AttemptResult transitionAttemptResult;
+ try {
+ StateTransitionResult.TransitionImpossible transitionImpossible = successorState.isTransitionToPossible(
+ walkStateGraphContext);
+ if (transitionImpossible != null) {
+ invokeConnectionStateMachineListener(new ConnectionStateEvent.TransitionNotPossible(initialState, successorState,
+ transitionImpossible));
+ return transitionImpossible;
+ }
+
+ invokeConnectionStateMachineListener(new ConnectionStateEvent.AboutToTransitionInto(initialState, successorState));
+ transitionAttemptResult = successorState.transitionInto(walkStateGraphContext);
+ } catch (SmackException | XMPPErrorException | SASLErrorException | IOException | InterruptedException
+ | FailedNonzaException e) {
+ // Unwind the state here too, since this state will not be unwound by walkStateGraph(), as it will not
+ // become a predecessor state in the walk.
+ unwindState(successorState);
+ throw e;
+ }
+ if (transitionAttemptResult instanceof StateTransitionResult.Failure) {
+ StateTransitionResult.Failure transitionFailureResult = (StateTransitionResult.Failure) transitionAttemptResult;
+ invokeConnectionStateMachineListener(
+ new ConnectionStateEvent.TransitionFailed(initialState, successorState, transitionFailureResult));
+ return transitionAttemptResult;
+ }
+
+ // If transitionAttemptResult is not an instance of TransitionFailureResult, then it has to be of type
+ // TransitionSuccessResult.
+ StateTransitionResult.Success transitionSuccessResult = (StateTransitionResult.Success) transitionAttemptResult;
+
+ currentStateVertex = successorStateVertex;
+ invokeConnectionStateMachineListener(
+ new ConnectionStateEvent.SuccessfullyTransitionedInto(successorState, transitionSuccessResult));
+
+ return transitionSuccessResult;
+ }
+
+ @Override
+ protected void sendStanzaInternal(Stanza stanza) throws NotConnectedException, InterruptedException {
+ sendTopLevelStreamElement(stanza);
+ }
+
+ @Override
+ public void sendNonza(Nonza nonza) throws NotConnectedException, InterruptedException {
+ sendTopLevelStreamElement(nonza);
+ }
+
+ private void sendTopLevelStreamElement(TopLevelStreamElement element) throws NotConnectedException, InterruptedException {
+ final XmppClientToServerTransport transport = activeTransport;
+ if (transport == null) {
+ throw new NotConnectedException();
+ }
+
+ outgoingElementsQueue.put(element);
+ transport.notifyAboutNewOutgoingElements();
+ }
+
+ @Override
+ protected void shutdown() {
+ shutdown(false);
+ }
+
+ @Override
+ public synchronized void instantShutdown() {
+ shutdown(true);
+ }
+
+ @Override
+ public ModularXmppClientToServerConnectionConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ private void shutdown(boolean instant) {
+ Class extends StateDescriptor> mandatoryIntermediateState;
+ if (instant) {
+ mandatoryIntermediateState = InstantShutdownStateDescriptor.class;
+ } else {
+ mandatoryIntermediateState = ShutdownStateDescriptor.class;
+ }
+
+ WalkStateGraphContext context = buildNewWalkTo(
+ DisconnectedStateDescriptor.class).withMandatoryIntermediateState(
+ mandatoryIntermediateState).build();
+
+ try {
+ walkStateGraph(context);
+ } catch (XMPPErrorException | SASLErrorException | IOException | SmackException | InterruptedException
+ | FailedNonzaException e) {
+ throw new IllegalStateException("A walk to disconnected state should never throw", e);
+ }
+ }
+
+ protected SSLSession getSSLSession() {
+ final XmppClientToServerTransport transport = activeTransport;
+ if (transport == null) {
+ return null;
+ }
+ return transport.getSslSession();
+ }
+
+ @Override
+ protected void afterFeaturesReceived() {
+ featuresReceived = true;
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+
+ protected void parseAndProcessElement(String element) {
+ try {
+ 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();
+ }
+ } catch (XmlPullParserException | IOException | InterruptedException | StreamErrorException
+ | SmackParsingException e) {
+ notifyConnectionError(e);
+ }
+ }
+
+ 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);
+ }
+
+ public static class DisconnectedStateDescriptor extends StateDescriptor {
+ protected DisconnectedStateDescriptor() {
+ super(DisconnectedState.class, StateDescriptor.Property.finalState);
+ addSuccessor(LookupRemoteConnectionEndpointsStateDescriptor.class);
+ }
+ }
+
+ private final class DisconnectedState extends State {
+
+ private DisconnectedState(StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(stateDescriptor, connectionInternal);
+ }
+
+ @Override
+ public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
+ synchronized (ModularXmppClientToServerConnection.this) {
+ if (inputOutputFilters.isEmpty()) {
+ previousInputOutputFilters = null;
+ } else {
+ previousInputOutputFilters = new ArrayList<>(inputOutputFilters.size());
+ previousInputOutputFilters.addAll(inputOutputFilters);
+ inputOutputFilters.clear();
+ }
+ }
+
+ // Reset all states we have visited when transitioning from disconnected to authenticated. This assumes that
+ // every state after authenticated does not need to be reset.
+ ListIterator it = walkFromDisconnectToAuthenticated.listIterator(
+ walkFromDisconnectToAuthenticated.size());
+ while (it.hasPrevious()) {
+ State stateToReset = it.previous();
+ stateToReset.resetState();
+ }
+ walkFromDisconnectToAuthenticated = null;
+
+ return StateTransitionResult.Success.EMPTY_INSTANCE;
+ }
+ }
+
+ public static final class LookupRemoteConnectionEndpointsStateDescriptor extends StateDescriptor {
+ private LookupRemoteConnectionEndpointsStateDescriptor() {
+ super(LookupRemoteConnectionEndpointsState.class);
+ }
+ }
+
+ private final class LookupRemoteConnectionEndpointsState extends State {
+ boolean outgoingElementsQueueWasShutdown;
+
+ private LookupRemoteConnectionEndpointsState(StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(stateDescriptor, connectionInternal);
+ }
+
+ @Override
+ public AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
+ SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
+ // There is a challenge here: We are going to trigger the discovery of endpoints which will run
+ // asynchronously. After a timeout, all discovered endpoints are collected. To prevent stale results from
+ // previous discover runs, the results are communicated via SmackFuture, so that we always handle the most
+ // up-to-date results.
+
+ Map>> lookupFutures = new HashMap<>(
+ transports.size());
+
+ final int numberOfFutures;
+ {
+ List> allFutures = new ArrayList<>();
+ for (XmppClientToServerTransport transport : transports.values()) {
+ // First we clear the transport of any potentially previously discovered connection endpoints.
+ transport.resetDiscoveredConnectionEndpoints();
+
+ // Ask the transport to start the discovery of remote connection endpoints asynchronously.
+ List> transportFutures = transport.lookupConnectionEndpoints();
+
+ lookupFutures.put(transport, transportFutures);
+ allFutures.addAll(transportFutures);
+ }
+
+ numberOfFutures = allFutures.size();
+
+ // Wait until all features are ready or if the timeout occurs. Note that we do not inspect and react the
+ // return value of SmackFuture.await() as we want to collect the LookupConnectionEndpointsFailed later.
+ SmackFuture.await(allFutures, getReplyTimeout(), TimeUnit.MILLISECONDS);
+ }
+
+ // Note that we do not pass the lookupFailures in case there is at least one successful transport. The
+ // lookup failures are also recording in LookupConnectionEndpointsSuccess, e.g. as part of
+ // RemoteXmppTcpConnectionEndpoints.Result.
+ List lookupFailures = new ArrayList<>(numberOfFutures);
+
+ boolean atLeastOneConnectionEndpointDiscovered = false;
+ for (Map.Entry>> entry : lookupFutures.entrySet()) {
+ XmppClientToServerTransport transport = entry.getKey();
+
+ for (SmackFuture future : entry.getValue()) {
+ LookupConnectionEndpointsResult result = future.getIfAvailable();
+
+ if (result == null) {
+ continue;
+ }
+
+ if (result instanceof LookupConnectionEndpointsFailed) {
+ LookupConnectionEndpointsFailed lookupFailure = (LookupConnectionEndpointsFailed) result;
+ lookupFailures.add(lookupFailure);
+ continue;
+ }
+
+ LookupConnectionEndpointsSuccess successResult = (LookupConnectionEndpointsSuccess) result;
+
+ // Arm the transport with the success result, so that its information can be used by the transport
+ // to establish the connection.
+ transport.loadConnectionEndpoints(successResult);
+
+ // Mark that the connection attempt can continue.
+ atLeastOneConnectionEndpointDiscovered = true;
+ }
+ }
+
+ if (!atLeastOneConnectionEndpointDiscovered) {
+ throw SmackException.NoEndpointsDiscoveredException.from(lookupFailures);
+ }
+
+ // Even though the outgoing elements queue is unrelated to the lookup remote connection endpoints state, we
+ // do start the queue at this point. The transports will need it available, and we use the state's reset()
+ // function to close the queue again on failure.
+ outgoingElementsQueueWasShutdown = outgoingElementsQueue.start();
+
+ return StateTransitionResult.Success.EMPTY_INSTANCE;
+ }
+
+ @Override
+ public void resetState() {
+ for (XmppClientToServerTransport transport : transports.values()) {
+ transport.resetDiscoveredConnectionEndpoints();
+ }
+
+ if (outgoingElementsQueueWasShutdown) {
+ // Reset the outgoing elements queue in this state, since we also start it in this state.
+ outgoingElementsQueue.shutdown();
+ }
+ }
+ }
+
+ public static final class ConnectedButUnauthenticatedStateDescriptor extends StateDescriptor {
+ private ConnectedButUnauthenticatedStateDescriptor() {
+ super(ConnectedButUnauthenticatedState.class, StateDescriptor.Property.finalState);
+ addSuccessor(SaslAuthenticationStateDescriptor.class);
+ addSuccessor(InstantShutdownStateDescriptor.class);
+ addSuccessor(ShutdownStateDescriptor.class);
+ }
+ }
+
+ private final class ConnectedButUnauthenticatedState extends State {
+ private ConnectedButUnauthenticatedState(StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(stateDescriptor, connectionInternal);
+ }
+
+ @Override
+ public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
+ assert walkFromDisconnectToAuthenticated == null;
+
+ if (walkStateGraphContext.isWalksFinalState(getStateDescriptor())) {
+ // If this is the final state, then record the walk so far.
+ walkFromDisconnectToAuthenticated = walkStateGraphContext.getWalk();
+ }
+
+ connected = true;
+ return StateTransitionResult.Success.EMPTY_INSTANCE;
+ }
+
+ @Override
+ public void resetState() {
+ connected = false;
+ }
+ }
+
+ public 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, ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(stateDescriptor, connectionInternal);
+ }
+
+ @Override
+ public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
+ throws XMPPErrorException, SASLErrorException, IOException, SmackException,
+ InterruptedException {
+ prepareToWaitForFeaturesReceived();
+
+ LoginContext loginContext = walkStateGraphContext.getLoginContext();
+ 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 StateTransitionResult.Success {
+ private final String saslMechanismName;
+
+ private SaslAuthenticationSuccessResult(SASLMechanism usedSaslMechanism) {
+ super("SASL authentication successfull using " + usedSaslMechanism.getName());
+ this.saslMechanismName = usedSaslMechanism.getName();
+ }
+
+ public String getSaslMechanismName() {
+ return saslMechanismName;
+ }
+ }
+
+ public static final class AuthenticatedButUnboundStateDescriptor extends StateDescriptor {
+ private AuthenticatedButUnboundStateDescriptor() {
+ super(StateDescriptor.Property.multiVisitState);
+ addSuccessor(ResourceBindingStateDescriptor.class);
+ }
+ }
+
+ public 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, ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(stateDescriptor, connectionInternal);
+ }
+
+ @Override
+ public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
+ throws XMPPErrorException, SASLErrorException, IOException, SmackException,
+ InterruptedException {
+ // Calling bindResourceAndEstablishSession() below requires the lastFeaturesReceived sync point to be signaled.
+ // Since we entered this state, the FSM has decided that the last features have been received, hence signal
+ // the sync point.
+ lastFeaturesReceived.reportSuccess();
+
+ LoginContext loginContext = walkStateGraphContext.getLoginContext();
+ Resourcepart resource = bindResourceAndEstablishSession(loginContext.resource);
+
+ // TODO: This should be a field in the Stream Management (SM) module. Here should be hook which the SM
+ // module can use to set the field instead.
+ streamResumed = false;
+
+ return new ResourceBoundResult(resource, loginContext.resource);
+ }
+ }
+
+ public static final class ResourceBoundResult extends StateTransitionResult.Success {
+ private final Resourcepart resource;
+
+ private ResourceBoundResult(Resourcepart boundResource, Resourcepart requestedResource) {
+ super("Resource '" + boundResource + "' bound (requested: '" + requestedResource + "')");
+ this.resource = boundResource;
+ }
+
+ public Resourcepart getResource() {
+ return resource;
+ }
+ }
+
+ private boolean compressionEnabled;
+
+ @Override
+ public boolean isUsingCompression() {
+ return compressionEnabled;
+ }
+
+ public static final class AuthenticatedAndResourceBoundStateDescriptor extends StateDescriptor {
+ private AuthenticatedAndResourceBoundStateDescriptor() {
+ super(AuthenticatedAndResourceBoundState.class, StateDescriptor.Property.finalState);
+ addSuccessor(InstantShutdownStateDescriptor.class);
+ addSuccessor(ShutdownStateDescriptor.class);
+ }
+ }
+
+ private final class AuthenticatedAndResourceBoundState extends State {
+ private AuthenticatedAndResourceBoundState(StateDescriptor stateDescriptor,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(stateDescriptor, connectionInternal);
+ }
+
+ @Override
+ public StateTransitionResult.AttemptResult 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.getWalk().get(0).getStateDescriptor().getClass()
+ != DisconnectedStateDescriptor.class;
+ // Append the current walk to the previous one.
+ walkStateGraphContext.appendWalkTo(walkFromDisconnectToAuthenticated);
+ } else {
+ walkFromDisconnectToAuthenticated = new ArrayList<>(
+ walkStateGraphContext.getWalkLength() + 1);
+ walkStateGraphContext.appendWalkTo(walkFromDisconnectToAuthenticated);
+ }
+ walkFromDisconnectToAuthenticated.add(this);
+
+ afterSuccessfulLogin(streamResumed);
+
+ return StateTransitionResult.Success.EMPTY_INSTANCE;
+ }
+
+ @Override
+ public void resetState() {
+ authenticated = false;
+ }
+ }
+
+ static final class ShutdownStateDescriptor extends StateDescriptor {
+ private ShutdownStateDescriptor() {
+ super(ShutdownState.class);
+ addSuccessor(CloseConnectionStateDescriptor.class);
+ }
+ }
+
+ private final class ShutdownState extends State {
+ private ShutdownState(StateDescriptor stateDescriptor,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(stateDescriptor, connectionInternal);
+ }
+
+ @Override
+ public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
+ ensureNotOnOurWayToAuthenticatedAndResourceBound(walkStateGraphContext);
+ return null;
+ }
+
+ @Override
+ public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
+ closingStreamReceived.init();
+
+ boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(StreamClose.INSTANCE);
+
+ if (streamCloseIssued) {
+ activeTransport.notifyAboutNewOutgoingElements();
+
+ boolean successfullyReceivedStreamClose = waitForClosingStreamTagFromServer();
+
+ if (successfullyReceivedStreamClose) {
+ for (Iterator it = connectionInternal.getXmppInputOutputFilterBeginIterator(); it.hasNext();) {
+ XmppInputOutputFilter filter = it.next();
+ filter.closeInputOutput();
+ }
+
+ // Closing the filters may produced new outgoing data. Notify the transport about it.
+ activeTransport.afterFiltersClosed();
+
+ for (Iterator it = connectionInternal.getXmppInputOutputFilterBeginIterator(); it.hasNext();) {
+ XmppInputOutputFilter filter = it.next();
+ try {
+ filter.waitUntilInputOutputClosed();
+ } catch (IOException | CertificateException | InterruptedException | SmackException e) {
+ LOGGER.log(Level.WARNING, "waitUntilInputOutputClosed() threw", e);
+ }
+ }
+
+ // For correctness we set authenticated to false here, even though we will later again set it to
+ // false in the disconnected state.
+ authenticated = false;
+ }
+ }
+
+ return StateTransitionResult.Success.EMPTY_INSTANCE;
+ }
+ }
+
+ static final class InstantShutdownStateDescriptor extends StateDescriptor {
+ private InstantShutdownStateDescriptor() {
+ super(InstantShutdownState.class);
+ addSuccessor(CloseConnectionStateDescriptor.class);
+ }
+ }
+
+ private static final class InstantShutdownState extends NoOpState {
+ private InstantShutdownState(ModularXmppClientToServerConnection connection, StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(connection, stateDescriptor, connectionInternal);
+ }
+
+ @Override
+ public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
+ ensureNotOnOurWayToAuthenticatedAndResourceBound(walkStateGraphContext);
+ return null;
+ }
+ }
+
+ private static final class CloseConnectionStateDescriptor extends StateDescriptor {
+ private CloseConnectionStateDescriptor() {
+ super(CloseConnectionState.class);
+ addSuccessor(DisconnectedStateDescriptor.class);
+ }
+ }
+
+ private final class CloseConnectionState extends State {
+ private CloseConnectionState(StateDescriptor stateDescriptor,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(stateDescriptor, connectionInternal);
+ }
+
+ @Override
+ public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
+ activeTransport.disconnect();
+ activeTransport = null;
+
+ authenticated = connected = false;
+
+ return StateTransitionResult.Success.EMPTY_INSTANCE;
+ }
+ }
+
+ 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);
+ }
+ });
+ }
+
+ @Override
+ public boolean isSecureConnection() {
+ final XmppClientToServerTransport transport = activeTransport;
+ if (transport == null) {
+ return false;
+ }
+ return transport.isTransportSecured();
+ }
+
+ @Override
+ protected void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException {
+ WalkStateGraphContext walkStateGraphContext = buildNewWalkTo(ConnectedButUnauthenticatedStateDescriptor.class)
+ .build();
+ walkStateGraph(walkStateGraphContext);
+ }
+
+ protected Map getFilterStats() {
+ Collection filters;
+ synchronized (this) {
+ if (inputOutputFilters.isEmpty() && previousInputOutputFilters != null) {
+ filters = previousInputOutputFilters;
+ } else {
+ filters = inputOutputFilters;
+ }
+ }
+
+ Map filterStats = new HashMap<>(filters.size());
+ for (XmppInputOutputFilter xmppInputOutputFilter : filters) {
+ Object stats = xmppInputOutputFilter.getStats();
+ String filterName = xmppInputOutputFilter.getFilterName();
+
+ filterStats.put(filterName, stats);
+ }
+
+ return filterStats;
+ }
+
+ public Stats getStats() {
+ Map, XmppClientToServerTransport.Stats> transportsStats = new HashMap<>(
+ transports.size());
+ for (Map.Entry, XmppClientToServerTransport> entry : transports.entrySet()) {
+ XmppClientToServerTransport.Stats transportStats = entry.getValue().getStats();
+
+ transportsStats.put(entry.getKey(), transportStats);
+ }
+
+ Map filterStats = getFilterStats();
+
+ return new Stats(transportsStats, filterStats);
+ }
+
+ public static final class Stats {
+ public final Map, XmppClientToServerTransport.Stats> transportsStats;
+ public final Map filtersStats;
+
+ private Stats(Map, XmppClientToServerTransport.Stats> transportsStats,
+ Map filtersStats) {
+ this.transportsStats = Collections.unmodifiableMap(transportsStats);
+ this.filtersStats = Collections.unmodifiableMap(filtersStats);
+ }
+
+ public void appendStatsTo(Appendable appendable) throws IOException {
+ StringUtils.appendHeading(appendable, "Connection stats", '#').append('\n');
+
+ for (Map.Entry, XmppClientToServerTransport.Stats> entry : transportsStats.entrySet()) {
+ Class extends ModularXmppClientToServerConnectionModuleDescriptor> transportClass = entry.getKey();
+ XmppClientToServerTransport.Stats stats = entry.getValue();
+
+ StringUtils.appendHeading(appendable, transportClass.getName());
+ appendable.append(stats.toString()).append('\n');
+ }
+
+ for (Map.Entry entry : filtersStats.entrySet()) {
+ String filterName = entry.getKey();
+ Object filterStats = entry.getValue();
+
+ StringUtils.appendHeading(appendable, filterName);
+ appendable.append(filterStats.toString()).append('\n');
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ try {
+ appendStatsTo(sb);
+ } catch (IOException e) {
+ // Should never happen.
+ throw new AssertionError(e);
+ }
+ return sb.toString();
+ }
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java
new file mode 100644
index 000000000..a3bf16911
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java
@@ -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 moduleDescriptors;
+
+ final GraphVertex initialStateDescriptorVertex;
+
+ private ModularXmppClientToServerConnectionConfiguration(Builder builder) {
+ super(builder);
+
+ moduleDescriptors = Collections.unmodifiableSet(CollectionUtil.newSetWith(builder.modulesDescriptors.values()));
+
+ Set> 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> 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 {
+
+ private final Map, 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 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;
+ }
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModule.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModule.java
new file mode 100644
index 000000000..eef942ebb
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModule.java
@@ -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 {
+
+ 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;
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModuleDescriptor.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModuleDescriptor.java
new file mode 100644
index 000000000..2c1054a36
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionModuleDescriptor.java
@@ -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> 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;
+ }
+
+ }
+
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java
new file mode 100644
index 000000000..d68440fc3
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/XmppClientToServerTransport.java
@@ -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> 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()?
+ }
+
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java
new file mode 100644
index 000000000..57452f270
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java
@@ -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 outgoingElementsQueue;
+
+ public ModularXmppClientToServerConnectionInternal(ModularXmppClientToServerConnection connection, SmackReactor reactor,
+ SmackDebugger smackDebugger, Queue 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 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 getXmppInputOutputFilterBeginIterator();
+
+ public abstract ListIterator 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 sendAndWaitForResponse(Nonza nonza,
+ Class successNonzaClass, Class 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);
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/WalkStateGraphContext.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/WalkStateGraphContext.java
new file mode 100644
index 000000000..ed2895d97
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/WalkStateGraphContext.java
@@ -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 walkedStateGraphPath = new ArrayList<>();
+
+ /**
+ * A linked Map of failed States with their reason as value.
+ */
+ final Map 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 maybeReturnMandatoryImmediateState(List> outgoingStateEdges) {
+ for (GraphVertex outgoingStateVertex : outgoingStateEdges) {
+ if (outgoingStateVertex.getElement().getStateDescriptor().getClass() == mandatoryIntermediateState) {
+ mandatoryIntermediateStateHandled = true;
+ return outgoingStateVertex;
+ }
+ }
+
+ return null;
+ }
+
+ public List getWalk() {
+ return CollectionUtil.newListWith(walkedStateGraphPath);
+ }
+
+ public int getWalkLength() {
+ return walkedStateGraphPath.size();
+ }
+
+ public void appendWalkTo(List 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 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 true
if it would loop, false
otherwise.
+ */
+ public boolean wouldCauseCycle(GraphVertex successorStateVertex) {
+ Set> visited = new HashSet<>();
+ return wouldCycleRecursive(successorStateVertex, visited);
+ }
+
+ private boolean wouldCycleRecursive(GraphVertex stateVertex, Set> 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 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);
+ }
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/package-info.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/package-info.java
new file mode 100644
index 000000000..9705f1cb1
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/package-info.java
@@ -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;
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/package-info.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/package-info.java
new file mode 100644
index 000000000..2c97e4c92
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/package-info.java
@@ -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;
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java b/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java
new file mode 100644
index 000000000..2a018899b
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java
@@ -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 {
+
+ 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);
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModuleDescriptor.java b/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModuleDescriptor.java
new file mode 100644
index 000000000..3cf43ee79
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModuleDescriptor.java
@@ -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> 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;
+ }
+
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/compression/zlib/ZlibXmppCompressionFactory.java b/smack-core/src/main/java/org/jivesoftware/smack/compression/zlib/ZlibXmppCompressionFactory.java
index cbb295c1b..096147931 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/compression/zlib/ZlibXmppCompressionFactory.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/compression/zlib/ZlibXmppCompressionFactory.java
@@ -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 {
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java
deleted file mode 100644
index 2c7168148..000000000
--- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java
+++ /dev/null
@@ -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 connectionStateMachineListeners = new CopyOnWriteArrayList<>();
-
- private boolean featuresReceived;
-
- protected boolean streamResumed;
-
- private GraphVertex currentStateVertex;
-
- private List walkFromDisconnectToAuthenticated;
-
- private final List inputOutputFilters = new CopyOnWriteArrayList<>();
- private List previousInputOutputFilters;
-
- protected AbstractXmppStateMachineConnection(ConnectionConfiguration configuration, GraphVertex 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 walkedStateGraphPath = new ArrayList<>();
-
- /**
- * A linked Map of failed States with their reason as value.
- */
- private final Map 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 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 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> 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 mandatoryIntermediateStateVertex = null;
- for (GraphVertex 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> it = outgoingStateEdges.iterator(); it.hasNext();) {
- GraphVertex 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 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 getXmppInputOutputFilterBeginIterator() {
- return inputOutputFilters.listIterator();
- }
-
- protected final ListIterator getXmppInputOutputFilterEndIterator() {
- return inputOutputFilters.listIterator(inputOutputFilters.size());
- }
-
- protected final synchronized List getFilterStats() {
- Collection filters;
- if (inputOutputFilters.isEmpty() && previousInputOutputFilters != null) {
- filters = previousInputOutputFilters;
- } else {
- filters = inputOutputFilters;
- }
-
- List 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 null
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 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);
- }
- });
- }
-}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/ConnectionStateEvent.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/ConnectionStateEvent.java
index 01e0faf22..d4883fa8d 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/ConnectionStateEvent.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/ConnectionStateEvent.java
@@ -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 currentStateVertex, GraphVertex 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;
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/ConnectionStateMachineListener.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/ConnectionStateMachineListener.java
index e8dcb02b1..704d35e0a 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/ConnectionStateMachineListener.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/ConnectionStateMachineListener.java
@@ -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);
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/LoginContext.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/LoginContext.java
index e0ac5ea14..9f58ce6d2 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/LoginContext.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/LoginContext.java
@@ -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;
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/NoOpState.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/NoOpState.java
new file mode 100644
index 000000000..e059bb097
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/NoOpState.java
@@ -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;
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java
new file mode 100644
index 000000000..f8749161d
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java
@@ -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 null
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.");
+ }
+ }
+
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java
index 261529f25..0ef98885c 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java
@@ -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 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> successors = new HashSet<>();
@@ -53,36 +51,36 @@ public abstract class StateDescriptor {
private final Set> 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 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);
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptorGraph.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptorGraph.java
index bc4fa7ca8..bbece8898 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptorGraph.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptorGraph.java
@@ -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, GraphVertex>> 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>> 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 convertToStateGraph(GraphVertex stateDescriptorVertex,
- AbstractXmppStateMachineConnection connection, Map> handledStateDescriptors) {
+ private static GraphVertex convertToStateGraph(GraphVertex stateDescriptorVertex,
+ ModularXmppClientToServerConnectionInternal connectionInternal, Map> handledStateDescriptors) {
StateDescriptor stateDescriptor = stateDescriptorVertex.getElement();
- GraphVertex stateVertex = handledStateDescriptors.get(stateDescriptor);
+ GraphVertex 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 successorStateDescriptorVertex : stateDescriptorVertex.getOutgoingEdges()) {
- GraphVertex successorStateVertex = convertToStateGraph(successorStateDescriptorVertex, connection, handledStateDescriptors);
+ GraphVertex 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 convertToStateGraph(GraphVertex initialStateDescriptor,
- AbstractXmppStateMachineConnection connection) {
- Map> handledStateDescriptors = new HashMap<>();
- GraphVertex initialState = convertToStateGraph(initialStateDescriptor, connection,
+ public static GraphVertex convertToStateGraph(GraphVertex initialStateDescriptor,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ Map> handledStateDescriptors = new HashMap<>();
+ GraphVertex initialState = convertToStateGraph(initialStateDescriptor, connectionInternal,
handledStateDescriptors);
return initialState;
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateMachineException.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateMachineException.java
index ed0f37fe1..818f506ca 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateMachineException.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateMachineException.java
@@ -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 walkedStateGraphPath;
- private final Map failedStates;
+ private final Map failedStates;
+
+ private final StateDescriptor deadEndState;
private static final long serialVersionUID = 1L;
- SmackStateGraphDeadEndException(List walkedStateGraphPath, Map failedStates) {
- this.walkedStateGraphPath = Collections.unmodifiableList(walkedStateGraphPath);
- this.failedStates = Collections.unmodifiableMap(failedStates);
+ private SmackStateGraphDeadEndException(String message, WalkStateGraphContext walkStateGraphContext, GraphVertex stateVertex) {
+ super(message);
+ this.walkedStateGraphPath = Collections.unmodifiableList(walkStateGraphContext.getWalk());
+ this.failedStates = Collections.unmodifiableMap(walkStateGraphContext.getFailedStates());
+
+ deadEndState = stateVertex.getElement().getStateDescriptor();
}
public List getWalkedStateGraph() {
return walkedStateGraphPath;
}
- public Map getFailedStates() {
+ public Map getFailedStates() {
return failedStates;
}
+
+ public StateDescriptor getDeadEndState() {
+ return deadEndState;
+ }
+
+ public static SmackStateGraphDeadEndException from(WalkStateGraphContext walkStateGraphContext, GraphVertex stateVertex) {
+ String message = stateVertex + " has no successor vertexes";
+
+ return new SmackStateGraphDeadEndException(message, walkStateGraphContext, stateVertex);
+ }
}
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java
new file mode 100644
index 000000000..053d35e77
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java
@@ -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 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)");
+ }
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/isr/InstantStreamResumptionModule.java b/smack-core/src/main/java/org/jivesoftware/smack/isr/InstantStreamResumptionModule.java
new file mode 100644
index 000000000..02219c20b
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/isr/InstantStreamResumptionModule.java
@@ -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 {
+
+ 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);
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/isr/InstantStreamResumptionModuleDescriptor.java b/smack-core/src/main/java/org/jivesoftware/smack/isr/InstantStreamResumptionModuleDescriptor.java
new file mode 100644
index 000000000..d26703eec
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/isr/InstantStreamResumptionModuleDescriptor.java
@@ -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> 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;
+ }
+
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/isr/package-info.java b/smack-core/src/main/java/org/jivesoftware/smack/isr/package-info.java
new file mode 100644
index 000000000..cf24d86ae
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/isr/package-info.java
@@ -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 XEP-0397: Instant Stream Resumption
+ */
+package org.jivesoftware.smack.isr;
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/ArrayBlockingQueueWithShutdown.java b/smack-core/src/main/java/org/jivesoftware/smack/util/ArrayBlockingQueueWithShutdown.java
index d70ed77c9..01938c43c 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/ArrayBlockingQueueWithShutdown.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/ArrayBlockingQueueWithShutdown.java
@@ -163,15 +163,20 @@ public class ArrayBlockingQueueWithShutdown extends AbstractQueue implemen
/**
* Start the queue. Newly created instances will be started automatically, thus this only needs
* to be called after {@link #shutdown()}.
+ *
+ * @return true
if the queues was shutdown before, false
if not.
*/
- public void start() {
+ public boolean start() {
+ boolean previousIsShutdown;
lock.lock();
try {
+ previousIsShutdown = isShutdown;
isShutdown = false;
}
finally {
lock.unlock();
}
+ return previousIsShutdown;
}
/**
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java
index 4170ce70d..eb76ef3be 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java
@@ -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 Set newSetWith(Collection extends T> collection) {
+ if (collection == null) {
+ return null;
+ }
+ return new HashSet<>(collection);
+ }
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/DNSUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/DNSUtil.java
index 3a2c6a316..7f98da029 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/DNSUtil.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/DNSUtil.java
@@ -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.
- *
- * As an example, a lookup for "example.com" may return "im.example.com:5269".
- *
- *
- * @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 resolveXMPPServiceDomain(DnsName domain, List 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.
- *
- * As an example, a lookup for "example.com" may return "im.example.com:5269".
- *
- *
- * @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 resolveXMPPServerDomain(DnsName domain, List 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 resolveDomain(DnsName domain, DomainType domainType,
- List failedAddresses, DnssecMode dnssecMode) {
- if (dnsResolver == null) {
- throw new IllegalStateException("No DNS Resolver active in Smack");
- }
-
- List addresses = new ArrayList();
-
- // Step one: Do SRV lookups
- DnsName srvDomain = DnsName.from(domainType.srvPrefix, domain);
-
- List 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 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 sortSRVRecords(List 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> buckets = new TreeMap>();
- for (SRVRecord r : records) {
- Integer priority = r.getPriority();
- List bucket = buckets.get(priority);
- // create the list of SRVRecords if it doesn't exist
- if (bucket == null) {
- bucket = new LinkedList();
- buckets.put(priority, bucket);
- }
- bucket.add(r);
- }
-
- List res = new ArrayList(records.size());
-
- for (Integer priority : buckets.keySet()) {
- List 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;
- }
-
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/Function.java b/smack-core/src/main/java/org/jivesoftware/smack/util/Function.java
index 760d3cf5d..2cc555208 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/Function.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/Function.java
@@ -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 apply(T t);
+ static Function identity() {
+ return t -> t;
+ }
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java b/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java
index ea58e469e..daa3241a8 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java
@@ -123,17 +123,26 @@ public class MultiMap implements TypedCloneable> {
}
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> valueListConsumer) {
boolean keyExisted;
List 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;
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java
index ff01779ed..b2958395b 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java
@@ -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 void appendTo(Collection collection, StringBuilder sb,
+ Consumer 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 void appendTo(Collection collection, String delimiter, StringBuilder sb,
+ Consumer appendFunction) {
+ for (Iterator 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');
+ }
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/dns/DNSResolver.java b/smack-core/src/main/java/org/jivesoftware/smack/util/dns/DNSResolver.java
index 1e7efe50c..41db7b073 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/dns/DNSResolver.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/dns/DNSResolver.java
@@ -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 lookupSRVRecords(DnsName name, List failedAddresses, DnssecMode dnssecMode) {
+ public final Collection lookupSrvRecords(DnsName name,
+ List lookupFailures, DnssecMode dnssecMode) {
checkIfDnssecRequestedAndSupported(dnssecMode);
- return lookupSRVRecords0(name, failedAddresses, dnssecMode);
+ return lookupSrvRecords0(name, lookupFailures, dnssecMode);
}
- protected abstract List lookupSRVRecords0(DnsName name, List failedAddresses, DnssecMode dnssecMode);
+ protected abstract Collection lookupSrvRecords0(DnsName name,
+ List lookupFailures, DnssecMode dnssecMode);
- public final HostAddress lookupHostAddress(DnsName name, int port, List failedAddresses, DnssecMode dnssecMode) {
+ public final List lookupHostAddress(DnsName name,
+ List lookupFailures, DnssecMode dnssecMode) {
checkIfDnssecRequestedAndSupported(dnssecMode);
- List 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 {
*
*
* @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 null
*/
- protected List lookupHostAddress0(DnsName name, List failedAddresses, DnssecMode dnssecMode) {
+ protected List lookupHostAddress0(DnsName name, List 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 hostAddresses) {
+ protected static boolean shouldContinue(CharSequence name, CharSequence hostname, List hostAddresses) {
if (hostAddresses == null) {
return true;
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/dns/HostAddress.java b/smack-core/src/main/java/org/jivesoftware/smack/util/dns/HostAddress.java
deleted file mode 100644
index 83300bbc6..000000000
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/dns/HostAddress.java
+++ /dev/null
@@ -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 exceptions = new LinkedHashMap<>();
- private final List 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 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 null
in case there host address is only numeric, i.e. an IP address.
- *
- * @return the fully qualified domain name or null
- */
- 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 getExceptions() {
- return Collections.unmodifiableMap(exceptions);
- }
-
- public List 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> iterator = exceptions.entrySet().iterator();
- while (iterator.hasNext()) {
- Entry 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();
- }
-}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/dns/SRVRecord.java b/smack-core/src/main/java/org/jivesoftware/smack/util/dns/SRVRecord.java
deleted file mode 100644
index e55dc08f1..000000000
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/dns/SRVRecord.java
+++ /dev/null
@@ -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 RFC 2782: A DNS RR for specifying the location of services (DNS
- * SRV)
- * @author Florian Schmaus
- *
- */
-public class SRVRecord extends HostAddress implements Comparable {
-
- 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 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;
- }
-}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpoint.java b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpoint.java
new file mode 100644
index 000000000..0fc31e85f
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpoint.java
@@ -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 {
+ 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 + ')';
+ }
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpointLookupFailure.java b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpointLookupFailure.java
new file mode 100644
index 000000000..73ce8a4b0
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionEndpointLookupFailure.java
@@ -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;
+ }
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionException.java b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionException.java
new file mode 100644
index 000000000..581b58a28
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/RemoteConnectionException.java
@@ -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 {
+
+ private final RemoteConnectionEndpoint.InetSocketAddressCoupling address;
+ private final Exception exception;
+
+ public RemoteConnectionException(RCE remoteConnectionEndpoint, InetAddress inetAddress,
+ Exception exception) {
+ this(new RemoteConnectionEndpoint.InetSocketAddressCoupling<>(remoteConnectionEndpoint, inetAddress), exception);
+ }
+
+ public RemoteConnectionException(RemoteConnectionEndpoint.InetSocketAddressCoupling address, Exception exception) {
+ this.address = address;
+ this.exception = exception;
+ }
+
+ public RemoteConnectionEndpoint.InetSocketAddressCoupling 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 RemoteConnectionException from(SARCE remoteConnectionEndpoint, Exception exception) {
+ return new RemoteConnectionException(remoteConnectionEndpoint, remoteConnectionEndpoint.getInetAddress(), exception);
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/rce/SingleAddressRemoteConnectionEndpoint.java b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/SingleAddressRemoteConnectionEndpoint.java
new file mode 100644
index 000000000..54946d793
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/SingleAddressRemoteConnectionEndpoint.java
@@ -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());
+ }
+}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/rce/package-info.java b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/package-info.java
new file mode 100644
index 000000000..ae0ef2035
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/rce/package-info.java
@@ -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;
diff --git a/smack-core/src/test/java/org/jivesoftware/smack/SmackExceptionTest.java b/smack-core/src/test/java/org/jivesoftware/smack/SmackExceptionTest.java
deleted file mode 100644
index 4403f7bf5..000000000
--- a/smack-core/src/test/java/org/jivesoftware/smack/SmackExceptionTest.java
+++ /dev/null
@@ -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 failedAddresses = new LinkedList();
-
- DnsName host = DnsName.from("foo.bar.example");
- InetAddress inetAddress = InetAddress.getByAddress(host.toString(), new byte[] { 0, 0, 0, 0 });
- List 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);
- }
-
-}
diff --git a/smack-core/src/test/java/org/jivesoftware/smack/util/DnsUtilTest.java b/smack-core/src/test/java/org/jivesoftware/smack/util/DnsUtilTest.java
index 626a3d4f4..eef18918f 100644
--- a/smack-core/src/test/java/org/jivesoftware/smack/util/DnsUtilTest.java
+++ b/smack-core/src/test/java/org/jivesoftware/smack/util/DnsUtilTest.java
@@ -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() {
diff --git a/smack-integration-test/build.gradle b/smack-integration-test/build.gradle
index f16e9b76c..f0b380cca 100644
--- a/smack-integration-test/build.gradle
+++ b/smack-integration-test/build.gradle
@@ -7,13 +7,7 @@ mainClassName = 'org.igniterealtime.smack.inttest.SmackIntegrationTestFramework'
applicationDefaultJvmArgs = ["-enableassertions"]
dependencies {
- compile project(':smack-java7')
- compile project(':smack-tcp')
- compile project(':smack-extensions')
- compile project(':smack-experimental')
- compile project(':smack-omemo')
- compile project(':smack-openpgp')
- compile project(':smack-debug')
+ api project(':smack-java8-full')
compile 'org.reflections:reflections:0.9.11'
compile 'eu.geekplace.javapinning:java-pinning-java7:1.1.0-alpha1'
compile group: 'commons-io', name: 'commons-io', version: "$commonsIoVersion"
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java
index dd1a7b194..57b85f722 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@ public abstract class AbstractSmackIntTest {
protected final Configuration sinttestConfiguration;
- protected AbstractSmackIntTest(SmackIntegrationTestEnvironment> environment) {
+ protected AbstractSmackIntTest(SmackIntegrationTestEnvironment environment) {
this.testRunId = environment.testRunId;
this.sinttestConfiguration = environment.configuration;
this.timeout = environment.configuration.replyTimeout;
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java
index 5f32f9268..befb94c08 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2018 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest
protected final List connections;
- public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
this.connection = this.conOne = environment.conOne;
this.conTwo = environment.conTwo;
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java
index cd0b89b48..25435627f 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ import org.jxmpp.jid.DomainBareJid;
public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmackIntTest {
- private final SmackIntegrationTestEnvironment> environment;
+ private final SmackIntegrationTestEnvironment environment;
/**
* The configuration
@@ -40,7 +40,7 @@ public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmack
protected final DomainBareJid service;
- protected AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ protected AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
this.environment = environment;
this.configuration = environment.configuration;
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackSpecificLowLevelIntegrationTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackSpecificLowLevelIntegrationTest.java
index 6c9bbe219..492f04c7e 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackSpecificLowLevelIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackSpecificLowLevelIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018-2019 Florian Schmaus
+ * Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,19 +28,22 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException;
public abstract class AbstractSmackSpecificLowLevelIntegrationTest
extends AbstractSmackLowLevelIntegrationTest {
- private final SmackIntegrationTestEnvironment> environment;
+ private final SmackIntegrationTestEnvironment environment;
protected final Class connectionClass;
private final XmppConnectionDescriptor> connectionDescriptor;
- public AbstractSmackSpecificLowLevelIntegrationTest(SmackIntegrationTestEnvironment> environment,
+ public AbstractSmackSpecificLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment,
Class connectionClass) {
super(environment);
this.environment = environment;
this.connectionClass = connectionClass;
connectionDescriptor = environment.connectionManager.getConnectionDescriptorFor(connectionClass);
+ if (connectionDescriptor == null) {
+ throw new IllegalStateException("No connection descriptor for " + connectionClass + " known");
+ }
}
public Class getConnectionClass() {
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java
index a6e3d6eda..9b308df3b 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2018 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,7 +33,9 @@ import javax.net.ssl.SSLContext;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
+import org.jivesoftware.smack.util.Function;
import org.jivesoftware.smack.util.Objects;
+import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.debugger.EnhancedDebugger;
@@ -94,80 +96,90 @@ public final class Configuration {
public final Set disabledTests;
+ public final String defaultConnectionNickname;
+
+ public final Set enabledConnections;
+
+ public final Set disabledConnections;
+
public final Set testPackages;
public final ConnectionConfigurationBuilderApplier configurationApplier;
- private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout,
- Debugger debugger, String accountOneUsername, String accountOnePassword, String accountTwoUsername,
- String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set enabledTests, Set disabledTests,
- Set testPackages, String adminAccountUsername, String adminAccountPassword)
- throws KeyManagementException, NoSuchAlgorithmException {
- this.service = Objects.requireNonNull(service,
+ public final boolean verbose;
+
+ private Configuration(Configuration.Builder builder) throws KeyManagementException, NoSuchAlgorithmException {
+ service = Objects.requireNonNull(builder.service,
"'service' must be set. Either via 'properties' files or via system property 'sinttest.service'.");
- this.serviceTlsPin = serviceTlsPin;
+ serviceTlsPin = builder.serviceTlsPin;
if (serviceTlsPin != null) {
tlsContext = Java7Pinning.forPin(serviceTlsPin);
} else {
tlsContext = null;
}
- this.securityMode = securityMode;
- if (replyTimeout > 0) {
- this.replyTimeout = replyTimeout;
+ securityMode = builder.securityMode;
+ if (builder.replyTimeout > 0) {
+ replyTimeout = builder.replyTimeout;
} else {
- this.replyTimeout = 60000;
+ replyTimeout = 60000;
}
- this.debugger = debugger;
- if (StringUtils.isNotEmpty(adminAccountUsername, adminAccountPassword)) {
+ debugger = builder.debugger;
+ if (StringUtils.isNotEmpty(builder.adminAccountUsername, builder.adminAccountPassword)) {
accountRegistration = AccountRegistration.serviceAdministration;
}
- else if (StringUtils.isNotEmpty(accountOneUsername, accountOnePassword, accountTwoUsername, accountTwoPassword,
- accountThreeUsername, accountThreePassword)) {
+ else if (StringUtils.isNotEmpty(builder.accountOneUsername, builder.accountOnePassword,
+ builder.accountTwoUsername, builder.accountTwoPassword, builder.accountThreeUsername,
+ builder.accountThreePassword)) {
accountRegistration = AccountRegistration.disabled;
}
else {
accountRegistration = AccountRegistration.inBandRegistration;
}
- this.adminAccountUsername = adminAccountUsername;
- this.adminAccountPassword = adminAccountPassword;
+ this.adminAccountUsername = builder.adminAccountUsername;
+ this.adminAccountPassword = builder.adminAccountPassword;
- boolean accountOnePasswordSet = StringUtils.isNotEmpty(accountOnePassword);
- if (accountOnePasswordSet != StringUtils.isNotEmpty(accountTwoPassword) ||
- accountOnePasswordSet != StringUtils.isNotEmpty(accountThreePassword)) {
+ boolean accountOnePasswordSet = StringUtils.isNotEmpty(builder.accountOnePassword);
+ if (accountOnePasswordSet != StringUtils.isNotEmpty(builder.accountTwoPassword) ||
+ accountOnePasswordSet != StringUtils.isNotEmpty(builder.accountThreePassword)) {
// Ensure the invariant that either all main accounts have a password set, or none.
throw new IllegalArgumentException();
}
- this.accountOneUsername = accountOneUsername;
- this.accountOnePassword = accountOnePassword;
- this.accountTwoUsername = accountTwoUsername;
- this.accountTwoPassword = accountTwoPassword;
- this.accountThreeUsername = accountThreeUsername;
- this.accountThreePassword = accountThreePassword;
- this.enabledTests = enabledTests;
- this.disabledTests = disabledTests;
- this.testPackages = testPackages;
+ this.accountOneUsername = builder.accountOneUsername;
+ this.accountOnePassword = builder.accountOnePassword;
+ this.accountTwoUsername = builder.accountTwoUsername;
+ this.accountTwoPassword = builder.accountTwoPassword;
+ this.accountThreeUsername = builder.accountThreeUsername;
+ this.accountThreePassword = builder.accountThreePassword;
+ this.enabledTests = builder.enabledTests;
+ this.disabledTests = builder.disabledTests;
+ this.defaultConnectionNickname = builder.defaultConnectionNickname;
+ this.enabledConnections = builder.enabledConnections;
+ this.disabledConnections = builder.disabledConnections;
+ this.testPackages = builder.testPackages;
- this.configurationApplier = builder -> {
+ this.configurationApplier = b -> {
if (tlsContext != null) {
- builder.setCustomSSLContext(tlsContext);
+ b.setCustomSSLContext(tlsContext);
}
- builder.setSecurityMode(securityMode);
- builder.setXmppDomain(service);
+ b.setSecurityMode(securityMode);
+ b.setXmppDomain(service);
switch (debugger) {
case enhanced:
- builder.setDebuggerFactory(EnhancedDebugger.Factory.INSTANCE);
+ b.setDebuggerFactory(EnhancedDebugger.Factory.INSTANCE);
break;
case console:
- builder.setDebuggerFactory(ConsoleDebugger.Factory.INSTANCE);
+ b.setDebuggerFactory(ConsoleDebugger.Factory.INSTANCE);
break;
case none:
// Nothing to do :).
break;
}
};
+
+ this.verbose = builder.verbose;
}
public boolean isAccountRegistrationPossible() {
@@ -210,8 +222,16 @@ public final class Configuration {
private Set disabledTests;
+ private String defaultConnectionNickname;
+
+ private Set enabledConnections;
+
+ private Set disabledConnections;
+
private Set testPackages;
+ private boolean verbose;
+
private Builder() {
}
@@ -324,6 +344,21 @@ public final class Configuration {
return this;
}
+ public Builder setDefaultConnection(String defaultConnectionNickname) {
+ this.defaultConnectionNickname = defaultConnectionNickname;
+ return this;
+ }
+
+ public Builder setEnabledConnections(String enabledConnectionsString) {
+ enabledConnections = split(enabledConnectionsString);
+ return this;
+ }
+
+ public Builder setDisabledConnections(String disabledConnectionsString) {
+ disabledConnections = split(disabledConnectionsString);
+ return this;
+ }
+
public Builder addTestPackages(String testPackagesString) {
if (testPackagesString != null) {
String[] testPackagesArray = testPackagesString.split(",");
@@ -350,10 +385,22 @@ public final class Configuration {
return this;
}
+ public Builder setVerbose(boolean verbose) {
+ this.verbose = verbose;
+ return this;
+ }
+
+ public Builder setVerbose(String verboseBooleanString) {
+ if (verboseBooleanString == null) {
+ return this;
+ }
+
+ boolean verbose = ParserUtils.parseXmlBoolean(verboseBooleanString);
+ return setVerbose(verbose);
+ }
+
public Configuration build() throws KeyManagementException, NoSuchAlgorithmException {
- return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debugger, accountOneUsername,
- accountOnePassword, accountTwoUsername, accountTwoPassword, accountThreeUsername, accountThreePassword, enabledTests, disabledTests,
- testPackages, adminAccountUsername, adminAccountPassword);
+ return new Configuration(this);
}
}
@@ -414,10 +461,15 @@ public final class Configuration {
builder.setDebugger(properties.getProperty("debugger"));
builder.setEnabledTests(properties.getProperty("enabledTests"));
builder.setDisabledTests(properties.getProperty("disabledTests"));
+ builder.setDefaultConnection(properties.getProperty("defaultConnection"));
+ builder.setEnabledConnections(properties.getProperty("enabledConnections"));
+ builder.setDisabledConnections(properties.getProperty("disabledConnections"));
builder.addTestPackages(properties.getProperty("testPackages"));
builder.addTestPackages(testPackages);
+ builder.setVerbose(properties.getProperty("verbose"));
+
return builder.build();
}
@@ -437,23 +489,36 @@ public final class Configuration {
return null;
}
- private static Set getTestSetFrom(String string) {
- if (string == null) {
+ private static Set split(String input) {
+ return split(input, Function.identity());
+ }
+
+ private static Set split(String input, Function transformer) {
+ if (input == null) {
return null;
}
- String[] stringArray = string.split(",");
- Set res = new HashSet<>(stringArray.length);
- for (String s : stringArray) {
- res.add(getFullTestStringFrom(s));
+
+ String[] inputArray = input.split(",");
+ Set res = new HashSet<>(inputArray.length);
+ for (String s : inputArray) {
+ s = transformer.apply(s);
+ boolean newElement = res.add(s);
+ if (!newElement) {
+ throw new IllegalArgumentException("The argument '" + s + "' was already provided.");
+ }
}
+
return res;
}
- private static String getFullTestStringFrom(String string) {
- string = string.trim();
- if (string.startsWith("smackx.") || string.startsWith("smack.")) {
- string = "org.jivesoftware." + string;
- }
- return string;
+ private static Set getTestSetFrom(String input) {
+ return split(input, s -> {
+ s = s.trim();
+ if (s.startsWith("smackx.") || s.startsWith("smack.")) {
+ s = "org.jivesoftware." + s;
+ }
+ return s;
+ });
}
+
}
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestEnvironment.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestEnvironment.java
index 58dae7fb9..938c2f52f 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestEnvironment.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestEnvironment.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,18 +18,18 @@ package org.igniterealtime.smack.inttest;
import org.jivesoftware.smack.AbstractXMPPConnection;
-public class SmackIntegrationTestEnvironment {
+public class SmackIntegrationTestEnvironment {
- public final C conOne, conTwo, conThree;
+ public final AbstractXMPPConnection conOne, conTwo, conThree;
public final String testRunId;
public final Configuration configuration;
- public final XmppConnectionManager connectionManager;
+ public final XmppConnectionManager connectionManager;
- SmackIntegrationTestEnvironment(C conOne, C conTwo, C conThree, String testRunId,
- Configuration configuration, XmppConnectionManager connectionManager) {
+ SmackIntegrationTestEnvironment(AbstractXMPPConnection conOne, AbstractXMPPConnection conTwo, AbstractXMPPConnection conThree, String testRunId,
+ Configuration configuration, XmppConnectionManager connectionManager) {
this.conOne = conOne;
this.conTwo = conTwo;
this.conThree = conThree;
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java
index 36e9afda2..cc1cf1944 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java
@@ -23,6 +23,7 @@ import static org.reflections.ReflectionUtils.withParametersCount;
import static org.reflections.ReflectionUtils.withReturnType;
import java.io.IOException;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -43,7 +44,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -53,7 +53,6 @@ import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.XMPPException;
-import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.TLSUtils;
@@ -70,7 +69,7 @@ import org.reflections.scanners.MethodParameterScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
-public class SmackIntegrationTestFramework {
+public class SmackIntegrationTestFramework {
static {
TLSUtils.setDefaultTrustStoreTypeToJksIfRequired();
@@ -80,14 +79,12 @@ public class SmackIntegrationTestFramework {
public static boolean SINTTEST_UNIT_TEST = false;
- private final Class defaultConnectionClass;
-
protected final Configuration config;
protected TestRunResult testRunResult;
- private SmackIntegrationTestEnvironment environment;
- protected XmppConnectionManager connectionManager;
+ private SmackIntegrationTestEnvironment environment;
+ protected XmppConnectionManager connectionManager;
public enum TestType {
Normal,
@@ -100,7 +97,7 @@ public class SmackIntegrationTestFramework {
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Configuration config = Configuration.newConfiguration(args);
- SmackIntegrationTestFramework sinttest = new SmackIntegrationTestFramework<>(config, XMPPTCPConnection.class);
+ SmackIntegrationTestFramework sinttest = new SmackIntegrationTestFramework(config);
TestRunResult testRunResult = sinttest.run();
for (Entry, Throwable> entry : testRunResult.impossibleTestClasses.entrySet()) {
@@ -116,11 +113,9 @@ public class SmackIntegrationTestFramework {
}
final int successfulTests = testRunResult.successfulIntegrationTests.size();
final int failedTests = testRunResult.failedIntegrationTests.size();
- final int totalIntegrationTests = successfulTests + failedTests;
final int availableTests = testRunResult.getNumberOfAvailableTests();
- final int possibleTests = testRunResult.getNumberOfPossibleTests();
- LOGGER.info("SmackIntegrationTestFramework[" + testRunResult.testRunId + ']' + ": Finished ["
- + successfulTests + '/' + totalIntegrationTests + "] (" + possibleTests + " test methods of " + availableTests + " where possible)");
+ LOGGER.info("SmackIntegrationTestFramework[" + testRunResult.testRunId + ']' + " finished: "
+ + successfulTests + '/' + availableTests + " [" + failedTests + " failed]");
final int exitStatus;
if (failedTests > 0) {
@@ -146,12 +141,8 @@ public class SmackIntegrationTestFramework {
System.exit(exitStatus);
}
- public SmackIntegrationTestFramework(Configuration configuration, Class defaultConnectionClass)
- throws KeyManagementException, InstantiationException, IllegalAccessException, IllegalArgumentException,
- InvocationTargetException, NoSuchAlgorithmException, SmackException, IOException, XMPPException,
- InterruptedException {
+ public SmackIntegrationTestFramework(Configuration configuration) {
this.config = configuration;
- this.defaultConnectionClass = defaultConnectionClass;
}
public synchronized TestRunResult run()
@@ -160,7 +151,7 @@ public class SmackIntegrationTestFramework {
testRunResult = new TestRunResult();
// Create a connection manager *after* we created the testRunId (in testRunResult).
- this.connectionManager = new XmppConnectionManager<>(this, defaultConnectionClass);
+ this.connectionManager = new XmppConnectionManager(this);
LOGGER.info("SmackIntegrationTestFramework [" + testRunResult.testRunId + ']' + ": Starting");
if (config.debugger != Configuration.Debugger.none) {
@@ -226,10 +217,13 @@ public class SmackIntegrationTestFramework {
return testRunResult;
}
- @SuppressWarnings({"unchecked", "Finally"})
+ @SuppressWarnings({"Finally"})
private void runTests(Set> classes)
throws InterruptedException, InstantiationException, IllegalAccessException,
IllegalArgumentException, SmackException, IOException, XMPPException {
+ List tests = new ArrayList<>(classes.size());
+ int numberOfAvailableTests = 0;
+
for (Class extends AbstractSmackIntTest> testClass : classes) {
final String testClassName = testClass.getName();
@@ -260,17 +254,19 @@ public class SmackIntegrationTestFramework {
// - https://discuss.gradle.org/t/main-vs-test-compile-vs-runtime-classpaths-in-eclipse-once-and-for-all-how/17403
// - https://bugs.eclipse.org/bugs/show_bug.cgi?id=376616 (Scope of dependencies has no effect on Eclipse compilation)
if (!SINTTEST_UNIT_TEST && testClassName.startsWith("org.igniterealtime.smack.inttest.unittest")) {
- LOGGER.finer("Skipping integration test '" + testClassName + "' from src/test classpath");
+ LOGGER.warning("Skipping integration test '" + testClassName + "' from src/test classpath (should not be in classpath)");
continue;
}
if (config.enabledTests != null && !isInSet(testClass, config.enabledTests)) {
- LOGGER.info("Skipping test class " + testClassName + " because it is not enabled");
+ DisabledTestClass disabledTestClass = new DisabledTestClass(testClass, "Skipping test class " + testClassName + " because it is not enabled");
+ testRunResult.disabledTestClasses.add(disabledTestClass);
continue;
}
if (isInSet(testClass, config.disabledTests)) {
- LOGGER.info("Skipping test class " + testClassName + " because it is disalbed");
+ DisabledTestClass disabledTestClass = new DisabledTestClass(testClass, "Skipping test class " + testClassName + " because it is disalbed");
+ testRunResult.disabledTestClasses.add(disabledTestClass);
continue;
}
@@ -301,8 +297,6 @@ public class SmackIntegrationTestFramework {
continue;
}
- testRunResult.numberOfAvailableTestMethods.addAndGet(smackIntegrationTestMethods.size());
-
final AbstractSmackIntTest test;
try {
test = cons.newInstance(environment);
@@ -360,12 +354,14 @@ public class SmackIntegrationTestFramework {
final String methodName = method.getName();
if (config.enabledTests != null && !(config.enabledTests.contains(methodName)
|| isInSet(testClass, config.enabledTests))) {
- LOGGER.fine("Skipping test method " + methodName + " because it is not enabled");
+ DisabledTest disabledTest = new DisabledTest(method, "Skipping test method " + methodName + " because it is not enabled");
+ testRunResult.disabledTests.add(disabledTest);
it.remove();
continue;
}
if (config.disabledTests != null && config.disabledTests.contains(methodName)) {
- LOGGER.info("Skipping test method " + methodName + " because it is disabled");
+ DisabledTest disabledTest = new DisabledTest(method, "Skipping test method " + methodName + " because it is disabled");
+ testRunResult.disabledTests.add(disabledTest);
it.remove();
continue;
}
@@ -376,106 +372,77 @@ public class SmackIntegrationTestFramework {
continue;
}
- final int detectedTestMethodsCount = smackIntegrationTestMethods.size();
- testRunResult.numberOfPossibleTestMethods.addAndGet(detectedTestMethodsCount);
+ List concreteTests = new ArrayList<>(smackIntegrationTestMethods.size());
- try {
- // Run the @BeforeClass methods (if any)
- Set beforeClassMethods = getAllMethods(testClass,
- withAnnotation(BeforeClass.class), withReturnType(Void.TYPE),
- withParametersCount(0), withModifier(Modifier.PUBLIC
- ));
-
- // See if there are any methods that have the @BeforeClassAnnotation but a wrong signature
- Set allBeforeClassMethods = getAllMethods(testClass, withAnnotation(BeforeClass.class));
- allBeforeClassMethods.removeAll(beforeClassMethods);
- if (!allBeforeClassMethods.isEmpty()) {
- throw new IllegalArgumentException("@BeforeClass methods with wrong signature found");
+ for (Method testMethod : smackIntegrationTestMethods) {
+ switch (testType) {
+ case Normal: {
+ ConcreteTest.Executor concreteTestExecutor = () -> testMethod.invoke(test);
+ ConcreteTest concreteTest = new ConcreteTest(testType, testMethod, concreteTestExecutor);
+ concreteTests.add(concreteTest);
}
-
- if (beforeClassMethods.size() == 1) {
- Method beforeClassMethod = beforeClassMethods.iterator().next();
- LOGGER.info("Executing @BeforeClass method of " + testClass);
- try {
- beforeClassMethod.invoke(test);
- }
- catch (InvocationTargetException | IllegalAccessException e) {
- LOGGER.log(Level.SEVERE, "Exception executing @BeforeClass method", e);
- }
- catch (IllegalArgumentException e) {
- throw new AssertionError(e);
- }
- }
- else if (beforeClassMethods.size() > 1) {
- throw new IllegalArgumentException("Only one @BeforeClass method allowed");
- }
-
- for (Method testMethod : smackIntegrationTestMethods) {
- List concreteTests = null;
+ break;
+ case LowLevel:
+ case SpecificLowLevel:
+ LowLevelTestMethod lowLevelTestMethod = new LowLevelTestMethod(testMethod);
switch (testType) {
- case Normal: {
- ConcreteTest.Executor concreteTestExecutor = () -> testMethod.invoke(test);
- ConcreteTest concreteTest = new ConcreteTest(testType, testMethod, concreteTestExecutor);
- concreteTests = Collections.singletonList(concreteTest);
- }
- break;
case LowLevel:
- case SpecificLowLevel:
- LowLevelTestMethod lowLevelTestMethod = new LowLevelTestMethod(testMethod);
- switch (testType) {
- case LowLevel:
- concreteTests = invokeLowLevel(lowLevelTestMethod, (AbstractSmackLowLevelIntegrationTest) test);
- break;
- case SpecificLowLevel: {
- ConcreteTest.Executor concreteTestExecutor = () -> invokeSpecificLowLevel(
- lowLevelTestMethod, (AbstractSmackSpecificLowLevelIntegrationTest>) test);
- ConcreteTest concreteTest = new ConcreteTest(testType, testMethod, concreteTestExecutor);
- concreteTests = Collections.singletonList(concreteTest);
- break;
- }
- default:
- throw new AssertionError();
- }
+ List concreteLowLevelTests = invokeLowLevel(lowLevelTestMethod, (AbstractSmackLowLevelIntegrationTest) test);
+ concreteTests.addAll(concreteLowLevelTests);
+ break;
+ case SpecificLowLevel: {
+ ConcreteTest.Executor concreteTestExecutor = () -> invokeSpecificLowLevel(
+ lowLevelTestMethod, (AbstractSmackSpecificLowLevelIntegrationTest>) test);
+ ConcreteTest concreteTest = new ConcreteTest(testType, testMethod, concreteTestExecutor);
+ concreteTests.add(concreteTest);
break;
}
-
- for (ConcreteTest concreteTest : concreteTests) {
- runConcreteTest(concreteTest);
+ default:
+ throw new AssertionError();
}
+ break;
}
}
- finally {
- // Run the @AfterClass method (if any)
- Set afterClassMethods = getAllMethods(testClass,
- withAnnotation(AfterClass.class), withReturnType(Void.TYPE),
- withParametersCount(0), withModifier(Modifier.PUBLIC
- ));
- // See if there are any methods that have the @AfterClassAnnotation but a wrong signature
- Set allAfterClassMethods = getAllMethods(testClass, withAnnotation(AfterClass.class));
- allAfterClassMethods.removeAll(afterClassMethods);
- if (!allAfterClassMethods.isEmpty()) {
- throw new IllegalArgumentException("@AfterClass methods with wrong signature found");
- }
+ // Instantiate the prepared test early as this will check the before and after class annotations.
+ PreparedTest preparedTest = new PreparedTest(test, concreteTests);
+ tests.add(preparedTest);
- if (afterClassMethods.size() == 1) {
- Method afterClassMethod = afterClassMethods.iterator().next();
- LOGGER.info("Executing @AfterClass method of " + testClass);
- try {
- afterClassMethod.invoke(test);
- }
- catch (InvocationTargetException | IllegalAccessException e) {
- LOGGER.log(Level.SEVERE, "Exception executing @AfterClass method", e);
- }
- catch (IllegalArgumentException e) {
- throw new AssertionError(e);
- }
- }
- else if (afterClassMethods.size() > 1) {
- throw new IllegalArgumentException("Only one @AfterClass method allowed");
- }
- }
+ numberOfAvailableTests += concreteTests.size();
}
+
+ // Print status information.
+ StringBuilder sb = new StringBuilder(1024);
+ sb.append("Smack Integration Test Framework\n");
+ sb.append("################################\n");
+ if (config.verbose) {
+ sb.append('\n');
+ if (!testRunResult.disabledTestClasses.isEmpty()) {
+ sb.append("The following test classes are disabled:\n");
+ for (DisabledTestClass disabledTestClass : testRunResult.disabledTestClasses) {
+ disabledTestClass.appendTo(sb).append('\n');
+ }
+ }
+ if (!testRunResult.disabledTests.isEmpty()) {
+ sb.append("The following tests are disabled:\n");
+ for (DisabledTest disabledTest : testRunResult.disabledTests) {
+ disabledTest.appendTo(sb).append('\n');
+ }
+ }
+ sb.append('\n');
+ }
+ sb.append("Available tests: ").append(numberOfAvailableTests)
+ .append("(#-classes: ").append(testRunResult.disabledTestClasses.size())
+ .append(", #-tests: ").append(testRunResult.disabledTests.size())
+ .append(")\n");
+ LOGGER.info(sb.toString());
+
+ for (PreparedTest test : tests) {
+ test.run();
+ }
+
+ // Assert that all tests in the 'tests' list produced a result.
+ assert numberOfAvailableTests == testRunResult.getNumberOfAvailableTests();
}
private void runConcreteTest(ConcreteTest concreteTest)
@@ -524,17 +491,35 @@ public class SmackIntegrationTestFramework {
}
private List invokeLowLevel(LowLevelTestMethod lowLevelTestMethod, AbstractSmackLowLevelIntegrationTest test) {
- Set> connectionClasses;
+ Collection extends XmppConnectionDescriptor, ?, ?>> connectionDescriptors;
if (lowLevelTestMethod.smackIntegrationTestAnnotation.onlyDefaultConnectionType()) {
- Class extends AbstractXMPPConnection> defaultConnectionClass = connectionManager.getDefaultConnectionClass();
- connectionClasses = Collections.singleton(defaultConnectionClass);
+ XmppConnectionDescriptor, ?, ?> defaultConnectionDescriptor = connectionManager.getDefaultConnectionDescriptor();
+ connectionDescriptors = Collections.singleton(defaultConnectionDescriptor);
} else {
- connectionClasses = connectionManager.getConnectionClasses();
+ connectionDescriptors = connectionManager.getConnectionDescriptors();
}
- List resultingConcreteTests = new ArrayList<>(connectionClasses.size());
+ List resultingConcreteTests = new ArrayList<>(connectionDescriptors.size());
+
+ for (XmppConnectionDescriptor, ?, ?> connectionDescriptor : connectionDescriptors) {
+ String connectionNick = connectionDescriptor.getNickname();
+
+ if (config.enabledConnections != null && !config.enabledConnections.contains(connectionNick)) {
+ DisabledTest disabledTest = new DisabledTest(lowLevelTestMethod.testMethod, "Not creating test for " + lowLevelTestMethod + " with connection '" + connectionNick
+ + "', as this connection type is not enabled");
+ testRunResult.disabledTests.add(disabledTest);
+ continue;
+ }
+
+ if (config.disabledConnections != null && config.disabledConnections.contains(connectionNick)) {
+ DisabledTest disabledTest = new DisabledTest(lowLevelTestMethod.testMethod, "Not creating test for " + lowLevelTestMethod + " with connection '" + connectionNick
+ + ", as this connection type is disabled");
+ testRunResult.disabledTests.add(disabledTest);
+ continue;
+ }
+
+ Class extends AbstractXMPPConnection> connectionClass = connectionDescriptor.getConnectionClass();
- for (Class extends AbstractXMPPConnection> connectionClass : connectionClasses) {
ConcreteTest.Executor executor = () -> lowLevelTestMethod.invoke(test, connectionClass);
ConcreteTest concreteTest = new ConcreteTest(TestType.LowLevel, lowLevelTestMethod.testMethod, executor, connectionClass.getSimpleName());
resultingConcreteTests.add(concreteTest);
@@ -543,7 +528,7 @@ public class SmackIntegrationTestFramework {
return resultingConcreteTests;
}
- private void invokeSpecificLowLevel(LowLevelTestMethod testMethod,
+ private static void invokeSpecificLowLevel(LowLevelTestMethod testMethod,
AbstractSmackSpecificLowLevelIntegrationTest test)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InterruptedException,
SmackException, IOException, XMPPException {
@@ -554,7 +539,7 @@ public class SmackIntegrationTestFramework {
testMethod.invoke(test, connectionClass);
}
- protected SmackIntegrationTestEnvironment prepareEnvironment() throws SmackException,
+ protected SmackIntegrationTestEnvironment prepareEnvironment() throws SmackException,
IOException, XMPPException, InterruptedException, KeyManagementException,
NoSuchAlgorithmException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return connectionManager.prepareEnvironment();
@@ -576,9 +561,6 @@ public class SmackIntegrationTestFramework {
private static Exception throwFatalException(Throwable e) throws Error, NoResponseException,
InterruptedException {
- if (e instanceof NoResponseException) {
- throw (NoResponseException) e;
- }
if (e instanceof InterruptedException) {
throw (InterruptedException) e;
}
@@ -612,9 +594,13 @@ public class SmackIntegrationTestFramework {
private final List successfulIntegrationTests = Collections.synchronizedList(new LinkedList());
private final List failedIntegrationTests = Collections.synchronizedList(new LinkedList());
private final List impossibleIntegrationTests = Collections.synchronizedList(new LinkedList());
+
+ // TODO: Ideally three would only be a list of disabledTests, but since we do not process a disabled test class
+ // any further, we can not determine the concrete disabled tests.
+ private final List disabledTestClasses = Collections.synchronizedList(new ArrayList<>());
+ private final List disabledTests = Collections.synchronizedList(new ArrayList<>());
+
private final Map, Throwable> impossibleTestClasses = new HashMap<>();
- private final AtomicInteger numberOfAvailableTestMethods = new AtomicInteger();
- private final AtomicInteger numberOfPossibleTestMethods = new AtomicInteger();
TestRunResult() {
}
@@ -624,11 +610,7 @@ public class SmackIntegrationTestFramework {
}
public int getNumberOfAvailableTests() {
- return numberOfAvailableTestMethods.get();
- }
-
- public int getNumberOfPossibleTests() {
- return numberOfPossibleTestMethods.get();
+ return successfulIntegrationTests.size() + failedIntegrationTests.size() + impossibleIntegrationTests.size();
}
public List getSuccessfulTests() {
@@ -648,6 +630,77 @@ public class SmackIntegrationTestFramework {
}
}
+ final class PreparedTest {
+ private final AbstractSmackIntTest test;
+ private final List concreteTests;
+
+ private final Method beforeClassMethod;
+ private final Method afterClassMethod;
+
+ private PreparedTest(AbstractSmackIntTest test, List concreteTests) {
+ this.test = test;
+ this.concreteTests = concreteTests;
+ Class extends AbstractSmackIntTest> testClass = test.getClass();
+
+ beforeClassMethod = getSinttestSpecialMethod(testClass, BeforeClass.class);
+ afterClassMethod = getSinttestSpecialMethod(testClass, AfterClass.class);
+ }
+
+ public void run() throws InterruptedException, XMPPException, IOException, SmackException {
+ try {
+ // Run the @BeforeClass methods (if any)
+ executeSinttestSpecialMethod(beforeClassMethod);
+
+ for (ConcreteTest concreteTest : concreteTests) {
+ runConcreteTest(concreteTest);
+ }
+ }
+ finally {
+ executeSinttestSpecialMethod(afterClassMethod);
+ }
+ }
+
+ private void executeSinttestSpecialMethod(Method method) {
+ if (method == null) {
+ return;
+ }
+
+ try {
+ method.invoke(test);
+ }
+ catch (InvocationTargetException | IllegalAccessException e) {
+ LOGGER.log(Level.SEVERE, "Exception executing " + method, e);
+ }
+ catch (IllegalArgumentException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Method getSinttestSpecialMethod(Class extends AbstractSmackIntTest> testClass, Class extends Annotation> annotation) {
+ Set specialClassMethods = getAllMethods(testClass,
+ withAnnotation(annotation), withReturnType(Void.TYPE),
+ withParametersCount(0), withModifier(Modifier.PUBLIC
+ ));
+
+ // See if there are any methods that have a special but a wrong signature
+ Set allSpecialClassMethods = getAllMethods(testClass, withAnnotation(annotation));
+ allSpecialClassMethods.removeAll(specialClassMethods);
+ if (!allSpecialClassMethods.isEmpty()) {
+ throw new IllegalArgumentException(annotation + " methods with wrong signature found");
+ }
+
+ if (specialClassMethods.size() == 1) {
+ return specialClassMethods.iterator().next();
+ }
+ else if (specialClassMethods.size() > 1) {
+ throw new IllegalArgumentException("Only one @BeforeClass method allowed");
+ }
+
+ return null;
+ }
+
static final class ConcreteTest {
private final TestType testType;
private final Method method;
@@ -705,6 +758,50 @@ public class SmackIntegrationTestFramework {
}
}
+ public static final class DisabledTestClass {
+ private final Class extends AbstractSmackIntTest> testClass;
+ private final String reason;
+
+ private DisabledTestClass(Class extends AbstractSmackIntTest> testClass, String reason) {
+ this.testClass = testClass;
+ this.reason = reason;
+ }
+
+ public Class extends AbstractSmackIntTest> getTestClass() {
+ return testClass;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+
+ public StringBuilder appendTo(StringBuilder sb) {
+ return sb.append("Disabled ").append(testClass).append(" because ").append(reason);
+ }
+ }
+
+ public static final class DisabledTest {
+ private final Method method;
+ private final String reason;
+
+ private DisabledTest(Method method, String reason) {
+ this.method = method;
+ this.reason = reason;
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+
+ public StringBuilder appendTo(StringBuilder sb) {
+ return sb.append("Disabled ").append(method).append(" because ").append(reason);
+ }
+ }
+
private final class LowLevelTestMethod {
private final Method testMethod;
private final SmackIntegrationTest smackIntegrationTestAnnotation;
@@ -718,6 +815,7 @@ public class SmackIntegrationTestFramework {
parameterListOfConnections = testMethodParametersIsListOfConnections(testMethod);
}
+ // TODO: The second parameter should probably be a connection descriptor?
private void invoke(AbstractSmackLowLevelIntegrationTest test,
Class extends AbstractXMPPConnection> connectionClass)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
@@ -746,6 +844,11 @@ public class SmackIntegrationTestFramework {
testMethod.invoke(test, connectionsArray);
}
}
+
+ @Override
+ public String toString() {
+ return testMethod.toString();
+ }
}
private static boolean testMethodParametersIsListOfConnections(Method testMethod) {
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionDescriptor.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionDescriptor.java
index 29a24aecc..6eaec61e0 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionDescriptor.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionDescriptor.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018-2019 Florian Schmaus
+ * Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,8 +29,13 @@ import java.util.List;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.util.Consumer;
-public class XmppConnectionDescriptor> {
+public final class XmppConnectionDescriptor<
+ C extends AbstractXMPPConnection,
+ CC extends ConnectionConfiguration,
+ CCB extends ConnectionConfiguration.Builder, CC>
+> {
private final Class connectionClass;
private final Class connectionConfigurationClass;
@@ -38,13 +43,18 @@ public class XmppConnectionDescriptor connectionConstructor;
private final Method builderMethod;
- public XmppConnectionDescriptor(Class connectionClass, Class connectionConfigurationClass)
- throws ClassNotFoundException, NoSuchMethodException, SecurityException {
- this.connectionClass = connectionClass;
- this.connectionConfigurationClass = connectionConfigurationClass;
+ private final Consumer extraBuilder;
- this.connectionConstructor = getConstructor(connectionClass, connectionConfigurationClass);
- this.builderMethod = getBuilderMethod(connectionConfigurationClass);
+ private final String nickname;
+
+ private XmppConnectionDescriptor(Builder builder) throws NoSuchMethodException, SecurityException {
+ connectionClass = builder.connectionClass;
+ connectionConfigurationClass = builder.connectionConfigurationClass;
+ extraBuilder = builder.extraBuilder;
+ nickname = builder.nickname;
+
+ connectionConstructor = getConstructor(connectionClass, connectionConfigurationClass);
+ builderMethod = getBuilderMethod(connectionConfigurationClass);
}
public C construct(Configuration sinttestConfiguration)
@@ -65,6 +75,9 @@ public class XmppConnectionDescriptor customConnectionConfigurationAppliers)
throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
CCB connectionConfigurationBuilder = getNewBuilder();
+ if (extraBuilder != null) {
+ extraBuilder.accept(connectionConfigurationBuilder);
+ }
for (ConnectionConfigurationBuilderApplier customConnectionConfigurationApplier : customConnectionConfigurationAppliers) {
customConnectionConfigurationApplier.applyConfigurationTo(connectionConfigurationBuilder);
}
@@ -88,6 +101,10 @@ public class XmppConnectionDescriptor Constructor getConstructor(Class connectionClass,
Class extends ConnectionConfiguration> connectionConfigurationClass)
throws NoSuchMethodException, SecurityException {
@@ -106,4 +123,47 @@ public class XmppConnectionDescriptor>
+ Builder buildWith(Class connectionClass, Class connectionConfigurationClass) {
+ return buildWith(connectionClass, connectionConfigurationClass, null);
+ }
+
+ public static >
+ Builder buildWith(Class connectionClass, Class connectionConfigurationClass, Class connectionConfigurationBuilderClass) {
+ return new Builder<>(connectionClass, connectionConfigurationClass, connectionConfigurationBuilderClass);
+ }
+
+ public static final class Builder> {
+ private final Class connectionClass;
+ private final Class connectionConfigurationClass;
+
+ private Consumer extraBuilder;
+
+ private String nickname;
+
+ // The connectionConfigurationBuilderClass merely exists for type-checking purposes.
+ @SuppressWarnings("UnusedVariable")
+ private Builder(Class connectionClass, Class connectionConfigurationClass,
+ Class connectionConfigurationBuilderClass) {
+ this.connectionClass = connectionClass;
+ this.connectionConfigurationClass = connectionConfigurationClass;
+
+ nickname = connectionClass.getSimpleName();
+ }
+
+ public Builder applyExtraConfguration(Consumer extraBuilder) {
+ this.extraBuilder = extraBuilder;
+ return this;
+ }
+
+ public Builder withNickname(String nickname) {
+ this.nickname = nickname;
+ return this;
+ }
+
+ public XmppConnectionDescriptor build() throws NoSuchMethodException, SecurityException {
+ return new XmppConnectionDescriptor<>(this);
+ }
+ }
}
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java
index c576a1948..207ab1c02 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018-2019 Florian Schmaus
+ * Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -38,9 +37,11 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
+import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
+import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
+import org.jivesoftware.smack.compression.CompressionModuleDescriptor;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
-import org.jivesoftware.smack.tcp.XmppNioTcpConnection;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.StringUtils;
@@ -54,55 +55,92 @@ import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.stringprep.XmppStringprepException;
-public class XmppConnectionManager {
+public class XmppConnectionManager {
private static final Logger LOGGER = Logger.getLogger(XmppConnectionManager.class.getName());
- private static final Map, XmppConnectionDescriptor extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder, ?>>> CONNECTION_DESCRIPTORS = new ConcurrentHashMap<>();
+ private static final XmppConnectionDescriptor extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder, ?>> DEFAULT_CONNECTION_DESCRIPTOR;
+
+ private static final Map>> NICKNAME_CONNECTION_DESCRIPTORS = new HashMap<>();
+
+ private static final MultiMap<
+ Class extends AbstractXMPPConnection>,
+ XmppConnectionDescriptor extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder, ?>>
+ > CONNECTION_DESCRIPTORS = new MultiMap<>();
static {
try {
- addConnectionDescriptor(XmppNioTcpConnection.class, XMPPTCPConnectionConfiguration.class);
- addConnectionDescriptor(XMPPTCPConnection.class, XMPPTCPConnectionConfiguration.class);
- } catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
+ DEFAULT_CONNECTION_DESCRIPTOR = XmppConnectionDescriptor.buildWith(XMPPTCPConnection.class, XMPPTCPConnectionConfiguration.class)
+ .withNickname("tcp")
+ .build();
+ addConnectionDescriptor(DEFAULT_CONNECTION_DESCRIPTOR);
+
+ addConnectionDescriptor(
+ XmppConnectionDescriptor.buildWith(ModularXmppClientToServerConnection.class, ModularXmppClientToServerConnectionConfiguration.class)
+ .withNickname("modular")
+ .build()
+ );
+ addConnectionDescriptor(
+ XmppConnectionDescriptor.buildWith(ModularXmppClientToServerConnection.class, ModularXmppClientToServerConnectionConfiguration.class, ModularXmppClientToServerConnectionConfiguration.Builder.class)
+ .withNickname("modular-nocompress")
+ .applyExtraConfguration(cb -> cb.removeModule(CompressionModuleDescriptor.class))
+ .build()
+ );
+ } catch (NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
}
- public static void addConnectionDescriptor(Class extends AbstractXMPPConnection> connectionClass,
- Class extends ConnectionConfiguration> connectionConfigurationClass) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
- XmppConnectionDescriptor extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder, ?>> connectionDescriptor = new XmppConnectionDescriptor<>(
- connectionClass, connectionConfigurationClass);
- addConnectionDescriptor(connectionDescriptor);
- }
-
- public static void addConnectionDescriptor(
+ public static boolean addConnectionDescriptor(
XmppConnectionDescriptor extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder, ?>> connectionDescriptor) {
+ String nickname = connectionDescriptor.getNickname();
Class extends AbstractXMPPConnection> connectionClass = connectionDescriptor.getConnectionClass();
- CONNECTION_DESCRIPTORS.put(connectionClass, connectionDescriptor);
+
+ boolean alreadyExisted;
+ synchronized (CONNECTION_DESCRIPTORS) {
+ alreadyExisted = removeConnectionDescriptor(nickname);
+
+ CONNECTION_DESCRIPTORS.put(connectionClass, connectionDescriptor);
+ NICKNAME_CONNECTION_DESCRIPTORS.put(connectionDescriptor.getNickname(), connectionDescriptor);
+ }
+ return alreadyExisted;
}
- public static void removeConnectionDescriptor(Class extends AbstractXMPPConnection> connectionClass) {
- CONNECTION_DESCRIPTORS.remove(connectionClass);
+ public static boolean removeConnectionDescriptor(String nickname) {
+ synchronized (CONNECTION_DESCRIPTORS) {
+ XmppConnectionDescriptor extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder, ?>> connectionDescriptor = NICKNAME_CONNECTION_DESCRIPTORS.remove(nickname);
+ if (connectionDescriptor == null) {
+ return false;
+ }
+
+ boolean removed = CONNECTION_DESCRIPTORS.removeOne(connectionDescriptor.getConnectionClass(), connectionDescriptor);
+ assert removed;
+ }
+
+ return true;
}
- private final XmppConnectionDescriptor> defaultConnectionDescriptor;
+ private final XmppConnectionDescriptor extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder, ?>> defaultConnectionDescriptor;
- private final Map, XmppConnectionDescriptor extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder, ?>>> connectionDescriptors = new HashMap<>(
- CONNECTION_DESCRIPTORS.size());
+ private final Map>> nicknameConnectionDescriptors;
- private final SmackIntegrationTestFramework> sinttestFramework;
+ private final MultiMap<
+ Class extends AbstractXMPPConnection>,
+ XmppConnectionDescriptor extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder, ?>>
+ > connectionDescriptors;
+
+ private final SmackIntegrationTestFramework sinttestFramework;
private final Configuration sinttestConfiguration;
private final String testRunId;
- private final DC accountRegistrationConnection;
+ private final AbstractXMPPConnection accountRegistrationConnection;
private final ServiceAdministrationManager adminManager;
private final AccountManager accountManager;
/**
* One of the three main connections. The type of the main connections is the default connection type.
*/
- DC conOne, conTwo, conThree;
+ AbstractXMPPConnection conOne, conTwo, conThree;
/**
* A pool of authenticated and free to use connections.
@@ -114,20 +152,25 @@ public class XmppConnectionManager {
*/
private final List connections = new ArrayList<>();
- @SuppressWarnings("unchecked")
- XmppConnectionManager(SmackIntegrationTestFramework> sinttestFramework,
- Class extends DC> defaultConnectionClass)
+ XmppConnectionManager(SmackIntegrationTestFramework sinttestFramework)
throws SmackException, IOException, XMPPException, InterruptedException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+ synchronized (CONNECTION_DESCRIPTORS) {
+ connectionDescriptors = CONNECTION_DESCRIPTORS.clone();
+ nicknameConnectionDescriptors = new HashMap<>(NICKNAME_CONNECTION_DESCRIPTORS);
+ }
+
this.sinttestFramework = sinttestFramework;
this.sinttestConfiguration = sinttestFramework.config;
this.testRunId = sinttestFramework.testRunResult.testRunId;
- connectionDescriptors.putAll(CONNECTION_DESCRIPTORS);
-
- defaultConnectionDescriptor = (XmppConnectionDescriptor>) connectionDescriptors.get(
- defaultConnectionClass);
- if (defaultConnectionDescriptor == null) {
- throw new IllegalArgumentException("Could not find a connection descriptor for " + defaultConnectionClass);
+ String configuredDefaultConnectionNickname = sinttestConfiguration.defaultConnectionNickname;
+ if (configuredDefaultConnectionNickname != null) {
+ defaultConnectionDescriptor = nicknameConnectionDescriptors.get(configuredDefaultConnectionNickname);
+ if (defaultConnectionDescriptor == null) {
+ throw new IllegalArgumentException("Could not find a connection descriptor for connection nickname '" + configuredDefaultConnectionNickname + "'");
+ }
+ } else {
+ defaultConnectionDescriptor = DEFAULT_CONNECTION_DESCRIPTOR;
}
switch (sinttestConfiguration.accountRegistration) {
@@ -157,11 +200,11 @@ public class XmppConnectionManager {
}
}
- SmackIntegrationTestEnvironment prepareEnvironment() throws KeyManagementException, NoSuchAlgorithmException,
+ SmackIntegrationTestEnvironment prepareEnvironment() throws KeyManagementException, NoSuchAlgorithmException,
InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
SmackException, IOException, XMPPException, InterruptedException {
prepareMainConnections();
- return new SmackIntegrationTestEnvironment(conOne, conTwo, conThree,
+ return new SmackIntegrationTestEnvironment(conOne, conTwo, conThree,
sinttestFramework.testRunResult.testRunId, sinttestConfiguration, this);
}
@@ -169,9 +212,9 @@ public class XmppConnectionManager {
IllegalAccessException, IllegalArgumentException, InvocationTargetException, SmackException, IOException,
XMPPException, InterruptedException {
final int mainAccountCount = AccountNum.values().length;
- List connections = new ArrayList<>(mainAccountCount);
+ List connections = new ArrayList<>(mainAccountCount);
for (AccountNum mainAccountNum : AccountNum.values()) {
- DC mainConnection = getConnectedMainConnectionFor(mainAccountNum);
+ AbstractXMPPConnection mainConnection = getConnectedMainConnectionFor(mainAccountNum);
connections.add(mainConnection);
}
conOne = connections.get(0);
@@ -179,18 +222,18 @@ public class XmppConnectionManager {
conThree = connections.get(2);
}
- public Class extends AbstractXMPPConnection> getDefaultConnectionClass() {
- return defaultConnectionDescriptor.getConnectionClass();
+ public XmppConnectionDescriptor extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder, ?>> getDefaultConnectionDescriptor() {
+ return defaultConnectionDescriptor;
}
- public Set> getConnectionClasses() {
- return Collections.unmodifiableSet(connectionDescriptors.keySet());
+ public Collection>> getConnectionDescriptors() {
+ return Collections.unmodifiableCollection(nicknameConnectionDescriptors.values());
}
@SuppressWarnings("unchecked")
public XmppConnectionDescriptor> getConnectionDescriptorFor(
Class connectionClass) {
- return (XmppConnectionDescriptor>) connectionDescriptors.get(
+ return (XmppConnectionDescriptor>) connectionDescriptors.getFirst(
connectionClass);
}
@@ -249,7 +292,7 @@ public class XmppConnectionManager {
private static final String USERNAME_PREFIX = "smack-inttest";
- private DC getConnectedMainConnectionFor(AccountNum accountNum) throws SmackException, IOException, XMPPException,
+ private AbstractXMPPConnection getConnectedMainConnectionFor(AccountNum accountNum) throws SmackException, IOException, XMPPException,
InterruptedException, KeyManagementException, NoSuchAlgorithmException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
String middlefix;
@@ -283,7 +326,7 @@ public class XmppConnectionManager {
registerAccount(finalAccountUsername, finalAccountPassword);
}
- DC mainConnection = defaultConnectionDescriptor.construct(sinttestConfiguration, builder -> {
+ AbstractXMPPConnection mainConnection = defaultConnectionDescriptor.construct(sinttestConfiguration, builder -> {
try {
builder.setUsernameAndPassword(finalAccountUsername, finalAccountPassword)
.setResource(middlefix + '-' + testRunId);
@@ -347,7 +390,7 @@ public class XmppConnectionManager {
@SuppressWarnings("unchecked")
XmppConnectionDescriptor> connectionDescriptor = (XmppConnectionDescriptor>) connectionDescriptors
- .get(connectionClass);
+ .getFirst(connectionClass);
for (int i = 0; i < count; i++) {
C connection = constructConnectedConnection(connectionDescriptor);
connections.add(connection);
@@ -367,7 +410,7 @@ public class XmppConnectionManager {
return connection;
}
- DC constructConnection()
+ AbstractXMPPConnection constructConnection()
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return constructConnection(defaultConnectionDescriptor);
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/ChatTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/ChatTest.java
index ec21761a5..ff058b8e8 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smack/ChatTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/ChatTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@ public class ChatTest extends AbstractSmackIntegrationTest {
private boolean invoked;
@SuppressWarnings("deprecation")
- public ChatTest(SmackIntegrationTestEnvironment> environment) {
+ public ChatTest(SmackIntegrationTestEnvironment environment) {
super(environment);
chatManagerOne = org.jivesoftware.smack.chat.ChatManager.getInstanceFor(conOne);
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/LoginIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/LoginIntegrationTest.java
index 07a239b00..f43f18f2b 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smack/LoginIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/LoginIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class LoginIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
- public LoginIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public LoginIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/StreamManagementTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/StreamManagementTest.java
index a26cf8e17..e5e77e4b0 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smack/StreamManagementTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/StreamManagementTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
public class StreamManagementTest extends AbstractSmackSpecificLowLevelIntegrationTest {
- public StreamManagementTest(SmackIntegrationTestEnvironment> environment) throws Exception {
+ public StreamManagementTest(SmackIntegrationTestEnvironment environment) throws Exception {
super(environment, XMPPTCPConnection.class);
XMPPTCPConnection connection = getSpecificUnconnectedConnection();
connection.connect().login();
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/WaitForClosingStreamElementTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/WaitForClosingStreamElementTest.java
index 8aa741ecb..50ddf0123 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smack/WaitForClosingStreamElementTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/WaitForClosingStreamElementTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class WaitForClosingStreamElementTest extends AbstractSmackLowLevelIntegrationTest {
- public WaitForClosingStreamElementTest(SmackIntegrationTestEnvironment> environment) {
+ public WaitForClosingStreamElementTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/XmppConnectionIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/XmppConnectionIntegrationTest.java
index 8ca3c8498..29dc527e6 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smack/XmppConnectionIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/XmppConnectionIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018-2019 Florian Schmaus
+ * Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ package org.jivesoftware.smack;
import java.util.List;
import java.util.logging.Level;
-import org.jivesoftware.smack.tcp.XmppNioTcpConnection;
+import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.igniterealtime.smack.XmppConnectionStressTest;
import org.igniterealtime.smack.XmppConnectionStressTest.StressTestFailedException.ErrorsWhileSendingOrReceivingException;
@@ -30,7 +30,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class XmppConnectionIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
- public XmppConnectionIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public XmppConnectionIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
@@ -52,12 +52,12 @@ public class XmppConnectionIntegrationTest extends AbstractSmackLowLevelIntegrat
final Level connectionStatsLogLevel = Level.FINE;
if (LOGGER.isLoggable(connectionStatsLogLevel)) {
- if (connections.get(0) instanceof XmppNioTcpConnection) {
+ if (connections.get(0) instanceof ModularXmppClientToServerConnection) {
for (XMPPConnection connection : connections) {
- XmppNioTcpConnection xmppNioTcpConnection = (XmppNioTcpConnection) connection;
- XmppNioTcpConnection.Stats stats = xmppNioTcpConnection.getStats();
+ ModularXmppClientToServerConnection xmppC2sConnection = (ModularXmppClientToServerConnection) connection;
+ ModularXmppClientToServerConnection.Stats stats = xmppC2sConnection.getStats();
LOGGER.log(connectionStatsLogLevel,
- "Connections stats for " + xmppNioTcpConnection + ":\n{}",
+ "Connections stats for " + xmppC2sConnection + ":\n{}",
stats);
}
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/AbstractChatIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/AbstractChatIntegrationTest.java
index 68db46c5a..3ebf4e852 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/AbstractChatIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/AbstractChatIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018 Florian Schmaus
+ * Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ public abstract class AbstractChatIntegrationTest extends AbstractSmackIntegrati
protected final ChatManager chatManagerTwo;
protected final ChatManager chatManagerThree;
- protected AbstractChatIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ protected AbstractChatIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
chatManagerOne = ChatManager.getInstanceFor(conOne);
chatManagerTwo = ChatManager.getInstanceFor(conTwo);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/IncomingMessageListenerIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/IncomingMessageListenerIntegrationTest.java
index b031be9d2..ca76ab280 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/IncomingMessageListenerIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/IncomingMessageListenerIntegrationTest.java
@@ -26,7 +26,7 @@ import org.jxmpp.jid.EntityBareJid;
public class IncomingMessageListenerIntegrationTest extends AbstractChatIntegrationTest {
- public IncomingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public IncomingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/OutgoingMessageListenerIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/OutgoingMessageListenerIntegrationTest.java
index c284495fa..6118d2ed2 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/OutgoingMessageListenerIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/chat2/OutgoingMessageListenerIntegrationTest.java
@@ -27,7 +27,7 @@ import org.jxmpp.jid.EntityBareJid;
public class OutgoingMessageListenerIntegrationTest extends AbstractChatIntegrationTest {
- public OutgoingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public OutgoingMessageListenerIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/LowLevelRosterIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/LowLevelRosterIntegrationTest.java
index 242491c96..8b73081be 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/LowLevelRosterIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/LowLevelRosterIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2016-2019 Florian Schmaus
+ * Copyright 2016-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ import org.jxmpp.jid.FullJid;
public class LowLevelRosterIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
- public LowLevelRosterIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public LowLevelRosterIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java
index beff7eb1b..6f2d0a8fb 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,7 +38,7 @@ public class RosterIntegrationTest extends AbstractSmackIntegrationTest {
private final Roster rosterOne;
private final Roster rosterTwo;
- public RosterIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public RosterIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
rosterOne = Roster.getInstanceFor(conOne);
rosterTwo = Roster.getInstanceFor(conTwo);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnectionLowLevelIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnectionLowLevelIntegrationTest.java
index f42f04ced..5971ceda6 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnectionLowLevelIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnectionLowLevelIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018-2019 Florian Schmaus
+ * Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,21 +22,22 @@ import java.security.NoSuchAlgorithmException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
import org.igniterealtime.smack.inttest.AbstractSmackSpecificLowLevelIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
-public class XmppNioTcpConnectionLowLevelIntegrationTest extends AbstractSmackSpecificLowLevelIntegrationTest {
+public class XmppNioTcpConnectionLowLevelIntegrationTest extends AbstractSmackSpecificLowLevelIntegrationTest {
- public XmppNioTcpConnectionLowLevelIntegrationTest(SmackIntegrationTestEnvironment> environment) {
- super(environment, XmppNioTcpConnection.class);
+ public XmppNioTcpConnectionLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) {
+ super(environment, ModularXmppClientToServerConnection.class);
}
@SmackIntegrationTest
public void testDisconnectAfterConnect() throws KeyManagementException, NoSuchAlgorithmException, SmackException,
IOException, XMPPException, InterruptedException {
- XmppNioTcpConnection connection = getSpecificUnconnectedConnection();
+ ModularXmppClientToServerConnection connection = getSpecificUnconnectedConnection();
connection.connect();
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/caps/EntityCapsTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/caps/EntityCapsTest.java
index c3aa5ede0..8a4946e51 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/caps/EntityCapsTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/caps/EntityCapsTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2013-2019 Florian Schmaus
+ * Copyright 2013-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,7 +57,7 @@ public class EntityCapsTest extends AbstractSmackIntegrationTest {
private final ServiceDiscoveryManager sdmOne;
private final ServiceDiscoveryManager sdmTwo;
- public EntityCapsTest(SmackIntegrationTestEnvironment> environment) {
+ public EntityCapsTest(SmackIntegrationTestEnvironment environment) {
super(environment);
ecmTwo = EntityCapsManager.getInstanceFor(environment.conTwo);
sdmOne = ServiceDiscoveryManager.getInstanceFor(environment.conOne);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/chatstate/ChatStateIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/chatstate/ChatStateIntegrationTest.java
index c236d8aa4..539bdb03d 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/chatstate/ChatStateIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/chatstate/ChatStateIntegrationTest.java
@@ -55,7 +55,7 @@ public class ChatStateIntegrationTest extends AbstractSmackIntegrationTest {
};
- public ChatStateIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public ChatStateIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferIntegrationTest.java
index 651137694..4a0af1d50 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ public class FileTransferIntegrationTest extends AbstractSmackIntegrationTest {
private final FileTransferManager ftManagerOne;
private final FileTransferManager ftManagerTwo;
- public FileTransferIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public FileTransferIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
ftManagerOne = FileTransferManager.getInstanceFor(conOne);
ftManagerTwo = FileTransferManager.getInstanceFor(conTwo);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java
index 6390a2e94..1d2e06914 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2017-2019 Florian Schmaus
+ * Copyright 2017-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@ public class HttpFileUploadIntegrationTest extends AbstractSmackIntegrationTest
private final HttpFileUploadManager hfumOne;
- public HttpFileUploadIntegrationTest(SmackIntegrationTestEnvironment> environment) throws XMPPErrorException,
+ public HttpFileUploadIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPErrorException,
NotConnectedException, NoResponseException, InterruptedException, TestNotPossibleException {
super(environment);
hfumOne = HttpFileUploadManager.getInstanceFor(conOne);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTControlIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTControlIntegrationTest.java
index c6f8b275d..4a346479b 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTControlIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTControlIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2016-2019 Florian Schmaus
+ * Copyright 2016-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@ public class IoTControlIntegrationTest extends AbstractSmackIntegrationTest {
private final IoTControlManager IoTControlManagerTwo;
- public IoTControlIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public IoTControlIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
IoTControlManagerOne = IoTControlManager.getInstanceFor(conOne);
IoTControlManagerTwo = IoTControlManager.getInstanceFor(conTwo);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDataIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDataIntegrationTest.java
index 1fec14577..dc6fc538e 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDataIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDataIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2016-2019 Florian Schmaus
+ * Copyright 2016-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,7 +45,7 @@ public class IoTDataIntegrationTest extends AbstractSmackIntegrationTest {
private final IoTDataManager iotDataManagerTwo;
- public IoTDataIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public IoTDataIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
iotDataManagerOne = IoTDataManager.getInstanceFor(conOne);
iotDataManagerTwo = IoTDataManager.getInstanceFor(conTwo);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDiscoveryIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDiscoveryIntegrationTest.java
index 1c1c8071d..92c950a86 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDiscoveryIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iot/IoTDiscoveryIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2016-2019 Florian Schmaus
+ * Copyright 2016-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@ public class IoTDiscoveryIntegrationTest extends AbstractSmackIntegrationTest {
private final IoTDiscoveryManager discoveryManagerOne;
private final IoTDiscoveryManager discoveryManagerTwo;
- public IoTDiscoveryIntegrationTest(SmackIntegrationTestEnvironment> environment) throws NoResponseException,
+ public IoTDiscoveryIntegrationTest(SmackIntegrationTestEnvironment environment) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException {
super(environment);
discoveryManagerOne = IoTDiscoveryManager.getInstanceFor(conOne);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iqversion/VersionIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iqversion/VersionIntegrationTest.java
index f22194452..577819e11 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/iqversion/VersionIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/iqversion/VersionIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
public class VersionIntegrationTest extends AbstractSmackIntegrationTest {
- public VersionIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public VersionIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java
index 0ee3d295b..b0cc99584 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2016 Fernando Ramirez, 2018-2019 Florian Schmaus
+ * Copyright 2016 Fernando Ramirez, 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ public class MamIntegrationTest extends AbstractSmackIntegrationTest {
private final MamManager mamManagerConTwo;
- public MamIntegrationTest(SmackIntegrationTestEnvironment> environment) throws NoResponseException,
+ public MamIntegrationTest(SmackIntegrationTestEnvironment environment) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException, NotLoggedInException {
super(environment);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java
index 902c2b97f..afac2dd12 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018 Paul Schaub, 2019 Florian Schmaus.
+ * Copyright 2018 Paul Schaub, 2019-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ public class MoodIntegrationTest extends AbstractSmackIntegrationTest {
private final MoodManager mm1;
private final MoodManager mm2;
- public MoodIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public MoodIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
mm1 = MoodManager.getInstanceFor(conOne);
mm2 = MoodManager.getInstanceFor(conTwo);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java
index c7651a052..aee855f6a 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,7 +54,7 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
private final MultiUserChatManager mucManagerTwo;
private final DomainBareJid mucService;
- public MultiUserChatIntegrationTest(SmackIntegrationTestEnvironment> environment)
+ public MultiUserChatIntegrationTest(SmackIntegrationTestEnvironment environment)
throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException, TestNotPossibleException {
super(environment);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatLowLevelIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatLowLevelIntegrationTest.java
index a47eef25f..ba4abd160 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatLowLevelIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatLowLevelIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@ import org.jxmpp.jid.parts.Resourcepart;
public class MultiUserChatLowLevelIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
- public MultiUserChatLowLevelIntegrationTest(SmackIntegrationTestEnvironment> environment) throws Exception {
+ public MultiUserChatLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) throws Exception {
super(environment);
AbstractXMPPConnection connection = getConnectedConnection();
try {
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoIntegrationTest.java
index f16ca3d88..4f944d3fa 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2017-2018 Florian Schmaus, Paul Schaub
+ * Copyright 2017-2020 Florian Schmaus, Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
*/
public abstract class AbstractOmemoIntegrationTest extends AbstractSmackIntegrationTest {
- public AbstractOmemoIntegrationTest(SmackIntegrationTestEnvironment> environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
+ public AbstractOmemoIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
super(environment);
// Test for server support
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java
index 68cd5f30c..1e324b77c 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java
@@ -40,7 +40,7 @@ public abstract class AbstractTwoUsersOmemoIntegrationTest extends AbstractOmemo
protected OmemoManager alice, bob;
- public AbstractTwoUsersOmemoIntegrationTest(SmackIntegrationTestEnvironment> environment)
+ public AbstractTwoUsersOmemoIntegrationTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, TestNotPossibleException {
super(environment);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/MessageEncryptionIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/MessageEncryptionIntegrationTest.java
index 938696441..26ba5ab08 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/MessageEncryptionIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/MessageEncryptionIntegrationTest.java
@@ -37,7 +37,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
*/
public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoIntegrationTest {
- public MessageEncryptionIntegrationTest(SmackIntegrationTestEnvironment> environment)
+ public MessageEncryptionIntegrationTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, TestNotPossibleException {
super(environment);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMamDecryptionTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMamDecryptionTest.java
index 8a36a2f22..ee39eec90 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMamDecryptionTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMamDecryptionTest.java
@@ -41,7 +41,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
* Then Bob fetches his Mam archive and decrypts the result.
*/
public class OmemoMamDecryptionTest extends AbstractTwoUsersOmemoIntegrationTest {
- public OmemoMamDecryptionTest(SmackIntegrationTestEnvironment> environment)
+ public OmemoMamDecryptionTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, TestNotPossibleException {
super(environment);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/ReadOnlyDeviceIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/ReadOnlyDeviceIntegrationTest.java
index dc13bdb8f..bed2b014d 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/ReadOnlyDeviceIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/ReadOnlyDeviceIntegrationTest.java
@@ -35,7 +35,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
public class ReadOnlyDeviceIntegrationTest extends AbstractTwoUsersOmemoIntegrationTest {
- public ReadOnlyDeviceIntegrationTest(SmackIntegrationTestEnvironment> environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
+ public ReadOnlyDeviceIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
super(environment);
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/SessionRenegotiationIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/SessionRenegotiationIntegrationTest.java
index fa863f1be..3b4391ba3 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/SessionRenegotiationIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/SessionRenegotiationIntegrationTest.java
@@ -27,7 +27,7 @@ import org.igniterealtime.smack.inttest.TestNotPossibleException;
public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIntegrationTest {
- public SessionRenegotiationIntegrationTest(SmackIntegrationTestEnvironment> environment)
+ public SessionRenegotiationIntegrationTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, TestNotPossibleException {
super(environment);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/AbstractOpenPgpIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/AbstractOpenPgpIntegrationTest.java
index d02d4f3f5..31d613c85 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/AbstractOpenPgpIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/AbstractOpenPgpIntegrationTest.java
@@ -42,7 +42,7 @@ public abstract class AbstractOpenPgpIntegrationTest extends AbstractSmackIntegr
protected final PepManager bobPepManager;
protected final PepManager chloePepManager;
- protected AbstractOpenPgpIntegrationTest(SmackIntegrationTestEnvironment> environment)
+ protected AbstractOpenPgpIntegrationTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, TestNotPossibleException, SmackException.NotConnectedException,
InterruptedException, SmackException.NoResponseException {
super(environment);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/OXSecretKeyBackupIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/OXSecretKeyBackupIntegrationTest.java
index 897dda339..e5e33f39b 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/OXSecretKeyBackupIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/OXSecretKeyBackupIntegrationTest.java
@@ -98,7 +98,7 @@ public class OXSecretKeyBackupIntegrationTest extends AbstractOpenPgpIntegration
* @throws InterruptedException if the calling thread was interrupted.
* @throws SmackException.NoResponseException if there was no response from the remote entity.
*/
- public OXSecretKeyBackupIntegrationTest(SmackIntegrationTestEnvironment> environment)
+ public OXSecretKeyBackupIntegrationTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, TestNotPossibleException, SmackException.NotConnectedException,
InterruptedException, SmackException.NoResponseException {
super(environment);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingIntegrationTest.java
index 1f68eeb4d..4ca076c2b 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingIntegrationTest.java
@@ -89,7 +89,7 @@ public class OXInstantMessagingIntegrationTest extends AbstractOpenPgpIntegratio
* @throws TestNotPossibleException if the test is not possible due to lacking server support for PEP.
* @throws SmackException.NoResponseException if there was no response from the remote entity.
*/
- public OXInstantMessagingIntegrationTest(SmackIntegrationTestEnvironment> environment)
+ public OXInstantMessagingIntegrationTest(SmackIntegrationTestEnvironment environment)
throws XMPPException.XMPPErrorException, InterruptedException, SmackException.NotConnectedException,
TestNotPossibleException, SmackException.NoResponseException {
super(environment);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/ping/PingIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/ping/PingIntegrationTest.java
index 1d19c5b4f..6bb73b8c1 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/ping/PingIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/ping/PingIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ import org.jxmpp.jid.Jid;
public class PingIntegrationTest extends AbstractSmackIntegrationTest {
- public PingIntegrationTest(SmackIntegrationTestEnvironment> environment) {
+ public PingIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/pubsub/PubSubIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/pubsub/PubSubIntegrationTest.java
index cc5811ad0..a0e0bff6c 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/pubsub/PubSubIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/pubsub/PubSubIntegrationTest.java
@@ -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.
@@ -37,7 +37,7 @@ public class PubSubIntegrationTest extends AbstractSmackIntegrationTest {
private final PubSubManager pubSubManagerOne;
- public PubSubIntegrationTest(SmackIntegrationTestEnvironment> environment)
+ public PubSubIntegrationTest(SmackIntegrationTestEnvironment environment)
throws TestNotPossibleException, NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException {
super(environment);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java
index c766bc6e5..b8492529b 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java
@@ -38,7 +38,7 @@ public class UserTuneIntegrationTest extends AbstractSmackIntegrationTest {
private final UserTuneManager utm1;
private final UserTuneManager utm2;
- public UserTuneIntegrationTest(SmackIntegrationTestEnvironment> environment) throws NotLoggedInException {
+ public UserTuneIntegrationTest(SmackIntegrationTestEnvironment environment) throws NotLoggedInException {
super(environment);
utm1 = UserTuneManager.getInstanceFor(conOne);
utm2 = UserTuneManager.getInstanceFor(conTwo);
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/xdata/FormTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/xdata/FormTest.java
index cf89a68f2..af49c251a 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/xdata/FormTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/xdata/FormTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2004 Jive Software, 2017-2019 Florian Schmaus.
+ * Copyright 2004 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.
@@ -41,7 +41,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
*/
public class FormTest extends AbstractSmackIntegrationTest {
- public FormTest(SmackIntegrationTestEnvironment> environment) {
+ public FormTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
diff --git a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/DummySmackIntegrationTestFramework.java b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/DummySmackIntegrationTestFramework.java
index 6617f9b0a..f16e2ba54 100644
--- a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/DummySmackIntegrationTestFramework.java
+++ b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/DummySmackIntegrationTestFramework.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2019 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,12 +26,19 @@ import org.jivesoftware.smack.DummyConnection.DummyConnectionConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
-public class DummySmackIntegrationTestFramework extends SmackIntegrationTestFramework {
+public class DummySmackIntegrationTestFramework extends SmackIntegrationTestFramework {
+
+ public static final String DUMMY_CONNECTION_NICKNAME = "dummy";
static {
try {
- XmppConnectionManager.addConnectionDescriptor(DummyConnection.class, DummyConnectionConfiguration.class);
- } catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
+ XmppConnectionManager.addConnectionDescriptor(
+ XmppConnectionDescriptor
+ .buildWith(DummyConnection.class, DummyConnectionConfiguration.class)
+ .withNickname(DUMMY_CONNECTION_NICKNAME)
+ .build()
+ );
+ } catch (NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
}
@@ -39,15 +46,15 @@ public class DummySmackIntegrationTestFramework extends SmackIntegrationTestFram
public DummySmackIntegrationTestFramework(Configuration configuration) throws KeyManagementException,
InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchAlgorithmException, SmackException, IOException, XMPPException, InterruptedException {
- super(configuration, DummyConnection.class);
+ super(configuration);
testRunResult = new TestRunResult();
}
@Override
- protected SmackIntegrationTestEnvironment prepareEnvironment() {
+ protected SmackIntegrationTestEnvironment prepareEnvironment() {
DummyConnection dummyConnection = new DummyConnection();
connectionManager.conOne = connectionManager.conTwo = connectionManager.conThree = dummyConnection;
- return new SmackIntegrationTestEnvironment(dummyConnection, dummyConnection, dummyConnection,
+ return new SmackIntegrationTestEnvironment(dummyConnection, dummyConnection, dummyConnection,
testRunResult.getTestRunId(), config, null);
}
diff --git a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestUnitTestUtil.java b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestUnitTestUtil.java
index de095b69a..c3cd0a0ab 100644
--- a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestUnitTestUtil.java
+++ b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestUnitTestUtil.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2017 Florian Schmaus
+ * Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,9 @@ public class SmackIntegrationTestUnitTestUtil {
Configuration configuration = Configuration.builder()
.setService(JidTestUtil.DOMAIN_BARE_JID_1)
.setUsernamesAndPassword("dummy1", "dummy1pass", "dummy2", "dummy2pass", "dummy3", "dummy3pass")
- .addEnabledTest(unitTest).build();
+ .setDefaultConnection(DummySmackIntegrationTestFramework.DUMMY_CONNECTION_NICKNAME)
+ .addEnabledTest(unitTest)
+ .build();
// @formatter:on
try {
return new DummySmackIntegrationTestFramework(configuration);
diff --git a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestXmppConnectionManagerTest.java b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestXmppConnectionManagerTest.java
index 6cd7d0d1c..1709f2a58 100644
--- a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestXmppConnectionManagerTest.java
+++ b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/SmackIntegrationTestXmppConnectionManagerTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018-2019 Florian Schmaus
+ * Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,8 +22,9 @@ import java.lang.reflect.InvocationTargetException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
-import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
-import org.jivesoftware.smack.tcp.XmppNioTcpConnection;
+import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
+import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
+import org.jivesoftware.smack.tcp.XmppTcpTransportModuleDescriptor;
import org.junit.Test;
import org.jxmpp.stringprep.XmppStringprepException;
@@ -34,11 +35,19 @@ public class SmackIntegrationTestXmppConnectionManagerTest {
public void simpleXmppConnectionDescriptorTest() throws ClassNotFoundException, NoSuchMethodException,
SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
KeyManagementException, NoSuchAlgorithmException, XmppStringprepException, InstantiationException {
- XmppConnectionDescriptor descriptor
- = new XmppConnectionDescriptor<>(XmppNioTcpConnection.class, XMPPTCPConnectionConfiguration.class);
+ XmppConnectionDescriptor<
+ ModularXmppClientToServerConnection,
+ ModularXmppClientToServerConnectionConfiguration,
+ ModularXmppClientToServerConnectionConfiguration.Builder
+ > descriptor = XmppConnectionDescriptor.buildWith(
+ ModularXmppClientToServerConnection.class,
+ ModularXmppClientToServerConnectionConfiguration.class,
+ ModularXmppClientToServerConnectionConfiguration.Builder.class)
+ .applyExtraConfguration(b -> b.removeAllModules().addModule(XmppTcpTransportModuleDescriptor.class))
+ .build();
Configuration sinttestConfiguration = Configuration.builder().setService("example.org").build();
- XmppNioTcpConnection connection = descriptor.construct(sinttestConfiguration);
+ ModularXmppClientToServerConnection connection = descriptor.construct(sinttestConfiguration);
assertEquals("example.org", connection.getXMPPServiceDomain().toString());
}
diff --git a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/unittest/SmackIntegrationTestFrameworkUnitTest.java b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/unittest/SmackIntegrationTestFrameworkUnitTest.java
index a73b432df..9df166d51 100644
--- a/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/unittest/SmackIntegrationTestFrameworkUnitTest.java
+++ b/smack-integration-test/src/test/java/org/igniterealtime/smack/inttest/unittest/SmackIntegrationTestFrameworkUnitTest.java
@@ -72,7 +72,7 @@ public class SmackIntegrationTestFrameworkUnitTest {
public static class ThrowsRuntimeExceptionDummyTest extends AbstractSmackIntegrationTest {
- public ThrowsRuntimeExceptionDummyTest(SmackIntegrationTestEnvironment> environment) {
+ public ThrowsRuntimeExceptionDummyTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
@@ -103,7 +103,7 @@ public class SmackIntegrationTestFrameworkUnitTest {
public static final String DESCRIPTIVE_TEXT = "I'm not fatal";
- public ThrowsNonFatalExceptionDummyTest(SmackIntegrationTestEnvironment> environment) {
+ public ThrowsNonFatalExceptionDummyTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
@@ -131,7 +131,7 @@ public class SmackIntegrationTestFrameworkUnitTest {
public static class BeforeAfterClassTest extends AbstractSmackIntegrationTest {
- public BeforeAfterClassTest(SmackIntegrationTestEnvironment> environment) {
+ public BeforeAfterClassTest(SmackIntegrationTestEnvironment environment) {
super(environment);
}
diff --git a/smack-java8-full/build.gradle b/smack-java8-full/build.gradle
new file mode 100644
index 000000000..8bf5dfe6a
--- /dev/null
+++ b/smack-java8-full/build.gradle
@@ -0,0 +1,48 @@
+description = """\
+Full Smack library for Java SE."""
+
+dependencies {
+ api project(':smack-bosh')
+ api project(':smack-debug')
+ api project(':smack-experimental')
+ api project(':smack-extensions')
+ api project(':smack-java7')
+ api project(':smack-legacy')
+ api project(':smack-omemo')
+ api project(':smack-omemo-signal')
+ api project(':smack-openpgp')
+ api project(':smack-resolver-minidns')
+ api project(':smack-resolver-minidns-dox')
+ api project(':smack-tcp')
+
+ testImplementation project(path: ":smack-core", configuration: "testRuntime")
+}
+
+task printModularXmppClientToServerConnectionStateGraph(type: JavaExec) {
+ classpath sourceSets.main.runtimeClasspath
+ main 'org.jivesoftware.smack.full.ModularXmppClientToServerConnectionTool'
+}
+
+task generateModularXmppClientToServerConnectionStateGraph(type: JavaExec) {
+ // TODO: Filter out all files which do not contain the String
+ // StateDescriptor.
+ inputs.files file('..').listFiles().findAll {it.name.endsWith('.java')}
+ outputs.files 'src/javadoc/org/jivesoftware/smack/full/doc-files/ModularXmppClientToServerConnectionStateGraph.dot'
+ classpath sourceSets.main.runtimeClasspath
+ main 'org.jivesoftware.smack.full.ModularXmppClientToServerConnectionTool'
+ args outputs.files
+}
+
+task convertModularXmppClientToServerConnectionStateGraphDotToPng(type: Exec) {
+ dependsOn generateModularXmppClientToServerConnectionStateGraph
+ inputs.files 'src/javadoc/org/jivesoftware/smack/full/doc-files/ModularXmppClientToServerConnectionStateGraph.dot'
+ outputs.files 'src/javadoc/org/jivesoftware/smack/full/doc-files/ModularXmppClientToServerConnectionStateGraph.png'
+
+ executable 'dot'
+ args "-Tpng", "-o", "${outputs.files.first()}", "${inputs.files.first()}"
+}
+
+task cleanGenerateFiles(type: Delete) {
+ delete 'src/javadoc/org/jivesoftware/smack/full/doc-files/ModularXmppClientToServerConnectionStateGraph.dot', 'src/javadoc/org/jivesoftware/smack/full/doc-files/ModularXmppClientToServerConnectionStateGraph.png'
+}
+clean.dependsOn cleanGenerateFiles
diff --git a/smack-java8-full/src/javadoc/org/jivesoftware/smack/full/doc-files/.gitignore b/smack-java8-full/src/javadoc/org/jivesoftware/smack/full/doc-files/.gitignore
new file mode 100644
index 000000000..59fc6dc8e
--- /dev/null
+++ b/smack-java8-full/src/javadoc/org/jivesoftware/smack/full/doc-files/.gitignore
@@ -0,0 +1,2 @@
+/ModularXmppClientToServerConnectionStateGraph.dot
+/ModularXmppClientToServerConnectionStateGraph.png
diff --git a/smack-java8-full/src/main/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionTool.java b/smack-java8-full/src/main/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionTool.java
new file mode 100644
index 000000000..9455d9e35
--- /dev/null
+++ b/smack-java8-full/src/main/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionTool.java
@@ -0,0 +1,71 @@
+/**
+ *
+ * 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.full;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
+
+import org.jxmpp.stringprep.XmppStringprepException;
+
+public class ModularXmppClientToServerConnectionTool {
+
+ @SuppressWarnings("DefaultCharset")
+ public static void main(String[] args) throws XmppStringprepException, FileNotFoundException {
+
+ final PrintWriter pw;
+ final boolean breakStateName;
+
+ switch (args.length) {
+ case 0:
+ pw = new PrintWriter(System.out);
+ breakStateName = false;
+ break;
+ case 1:
+ Path outputFilePath = Paths.get(args[0]);
+ File outputFile = outputFilePath.toFile();
+ if (outputFile.exists()) {
+ outputFile.delete();
+ }
+ pw = new PrintWriter(outputFile);
+ breakStateName = true;
+ break;
+ default:
+ throw new IllegalArgumentException("At most one argument allowed");
+ }
+
+ printStateGraph(pw, breakStateName);
+ pw.flush();
+ }
+
+ public static void printStateGraph(PrintWriter pw, boolean breakStateName) throws XmppStringprepException {
+ ModularXmppClientToServerConnectionConfiguration.Builder configurationBuilder = ModularXmppClientToServerConnectionConfiguration.builder()
+ .setUsernameAndPassword("user", "password")
+ .setXmppDomain("example.org");
+
+ ModularXmppClientToServerConnectionConfiguration configuration = configurationBuilder.build();
+
+ configuration.printStateGraphInDotFormat(pw, breakStateName);
+
+ pw.flush();
+ }
+
+}
diff --git a/smack-java8-full/src/main/java/org/jivesoftware/smack/full/package-info.java b/smack-java8-full/src/main/java/org/jivesoftware/smack/full/package-info.java
new file mode 100644
index 000000000..e316eabb0
--- /dev/null
+++ b/smack-java8-full/src/main/java/org/jivesoftware/smack/full/package-info.java
@@ -0,0 +1,28 @@
+/**
+ *
+ * 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.
+ */
+
+/**
+ * The Smack XMPP client library.
+
+ * Modular XMPP c2s connection
+ *
+ * The graph below shows the current states of the modular xmpp client connection. Only some states are final states,
+ * most states are intermediate states in order to reach a final state.
+ *
+ *
+ */
+package org.jivesoftware.smack.full;
diff --git a/smack-java8-full/src/test/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionStateGraphTest.java b/smack-java8-full/src/test/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionStateGraphTest.java
new file mode 100644
index 000000000..624679481
--- /dev/null
+++ b/smack-java8-full/src/test/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionStateGraphTest.java
@@ -0,0 +1,83 @@
+/**
+ *
+ * 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.full;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+import org.jivesoftware.smack.util.EqualsUtil;
+import org.jivesoftware.smack.util.HashCode;
+
+import com.google.common.io.Resources;
+import org.jgrapht.graph.DefaultEdge;
+import org.jgrapht.graph.DirectedPseudograph;
+import org.jgrapht.io.DOTImporter;
+import org.jgrapht.io.ImportException;
+import org.junit.jupiter.api.Test;
+
+public class ModularXmppClientToServerConnectionStateGraphTest {
+
+ @Test
+ public void testStateGraphDotOutput() throws IOException, ImportException {
+ URL stateGraphDotFileUrl = Resources.getResource("state-graph.dot");
+ String expectedStateGraphDot = Resources.toString(stateGraphDotFileUrl, StandardCharsets.UTF_8);
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ ModularXmppClientToServerConnectionTool.printStateGraph(pw, false);
+ String currentStateGraphDot = sw.toString();
+
+ @SuppressWarnings("serial")
+ DOTImporter dotImporter = new DOTImporter<>(
+ (id, attributes) -> id,
+ (from, to, label, attributes) -> {
+ return new DefaultEdge() {
+ @Override
+ public int hashCode() {
+ return HashCode.builder()
+ .append(getSource())
+ .append(getTarget())
+ .build();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return EqualsUtil.equals(this, other, (b, o) ->
+ b.append(getSource(), o.getSource())
+ .append(getTarget(), o.getTarget())
+ );
+ }
+ };
+ }
+ );
+
+ DirectedPseudograph currentStateGraph = new DirectedPseudograph<>(DefaultEdge.class);
+ DirectedPseudograph expectedStateGraph = new DirectedPseudograph<>(DefaultEdge.class);
+
+ dotImporter.importGraph(expectedStateGraph, new StringReader(expectedStateGraphDot));
+ dotImporter.importGraph(currentStateGraph, new StringReader(currentStateGraphDot));
+
+ assertEquals(expectedStateGraph, currentStateGraph);
+ }
+
+}
diff --git a/smack-java8-full/src/test/resources/state-graph.dot b/smack-java8-full/src/test/resources/state-graph.dot
new file mode 100644
index 000000000..650f3bff6
--- /dev/null
+++ b/smack-java8-full/src/test/resources/state-graph.dot
@@ -0,0 +1,36 @@
+digraph {
+ "Disconnected" -> "LookupRemoteConnectionEndpoints";
+ "LookupRemoteConnectionEndpoints" -> "EstablishingTcpConnection";
+ "EstablishingTcpConnection" -> "EstablishTls (RFC 6120 § 5)" [xlabel="1"];
+ "EstablishTls (RFC 6120 § 5)" -> "ConnectedButUnauthenticated";
+ "ConnectedButUnauthenticated" -> "Bind2 (XEP-0386)" [xlabel="1"];
+ "Bind2 (XEP-0386)" -> "AuthenticatedAndResourceBound";
+ "AuthenticatedAndResourceBound" -> "Shutdown" [xlabel="1"];
+ "Shutdown" -> "CloseConnection";
+ "CloseConnection" -> "Disconnected";
+ "AuthenticatedAndResourceBound" -> "InstantShutdown" [xlabel="2"];
+ "InstantShutdown" -> "CloseConnection";
+"AuthenticatedAndResourceBound" [ style=filled ]
+"Bind2 (XEP-0386)" [ style=dashed ]
+ "ConnectedButUnauthenticated" -> "InstantStreamResumption (XEP-0397)" [xlabel="2"];
+ "InstantStreamResumption (XEP-0397)" -> "AuthenticatedAndResourceBound";
+"InstantStreamResumption (XEP-0397)" [ style=dashed ]
+ "ConnectedButUnauthenticated" -> "SaslAuthentication (RFC 6120 § 6)" [xlabel="3"];
+ "SaslAuthentication (RFC 6120 § 6)" -> "AuthenticatedButUnbound";
+ "AuthenticatedButUnbound" -> "Compression (XEP-0138)" [xlabel="1"];
+ "Compression (XEP-0138)" -> "AuthenticatedButUnbound";
+ "AuthenticatedButUnbound" -> "ResumeStream (XEP-0198)" [xlabel="2"];
+ "ResumeStream (XEP-0198)" -> "AuthenticatedAndResourceBound";
+"ResumeStream (XEP-0198)" [ style=dashed ]
+ "AuthenticatedButUnbound" -> "ResourceBinding (RFC 6120 § 7)" [xlabel="3"];
+ "ResourceBinding (RFC 6120 § 7)" -> "EnableStreamManagement (XEP-0198)" [xlabel="1"];
+ "EnableStreamManagement (XEP-0198)" -> "AuthenticatedAndResourceBound";
+"EnableStreamManagement (XEP-0198)" [ style=dashed ]
+ "ResourceBinding (RFC 6120 § 7)" -> "AuthenticatedAndResourceBound" [xlabel="2"];
+"AuthenticatedButUnbound" [ style=bold ]
+ "ConnectedButUnauthenticated" -> "Shutdown" [xlabel="4"];
+ "ConnectedButUnauthenticated" -> "InstantShutdown" [xlabel="5"];
+"ConnectedButUnauthenticated" [ style=filled ]
+ "EstablishingTcpConnection" -> "ConnectedButUnauthenticated" [xlabel="2"];
+"Disconnected" [ style=filled ]
+}
diff --git a/smack-repl/build.gradle b/smack-repl/build.gradle
index f7f246d6d..dc691a89c 100644
--- a/smack-repl/build.gradle
+++ b/smack-repl/build.gradle
@@ -10,18 +10,14 @@ ext {
}
dependencies {
- compile project(':smack-tcp')
- compile project(':smack-bosh')
- compile project(':smack-java7')
- compile project(':smack-resolver-minidns')
- compile project(':smack-resolver-minidns-dox')
- compile project(':smack-extensions')
- compile project(':smack-experimental')
- compile project(':smack-legacy')
- compile project(':smack-integration-test')
- compile project(':smack-omemo-signal')
+ // Smack's integration test framework (sintest) depends on
+ // smack-java*-full and since we may want to use parts of sinttest
+ // in the REPL, we simply depend sinttest.
+ api project(':smack-integration-test')
+
compile "org.scala-lang:scala-library:$scalaVersion"
compile "com.lihaoyi:ammonite_$scalaVersion:1.3.2"
+
testCompile project(path: ":smack-core", configuration: "testRuntime")
}
@@ -38,8 +34,3 @@ task printClasspath(dependsOn: assemble) {
println sourceSets.main.runtimeClasspath.asPath
}
}
-
-task printXmppNioTcpConnectionStateGraph(type: JavaExec) {
- classpath sourceSets.main.runtimeClasspath
- main 'org.igniterealtime.smack.smackrepl.StateGraph'
-}
diff --git a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/Nio.java b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/Nio.java
index 9f73c42c5..d9d766447 100644
--- a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/Nio.java
+++ b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/Nio.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2018 Florian Schmaus
+ * Copyright 2018-2020 Florian Schmaus
*
* This file is part of smack-repl.
*
@@ -20,20 +20,27 @@
*/
package org.igniterealtime.smack.smackrepl;
+import java.io.BufferedWriter;
import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.logging.Logger;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
+import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
+import org.jivesoftware.smack.compression.CompressionModuleDescriptor;
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
import org.jivesoftware.smack.compression.XMPPInputOutputStream.FlushMethod;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
import org.jivesoftware.smack.packet.Message;
-import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
-import org.jivesoftware.smack.tcp.XmppNioTcpConnection;
+import org.jivesoftware.smack.sm.StreamManagementModuleDescriptor;
+import org.jivesoftware.smack.tcp.XmppTcpTransportModuleDescriptor;
import org.jxmpp.util.XmppDateTime;
@@ -48,10 +55,11 @@ public class Nio {
public static void doNio(String username, String password, String service)
throws SmackException, IOException, XMPPException, InterruptedException {
boolean useTls = true;
+ boolean useStreamMangement = false;
boolean useCompression = true;
boolean useFullFlush = true;
boolean javaNetDebug = false;
- boolean smackDebug = false;
+ boolean smackDebug = true;
if (useFullFlush) {
XMPPInputOutputStream.setFlushMethod(FlushMethod.FULL_FLUSH);
@@ -75,15 +83,29 @@ public class Nio {
smackDebuggerFactory = null;
}
- XMPPTCPConnectionConfiguration configuration = XMPPTCPConnectionConfiguration.builder()
+ ModularXmppClientToServerConnectionConfiguration.Builder configurationBuilder = ModularXmppClientToServerConnectionConfiguration.builder()
.setUsernameAndPassword(username, password)
.setXmppDomain(service)
.setDebuggerFactory(smackDebuggerFactory)
- .setCompressionEnabled(useCompression)
.setSecurityMode(securityMode)
- .build();
+ .removeAllModules()
+ .addModule(XmppTcpTransportModuleDescriptor.class);
- XmppNioTcpConnection connection = new XmppNioTcpConnection(configuration);
+ if (useCompression) {
+ configurationBuilder.addModule(CompressionModuleDescriptor.class);
+ configurationBuilder.setCompressionEnabled(true);
+ }
+ if (useStreamMangement) {
+ configurationBuilder.addModule(StreamManagementModuleDescriptor.class);
+ }
+
+ ModularXmppClientToServerConnectionConfiguration configuration = configurationBuilder.build();
+
+ PrintWriter printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8)));
+ configuration.printStateGraphInDotFormat(printWriter, false);
+ printWriter.flush();
+
+ ModularXmppClientToServerConnection connection = new ModularXmppClientToServerConnection(configuration);
connection.setReplyTimeout(5 * 60 * 1000);
@@ -105,7 +127,7 @@ public class Nio {
connection.disconnect();
- XmppNioTcpConnection.Stats connectionStats = connection.getStats();
+ ModularXmppClientToServerConnection.Stats connectionStats = connection.getStats();
// CHECKSTYLE:OFF
System.out.println("NIO successfully finished, yeah!\n" + connectionStats);
diff --git a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/StateGraph.java b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/StateGraph.java
deleted file mode 100644
index 2908c087f..000000000
--- a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/StateGraph.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- *
- * Copyright 2018 Florian Schmaus
- *
- * This file is part of smack-repl.
- *
- * smack-repl is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-package org.igniterealtime.smack.smackrepl;
-
-import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
-import java.util.Collections;
-
-import org.jivesoftware.smack.fsm.StateDescriptor;
-import org.jivesoftware.smack.fsm.StateDescriptorGraph;
-import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
-import org.jivesoftware.smack.tcp.XmppNioTcpConnection;
-
-public class StateGraph {
-
- @SuppressWarnings("DefaultCharset")
- public static void main(String[] args) throws InstantiationException, IllegalAccessException,
- IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
- GraphVertex stateGraph = StateDescriptorGraph.constructStateDescriptorGraph(XmppNioTcpConnection.getBackwardEdgesStateDescriptors());
-
- PrintWriter pw = new PrintWriter(System.out);
-
- boolean breakStateName = args.length == 0;
-
- StateDescriptorGraph.stateDescriptorGraphToDot(Collections.singleton(stateGraph), pw, breakStateName);
-
- pw.flush();
- }
-
-}
diff --git a/smack-resolver-dnsjava/src/main/java/org/jivesoftware/smack/util/dns/dnsjava/DNSJavaResolver.java b/smack-resolver-dnsjava/src/main/java/org/jivesoftware/smack/util/dns/dnsjava/DNSJavaResolver.java
index afa4dbd72..dc5e096cf 100644
--- a/smack-resolver-dnsjava/src/main/java/org/jivesoftware/smack/util/dns/dnsjava/DNSJavaResolver.java
+++ b/smack-resolver-dnsjava/src/main/java/org/jivesoftware/smack/util/dns/dnsjava/DNSJavaResolver.java
@@ -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.
@@ -16,7 +16,6 @@
*/
package org.jivesoftware.smack.util.dns.dnsjava;
-import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
@@ -24,10 +23,10 @@ import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
import org.jivesoftware.smack.initializer.SmackInitializer;
import org.jivesoftware.smack.util.DNSUtil;
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.rce.RemoteConnectionEndpointLookupFailure;
import org.minidns.dnsname.DnsName;
+import org.minidns.record.SRV;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.TextParseException;
@@ -50,21 +49,26 @@ public class DNSJavaResolver extends DNSResolver implements SmackInitializer {
}
@Override
- protected List lookupSRVRecords0(DnsName name, List failedAddresses, DnssecMode dnssecMode) {
- List res = new ArrayList<>();
-
+ protected List lookupSrvRecords0(DnsName name, List lookupFailures,
+ DnssecMode dnssecMode) {
Lookup lookup;
try {
lookup = new Lookup(name.ace, Type.SRV);
}
catch (TextParseException e) {
- throw new IllegalStateException(e);
+ RemoteConnectionEndpointLookupFailure failure = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
+ name, e);
+ lookupFailures.add(failure);
+ return null;
}
Record[] recs = lookup.run();
- if (recs == null)
- return res;
+ if (recs == null) {
+ // TODO: When does this happen? Do we want/need to record a lookup failure?
+ return null;
+ }
+ List res = new ArrayList<>();
for (Record record : recs) {
org.xbill.DNS.SRVRecord srvRecord = (org.xbill.DNS.SRVRecord) record;
if (srvRecord != null && srvRecord.getTarget() != null) {
@@ -73,12 +77,7 @@ public class DNSJavaResolver extends DNSResolver implements SmackInitializer {
int priority = srvRecord.getPriority();
int weight = srvRecord.getWeight();
- List hostAddresses = lookupHostAddress0(host, failedAddresses, dnssecMode);
- if (shouldContinue(name, host, hostAddresses)) {
- continue;
- }
-
- SRVRecord r = new SRVRecord(host, port, priority, weight, hostAddresses);
+ SRV r = new SRV(priority, weight, port, host);
res.add(r);
}
}
diff --git a/smack-resolver-javax/src/main/java/org/jivesoftware/smack/util/dns/javax/JavaxResolver.java b/smack-resolver-javax/src/main/java/org/jivesoftware/smack/util/dns/javax/JavaxResolver.java
index 957d8006c..ea508f0fe 100644
--- a/smack-resolver-javax/src/main/java/org/jivesoftware/smack/util/dns/javax/JavaxResolver.java
+++ b/smack-resolver-javax/src/main/java/org/jivesoftware/smack/util/dns/javax/JavaxResolver.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2013-2019 Florian Schmaus
+ * Copyright 2013-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
*/
package org.jivesoftware.smack.util.dns.javax;
-import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
@@ -34,10 +33,10 @@ import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
import org.jivesoftware.smack.initializer.SmackInitializer;
import org.jivesoftware.smack.util.DNSUtil;
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.rce.RemoteConnectionEndpointLookupFailure;
import org.minidns.dnsname.DnsName;
+import org.minidns.record.SRV;
/**
* A DNS resolver (mostly for SRV records), which makes use of the API provided in the javax.* namespace.
@@ -84,9 +83,8 @@ public class JavaxResolver extends DNSResolver implements SmackInitializer {
}
@Override
- protected List lookupSRVRecords0(DnsName name, List failedAddresses, DnssecMode dnssecMode) {
- List res = null;
-
+ protected List lookupSrvRecords0(DnsName name, List lookupFailures,
+ DnssecMode dnssecMode) {
Attribute srvAttribute;
try {
Attributes dnsLookup = dirContext.getAttributes(name.ace, new String[] { "SRV" });
@@ -97,14 +95,16 @@ public class JavaxResolver extends DNSResolver implements SmackInitializer {
LOGGER.log(Level.FINEST, "No DNS SRV RR found for " + name, e);
return null;
} catch (NamingException e) {
- LOGGER.log(Level.WARNING, "Exception while resolving DNS SRV RR for " + name, e);
+ RemoteConnectionEndpointLookupFailure failure = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
+ name, e);
+ lookupFailures.add(failure);
return null;
}
+ List res = new ArrayList<>();
try {
@SuppressWarnings("unchecked")
NamingEnumeration srvRecords = (NamingEnumeration) srvAttribute.getAll();
- res = new ArrayList<>();
while (srvRecords.hasMore()) {
String srvRecordString = srvRecords.next();
String[] srvRecordEntries = srvRecordString.split(" ");
@@ -118,19 +118,15 @@ public class JavaxResolver extends DNSResolver implements SmackInitializer {
if (srvTarget.length() > 0 && srvTarget.charAt(srvTarget.length() - 1) == '.') {
srvTarget = srvTarget.substring(0, srvTarget.length() - 1);
}
- DnsName host = DnsName.from(srvTarget);
- List hostAddresses = lookupHostAddress0(host, failedAddresses, dnssecMode);
- if (shouldContinue(name, host, hostAddresses)) {
- continue;
- }
-
- SRVRecord srvRecord = new SRVRecord(host, port, priority, weight, hostAddresses);
+ SRV srvRecord = new SRV(priority, weight, port, srvTarget);
res.add(srvRecord);
}
}
catch (NamingException e) {
- LOGGER.log(Level.SEVERE, "Exception while resolving DNS SRV RR for" + name, e);
+ RemoteConnectionEndpointLookupFailure failure = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
+ name, e);
+ lookupFailures.add(failure);
}
return res;
diff --git a/smack-resolver-minidns/src/main/java/org/jivesoftware/smack/util/dns/minidns/MiniDnsResolver.java b/smack-resolver-minidns/src/main/java/org/jivesoftware/smack/util/dns/minidns/MiniDnsResolver.java
index 615c6a3e1..e4cec5d44 100644
--- a/smack-resolver-minidns/src/main/java/org/jivesoftware/smack/util/dns/minidns/MiniDnsResolver.java
+++ b/smack-resolver-minidns/src/main/java/org/jivesoftware/smack/util/dns/minidns/MiniDnsResolver.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2014-2017 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.
@@ -21,7 +21,6 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -29,12 +28,12 @@ import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
import org.jivesoftware.smack.initializer.SmackInitializer;
import org.jivesoftware.smack.util.DNSUtil;
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.rce.RemoteConnectionEndpointLookupFailure;
import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;
import org.minidns.dnsmessage.Question;
import org.minidns.dnsname.DnsName;
+import org.minidns.dnssec.DnssecResultNotAuthenticException;
import org.minidns.hla.DnssecResolverApi;
import org.minidns.hla.ResolutionUnsuccessfulException;
import org.minidns.hla.ResolverApi;
@@ -66,44 +65,38 @@ public class MiniDnsResolver extends DNSResolver implements SmackInitializer {
}
@Override
- protected List lookupSRVRecords0(final DnsName name, List failedAddresses, DnssecMode dnssecMode) {
+ protected Set lookupSrvRecords0(final DnsName name, List lookupFailures,
+ DnssecMode dnssecMode) {
final ResolverApi resolver = getResolver(dnssecMode);
SrvResolverResult result;
try {
result = resolver.resolveSrv(name);
} catch (IOException e) {
- failedAddresses.add(new HostAddress(name, e));
+ RemoteConnectionEndpointLookupFailure failure = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
+ name, e);
+ lookupFailures.add(failure);
return null;
}
ResolutionUnsuccessfulException resolutionUnsuccessfulException = result.getResolutionUnsuccessfulException();
if (resolutionUnsuccessfulException != null) {
- failedAddresses.add(new HostAddress(name, resolutionUnsuccessfulException));
+ RemoteConnectionEndpointLookupFailure failure = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
+ name, resolutionUnsuccessfulException);
+ lookupFailures.add(failure);
return null;
}
- if (shouldAbortIfNotAuthentic(name, dnssecMode, result, failedAddresses)) {
+ if (shouldAbortIfNotAuthentic(name, dnssecMode, result, lookupFailures)) {
return null;
}
- List res = new LinkedList<>();
- for (SRV srv : result.getAnswers()) {
- DnsName hostname = srv.target;
- List hostAddresses = lookupHostAddress0(hostname, failedAddresses, dnssecMode);
- if (shouldContinue(name, hostname, hostAddresses)) {
- continue;
- }
-
- SRVRecord srvRecord = new SRVRecord(hostname, srv.port, srv.priority, srv.weight, hostAddresses);
- res.add(srvRecord);
- }
-
- return res;
+ return result.getAnswers();
}
@Override
- protected List lookupHostAddress0(final DnsName name, List failedAddresses, DnssecMode dnssecMode) {
+ protected List lookupHostAddress0(final DnsName name,
+ List lookupFailures, DnssecMode dnssecMode) {
final ResolverApi resolver = getResolver(dnssecMode);
final ResolverResult aResult;
@@ -113,19 +106,25 @@ public class MiniDnsResolver extends DNSResolver implements SmackInitializer {
aResult = resolver.resolve(name, A.class);
aaaaResult = resolver.resolve(name, AAAA.class);
} catch (IOException e) {
- failedAddresses.add(new HostAddress(name, e));
+ RemoteConnectionEndpointLookupFailure failure = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
+ name, e);
+ lookupFailures.add(failure);
return null;
}
if (!aResult.wasSuccessful() && !aaaaResult.wasSuccessful()) {
// Both results where not successful.
- failedAddresses.add(new HostAddress(name, getExceptionFrom(aResult)));
- failedAddresses.add(new HostAddress(name, getExceptionFrom(aaaaResult)));
+ RemoteConnectionEndpointLookupFailure failureA = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
+ name, getExceptionFrom(aResult));
+ lookupFailures.add(failureA);
+ RemoteConnectionEndpointLookupFailure failureAaaa = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
+ name, getExceptionFrom(aaaaResult));
+ lookupFailures.add(failureAaaa);
return null;
}
- if (shouldAbortIfNotAuthentic(name, dnssecMode, aResult, failedAddresses)
- || shouldAbortIfNotAuthentic(name, dnssecMode, aaaaResult, failedAddresses)) {
+ if (shouldAbortIfNotAuthentic(name, dnssecMode, aResult, lookupFailures)
+ || shouldAbortIfNotAuthentic(name, dnssecMode, aaaaResult, lookupFailures)) {
return null;
}
@@ -194,15 +193,16 @@ public class MiniDnsResolver extends DNSResolver implements SmackInitializer {
}
private static boolean shouldAbortIfNotAuthentic(DnsName name, DnssecMode dnssecMode,
- ResolverResult> result, List failedAddresses) {
+ ResolverResult> result, List lookupFailures) {
switch (dnssecMode) {
case needsDnssec:
case needsDnssecAndDane:
// Check if the result is authentic data, i.e. there a no reasons the result is unverified.
- // TODO: Use ResolverResult.getDnssecResultNotAuthenticException() of newer MiniDNS versions.
- if (!result.isAuthenticData()) {
- Exception exception = new Exception("DNSSEC verification failed: " + result.getUnverifiedReasons().iterator().next().getReasonString());
- failedAddresses.add(new HostAddress(name, exception));
+ DnssecResultNotAuthenticException exception = result.getDnssecResultNotAuthenticException();
+ if (exception != null) {
+ RemoteConnectionEndpointLookupFailure failure = new RemoteConnectionEndpointLookupFailure.DnsLookupFailure(
+ name, exception);
+ lookupFailures.add(failure);
return true;
}
break;
diff --git a/smack-tcp/Makefile b/smack-tcp/Makefile
deleted file mode 100644
index 8decb8444..000000000
--- a/smack-tcp/Makefile
+++ /dev/null
@@ -1,21 +0,0 @@
-.PHONY := all clean
-
-GRADLE_QUITE_ARGS := --quiet --console plain
-
-XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG := src/javadoc/org/jivesoftware/smack/tcp/doc-files/XmppNioTcpConnectionStateGraph.png
-XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT := $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG:.png=.dot)
-
-GENERATED_FILES := $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG) $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT)
-
-all: $(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_PNG)
-
-clean:
- rm -f $(GENERATED_FILES)
-
-%.png: %.dot
- dot -Tpng -o $@ $^
-
-$(XMPP_NIO_TCP_CONNECTION_STATE_GRAPH_DOT): src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnection.java ../smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java
- # TODO: This also creates the dot file even if the command
- # fails. It would be better if this was not the case.
- gradle $(GRADLE_QUITE_ARGS) :smack-repl:printXmppNioTcpConnectionStateGraph > $@
diff --git a/smack-tcp/src/javadoc/org/jivesoftware/smack/tcp/doc-files/.gitignore b/smack-tcp/src/javadoc/org/jivesoftware/smack/tcp/doc-files/.gitignore
deleted file mode 100644
index 35616fd31..000000000
--- a/smack-tcp/src/javadoc/org/jivesoftware/smack/tcp/doc-files/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/XmppNioTcpConnectionStateGraph.png
-/XmppNioTcpConnectionStateGraph.dot
diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java b/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java
new file mode 100644
index 000000000..58be31a34
--- /dev/null
+++ b/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModule.java
@@ -0,0 +1,141 @@
+/**
+ *
+ * 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.sm;
+
+import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedAndResourceBoundStateDescriptor;
+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.compression.CompressionModule.CompressionStateDescriptor;
+import org.jivesoftware.smack.fsm.State;
+import org.jivesoftware.smack.fsm.StateDescriptor;
+import org.jivesoftware.smack.fsm.StateTransitionResult;
+
+public class StreamManagementModule extends ModularXmppClientToServerConnectionModule {
+
+ protected StreamManagementModule(StreamManagementModuleDescriptor moduleDescriptor,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(moduleDescriptor, connectionInternal);
+ }
+
+ private boolean useSm = true;
+
+ private boolean useSmResumption = true;
+
+ public static final class EnableStreamManagementStateDescriptor extends StateDescriptor {
+
+ private EnableStreamManagementStateDescriptor() {
+ super(StreamManagementModule.EnableStreamManagementState.class, 198, StateDescriptor.Property.notImplemented);
+
+ addPredeccessor(ResourceBindingStateDescriptor.class);
+ addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
+ declarePrecedenceOver(AuthenticatedAndResourceBoundStateDescriptor.class);
+ }
+
+ @Override
+ protected StreamManagementModule.EnableStreamManagementState 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.
+ StreamManagementModule smModule = connectionInternal.connection.getConnectionModuleFor(StreamManagementModuleDescriptor.class);
+ return smModule.constructEnableStreamMangementState(this, connectionInternal);
+ }
+
+ }
+
+ private EnableStreamManagementState constructEnableStreamMangementState(
+ EnableStreamManagementStateDescriptor enableStreamManagementStateDescriptor,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ return new EnableStreamManagementState(enableStreamManagementStateDescriptor, connectionInternal);
+ }
+
+ private final class EnableStreamManagementState extends State {
+ private EnableStreamManagementState(EnableStreamManagementStateDescriptor stateDescriptor,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(stateDescriptor, connectionInternal);
+ }
+
+ @Override
+ public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
+ if (!useSm) {
+ return new StateTransitionResult.TransitionImpossibleReason("Stream management not enabled");
+ }
+
+ return new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(stateDescriptor);
+ }
+
+ @Override
+ public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
+ throw new IllegalStateException("SM not implemented");
+ }
+ }
+
+ public static final class ResumeStreamStateDescriptor extends StateDescriptor {
+ private ResumeStreamStateDescriptor() {
+ super(StreamManagementModule.ResumeStreamState.class, 198, StateDescriptor.Property.notImplemented);
+
+ addPredeccessor(AuthenticatedButUnboundStateDescriptor.class);
+ addSuccessor(AuthenticatedAndResourceBoundStateDescriptor.class);
+ declarePrecedenceOver(ResourceBindingStateDescriptor.class);
+ declareInferiortyTo(CompressionStateDescriptor.class);
+ }
+
+ @Override
+ protected StreamManagementModule.ResumeStreamState constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
+ StreamManagementModule smModule = connectionInternal.connection.getConnectionModuleFor(StreamManagementModuleDescriptor.class);
+ return smModule.constructResumeStreamState(this, connectionInternal);
+ }
+
+ }
+
+ private ResumeStreamState constructResumeStreamState(
+ ResumeStreamStateDescriptor resumeStreamStateDescriptor,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ return new ResumeStreamState(resumeStreamStateDescriptor, connectionInternal);
+ }
+
+ private final class ResumeStreamState extends State {
+ private ResumeStreamState(ResumeStreamStateDescriptor stateDescriptor,
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ super(stateDescriptor, connectionInternal);
+ }
+
+
+ @Override
+ public StateTransitionResult.TransitionImpossible isTransitionToPossible(WalkStateGraphContext walkStateGraphContext) {
+ if (!useSmResumption) {
+ return new StateTransitionResult.TransitionImpossibleReason("Stream resumption not enabled");
+ }
+
+ return new StateTransitionResult.TransitionImpossibleBecauseNotImplemented(stateDescriptor);
+ }
+
+ @Override
+ public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
+ throw new IllegalStateException("Stream resumption not implemented");
+ }
+ }
+
+ public void setStreamManagementEnabled(boolean useSm) {
+ this.useSm = useSm;
+ }
+
+ public void setStreamResumptionEnabled(boolean useSmResumption) {
+ this.useSmResumption = useSmResumption;
+ }
+
+}
diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModuleDescriptor.java b/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModuleDescriptor.java
new file mode 100644
index 000000000..3620ca63a
--- /dev/null
+++ b/smack-tcp/src/main/java/org/jivesoftware/smack/sm/StreamManagementModuleDescriptor.java
@@ -0,0 +1,58 @@
+/**
+ *
+ * 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.sm;
+
+import java.util.HashSet;
+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;
+import org.jivesoftware.smack.sm.StreamManagementModule.EnableStreamManagementStateDescriptor;
+import org.jivesoftware.smack.sm.StreamManagementModule.ResumeStreamStateDescriptor;
+
+public class StreamManagementModuleDescriptor extends ModularXmppClientToServerConnectionModuleDescriptor {
+
+ private static final StreamManagementModuleDescriptor INSTANCE = new StreamManagementModuleDescriptor();
+
+ @Override
+ protected Set> getStateDescriptors() {
+ Set> res = new HashSet<>();
+ res.add(EnableStreamManagementStateDescriptor.class);
+ res.add(ResumeStreamStateDescriptor.class);
+ return res;
+ }
+
+ @Override
+ protected StreamManagementModule constructXmppConnectionModule(
+ ModularXmppClientToServerConnectionInternal connectionInternal) {
+ return new StreamManagementModule(this, connectionInternal);
+ }
+
+ public static class Builder extends ModularXmppClientToServerConnectionModuleDescriptor.Builder {
+
+ protected Builder(ModularXmppClientToServerConnectionConfiguration.Builder connectionConfigurationBuilder) {
+ super(connectionConfigurationBuilder);
+ }
+
+ @Override
+ protected StreamManagementModuleDescriptor build() {
+ return INSTANCE;
+ }
+ }
+}
diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/ConnectionAttemptState.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/ConnectionAttemptState.java
new file mode 100644
index 000000000..415af19f9
--- /dev/null
+++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/ConnectionAttemptState.java
@@ -0,0 +1,175 @@
+/**
+ *
+ * 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.tcp;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jivesoftware.smack.SmackException.ConnectionException;
+import org.jivesoftware.smack.SmackException.EndpointConnectionException;
+import org.jivesoftware.smack.SynchronizationPoint;
+import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
+import org.jivesoftware.smack.tcp.XmppTcpTransportModule.EstablishingTcpConnectionState;
+import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
+import org.jivesoftware.smack.util.Async;
+import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
+import org.jivesoftware.smack.util.rce.RemoteConnectionException;
+
+public final class ConnectionAttemptState {
+
+ private final ModularXmppClientToServerConnectionInternal connectionInternal;
+
+ private final XmppTcpTransportModule.XmppTcpNioTransport.DiscoveredTcpEndpoints discoveredEndpoints;
+
+ private final EstablishingTcpConnectionState establishingTcpConnectionState;
+
+ // TODO: Check if we can re-use the socket channel in case some InetSocketAddress fail to connect to.
+ final SocketChannel socketChannel;
+
+ final List> connectionExceptions;
+ final SynchronizationPoint tcpConnectionEstablishedSyncPoint;
+
+ final Iterator connectionEndpointIterator;
+ /** The current connection endpoint we are trying */
+ Rfc6120TcpRemoteConnectionEndpoint connectionEndpoint;
+ Iterator extends InetAddress> inetAddressIterator;
+
+ ConnectionAttemptState(ModularXmppClientToServerConnectionInternal connectionInternal,
+ XmppTcpTransportModule.XmppTcpNioTransport.DiscoveredTcpEndpoints discoveredEndpoints,
+ EstablishingTcpConnectionState establishingTcpConnectionState) throws IOException {
+ this.connectionInternal = connectionInternal;
+ this.discoveredEndpoints = discoveredEndpoints;
+ this.establishingTcpConnectionState = establishingTcpConnectionState;
+
+ socketChannel = SocketChannel.open();
+ socketChannel.configureBlocking(false);
+
+ connectionEndpointIterator = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.iterator();
+ connectionEndpoint = connectionEndpointIterator.next();
+ connectionExceptions = new ArrayList<>(discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.size());
+
+ tcpConnectionEstablishedSyncPoint = new SynchronizationPoint<>(connectionInternal.connection,
+ "TCP connection establishment");
+ }
+
+ void establishTcpConnection() {
+ RemoteConnectionEndpoint.InetSocketAddressCoupling address = nextAddress();
+ establishTcpConnection(address);
+ }
+
+ private void establishTcpConnection(
+ RemoteConnectionEndpoint.InetSocketAddressCoupling address) {
+ TcpHostEvent.ConnectingToHostEvent connectingToHostEvent = new TcpHostEvent.ConnectingToHostEvent(
+ establishingTcpConnectionState, address);
+ connectionInternal.invokeConnectionStateMachineListener(connectingToHostEvent);
+
+ final boolean connected;
+ final InetSocketAddress inetSocketAddress = address.getInetSocketAddress();
+ try {
+ connected = socketChannel.connect(inetSocketAddress);
+ } catch (IOException e) {
+ onIOExceptionWhenEstablishingTcpConnection(e, address);
+ return;
+ }
+
+ if (connected) {
+ TcpHostEvent.ConnectedToHostEvent connectedToHostEvent = new TcpHostEvent.ConnectedToHostEvent(
+ establishingTcpConnectionState, address, true);
+ connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
+
+ tcpConnectionEstablishedSyncPoint.reportSuccess();
+ return;
+ }
+
+ try {
+ connectionInternal.registerWithSelector(socketChannel, SelectionKey.OP_CONNECT,
+ (selectedChannel, selectedSelectionKey) -> {
+ SocketChannel selectedSocketChannel = (SocketChannel) selectedChannel;
+
+ boolean finishConnected;
+ try {
+ finishConnected = selectedSocketChannel.finishConnect();
+ } catch (IOException e) {
+ Async.go(() -> onIOExceptionWhenEstablishingTcpConnection(e, address));
+ return;
+ }
+
+ if (!finishConnected) {
+ Async.go(() -> onIOExceptionWhenEstablishingTcpConnection(new IOException("finishConnect() failed"), address));
+ return;
+ }
+
+ TcpHostEvent.ConnectedToHostEvent connectedToHostEvent = new TcpHostEvent.ConnectedToHostEvent(
+ establishingTcpConnectionState, address, false);
+ connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
+
+ // Do not set 'state' here, since this is processed by a reactor thread, which doesn't hold
+ // the objects lock.
+ tcpConnectionEstablishedSyncPoint.reportSuccess();
+ });
+ } catch (ClosedChannelException e) {
+ onIOExceptionWhenEstablishingTcpConnection(e, address);
+ }
+ }
+
+ private void onIOExceptionWhenEstablishingTcpConnection(IOException exception,
+ RemoteConnectionEndpoint.InetSocketAddressCoupling failedAddress) {
+ RemoteConnectionEndpoint.InetSocketAddressCoupling nextInetSocketAddress = nextAddress();
+ if (nextInetSocketAddress == null) {
+ EndpointConnectionException connectionException = EndpointConnectionException.from(
+ discoveredEndpoints.result.lookupFailures, connectionExceptions);
+ tcpConnectionEstablishedSyncPoint.reportFailure(connectionException);
+ return;
+ }
+
+ tcpConnectionEstablishedSyncPoint.resetTimeout();
+
+ RemoteConnectionException rce = new RemoteConnectionException<>(
+ failedAddress, exception);
+ connectionExceptions.add(rce);
+
+ TcpHostEvent.ConnectionToHostFailedEvent connectionToHostFailedEvent = new TcpHostEvent.ConnectionToHostFailedEvent(
+ establishingTcpConnectionState, nextInetSocketAddress, exception);
+ connectionInternal.invokeConnectionStateMachineListener(connectionToHostFailedEvent);
+
+ establishTcpConnection(nextInetSocketAddress);
+ }
+
+ private RemoteConnectionEndpoint.InetSocketAddressCoupling nextAddress() {
+ if (inetAddressIterator == null || !inetAddressIterator.hasNext()) {
+ if (!connectionEndpointIterator.hasNext()) {
+ return null;
+ }
+
+ connectionEndpoint = connectionEndpointIterator.next();
+ inetAddressIterator = connectionEndpoint.getInetAddresses().iterator();
+ // Every valid connection addresspoint must have a non-empty collection of inet addresses.
+ assert inetAddressIterator.hasNext();
+ }
+
+ InetAddress inetAddress = inetAddressIterator.next();
+
+ return new RemoteConnectionEndpoint.InetSocketAddressCoupling<>(connectionEndpoint, inetAddress);
+ }
+}
diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/TCPInitializer.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/TCPInitializer.java
index 7ecf5b4db..0630af22a 100644
--- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/TCPInitializer.java
+++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/TCPInitializer.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright © 2014 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,10 +16,17 @@
*/
package org.jivesoftware.smack.tcp;
+import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.initializer.UrlInitializer;
+import org.jivesoftware.smack.sm.StreamManagementModuleDescriptor;
public class TCPInitializer extends UrlInitializer {
+ static {
+ SmackConfiguration.addModule(StreamManagementModuleDescriptor.class);
+ SmackConfiguration.addModule(XmppTcpTransportModuleDescriptor.class);
+ }
+
@Override
protected String getProvidersUri() {
return "classpath:org.jivesoftware.smack.tcp/smacktcp.providers";
diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/TcpHostEvent.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/TcpHostEvent.java
new file mode 100644
index 000000000..8c25987d6
--- /dev/null
+++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/TcpHostEvent.java
@@ -0,0 +1,79 @@
+/**
+ *
+ * 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.tcp;
+
+import java.io.IOException;
+
+import org.jivesoftware.smack.fsm.ConnectionStateEvent.DetailedTransitionIntoInformation;
+import org.jivesoftware.smack.fsm.State;
+import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
+import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
+
+public abstract class TcpHostEvent extends DetailedTransitionIntoInformation {
+ protected final RemoteConnectionEndpoint.InetSocketAddressCoupling address;
+
+ protected TcpHostEvent(State state, RemoteConnectionEndpoint.InetSocketAddressCoupling address) {
+ super(state);
+ this.address = address;
+ }
+
+ public RemoteConnectionEndpoint.InetSocketAddressCoupling getAddress() {
+ return address;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ": " + address;
+ }
+
+ public static final class ConnectingToHostEvent extends TcpHostEvent {
+ ConnectingToHostEvent(State state,
+ RemoteConnectionEndpoint.InetSocketAddressCoupling address) {
+ super(state, address);
+ }
+ }
+
+ public static final class ConnectedToHostEvent extends TcpHostEvent {
+ private final boolean connectionEstablishedImmediately;
+
+ ConnectedToHostEvent(State state, RemoteConnectionEndpoint.InetSocketAddressCoupling address, boolean immediately) {
+ super(state, address);
+ this.connectionEstablishedImmediately = immediately;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + (connectionEstablishedImmediately ? "" : " not") + " connected immediately";
+ }
+ }
+
+ public static final class ConnectionToHostFailedEvent extends TcpHostEvent {
+ private final IOException ioException;
+
+ ConnectionToHostFailedEvent(State state,
+ RemoteConnectionEndpoint.InetSocketAddressCoupling address,
+ IOException ioException) {
+ super(state, address);
+ this.ioException = ioException;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ioException;
+ }
+ }
+}
diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
index c4ee49ca9..068dc867d 100644
--- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
+++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
@@ -64,6 +64,7 @@ import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.AlreadyConnectedException;
import org.jivesoftware.smack.SmackException.AlreadyLoggedInException;
import org.jivesoftware.smack.SmackException.ConnectionException;
+import org.jivesoftware.smack.SmackException.EndpointConnectionException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.NotLoggedInException;
@@ -78,6 +79,7 @@ import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.jivesoftware.smack.compress.packet.Compress;
import org.jivesoftware.smack.compress.packet.Compressed;
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
+import org.jivesoftware.smack.datatypes.UInt16;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.packet.IQ;
@@ -105,6 +107,8 @@ import org.jivesoftware.smack.sm.packet.StreamManagement.Resumed;
import org.jivesoftware.smack.sm.packet.StreamManagement.StreamManagementFeature;
import org.jivesoftware.smack.sm.predicates.Predicate;
import org.jivesoftware.smack.sm.provider.ParseStreamManagement;
+import org.jivesoftware.smack.tcp.rce.RemoteXmppTcpConnectionEndpoints;
+import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.CloseableUtil;
@@ -112,7 +116,7 @@ import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.TLSUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
-import org.jivesoftware.smack.util.dns.HostAddress;
+import org.jivesoftware.smack.util.rce.RemoteConnectionException;
import org.jivesoftware.smack.xml.SmackXmlParser;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
@@ -556,19 +560,23 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
private void connectUsingConfiguration() throws ConnectionException, IOException, InterruptedException {
- List failedAddresses = populateHostAddresses();
+ RemoteXmppTcpConnectionEndpoints.Result result = RemoteXmppTcpConnectionEndpoints.lookup(config);
+
+ List> connectionExceptions = new ArrayList<>();
+
SocketFactory socketFactory = config.getSocketFactory();
ProxyInfo proxyInfo = config.getProxyInfo();
int timeout = config.getConnectTimeout();
if (socketFactory == null) {
socketFactory = SocketFactory.getDefault();
}
- for (HostAddress hostAddress : hostAddresses) {
- Iterator inetAddresses;
- String host = hostAddress.getHost();
- int port = hostAddress.getPort();
+ for (Rfc6120TcpRemoteConnectionEndpoint endpoint : result.discoveredRemoteConnectionEndpoints) {
+ Iterator extends InetAddress> inetAddresses;
+ String host = endpoint.getHost().toString();
+ UInt16 portUint16 = endpoint.getPort();
+ int port = portUint16.intValue();
if (proxyInfo == null) {
- inetAddresses = hostAddress.getInetAddresses().iterator();
+ inetAddresses = endpoint.getInetAddresses().iterator();
assert inetAddresses.hasNext();
innerloop: while (inetAddresses.hasNext()) {
@@ -584,7 +592,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
try {
socket = socketFuture.getOrThrow();
} catch (IOException e) {
- hostAddress.setException(inetAddress, e);
+ RemoteConnectionException rce = new RemoteConnectionException<>(
+ endpoint, inetAddress, e);
+ connectionExceptions.add(rce);
if (inetAddresses.hasNext()) {
continue innerloop;
} else {
@@ -594,34 +604,36 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
LOGGER.finer("Established TCP connection to " + inetSocketAddress);
// We found a host to connect to, return here
this.host = host;
- this.port = port;
+ this.port = portUint16;
return;
}
- failedAddresses.add(hostAddress);
} else {
+ // TODO: Move this into the inner-loop above. There appears no reason why we should not try a proxy
+ // connection to every inet address of each connection endpoint.
socket = socketFactory.createSocket();
- StringUtils.requireNotNullNorEmpty(host, "Host of HostAddress " + hostAddress + " must not be null when using a Proxy");
+ StringUtils.requireNotNullNorEmpty(host, "Host of endpoint " + endpoint + " must not be null when using a Proxy");
final String hostAndPort = host + " at port " + port;
LOGGER.finer("Trying to establish TCP connection via Proxy to " + hostAndPort);
try {
proxyInfo.getProxySocketConnection().connect(socket, host, port, timeout);
} catch (IOException e) {
CloseableUtil.maybeClose(socket, LOGGER);
- hostAddress.setException(e);
- failedAddresses.add(hostAddress);
+ RemoteConnectionException rce = new RemoteConnectionException<>(endpoint, null, e);
+ connectionExceptions.add(rce);
continue;
}
LOGGER.finer("Established TCP connection to " + hostAndPort);
// We found a host to connect to, return here
this.host = host;
- this.port = port;
+ this.port = portUint16;
return;
}
}
+
// There are no more host addresses to try
// throw an exception and report all tried
// HostAddresses in the exception
- throw ConnectionException.from(failedAddresses);
+ throw EndpointConnectionException.from(result.lookupFailures, connectionExceptions);
}
/**
@@ -815,7 +827,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
*/
@Override
protected void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException {
- closingStreamReceived.init();
// Establishes the TCP connection to the server and does setup the reader and writer. Throws an exception if
// there is an error establishing the connection
connectUsingConfiguration();
@@ -1125,6 +1136,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
}
catch (Exception e) {
+ // TODO: Move the call closingStreamReceived.reportFailure(e) into notifyConnectionError?
closingStreamReceived.reportFailure(e);
// The exception can be ignored if the the connection is 'done'
// or if the it was caused because the socket got closed. It can not be ignored if it
diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnection.java
deleted file mode 100644
index 94d4abf49..000000000
--- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnection.java
+++ /dev/null
@@ -1,1883 +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.tcp;
-
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.nio.Buffer;
-import java.nio.ByteBuffer;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-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.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSession;
-
-import org.jivesoftware.smack.AbstractXmppNioConnection;
-import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
-import org.jivesoftware.smack.SmackException;
-import org.jivesoftware.smack.SmackException.ConnectionException;
-import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
-import org.jivesoftware.smack.SmackException.NoResponseException;
-import org.jivesoftware.smack.SmackException.NotConnectedException;
-import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
-import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
-import org.jivesoftware.smack.SmackException.SmackWrappedException;
-import org.jivesoftware.smack.SmackReactor.ChannelSelectedCallback;
-import org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment;
-import org.jivesoftware.smack.SynchronizationPoint;
-import org.jivesoftware.smack.XMPPException;
-import org.jivesoftware.smack.XMPPException.FailedNonzaException;
-import org.jivesoftware.smack.XMPPException.XMPPErrorException;
-import org.jivesoftware.smack.XmppInputOutputFilter;
-import org.jivesoftware.smack.fsm.ConnectionStateEvent.DetailedTransitionIntoInformation;
-import org.jivesoftware.smack.fsm.StateDescriptor;
-import org.jivesoftware.smack.fsm.StateDescriptorGraph;
-import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
-import org.jivesoftware.smack.packet.Nonza;
-import org.jivesoftware.smack.packet.Stanza;
-import org.jivesoftware.smack.packet.StartTls;
-import org.jivesoftware.smack.packet.StreamClose;
-import org.jivesoftware.smack.packet.StreamOpen;
-import org.jivesoftware.smack.packet.TlsFailure;
-import org.jivesoftware.smack.packet.TlsProceed;
-import org.jivesoftware.smack.packet.TopLevelStreamElement;
-import org.jivesoftware.smack.sasl.SASLErrorException;
-import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
-import org.jivesoftware.smack.util.Async;
-import org.jivesoftware.smack.util.CollectionUtil;
-import org.jivesoftware.smack.util.PacketParserUtils;
-import org.jivesoftware.smack.util.StringUtils;
-import org.jivesoftware.smack.util.UTF8;
-import org.jivesoftware.smack.util.XmlStringBuilder;
-import org.jivesoftware.smack.util.dns.HostAddress;
-import org.jivesoftware.smack.xml.XmlPullParser;
-import org.jivesoftware.smack.xml.XmlPullParserException;
-
-import org.jxmpp.jid.DomainBareJid;
-import org.jxmpp.jid.Jid;
-import org.jxmpp.jid.impl.JidCreate;
-import org.jxmpp.jid.util.JidUtil;
-import org.jxmpp.stringprep.XmppStringprepException;
-import org.jxmpp.xml.splitter.Utf8ByteXmppXmlSplitter;
-import org.jxmpp.xml.splitter.XmlPrettyPrinter;
-import org.jxmpp.xml.splitter.XmlPrinter;
-import org.jxmpp.xml.splitter.XmppElementCallback;
-import org.jxmpp.xml.splitter.XmppXmlSplitter;
-
-/**
- * Represents and manages a client connection to an XMPP server via TCP.
- *
- * Smack XMPP TCP NIO connection states
- *
- * The graph below shows the current graph of states of this XMPP connection. Only some states are final states, most
- * states are intermediate states in order to reach a final state.
- *
- *
- *
- */
-public class XmppNioTcpConnection extends AbstractXmppNioConnection {
-
- private static final Logger LOGGER = Logger.getLogger(XmppNioTcpConnection.class.getName());
-
- private static final Set> BACKWARD_EDGES_STATE_DESCRIPTORS = new HashSet<>();
-
- static final GraphVertex INITIAL_STATE_DESCRIPTOR_VERTEX;
-
- static {
- BACKWARD_EDGES_STATE_DESCRIPTORS.add(LookupHostAddressesStateDescriptor.class);
- BACKWARD_EDGES_STATE_DESCRIPTORS.add(EnableStreamManagementStateDescriptor.class);
- BACKWARD_EDGES_STATE_DESCRIPTORS.add(ResumeStreamStateDescriptor.class);
- BACKWARD_EDGES_STATE_DESCRIPTORS.add(InstantStreamResumptionStateDescriptor.class);
- BACKWARD_EDGES_STATE_DESCRIPTORS.add(Bind2StateDescriptor.class);
- BACKWARD_EDGES_STATE_DESCRIPTORS.add(InstantShutdownStateDescriptor.class);
- BACKWARD_EDGES_STATE_DESCRIPTORS.add(ShutdownStateDescriptor.class);
-
- try {
- INITIAL_STATE_DESCRIPTOR_VERTEX = StateDescriptorGraph.constructStateDescriptorGraph(BACKWARD_EDGES_STATE_DESCRIPTORS);
- } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
- | NoSuchMethodException | SecurityException e) {
- throw new IllegalStateException(e);
- }
- }
-
- private static final int CALLBACK_MAX_BYTES_READ = 10 * 1024 * 1024;
- private static final int CALLBACK_MAX_BYTES_WRITEN = CALLBACK_MAX_BYTES_READ;
-
- private static final int MAX_ELEMENT_SIZE = 64 * 1024;
-
- private SelectionKey selectionKey;
- private SelectionKeyAttachment selectionKeyAttachment;
- private SocketChannel socketChannel;
- private InetSocketAddress remoteAddress;
- private TlsState tlsState;
-
- /**
- * Note that this field is effective final, but due to https://stackoverflow.com/q/30360824/194894 we have to declare it non-final.
- */
- private Utf8ByteXmppXmlSplitter splitter;
-
- /**
- * Note that this field is effective final, but due to https://stackoverflow.com/q/30360824/194894 we have to declare it non-final.
- */
- private XmppXmlSplitter outputDebugSplitter;
-
- private static final Level STREAM_OPEN_CLOSE_DEBUG_LOG_LEVEL = Level.FINER;
-
- private final XmppElementCallback xmppElementCallback = new XmppElementCallback() {
- private String streamOpen;
- private String streamClose;
-
- @Override
- public void onCompleteElement(String completeElement) {
- assert streamOpen != null;
- assert streamClose != null;
-
- if (debugger != null) {
- debugger.onIncomingElementCompleted();
- }
-
- String wrappedCompleteElement = streamOpen + completeElement + streamClose;
- try {
- parseAndProcessElement(wrappedCompleteElement);
- } catch (Exception e) {
- notifyConnectionError(e);
- }
- }
-
-
- @Override
- public void streamOpened(String prefix, Map attributes) {
- if (LOGGER.isLoggable(STREAM_OPEN_CLOSE_DEBUG_LOG_LEVEL)) {
- LOGGER.log(STREAM_OPEN_CLOSE_DEBUG_LOG_LEVEL,
- "Stream of " + this + " opened. prefix=" + prefix + " attributes=" + attributes);
- }
-
- final String prefixXmlns = "xmlns:" + prefix;
- final StringBuilder streamClose = new StringBuilder(32);
- final StringBuilder streamOpen = new StringBuilder(256);
-
- streamOpen.append('<');
- streamClose.append("");
- if (StringUtils.isNotEmpty(prefix)) {
- streamOpen.append(prefix).append(':');
- streamClose.append(prefix).append(':');
- }
- streamOpen.append("stream");
- streamClose.append("stream>");
- for (Entry entry : attributes.entrySet()) {
- String attributeName = entry.getKey();
- String attributeValue = entry.getValue();
- switch (attributeName) {
- case "id":
- streamId = attributeValue;
- break;
- case "version":
- break;
- case "xml:lang":
- streamOpen.append(" xml:lang='").append(attributeValue).append('\'');
- break;
- case "to":
- break;
- case "from":
- DomainBareJid reportedServerDomain;
- try {
- reportedServerDomain = JidCreate.domainBareFrom(attributeValue);
- } catch (XmppStringprepException e) {
- IllegalStateException ise = new IllegalStateException(
- "Reporting server domain '" + attributeValue + "' is not a valid JID", e);
- notifyConnectionError(ise);
- return;
- }
- assert config.getXMPPServiceDomain().equals(reportedServerDomain);
- break;
- case "xmlns":
- streamOpen.append(" xmlns='").append(attributeValue).append('\'');
- break;
- default:
- if (attributeName.equals(prefixXmlns)) {
- streamOpen.append(' ').append(prefixXmlns).append("='").append(attributeValue).append('\'');
- break;
- }
- LOGGER.info("Unknown attribute: " + attributeName);
- break;
- }
- }
- streamOpen.append('>');
-
- this.streamOpen = streamOpen.toString();
- this.streamClose = streamClose.toString();
-
- XmlPullParser streamOpenParser;
- try {
- streamOpenParser = PacketParserUtils.getParserFor(this.streamOpen);
- } catch (XmlPullParserException | IOException e) {
- // Should never happen.
- throw new AssertionError(e);
- }
- onStreamOpen(streamOpenParser);
- }
-
- @Override
- public void streamClosed() {
- if (LOGGER.isLoggable(STREAM_OPEN_CLOSE_DEBUG_LOG_LEVEL)) {
- LOGGER.log(STREAM_OPEN_CLOSE_DEBUG_LOG_LEVEL, "Stream of " + this + " closed");
- }
-
- closingStreamReceived.reportSuccess();
- }
- };
-
- private final ArrayBlockingQueueWithShutdown outgoingElementsQueue = new ArrayBlockingQueueWithShutdown<>(
- 100, true);
-
- private Iterator outgoingCharSequenceIterator;
-
- private final List currentlyOutgoingElements = new ArrayList<>();
- private final Map> bufferToElementMap = new IdentityHashMap<>();
-
- private ByteBuffer outgoingBuffer;
- private ByteBuffer filteredOutgoingBuffer;
- private final List networkOutgoingBuffers = new ArrayList<>();
- private long networkOutgoingBuffersBytes;
-
- // TODO: Make the size of the incomingBuffer configurable.
- private final ByteBuffer incomingBuffer = ByteBuffer.allocateDirect(2 * 4096);
-
- private final ReentrantLock channelSelectedCallbackLock = new ReentrantLock();
-
- private long totalBytesRead;
- private long totalBytesWritten;
- private long totalBytesReadAfterFilter;
- private long totalBytesWrittenBeforeFilter;
- private long handledChannelSelectedCallbacks;
- private long callbackPreemtBecauseBytesWritten;
- private long callbackPreemtBecauseBytesRead;
- private int sslEngineDelegatedTasks;
- private int maxPendingSslEngineDelegatedTasks;
-
- // TODO: Use LongAdder once Smack's minimum Android API level is 24 or higher.
- private final AtomicLong setWriteInterestAfterChannelSelectedCallback = new AtomicLong();
- private final AtomicLong reactorThreadAlreadyRacing = new AtomicLong();
- private final AtomicLong afterOutgoingElementsQueueModifiedSetInterestOps = new AtomicLong();
- private final AtomicLong rejectedChannelSelectedCallbacks = new AtomicLong();
-
- private Jid lastDestinationAddress;
-
- private boolean pendingInputFilterData;
- private boolean pendingOutputFilterData;
-
- private boolean pendingWriteInterestAfterRead;
-
- private boolean useDirectTls = false;
-
- private boolean useSm = false;
- private boolean useSmResumption = false;
- private boolean useIsr = false;
-
- private boolean useBind2 = false;
-
- public XmppNioTcpConnection(XMPPTCPConnectionConfiguration configuration) {
- super(configuration, INITIAL_STATE_DESCRIPTOR_VERTEX);
-
- XmlPrinter incomingDebugPrettyPrinter = null;
- if (debugger != null) {
- // Incoming stream debugging.
- incomingDebugPrettyPrinter = XmlPrettyPrinter.builder()
- .setPrettyWriter(sb -> debugger.incomingStreamSink(sb))
- .build();
-
- // Outgoing stream debugging.
- XmlPrinter outgoingDebugPrettyPrinter = XmlPrettyPrinter.builder()
- .setPrettyWriter(sb -> debugger.outgoingStreamSink(sb))
- .build();
- outputDebugSplitter = new XmppXmlSplitter(outgoingDebugPrettyPrinter);
- }
-
- XmppXmlSplitter xmppXmlSplitter = new XmppXmlSplitter(MAX_ELEMENT_SIZE, xmppElementCallback,
- incomingDebugPrettyPrinter);
- splitter = new Utf8ByteXmppXmlSplitter(xmppXmlSplitter);
- }
-
- private final ChannelSelectedCallback channelSelectedCallback =
- (selectedChannel, selectedSelectionKey) -> {
- assert selectionKey == null || selectionKey == selectedSelectionKey;
- SocketChannel selectedSocketChannel = (SocketChannel) selectedChannel;
- // We are *always* interested in OP_READ.
- int newInterestedOps = SelectionKey.OP_READ;
- boolean newPendingOutputFilterData = false;
-
- if (!channelSelectedCallbackLock.tryLock()) {
- rejectedChannelSelectedCallbacks.incrementAndGet();
- return;
- }
-
- // LOGGER.info("Accepted channel selected callback");
-
- handledChannelSelectedCallbacks++;
-
- long callbackBytesRead = 0;
- long callbackBytesWritten = 0;
-
- try {
- boolean destinationAddressChanged = false;
- boolean isLastPartOfElement = false;
- TopLevelStreamElement currentlyOutgonigTopLevelStreamElement = null;
- StringBuilder outgoingStreamForDebugger = null;
-
- writeLoop: while (true) {
- final boolean moreDataAvailable = !isLastPartOfElement || !outgoingElementsQueue.isEmpty();
-
- if (filteredOutgoingBuffer != null || !networkOutgoingBuffers.isEmpty()) {
- if (filteredOutgoingBuffer != null) {
- networkOutgoingBuffers.add(filteredOutgoingBuffer);
- networkOutgoingBuffersBytes += filteredOutgoingBuffer.remaining();
-
- filteredOutgoingBuffer = null;
- if (moreDataAvailable && networkOutgoingBuffersBytes < 8096) {
- continue;
- }
- }
-
- ByteBuffer[] output = networkOutgoingBuffers.toArray(new ByteBuffer[networkOutgoingBuffers.size()]);
- long bytesWritten;
- try {
- bytesWritten = selectedSocketChannel.write(output);
- } catch (IOException e) {
- // We have seen here so far
- // - IOException "Broken pipe"
- handleReadWriteIoException(e);
- break;
- }
-
- if (bytesWritten == 0) {
- newInterestedOps |= SelectionKey.OP_WRITE;
- break;
- }
-
- callbackBytesWritten += bytesWritten;
-
- networkOutgoingBuffersBytes -= bytesWritten;
-
- List extends Buffer> prunedBuffers = pruneBufferList(networkOutgoingBuffers);
-
- for (Buffer prunedBuffer : prunedBuffers) {
- List sendElements = bufferToElementMap.remove(prunedBuffer);
- if (sendElements == null) {
- continue;
- }
- for (TopLevelStreamElement elementJustSend : sendElements) {
- firePacketSendingListeners(elementJustSend);
- }
- }
-
- // Prevent one callback from dominating the reactor thread. Break out of the write-loop if we have
- // written a certain amount.
- if (callbackBytesWritten > CALLBACK_MAX_BYTES_WRITEN) {
- newInterestedOps |= SelectionKey.OP_WRITE;
- callbackPreemtBecauseBytesWritten++;
- break;
- }
- } else if (outgoingBuffer != null || pendingOutputFilterData) {
- pendingOutputFilterData = false;
-
- if (outgoingBuffer != null) {
- totalBytesWrittenBeforeFilter += outgoingBuffer.remaining();
- if (isLastPartOfElement) {
- assert currentlyOutgonigTopLevelStreamElement != null;
- currentlyOutgoingElements.add(currentlyOutgonigTopLevelStreamElement);
- }
- }
-
- ByteBuffer outputFilterInputData = outgoingBuffer;
- // We can now null the outgoingBuffer since the filter step will take care of it from now on.
- outgoingBuffer = null;
-
- for (ListIterator it = getXmppInputOutputFilterBeginIterator(); it.hasNext();) {
- XmppInputOutputFilter inputOutputFilter = it.next();
- XmppInputOutputFilter.OutputResult outputResult;
- try {
- outputResult = inputOutputFilter.output(outputFilterInputData, isLastPartOfElement,
- destinationAddressChanged, moreDataAvailable);
- } catch (IOException e) {
- notifyConnectionError(e);
- break writeLoop;
- }
- newPendingOutputFilterData |= outputResult.pendingFilterData;
- outputFilterInputData = outputResult.filteredOutputData;
- if (outputFilterInputData != null) {
- outputFilterInputData.flip();
- }
- }
-
- // It is ok if outpuFilterInputData is 'null' here, this is expected behavior.
- if (outputFilterInputData != null && outputFilterInputData.hasRemaining()) {
- filteredOutgoingBuffer = outputFilterInputData;
- } else {
- filteredOutgoingBuffer = null;
- }
-
- // If the filters did eventually not produce any output data but if there is
- // pending output data then we have a pending write request after read.
- if (filteredOutgoingBuffer == null && newPendingOutputFilterData) {
- pendingWriteInterestAfterRead = true;
- }
-
- if (filteredOutgoingBuffer != null && isLastPartOfElement) {
- bufferToElementMap.put(filteredOutgoingBuffer, new ArrayList<>(currentlyOutgoingElements));
- currentlyOutgoingElements.clear();
- }
-
- // Reset that the destination address has changed.
- if (destinationAddressChanged) {
- destinationAddressChanged = false;
- }
- } else if (outgoingCharSequenceIterator != null) {
- CharSequence nextCharSequence = outgoingCharSequenceIterator.next();
- outgoingBuffer = UTF8.encode(nextCharSequence);
- if (!outgoingCharSequenceIterator.hasNext()) {
- outgoingCharSequenceIterator = null;
- isLastPartOfElement = true;
- } else {
- isLastPartOfElement = false;
- }
-
- if (debugger != null) {
- if (outgoingStreamForDebugger == null) {
- outgoingStreamForDebugger = new StringBuilder();
- }
- outgoingStreamForDebugger.append(nextCharSequence);
-
- if (isLastPartOfElement) {
- try {
- outputDebugSplitter.append(outgoingStreamForDebugger);
- } catch (IOException e) {
- throw new AssertionError(e);
- }
- debugger.onOutgoingElementCompleted();
- outgoingStreamForDebugger = null;
- }
- }
- } else if (!outgoingElementsQueue.isEmpty()) {
- currentlyOutgonigTopLevelStreamElement = outgoingElementsQueue.poll();
- if (currentlyOutgonigTopLevelStreamElement instanceof Stanza) {
- Stanza currentlyOutgoingStanza = (Stanza) currentlyOutgonigTopLevelStreamElement;
- Jid currentDestinationAddress = currentlyOutgoingStanza.getTo();
- destinationAddressChanged = !JidUtil.equals(lastDestinationAddress, currentDestinationAddress);
- lastDestinationAddress = currentDestinationAddress;
- }
- CharSequence nextCharSequence = currentlyOutgonigTopLevelStreamElement.toXML(StreamOpen.CLIENT_NAMESPACE);
- if (nextCharSequence instanceof XmlStringBuilder) {
- XmlStringBuilder xmlStringBuilder = (XmlStringBuilder) nextCharSequence;
- outgoingCharSequenceIterator = xmlStringBuilder.toList(outgoingStreamXmlEnvironment).iterator();
- } else {
- outgoingCharSequenceIterator = Collections.singletonList(nextCharSequence).iterator();
- }
- assert outgoingCharSequenceIterator != null;
- } else {
- // There is nothing more to write.
- break;
- }
- }
-
- pendingOutputFilterData = newPendingOutputFilterData;
- if (!pendingWriteInterestAfterRead && pendingOutputFilterData) {
- newInterestedOps |= SelectionKey.OP_WRITE;
- }
-
- readLoop: while (true) {
- // Prevent one callback from dominating the reactor thread. Break out of the read-loop if we have
- // read a certain amount.
- if (callbackBytesRead > CALLBACK_MAX_BYTES_READ) {
- callbackPreemtBecauseBytesRead++;
- break;
- }
-
- int bytesRead;
- incomingBuffer.clear();
- try {
- bytesRead = selectedSocketChannel.read(incomingBuffer);
- } catch (IOException e) {
- handleReadWriteIoException(e);
- return;
- }
-
- if (bytesRead < 0) {
- LOGGER.finer("NIO read() returned " + bytesRead
- + " for " + this + ". This probably means that the TCP connection was terminated.");
- // According to the socket channel javadoc section about "asynchronous reads" a socket channel's
- // read() may return -1 if the input side of a socket is shut down.
-
- // Note that we do not call notifyConnectionError() here because the connection may be
- // cleanly shutdown which would also cause read() to return '-1. I assume that this socket
- // will be selected again, on which read() would throw an IOException, which will be catched
- // and invoke notifyConnectionError() (see a few lines above).
- /*
- IOException exception = new IOException("NIO read() returned " + bytesRead);
- notifyConnectionError(exception);
- */
- return;
- }
-
- if (!pendingInputFilterData) {
- if (bytesRead == 0) {
- // Nothing more to read.
- break;
- }
- } else {
- pendingInputFilterData = false;
- }
-
- // We have successfully read something. It is now possible that a filter is now also able to write
- // additional data (for example SSLEngine).
- if (pendingWriteInterestAfterRead) {
- pendingWriteInterestAfterRead = false;
- newInterestedOps |= SelectionKey.OP_WRITE;
- }
-
- callbackBytesRead += bytesRead;
-
- ByteBuffer filteredIncomingBuffer = incomingBuffer;
- for (ListIterator it = getXmppInputOutputFilterEndIterator(); it.hasPrevious();) {
- filteredIncomingBuffer.flip();
-
- ByteBuffer newFilteredIncomingBuffer;
- try {
- newFilteredIncomingBuffer = it.previous().input(filteredIncomingBuffer);
- } catch (IOException e) {
- notifyConnectionError(e);
- return;
- }
- if (newFilteredIncomingBuffer == null) {
- break readLoop;
- }
- filteredIncomingBuffer = newFilteredIncomingBuffer;
- }
-
- final int bytesReadAfterFilter = filteredIncomingBuffer.flip().remaining();
-
- totalBytesReadAfterFilter += bytesReadAfterFilter;
-
- try {
- splitter.write(filteredIncomingBuffer);
- } catch (IOException e) {
- notifyConnectionError(e);
- return;
- }
- }
- } finally {
- totalBytesWritten += callbackBytesWritten;
- totalBytesRead += callbackBytesRead;
-
- channelSelectedCallbackLock.unlock();
- }
-
- // Indicate that there is no reactor thread racing towards handling this selection key.
- final SelectionKeyAttachment selectionKeyAttachment = this.selectionKeyAttachment;
- if (selectionKeyAttachment != null) {
- selectionKeyAttachment.resetReactorThreadRacing();
- }
-
- // Check the queue again to prevent lost wakeups caused by elements inserted before we
- // called resetReactorThreadRacing() a few lines above.
- if (!outgoingElementsQueue.isEmpty()) {
- setWriteInterestAfterChannelSelectedCallback.incrementAndGet();
- newInterestedOps |= SelectionKey.OP_WRITE;
- }
-
- setInterestOps(selectionKey, newInterestedOps);
- };
-
- private void handleReadWriteIoException(IOException e) {
- if (e instanceof ClosedChannelException && !isConnected()) {
- // The connection is already closed.
- return;
- }
-
- notifyConnectionError(e);
- }
-
- private void callChannelSelectedCallback(boolean setPendingInputFilterData, boolean setPendingOutputFilterData) {
- final SocketChannel channel = socketChannel;
- final SelectionKey key = selectionKey;
- if (channel == null || key == null) {
- LOGGER.info("Not calling channel selected callback because the connection was eventually disconnected");
- return;
- }
-
- channelSelectedCallbackLock.lock();
- try {
- // Note that it is important that we send the pending(Input|Output)FilterData flags while holding the lock.
- if (setPendingInputFilterData) {
- pendingInputFilterData = true;
- }
- if (setPendingOutputFilterData) {
- pendingOutputFilterData = true;
- }
-
- channelSelectedCallback.onChannelSelected(channel, key);
- } finally {
- channelSelectedCallbackLock.unlock();
- }
- }
-
- private abstract static class TcpHostEvent extends DetailedTransitionIntoInformation {
- protected final InetSocketAddress inetSocketAddress;
-
- protected TcpHostEvent(State state, InetSocketAddress inetSocketAddress) {
- super(state);
- this.inetSocketAddress = inetSocketAddress;
- }
-
- public InetSocketAddress getInetSocketAddress() {
- return inetSocketAddress;
- }
-
- @Override
- public String toString() {
- return super.toString() + ": " + inetSocketAddress;
- }
- }
-
- public static final class ConnectingToHostEvent extends TcpHostEvent {
- private ConnectingToHostEvent(State state, InetSocketAddress inetSocketAddress) {
- super(state, inetSocketAddress);
- }
- }
-
- public static final class ConnectedToHostEvent extends TcpHostEvent {
- private final boolean connectionEstablishedImmediately;
-
- private ConnectedToHostEvent(State state, InetSocketAddress inetSocketAddress, boolean immediately) {
- super(state, inetSocketAddress);
- this.connectionEstablishedImmediately = immediately;
- }
-
- @Override
- public String toString() {
- return super.toString() + (connectionEstablishedImmediately ? "" : " not") + " connected immediately";
- }
- }
-
- public static final class ConnectionToHostFailedEvent extends TcpHostEvent {
- private final IOException ioException;
-
- private ConnectionToHostFailedEvent(State state, InetSocketAddress inetSocketAddress, IOException ioException) {
- super(state, inetSocketAddress);
- this.ioException = ioException;
- }
-
- @Override
- public String toString() {
- return super.toString() + ioException;
- }
- }
-
- private final class ConnectionAttemptState {
- private final ConnectingToHostState connectingToHostState;
- InetSocketAddress inetSocketAddress;
- // TODO: Check if we can re-use the socket channel in case some InetSocketAddress fail to connect to.
- final SocketChannel socketChannel;
- final Iterator remainingAddresses;
- final List failedAddresses;
- final SynchronizationPoint tcpConnectionEstablishedSyncPoint;
-
- private ConnectionAttemptState(List inetSocketAddresses, List failedAddresses,
- ConnectingToHostState connectingToHostState) throws IOException {
- socketChannel = SocketChannel.open();
- socketChannel.configureBlocking(false);
- remainingAddresses = inetSocketAddresses.iterator();
- inetSocketAddress = remainingAddresses.next();
- this.failedAddresses = failedAddresses;
- this.connectingToHostState = connectingToHostState;
-
- tcpConnectionEstablishedSyncPoint = new SynchronizationPoint<>(XmppNioTcpConnection.this,
- "TCP connection establishment");
- }
-
- private void establishTcpConnection() {
- ConnectingToHostEvent connectingToHostEvent = new ConnectingToHostEvent(connectingToHostState, inetSocketAddress);
- invokeConnectionStateMachineListener(connectingToHostEvent);
-
- final boolean connected;
- try {
- connected = socketChannel.connect(inetSocketAddress);
- } catch (IOException e) {
- onIOExceptionWhenEstablishingTcpConnection(e);
- return;
- }
-
- if (connected) {
- ConnectedToHostEvent connectedToHostEvent = new ConnectedToHostEvent(connectingToHostState,
- inetSocketAddress, true);
- invokeConnectionStateMachineListener(connectedToHostEvent);
-
- tcpConnectionEstablishedSyncPoint.reportSuccess();
- return;
- }
-
- try {
- registerWithSelector(socketChannel, SelectionKey.OP_CONNECT,
- (selectedChannel, selectedSelectionKey) -> {
- SocketChannel selectedSocketChannel = (SocketChannel) selectedChannel;
-
- boolean finishConnected;
- try {
- finishConnected = selectedSocketChannel.finishConnect();
- } catch (IOException e) {
- Async.go(() -> onIOExceptionWhenEstablishingTcpConnection(e));
- return;
- }
-
- if (!finishConnected) {
- Async.go(() -> onIOExceptionWhenEstablishingTcpConnection(new IOException("finishConnect() failed")));
- return;
- }
-
- ConnectedToHostEvent connectedToHostEvent = new ConnectedToHostEvent(connectingToHostState, inetSocketAddress, false);
- invokeConnectionStateMachineListener(connectedToHostEvent);
-
- // Do not set 'state' here, since this is processed by a reactor thread, which doesn't hold
- // the objects lock.
- tcpConnectionEstablishedSyncPoint.reportSuccess();
- });
- } catch (ClosedChannelException e) {
- onIOExceptionWhenEstablishingTcpConnection(e);
- }
- }
-
- private void onIOExceptionWhenEstablishingTcpConnection(IOException exception) {
- if (!remainingAddresses.hasNext()) {
- ConnectionException connectionException = ConnectionException.from(failedAddresses);
- tcpConnectionEstablishedSyncPoint.reportFailure(connectionException);
- return;
- }
-
- tcpConnectionEstablishedSyncPoint.resetTimeout();
-
- HostAddress failedHostAddress = new HostAddress(inetSocketAddress, exception);
- failedAddresses.add(failedHostAddress);
-
- ConnectionToHostFailedEvent connectionToHostFailedEvent = new ConnectionToHostFailedEvent(
- connectingToHostState, inetSocketAddress, exception);
- invokeConnectionStateMachineListener(connectionToHostFailedEvent);
-
- inetSocketAddress = remainingAddresses.next();
-
- establishTcpConnection();
- }
- }
-
- @Override
- protected void connectInternal() throws SmackException, IOException, XMPPException, InterruptedException {
- // TODO: Check if those initialization methods can be invoked later.
- outgoingElementsQueue.start();
- closingStreamReceived.init();
-
- WalkStateGraphContext walkStateGraphContext = buildNewWalkTo(ConnectedButUnauthenticatedStateDescriptor.class)
- .build();
-
- walkStateGraph(walkStateGraphContext);
- }
-
- private List