mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-09-09 18:29:45 +02:00
SmackReactor/NIO, Java8/Android19, Pretty print XML, FSM connections
This commit adds - SmackReactor / NIO - a framework for finite state machine connections - support for Java 8 - pretty printed XML debug output It also - reworks the integration test framework - raises the minimum Android API level to 19 - introduces XmppNioTcpConnection Furthermore fixes SMACK-801 (at least partly). Java 8 language features are available, but not all runtime library methods. For that we would need to raise the Android API level to 24 or higher.
This commit is contained in:
parent
dba12919d0
commit
e98d42790a
144 changed files with 8692 additions and 1455 deletions
12
smack-tcp/Makefile
Normal file
12
smack-tcp/Makefile
Normal file
|
@ -0,0 +1,12 @@
|
|||
.PHONY := clean generate
|
||||
|
||||
GRADLE_QUITE_ARGS := --quiet --console plain
|
||||
|
||||
GENERATED_FILES := src/javadoc/org/jivesoftware/smack/tcp/doc-files/XmppNioTcpConnectionStateGraph.png
|
||||
generate: $(GENERATED_FILES)
|
||||
|
||||
clean:
|
||||
rm -f $(GENERATED_FILES)
|
||||
|
||||
src/javadoc/org/jivesoftware/smack/tcp/doc-files/XmppNioTcpConnectionStateGraph.png: src/main/java/org/jivesoftware/smack/tcp/XmppNioTcpConnection.java ../smack-core/src/main/java/org/jivesoftware/smack/fsm/AbstractXmppStateMachineConnection.java
|
||||
gradle $(GRADLE_QUITE_ARGS) :smack-repl:printXmppNioTcpConnectionStateGraph | dot -Tpng -o $@
|
1
smack-tcp/src/javadoc/org/jivesoftware/smack/tcp/doc-files/.gitignore
vendored
Normal file
1
smack-tcp/src/javadoc/org/jivesoftware/smack/tcp/doc-files/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/XmppNioTcpConnectionStateGraph.png
|
|
@ -17,26 +17,19 @@
|
|||
package org.jivesoftware.smack.tcp;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -58,21 +51,12 @@ import java.util.logging.Logger;
|
|||
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
|
||||
import org.jivesoftware.smack.AbstractConnectionListener;
|
||||
import org.jivesoftware.smack.AbstractXMPPConnection;
|
||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||
import org.jivesoftware.smack.SmackConfiguration;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
|
@ -127,14 +111,11 @@ import org.jivesoftware.smack.sm.provider.ParseStreamManagement;
|
|||
import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
|
||||
import org.jivesoftware.smack.util.Async;
|
||||
import org.jivesoftware.smack.util.CloseableUtil;
|
||||
import org.jivesoftware.smack.util.DNSUtil;
|
||||
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.dns.SmackDaneProvider;
|
||||
import org.jivesoftware.smack.util.dns.SmackDaneVerifier;
|
||||
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.jxmpp.jid.parts.Resourcepart;
|
||||
|
@ -192,13 +173,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
private final SynchronizationPoint<SmackException> compressSyncPoint = new SynchronizationPoint<>(
|
||||
this, "stream compression");
|
||||
|
||||
/**
|
||||
* A synchronization point which is successful if this connection has received the closing
|
||||
* stream element from the remote end-point, i.e. the server.
|
||||
*/
|
||||
private final SynchronizationPoint<Exception> closingStreamReceived = new SynchronizationPoint<>(
|
||||
this, "stream closing element received");
|
||||
|
||||
/**
|
||||
* The default bundle and defer callback, used for new connections.
|
||||
* @see bundleAndDeferCallback
|
||||
|
@ -478,6 +452,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
/**
|
||||
* Performs an unclean disconnect and shutdown of the connection. Does not send a closing stream stanza.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void instantShutdown() {
|
||||
shutdown(true);
|
||||
}
|
||||
|
@ -496,15 +471,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
LOGGER.finer("PacketWriter has been shut down");
|
||||
|
||||
if (!instant) {
|
||||
try {
|
||||
// After we send the closing stream element, check if there was already a
|
||||
// closing stream element sent by the server or wait with a timeout for a
|
||||
// closing stream element to be received from the server.
|
||||
@SuppressWarnings("unused")
|
||||
Exception res = closingStreamReceived.checkIfSuccessOrWait();
|
||||
} catch (InterruptedException | NoResponseException e) {
|
||||
LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, e);
|
||||
}
|
||||
waitForClosingStreamTagFromServer();
|
||||
}
|
||||
|
||||
if (packetReader != null) {
|
||||
|
@ -682,117 +649,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
*/
|
||||
@SuppressWarnings("LiteralClassName")
|
||||
private void proceedTLSReceived() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, NoSuchProviderException, UnrecoverableKeyException, KeyManagementException, SmackException {
|
||||
SmackDaneVerifier daneVerifier = null;
|
||||
|
||||
if (config.getDnssecMode() == DnssecMode.needsDnssecAndDane) {
|
||||
SmackDaneProvider daneProvider = DNSUtil.getDaneProvider();
|
||||
if (daneProvider == null) {
|
||||
throw new UnsupportedOperationException("DANE enabled but no SmackDaneProvider configured");
|
||||
}
|
||||
daneVerifier = daneProvider.newInstance();
|
||||
if (daneVerifier == null) {
|
||||
throw new IllegalStateException("DANE requested but DANE provider did not return a DANE verifier");
|
||||
}
|
||||
}
|
||||
|
||||
SSLContext context = this.config.getCustomSSLContext();
|
||||
KeyStore ks = null;
|
||||
PasswordCallback pcb = null;
|
||||
|
||||
if (context == null) {
|
||||
final String keyStoreType = config.getKeystoreType();
|
||||
final CallbackHandler callbackHandler = config.getCallbackHandler();
|
||||
final String keystorePath = config.getKeystorePath();
|
||||
if ("PKCS11".equals(keyStoreType)) {
|
||||
try {
|
||||
Constructor<?> c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class);
|
||||
String pkcs11Config = "name = SmartCard\nlibrary = " + config.getPKCS11Library();
|
||||
ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StringUtils.UTF8));
|
||||
Provider p = (Provider) c.newInstance(config);
|
||||
Security.addProvider(p);
|
||||
ks = KeyStore.getInstance("PKCS11",p);
|
||||
pcb = new PasswordCallback("PKCS11 Password: ",false);
|
||||
callbackHandler.handle(new Callback[] {pcb});
|
||||
ks.load(null,pcb.getPassword());
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.log(Level.WARNING, "Exception", e);
|
||||
ks = null;
|
||||
}
|
||||
}
|
||||
else if ("Apple".equals(keyStoreType)) {
|
||||
ks = KeyStore.getInstance("KeychainStore","Apple");
|
||||
ks.load(null,null);
|
||||
// pcb = new PasswordCallback("Apple Keychain",false);
|
||||
// pcb.setPassword(null);
|
||||
}
|
||||
else if (keyStoreType != null) {
|
||||
ks = KeyStore.getInstance(keyStoreType);
|
||||
if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) {
|
||||
try {
|
||||
pcb = new PasswordCallback("Keystore Password: ", false);
|
||||
callbackHandler.handle(new Callback[] { pcb });
|
||||
ks.load(new FileInputStream(keystorePath), pcb.getPassword());
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.log(Level.WARNING, "Exception", e);
|
||||
ks = null;
|
||||
}
|
||||
} else {
|
||||
ks.load(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
KeyManager[] kms = null;
|
||||
|
||||
if (ks != null) {
|
||||
String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
|
||||
KeyManagerFactory kmf = null;
|
||||
try {
|
||||
kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
LOGGER.log(Level.FINE, "Could get the default KeyManagerFactory for the '"
|
||||
+ keyManagerFactoryAlgorithm + "' algorithm", e);
|
||||
}
|
||||
if (kmf != null) {
|
||||
try {
|
||||
if (pcb == null) {
|
||||
kmf.init(ks, null);
|
||||
}
|
||||
else {
|
||||
kmf.init(ks, pcb.getPassword());
|
||||
pcb.clearPassword();
|
||||
}
|
||||
kms = kmf.getKeyManagers();
|
||||
}
|
||||
catch (NullPointerException npe) {
|
||||
LOGGER.log(Level.WARNING, "NullPointerException", npe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the user didn't specify a SSLContext, use the default one
|
||||
context = SSLContext.getInstance("TLS");
|
||||
|
||||
final SecureRandom secureRandom = new java.security.SecureRandom();
|
||||
X509TrustManager customTrustManager = config.getCustomX509TrustManager();
|
||||
|
||||
if (daneVerifier != null) {
|
||||
// User requested DANE verification.
|
||||
daneVerifier.init(context, kms, customTrustManager, secureRandom);
|
||||
} else {
|
||||
TrustManager[] customTrustManagers = null;
|
||||
if (customTrustManager != null) {
|
||||
customTrustManagers = new TrustManager[] { customTrustManager };
|
||||
}
|
||||
context.init(kms, customTrustManagers, secureRandom);
|
||||
}
|
||||
}
|
||||
SmackTlsContext smackTlsContext = getSmackTlsContext();
|
||||
|
||||
Socket plain = socket;
|
||||
// Secure the plain connection
|
||||
socket = context.getSocketFactory().createSocket(plain,
|
||||
socket = smackTlsContext.sslContext.getSocketFactory().createSocket(plain,
|
||||
config.getXMPPServiceDomain().toString(), plain.getPort(), true);
|
||||
|
||||
final SSLSocket sslSocket = (SSLSocket) socket;
|
||||
|
@ -807,8 +668,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
// Proceed to do the handshake
|
||||
sslSocket.startHandshake();
|
||||
|
||||
if (daneVerifier != null) {
|
||||
daneVerifier.finish(sslSocket);
|
||||
if (smackTlsContext.daneVerifier != null) {
|
||||
smackTlsContext.daneVerifier.finish(sslSocket);
|
||||
}
|
||||
|
||||
final HostnameVerifier verifier = getConfiguration().getHostnameVerifier();
|
||||
|
@ -909,26 +770,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends out a notification that there was an error with the connection
|
||||
* and closes the connection. Also prints the stack trace of the given exception
|
||||
*
|
||||
* @param e the exception that causes the connection close event.
|
||||
*/
|
||||
private synchronized void notifyConnectionError(Exception e) {
|
||||
// Listeners were already notified of the exception, return right here.
|
||||
if ((packetReader == null || packetReader.done) &&
|
||||
(packetWriter == null || packetWriter.done())) return;
|
||||
|
||||
// Closes the connection temporary. A reconnection is possible
|
||||
// Note that a connection listener of XMPPTCPConnection will drop the SM state in
|
||||
// case the Exception is a StreamErrorException.
|
||||
instantShutdown();
|
||||
|
||||
// Notify connection listeners of the error.
|
||||
callConnectionClosedOnErrorListener(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* For unit testing purposes
|
||||
*
|
||||
|
@ -975,18 +816,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
* @throws InterruptedException
|
||||
*/
|
||||
void openStream() throws SmackException, InterruptedException {
|
||||
// If possible, provide the receiving entity of the stream open tag, i.e. the server, as much information as
|
||||
// possible. The 'to' attribute is *always* available. The 'from' attribute if set by the user and no external
|
||||
// mechanism is used to determine the local entity (user). And the 'id' attribute is available after the first
|
||||
// response from the server (see e.g. RFC 6120 § 9.1.1 Step 2.)
|
||||
CharSequence to = getXMPPServiceDomain();
|
||||
CharSequence from = null;
|
||||
CharSequence localpart = config.getUsername();
|
||||
if (localpart != null) {
|
||||
from = XmppStringUtils.completeJidFrom(localpart, to);
|
||||
}
|
||||
String id = getStreamId();
|
||||
sendNonza(new StreamOpen(to, from, id));
|
||||
sendStreamOpen();
|
||||
try {
|
||||
packetReader.parser = PacketParserUtils.newXmppParser(reader);
|
||||
}
|
||||
|
@ -1045,12 +875,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
}
|
||||
break;
|
||||
case "stream":
|
||||
// We found an opening stream.
|
||||
if ("jabber:client".equals(parser.getNamespace(null))) {
|
||||
streamId = parser.getAttributeValue("", "id");
|
||||
String reportedServerDomain = parser.getAttributeValue("", "from");
|
||||
assert (config.getXMPPServiceDomain().equals(reportedServerDomain));
|
||||
}
|
||||
onStreamOpen(parser);
|
||||
break;
|
||||
case "error":
|
||||
StreamError streamError = PacketParserUtils.parseStreamError(parser);
|
||||
|
@ -1061,7 +886,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
tlsHandled.reportSuccess();
|
||||
throw new StreamErrorException(streamError);
|
||||
case "features":
|
||||
parseFeatures(parser);
|
||||
parseFeaturesAndNotify(parser);
|
||||
break;
|
||||
case "proceed":
|
||||
try {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
*
|
||||
* 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.tcp;
|
||||
|
||||
import org.jivesoftware.smack.fsm.StateDescriptor;
|
||||
import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
|
||||
import org.jivesoftware.smack.tcp.XmppNioTcpConnection.InstantShutdownStateDescriptor;
|
||||
|
||||
public class XmppNioTcpConnectionTest {
|
||||
|
||||
public void graphComplete() {
|
||||
assertContains(XmppNioTcpConnection.INITIAL_STATE_DESCRIPTOR_VERTEX, InstantShutdownStateDescriptor.class);
|
||||
}
|
||||
|
||||
private static void assertContains(GraphVertex<StateDescriptor> graph, Class<? extends StateDescriptor> state) {
|
||||
throw new Error("Implement me");
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue