mirror of
https://github.com/vanitasvitae/Smack.git
synced 2025-09-10 17:49:38 +02:00
Compare commits
41 commits
f00acbff89
...
ccc785062e
Author | SHA1 | Date | |
---|---|---|---|
|
ccc785062e | ||
|
15499ad1f8 | ||
|
f5448c5faa | ||
|
7156849c77 | ||
|
70188dbe57 | ||
|
d65f2c932e | ||
|
1c0bdfae40 | ||
|
f3207e8c27 | ||
|
f750c79392 | ||
|
c7c5b10c41 | ||
|
cac874bdc7 | ||
|
9a8ee3c8e3 | ||
|
f045c0dd08 | ||
|
a51663448b | ||
|
65aa543c57 | ||
|
ebe5c49e92 | ||
|
5bfe789e08 | ||
|
a137944e50 | ||
|
0db6406262 | ||
|
54d6bc8658 | ||
|
027358fc63 | ||
|
68a453d3b3 | ||
|
054fd9ae44 | ||
|
962071762a | ||
|
b3c57ef918 | ||
|
ca85b8326a | ||
|
c4ad857c0d | ||
|
33720e8d97 | ||
|
cbcf1d15f5 | ||
|
f97397f5ee | ||
|
d69f62d012 | ||
|
2679c72f0f | ||
|
87591655ad | ||
|
4239dac440 | ||
|
46ba273689 | ||
|
72c5dc5886 | ||
|
4e5536e227 | ||
|
6daf19dbd3 | ||
|
dfdd0acf91 | ||
|
b79ee8f6a9 | ||
dc64a43f12 |
82 changed files with 1338 additions and 1198 deletions
11
.travis.yml
11
.travis.yml
|
@ -35,7 +35,16 @@ install: gradle assemble --stacktrace
|
|||
# functional. Which hasn't always be the case in the past, see
|
||||
# 90cbcaebc7a89f4f771f733a33ac9f389df85be2
|
||||
# Also run javadocAll to ensure it works.
|
||||
script: gradle check publishToMavenLocal javadocAll --stacktrace
|
||||
script:
|
||||
- |
|
||||
JAVAC_MAJOR_VERSION=$(javac -version | sed -E 's/javac ([[:digit:]]+).*/\1/')
|
||||
GRADLE_TASKS=()
|
||||
GRADLE_TASKS+=(check)
|
||||
GRADLE_TASKS+=(publishToMavenLocal)
|
||||
if [[ ${JAVAC_MAJOR_VERSION} -ge 11 ]]; then
|
||||
GRADLE_TASKS+=(javadocAll)
|
||||
fi
|
||||
gradle ${GRADLE_TASKS[@]} --stacktrace
|
||||
|
||||
after_success:
|
||||
- JAVAC_VERSION=$((javac -version) 2>&1)
|
||||
|
|
12
build.gradle
12
build.gradle
|
@ -122,10 +122,10 @@ allprojects {
|
|||
// See also:
|
||||
// - https://issues.apache.org/jira/browse/MNG-6232
|
||||
// - https://issues.igniterealtime.org/browse/SMACK-858
|
||||
jxmppVersion = '0.7.0-alpha5'
|
||||
miniDnsVersion = '0.4.0-alpha3'
|
||||
jxmppVersion = '0.7.0-alpha6'
|
||||
miniDnsVersion = '0.4.0-alpha5'
|
||||
smackMinAndroidSdk = 19
|
||||
junitVersion = '5.6.0'
|
||||
junitVersion = '5.6.2'
|
||||
commonsIoVersion = '2.6'
|
||||
bouncyCastleVersion = '1.65'
|
||||
|
||||
|
@ -283,7 +283,7 @@ tasks.withType(Javadoc) {
|
|||
testFixturesApi "org.mockito:mockito-core:3.3.3"
|
||||
testImplementation 'com.jamesmurty.utils:java-xmlbuilder:1.2'
|
||||
|
||||
errorprone 'com.google.errorprone:error_prone_core:2.3.3'
|
||||
errorprone 'com.google.errorprone:error_prone_core:2.3.4'
|
||||
errorproneJavac('com.google.errorprone:javac:9+181-r4173-1')
|
||||
}
|
||||
|
||||
|
@ -409,7 +409,7 @@ subprojects {
|
|||
apply plugin: 'org.kordamp.gradle.clirr'
|
||||
|
||||
checkstyle {
|
||||
toolVersion = '8.22'
|
||||
toolVersion = '8.27'
|
||||
}
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
classifier = 'sources'
|
||||
|
@ -441,7 +441,7 @@ subprojects {
|
|||
packaging = 'jar'
|
||||
inceptionYear = '2003'
|
||||
url = 'http://www.igniterealtime.org/projects/smack/'
|
||||
description project.description
|
||||
description = project.description
|
||||
|
||||
issueManagement {
|
||||
system = 'JIRA'
|
||||
|
|
|
@ -104,13 +104,7 @@
|
|||
</module>
|
||||
<module name="JavadocMethod">
|
||||
<!-- TODO stricten those checks -->
|
||||
<property name="scope" value="public"/>
|
||||
<property name="allowUndeclaredRTE" value="true"/>
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingThrowsTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
<property name="allowMissingJavadoc" value="true"/>
|
||||
<property name="suppressLoadErrors" value="true"/>
|
||||
<property name="scope" value="protected"/>
|
||||
</module>
|
||||
<module name="JavadocStyle">
|
||||
<property name="scope" value="public"/>
|
||||
|
|
|
@ -16,24 +16,9 @@
|
|||
*/
|
||||
package org.jivesoftware.smack;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
@ -56,18 +41,9 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
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 javax.xml.namespace.QName;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||
import org.jivesoftware.smack.SmackConfiguration.UnknownIqRequestReplyMode;
|
||||
import org.jivesoftware.smack.SmackException.AlreadyConnectedException;
|
||||
|
@ -92,6 +68,7 @@ import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
|
|||
import org.jivesoftware.smack.filter.IQReplyFilter;
|
||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||
import org.jivesoftware.smack.filter.StanzaIdFilter;
|
||||
import org.jivesoftware.smack.internal.SmackTlsContext;
|
||||
import org.jivesoftware.smack.iqrequest.IQRequestHandler;
|
||||
import org.jivesoftware.smack.packet.Bind;
|
||||
import org.jivesoftware.smack.packet.ErrorIQ;
|
||||
|
@ -128,16 +105,12 @@ import org.jivesoftware.smack.sasl.packet.SaslNonza;
|
|||
import org.jivesoftware.smack.util.Async;
|
||||
import org.jivesoftware.smack.util.CollectionUtil;
|
||||
import org.jivesoftware.smack.util.Consumer;
|
||||
import org.jivesoftware.smack.util.DNSUtil;
|
||||
import org.jivesoftware.smack.util.MultiMap;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
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.SmackDaneProvider;
|
||||
import org.jivesoftware.smack.util.dns.SmackDaneVerifier;
|
||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||
|
||||
|
@ -372,17 +345,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
|
||||
protected final AsyncButOrdered<StanzaListener> inOrderListeners = new AsyncButOrdered<>();
|
||||
|
||||
/**
|
||||
* An executor which uses {@link #asyncGoLimited(Runnable)} to limit the number of asynchronously processed runnables
|
||||
* per connection.
|
||||
*/
|
||||
private final Executor limitedExcutor = new Executor() {
|
||||
@Override
|
||||
public void execute(Runnable runnable) {
|
||||
asyncGoLimited(runnable);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The used host to establish the connection to
|
||||
*/
|
||||
|
@ -1523,7 +1485,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
executorService = ASYNC_BUT_ORDERED.asExecutorFor(this);
|
||||
break;
|
||||
case async:
|
||||
executorService = limitedExcutor;
|
||||
executorService = this::asyncGoLimited;
|
||||
break;
|
||||
}
|
||||
final IQRequestHandler finalIqRequestHandler = iqRequestHandler;
|
||||
|
@ -2162,7 +2124,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
}
|
||||
|
||||
protected static ScheduledAction schedule(Runnable runnable, long delay, TimeUnit unit) {
|
||||
return SMACK_REACTOR.schedule(runnable, delay, unit);
|
||||
return SMACK_REACTOR.schedule(runnable, delay, unit, ScheduledAction.Kind.NonBlocking);
|
||||
}
|
||||
|
||||
protected void onStreamOpen(XmlPullParser parser) {
|
||||
|
@ -2213,148 +2175,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
outgoingStreamXmlEnvironment = xmlEnvironmentBuilder.build();
|
||||
}
|
||||
|
||||
public static final class SmackTlsContext {
|
||||
public final SSLContext sslContext;
|
||||
public final SmackDaneVerifier daneVerifier;
|
||||
|
||||
private SmackTlsContext(SSLContext sslContext, SmackDaneVerifier daneVerifier) {
|
||||
assert sslContext != null;
|
||||
this.sslContext = sslContext;
|
||||
this.daneVerifier = daneVerifier;
|
||||
}
|
||||
}
|
||||
|
||||
protected final SmackTlsContext getSmackTlsContext() throws KeyManagementException, NoSuchAlgorithmException,
|
||||
CertificateException, IOException, UnrecoverableKeyException, KeyStoreException, NoSuchProviderException {
|
||||
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(StandardCharsets.UTF_8));
|
||||
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 {
|
||||
InputStream stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
|
||||
try {
|
||||
// Note that PKCS12 keystores need a password one some Java platforms. Hence we try the famous
|
||||
// 'changeit' here. See https://bugs.openjdk.java.net/browse/JDK-8194702
|
||||
char[] password = "changeit".toCharArray();
|
||||
try {
|
||||
ks.load(stream, password);
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.FINE, "KeyStore load() threw, attempting 'jks' fallback", e);
|
||||
|
||||
ks = KeyStore.getInstance("jks");
|
||||
// Open the stream again, so that we read it from the beginning.
|
||||
stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
|
||||
try {
|
||||
ks.load(stream, null);
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 trustManager = config.getCustomX509TrustManager();
|
||||
if (trustManager == null) {
|
||||
trustManager = TLSUtils.getDefaultX509TrustManager(ks);
|
||||
}
|
||||
|
||||
if (daneVerifier != null) {
|
||||
// User requested DANE verification.
|
||||
daneVerifier.init(context, kms, trustManager, secureRandom);
|
||||
} else {
|
||||
TrustManager[] customTrustManagers = new TrustManager[] { trustManager };
|
||||
context.init(kms, customTrustManagers, secureRandom);
|
||||
}
|
||||
}
|
||||
|
||||
return new SmackTlsContext(context, daneVerifier);
|
||||
protected final SmackTlsContext getSmackTlsContext() {
|
||||
return config.smackTlsContext;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,24 @@
|
|||
|
||||
package org.jivesoftware.smack;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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.Security;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -31,20 +46,34 @@ 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.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 javax.security.auth.callback.UnsupportedCallbackException;
|
||||
|
||||
import org.jivesoftware.smack.datatypes.UInt16;
|
||||
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
|
||||
import org.jivesoftware.smack.internal.SmackTlsContext;
|
||||
import org.jivesoftware.smack.packet.id.StandardStanzaIdSource;
|
||||
import org.jivesoftware.smack.packet.id.StanzaIdSource;
|
||||
import org.jivesoftware.smack.packet.id.StanzaIdSourceFactory;
|
||||
import org.jivesoftware.smack.proxy.ProxyInfo;
|
||||
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||
import org.jivesoftware.smack.sasl.core.SASLAnonymous;
|
||||
import org.jivesoftware.smack.util.CloseableUtil;
|
||||
import org.jivesoftware.smack.util.CollectionUtil;
|
||||
import org.jivesoftware.smack.util.DNSUtil;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.SslContextFactory;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.TLSUtils;
|
||||
import org.jivesoftware.smack.util.dns.SmackDaneProvider;
|
||||
import org.jivesoftware.smack.util.dns.SmackDaneVerifier;
|
||||
|
||||
import org.jxmpp.jid.DomainBareJid;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
|
@ -101,12 +130,7 @@ public abstract class ConnectionConfiguration {
|
|||
|
||||
protected final InetAddress hostAddress;
|
||||
protected final DnsName host;
|
||||
protected final int port;
|
||||
|
||||
private final String keystorePath;
|
||||
private final String keystoreType;
|
||||
private final String pkcs11Library;
|
||||
private final SSLContext customSSLContext;
|
||||
protected final UInt16 port;
|
||||
|
||||
/**
|
||||
* Used to get information from the user
|
||||
|
@ -137,9 +161,9 @@ public abstract class ConnectionConfiguration {
|
|||
|
||||
private final SecurityMode securityMode;
|
||||
|
||||
private final DnssecMode dnssecMode;
|
||||
final SmackTlsContext smackTlsContext;
|
||||
|
||||
private final X509TrustManager customX509TrustManager;
|
||||
private final DnssecMode dnssecMode;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -165,6 +189,17 @@ public abstract class ConnectionConfiguration {
|
|||
private final StanzaIdSourceFactory stanzaIdSourceFactory;
|
||||
|
||||
protected ConnectionConfiguration(Builder<?, ?> builder) {
|
||||
try {
|
||||
smackTlsContext = getSmackTlsContext(builder.dnssecMode, builder.sslContextFactory,
|
||||
builder.customX509TrustManager, builder.keystoreType, builder.keystorePath,
|
||||
builder.callbackHandler, builder.pkcs11Library);
|
||||
} catch (UnrecoverableKeyException | KeyManagementException | NoSuchAlgorithmException | CertificateException
|
||||
| KeyStoreException | NoSuchProviderException | IOException | NoSuchMethodException
|
||||
| SecurityException | ClassNotFoundException | InstantiationException | IllegalAccessException
|
||||
| IllegalArgumentException | InvocationTargetException | UnsupportedCallbackException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
|
||||
authzid = builder.authzid;
|
||||
username = builder.username;
|
||||
password = builder.password;
|
||||
|
@ -201,13 +236,7 @@ public abstract class ConnectionConfiguration {
|
|||
|
||||
dnssecMode = builder.dnssecMode;
|
||||
|
||||
customX509TrustManager = builder.customX509TrustManager;
|
||||
|
||||
securityMode = builder.securityMode;
|
||||
keystoreType = builder.keystoreType;
|
||||
keystorePath = builder.keystorePath;
|
||||
pkcs11Library = builder.pkcs11Library;
|
||||
customSSLContext = builder.customSSLContext;
|
||||
enabledSSLProtocols = builder.enabledSSLProtocols;
|
||||
enabledSSLCiphers = builder.enabledSSLCiphers;
|
||||
hostnameVerifier = builder.hostnameVerifier;
|
||||
|
@ -222,11 +251,115 @@ public abstract class ConnectionConfiguration {
|
|||
|
||||
// If the enabledSaslmechanisms are set, then they must not be empty
|
||||
assert enabledSaslMechanisms == null || !enabledSaslMechanisms.isEmpty();
|
||||
}
|
||||
|
||||
if (dnssecMode != DnssecMode.disabled && customSSLContext != null) {
|
||||
throw new IllegalStateException("You can not use a custom SSL context with DNSSEC enabled");
|
||||
private static SmackTlsContext getSmackTlsContext(DnssecMode dnssecMode, SslContextFactory sslContextFactory,
|
||||
X509TrustManager trustManager, String keystoreType, String keystorePath,
|
||||
CallbackHandler callbackHandler, String pkcs11Library) throws NoSuchAlgorithmException,
|
||||
CertificateException, IOException, KeyStoreException, NoSuchProviderException,
|
||||
UnrecoverableKeyException, KeyManagementException, UnsupportedCallbackException,
|
||||
NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException,
|
||||
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
final SSLContext context;
|
||||
if (sslContextFactory != null) {
|
||||
context = sslContextFactory.createSslContext();
|
||||
} else {
|
||||
// If the user didn't specify a SslContextFactory, use the default one
|
||||
context = SSLContext.getInstance("TLS");
|
||||
}
|
||||
|
||||
KeyStore ks = null;
|
||||
PasswordCallback pcb = null;
|
||||
KeyManager[] kms = null;
|
||||
|
||||
if ("PKCS11".equals(keystoreType)) {
|
||||
Constructor<?> c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class);
|
||||
String pkcs11Config = "name = SmartCard\nlibrary = " + pkcs11Library;
|
||||
ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StandardCharsets.UTF_8));
|
||||
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());
|
||||
} 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)) {
|
||||
pcb = new PasswordCallback("Keystore Password: ", false);
|
||||
callbackHandler.handle(new Callback[] { pcb });
|
||||
ks.load(new FileInputStream(keystorePath), pcb.getPassword());
|
||||
} else {
|
||||
InputStream stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
|
||||
try {
|
||||
// Note that PKCS12 keystores need a password one some Java platforms. Hence we try the famous
|
||||
// 'changeit' here. See https://bugs.openjdk.java.net/browse/JDK-8194702
|
||||
char[] password = "changeit".toCharArray();
|
||||
try {
|
||||
ks.load(stream, password);
|
||||
} finally {
|
||||
CloseableUtil.maybeClose(stream);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.FINE, "KeyStore load() threw, attempting 'jks' fallback", e);
|
||||
|
||||
ks = KeyStore.getInstance("jks");
|
||||
// Open the stream again, so that we read it from the beginning.
|
||||
stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
|
||||
try {
|
||||
ks.load(stream, null);
|
||||
} finally {
|
||||
CloseableUtil.maybeClose(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ks != null) {
|
||||
String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
|
||||
if (kmf != null) {
|
||||
if (pcb == null) {
|
||||
kmf.init(ks, null);
|
||||
} else {
|
||||
kmf.init(ks, pcb.getPassword());
|
||||
pcb.clearPassword();
|
||||
}
|
||||
kms = kmf.getKeyManagers();
|
||||
}
|
||||
}
|
||||
|
||||
SmackDaneVerifier daneVerifier = null;
|
||||
if (dnssecMode == 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");
|
||||
}
|
||||
|
||||
// User requested DANE verification.
|
||||
daneVerifier.init(context, kms, trustManager, null);
|
||||
} else {
|
||||
final TrustManager[] trustManagers;
|
||||
if (trustManager != null) {
|
||||
trustManagers = new TrustManager[] { trustManager };
|
||||
} else {
|
||||
// Ensure trustManagers is null in case there was no explicit trust manager provided, so that the
|
||||
// default one is used.
|
||||
trustManagers = null;
|
||||
}
|
||||
|
||||
context.init(kms, trustManagers, null);
|
||||
}
|
||||
|
||||
return new SmackTlsContext(context, daneVerifier);
|
||||
}
|
||||
|
||||
public DnsName getHost() {
|
||||
|
@ -237,7 +370,7 @@ public abstract class ConnectionConfiguration {
|
|||
return hostAddress;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
public UInt16 getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
|
@ -286,50 +419,6 @@ public abstract class ConnectionConfiguration {
|
|||
return dnssecMode;
|
||||
}
|
||||
|
||||
public X509TrustManager getCustomX509TrustManager() {
|
||||
return customX509TrustManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retuns the path to the keystore file. The key store file contains the
|
||||
* certificates that may be used to authenticate the client to the server,
|
||||
* in the event the server requests or requires it.
|
||||
*
|
||||
* @return the path to the keystore file.
|
||||
*/
|
||||
public String getKeystorePath() {
|
||||
return keystorePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keystore type, or <code>null</code> if it's not set.
|
||||
*
|
||||
* @return the keystore type.
|
||||
*/
|
||||
public String getKeystoreType() {
|
||||
return keystoreType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PKCS11 library file location, needed when the
|
||||
* Keystore type is PKCS11.
|
||||
*
|
||||
* @return the path to the PKCS11 library file
|
||||
*/
|
||||
public String getPKCS11Library() {
|
||||
return pkcs11Library;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom SSLContext previously set with {@link ConnectionConfiguration.Builder#setCustomSSLContext(SSLContext)} for
|
||||
* SSL sockets. This is null by default.
|
||||
*
|
||||
* @return the custom SSLContext or null.
|
||||
*/
|
||||
public SSLContext getCustomSSLContext() {
|
||||
return this.customSSLContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the enabled SSL/TLS protocols.
|
||||
*
|
||||
|
@ -599,12 +688,12 @@ public abstract class ConnectionConfiguration {
|
|||
* @param <C> the resulting connection configuration type parameter.
|
||||
*/
|
||||
public abstract static class Builder<B extends Builder<B, C>, C extends ConnectionConfiguration> {
|
||||
private SecurityMode securityMode = SecurityMode.ifpossible;
|
||||
private SecurityMode securityMode = SecurityMode.required;
|
||||
private DnssecMode dnssecMode = DnssecMode.disabled;
|
||||
private String keystorePath = System.getProperty("javax.net.ssl.keyStore");
|
||||
private String keystoreType = KeyStore.getDefaultType();
|
||||
private String keystorePath;
|
||||
private String keystoreType;
|
||||
private String pkcs11Library = "pkcs11.config";
|
||||
private SSLContext customSSLContext;
|
||||
private SslContextFactory sslContextFactory;
|
||||
private String[] enabledSSLProtocols;
|
||||
private String[] enabledSSLCiphers;
|
||||
private HostnameVerifier hostnameVerifier;
|
||||
|
@ -621,7 +710,7 @@ public abstract class ConnectionConfiguration {
|
|||
private DomainBareJid xmppServiceDomain;
|
||||
private InetAddress hostAddress;
|
||||
private DnsName host;
|
||||
private int port = 5222;
|
||||
private UInt16 port = UInt16.from(5222);
|
||||
private boolean allowEmptyOrNullUsername = false;
|
||||
private boolean saslMechanismsSealed;
|
||||
private Set<String> enabledSaslMechanisms;
|
||||
|
@ -837,7 +926,12 @@ public abstract class ConnectionConfiguration {
|
|||
throw new IllegalArgumentException(
|
||||
"Port must be a 16-bit unsigned integer (i.e. between 0-65535. Port was: " + port);
|
||||
}
|
||||
this.port = port;
|
||||
UInt16 portUint16 = UInt16.from(port);
|
||||
return setPort(portUint16);
|
||||
}
|
||||
|
||||
public B setPort(UInt16 port) {
|
||||
this.port = Objects.requireNonNull(port);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
|
@ -923,9 +1017,28 @@ public abstract class ConnectionConfiguration {
|
|||
*
|
||||
* @param context the custom SSLContext for new sockets.
|
||||
* @return a reference to this builder.
|
||||
* @deprecated use {@link #setSslContextFactory(SslContextFactory)} instead}.
|
||||
*/
|
||||
// TODO: Remove in Smack 4.5.
|
||||
@Deprecated
|
||||
public B setCustomSSLContext(SSLContext context) {
|
||||
this.customSSLContext = Objects.requireNonNull(context, "The SSLContext must not be null");
|
||||
return setSslContextFactory(() -> {
|
||||
return context;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom SSLContext for creating SSL sockets.
|
||||
* <p>
|
||||
* For more information on how to create a SSLContext see <a href=
|
||||
* "http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#X509TrustManager"
|
||||
* >Java Secure Socket Extension (JSEE) Reference Guide: Creating Your Own X509TrustManager</a>
|
||||
*
|
||||
* @param sslContextFactory the custom SSLContext for new sockets.
|
||||
* @return a reference to this builder.
|
||||
*/
|
||||
public B setSslContextFactory(SslContextFactory sslContextFactory) {
|
||||
this.sslContextFactory = Objects.requireNonNull(sslContextFactory, "The provided SslContextFactory must not be null");
|
||||
return getThis();
|
||||
}
|
||||
|
||||
|
@ -1166,5 +1279,4 @@ public abstract class ConnectionConfiguration {
|
|||
|
||||
protected abstract B getThis();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -54,6 +54,14 @@ public abstract class Manager {
|
|||
}
|
||||
|
||||
protected static final ScheduledAction schedule(Runnable runnable, long delay, TimeUnit unit) {
|
||||
return AbstractXMPPConnection.SMACK_REACTOR.schedule(runnable, delay, unit);
|
||||
return schedule(runnable, delay, unit, ScheduledAction.Kind.NonBlocking);
|
||||
}
|
||||
|
||||
protected static final ScheduledAction scheduleBlocking(Runnable runnable, long delay, TimeUnit unit) {
|
||||
return schedule(runnable, delay, unit, ScheduledAction.Kind.Blocking);
|
||||
}
|
||||
|
||||
protected static final ScheduledAction schedule(Runnable runnable, long delay, TimeUnit unit, ScheduledAction.Kind scheduledActionKind) {
|
||||
return AbstractXMPPConnection.SMACK_REACTOR.schedule(runnable, delay, unit, scheduledActionKind);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,20 +20,34 @@ import java.util.Date;
|
|||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.jivesoftware.smack.util.Async;
|
||||
|
||||
public class ScheduledAction implements Delayed {
|
||||
|
||||
final Runnable action;
|
||||
enum Kind {
|
||||
NonBlocking,
|
||||
Blocking,
|
||||
}
|
||||
|
||||
private final Runnable action;
|
||||
final Date releaseTime;
|
||||
final SmackReactor smackReactor;
|
||||
final Kind kind;
|
||||
|
||||
ScheduledAction(Runnable action, Date releaseTime, SmackReactor smackReactor) {
|
||||
ScheduledAction(Runnable action, Date releaseTime, SmackReactor smackReactor, Kind kind) {
|
||||
this.action = action;
|
||||
this.releaseTime = releaseTime;
|
||||
this.smackReactor = smackReactor;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
smackReactor.cancel(this);
|
||||
/**
|
||||
* Cancels this scheduled action.
|
||||
*
|
||||
* @return <code>true</code> if the scheduled action was still pending and got removed, <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean cancel() {
|
||||
return smackReactor.cancel(this);
|
||||
}
|
||||
|
||||
public boolean isDue() {
|
||||
|
@ -63,4 +77,15 @@ public class ScheduledAction implements Delayed {
|
|||
long delayInMillis = getTimeToDueMillis();
|
||||
return unit.convert(delayInMillis, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
void run() {
|
||||
switch (kind) {
|
||||
case NonBlocking:
|
||||
action.run();
|
||||
break;
|
||||
case Blocking:
|
||||
Async.go(() -> action.run());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,15 +145,21 @@ public class SmackReactor {
|
|||
}
|
||||
}
|
||||
|
||||
ScheduledAction schedule(Runnable runnable, long delay, TimeUnit unit) {
|
||||
ScheduledAction schedule(Runnable runnable, long delay, TimeUnit unit, ScheduledAction.Kind scheduledActionKind) {
|
||||
long releaseTimeEpoch = System.currentTimeMillis() + unit.toMillis(delay);
|
||||
Date releaseTimeDate = new Date(releaseTimeEpoch);
|
||||
ScheduledAction scheduledAction = new ScheduledAction(runnable, releaseTimeDate, this);
|
||||
ScheduledAction scheduledAction = new ScheduledAction(runnable, releaseTimeDate, this, scheduledActionKind);
|
||||
scheduledActions.add(scheduledAction);
|
||||
selector.wakeup();
|
||||
return scheduledAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the scheduled action.
|
||||
*
|
||||
* @param scheduledAction the scheduled action to cancel.
|
||||
* @return <code>true</code> if the scheduled action was still pending and got removed, <code>false</code> otherwise.
|
||||
*/
|
||||
boolean cancel(ScheduledAction scheduledAction) {
|
||||
return scheduledActions.remove(scheduledAction);
|
||||
}
|
||||
|
@ -200,7 +206,7 @@ public class SmackReactor {
|
|||
}
|
||||
|
||||
if (dueScheduledAction != null) {
|
||||
dueScheduledAction.action.run();
|
||||
dueScheduledAction.run();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,11 +17,6 @@
|
|||
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;
|
||||
|
@ -65,6 +60,8 @@ 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.internal.AbstractStats;
|
||||
import org.jivesoftware.smack.internal.SmackTlsContext;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.Nonza;
|
||||
|
@ -78,6 +75,7 @@ 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.ExtendedAppendable;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||
|
@ -184,9 +182,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
|||
}
|
||||
|
||||
@Override
|
||||
public SmackTlsContext getSmackTlsContext()
|
||||
throws KeyManagementException, NoSuchAlgorithmException, CertificateException, IOException,
|
||||
UnrecoverableKeyException, KeyStoreException, NoSuchProviderException {
|
||||
public SmackTlsContext getSmackTlsContext() {
|
||||
return ModularXmppClientToServerConnection.this.getSmackTlsContext();
|
||||
}
|
||||
|
||||
|
@ -1069,7 +1065,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
|||
return new Stats(transportsStats, filterStats);
|
||||
}
|
||||
|
||||
public static final class Stats {
|
||||
public static final class Stats extends AbstractStats {
|
||||
public final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport.Stats> transportsStats;
|
||||
public final Map<String, Object> filtersStats;
|
||||
|
||||
|
@ -1079,7 +1075,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
|||
this.filtersStats = Collections.unmodifiableMap(filtersStats);
|
||||
}
|
||||
|
||||
public void appendStatsTo(Appendable appendable) throws IOException {
|
||||
@Override
|
||||
public void appendStatsTo(ExtendedAppendable appendable) throws IOException {
|
||||
StringUtils.appendHeading(appendable, "Connection stats", '#').append('\n');
|
||||
|
||||
for (Map.Entry<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, XmppClientToServerTransport.Stats> entry : transportsStats.entrySet()) {
|
||||
|
@ -1099,16 +1096,5 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try {
|
||||
appendStatsTo(sb);
|
||||
} catch (IOException e) {
|
||||
// Should never happen.
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,20 +16,12 @@
|
|||
*/
|
||||
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;
|
||||
|
@ -41,6 +33,7 @@ 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.internal.SmackTlsContext;
|
||||
import org.jivesoftware.smack.packet.Nonza;
|
||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||
|
@ -107,9 +100,7 @@ public abstract class ModularXmppClientToServerConnectionInternal {
|
|||
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 SmackTlsContext getSmackTlsContext();
|
||||
|
||||
public abstract <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(Nonza nonza,
|
||||
Class<SN> successNonzaClass, Class<FN> failedNonzaClass)
|
||||
|
|
|
@ -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.
|
||||
|
@ -20,9 +20,11 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
|
||||
public abstract class XMPPInputOutputStream {
|
||||
|
||||
protected static FlushMethod flushMethod;
|
||||
protected static FlushMethod flushMethod = FlushMethod.SYNC_FLUSH;
|
||||
|
||||
/**
|
||||
* Set the used flushed method when compressing data. The default is full flush which may not
|
||||
|
@ -33,7 +35,7 @@ public abstract class XMPPInputOutputStream {
|
|||
* @param flushMethod TODO javadoc me please
|
||||
*/
|
||||
public static void setFlushMethod(FlushMethod flushMethod) {
|
||||
XMPPInputOutputStream.flushMethod = flushMethod;
|
||||
XMPPInputOutputStream.flushMethod = Objects.requireNonNull(flushMethod);
|
||||
}
|
||||
|
||||
public static FlushMethod getFlushMethod() {
|
||||
|
|
|
@ -66,4 +66,13 @@ public interface StanzaFilter extends Predicate<Stanza> {
|
|||
default boolean test(Stanza stanza) {
|
||||
return accept(stanza);
|
||||
}
|
||||
|
||||
default <S extends Stanza> Predicate<S> asPredicate(Class<?> stanzaClass) {
|
||||
return s -> {
|
||||
if (!stanzaClass.isAssignableFrom(s.getClass())) {
|
||||
return false;
|
||||
}
|
||||
return accept(s);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
*
|
||||
* 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.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jivesoftware.smack.util.ExtendedAppendable;
|
||||
|
||||
public abstract class AbstractStats {
|
||||
|
||||
public final void appendStatsTo(Appendable appendable) throws IOException {
|
||||
appendStatsTo(new ExtendedAppendable(appendable));
|
||||
}
|
||||
|
||||
public abstract void appendStatsTo(ExtendedAppendable appendable) throws IOException;
|
||||
|
||||
private transient String toStringCache;
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
if (toStringCache != null) {
|
||||
return toStringCache;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try {
|
||||
appendStatsTo(sb);
|
||||
} catch (IOException e) {
|
||||
// Should never happen.
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
toStringCache = sb.toString();
|
||||
|
||||
return toStringCache;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
*
|
||||
* 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.internal;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.jivesoftware.smack.util.dns.SmackDaneVerifier;
|
||||
|
||||
public final class SmackTlsContext {
|
||||
public final SSLContext sslContext;
|
||||
public final SmackDaneVerifier daneVerifier;
|
||||
|
||||
public SmackTlsContext(SSLContext sslContext, SmackDaneVerifier daneVerifier) {
|
||||
assert sslContext != null;
|
||||
this.sslContext = sslContext;
|
||||
this.daneVerifier = daneVerifier;
|
||||
}
|
||||
}
|
|
@ -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 internal classes and interfaces.
|
||||
*/
|
||||
package org.jivesoftware.smack.internal;
|
|
@ -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.
|
||||
|
@ -23,6 +23,10 @@ public abstract class AbstractIqBuilder<IB extends AbstractIqBuilder<IB>> extend
|
|||
|
||||
protected IQ.Type type = IQ.Type.get;
|
||||
|
||||
AbstractIqBuilder(IQ other, String stanzaId) {
|
||||
super(other, stanzaId);
|
||||
}
|
||||
|
||||
AbstractIqBuilder(AbstractIqBuilder<?> other) {
|
||||
super(other);
|
||||
type = other.type;
|
||||
|
|
|
@ -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.
|
||||
|
@ -22,6 +22,10 @@ import org.jivesoftware.smack.util.Objects;
|
|||
public abstract class IqBuilder<IB extends IqBuilder<IB, I>, I extends IQ>
|
||||
extends AbstractIqBuilder<IB> {
|
||||
|
||||
protected IqBuilder(IQ other, String stanzaId) {
|
||||
super(other, stanzaId);
|
||||
}
|
||||
|
||||
protected IqBuilder(AbstractIqBuilder<?> other) {
|
||||
super(other);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
* Copyright 2003-2007 Jive Software, 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.
|
||||
|
@ -17,10 +17,9 @@
|
|||
|
||||
package org.jivesoftware.smack.packet;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.TypedCloneable;
|
||||
|
@ -29,7 +28,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
|
|||
import org.jxmpp.jid.Jid;
|
||||
|
||||
/**
|
||||
* Represents XMPP presence packets. Every presence stanza has a type, which is one of
|
||||
* Represents XMPP presence stanzas. Every presence stanza has a type, which is one of
|
||||
* the following values:
|
||||
* <ul>
|
||||
* <li>{@link Presence.Type#available available} -- (Default) indicates the user is available to
|
||||
|
@ -55,7 +54,7 @@ import org.jxmpp.jid.Jid;
|
|||
* {@link Mode#dnd dnd} (do not disturb).
|
||||
* </ul><p>
|
||||
*
|
||||
* Presence packets are used for two purposes. First, to notify the server of
|
||||
* Presence stanzas are used for two purposes. First, to notify the server of
|
||||
* the user's current presence status. Second, they are used to subscribe and
|
||||
* unsubscribe users from the roster.
|
||||
*
|
||||
|
@ -83,7 +82,7 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
|||
* Creates a new presence update. Status, priority, and mode are left un-set.
|
||||
*
|
||||
* @param type the type.
|
||||
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
|
||||
* @deprecated use {@link PresenceBuilder} or {@link org.jivesoftware.smack.XMPPConnection#getStanzaFactory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.5.
|
||||
|
@ -99,7 +98,7 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
|||
* @param to the recipient.
|
||||
* @param type the type.
|
||||
* @since 4.2
|
||||
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
|
||||
* @deprecated use {@link PresenceBuilder} or {@link org.jivesoftware.smack.XMPPConnection#getStanzaFactory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.5.
|
||||
|
@ -115,7 +114,7 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
|||
* @param status a text message describing the presence update.
|
||||
* @param priority the priority of this presence update.
|
||||
* @param mode the mode type for this presence update.
|
||||
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
|
||||
* @deprecated use {@link PresenceBuilder} or {@link org.jivesoftware.smack.XMPPConnection#getStanzaFactory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.5.
|
||||
|
@ -191,7 +190,7 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
|||
* Sets the type of the presence packet.
|
||||
*
|
||||
* @param type the type of the presence packet.
|
||||
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
|
||||
* @deprecated use {@link PresenceBuilder} or {@link org.jivesoftware.smack.XMPPConnection#getStanzaFactory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.5.
|
||||
|
@ -209,7 +208,7 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
|||
* describing a user's presence (i.e., "gone to lunch").
|
||||
*
|
||||
* @param status the status message.
|
||||
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
|
||||
* @deprecated use {@link PresenceBuilder} or {@link org.jivesoftware.smack.XMPPConnection#getStanzaFactory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.5.
|
||||
|
@ -236,7 +235,7 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
|||
* @param priority the priority of the presence.
|
||||
* @throws IllegalArgumentException if the priority is outside the valid range.
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6121#section-4.7.2.3">RFC 6121 § 4.7.2.3. Priority Element</a>
|
||||
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
|
||||
* @deprecated use {@link PresenceBuilder} or {@link org.jivesoftware.smack.XMPPConnection#getStanzaFactory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.5.
|
||||
|
@ -265,7 +264,7 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
|||
* to be the same thing as {@link Presence.Mode#available}.
|
||||
*
|
||||
* @param mode the mode.
|
||||
* @deprecated use {@link StanzaBuilder} or {@link SocketFactory} instead.
|
||||
* @deprecated use {@link PresenceBuilder} or {@link org.jivesoftware.smack.XMPPConnection#getStanzaFactory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.5.
|
||||
|
@ -309,6 +308,16 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
|||
if (type != Type.available) {
|
||||
buf.attribute("type", type);
|
||||
}
|
||||
|
||||
List<ExtensionElement> extensions = getExtensions();
|
||||
if (status == null
|
||||
&& priority == null
|
||||
&& (mode == null || mode == Mode.available)
|
||||
&& extensions.isEmpty()
|
||||
&& getError() == null) {
|
||||
return buf.closeEmptyElement();
|
||||
}
|
||||
|
||||
buf.rightAngleBracket();
|
||||
|
||||
buf.optElement("status", status);
|
||||
|
@ -317,7 +326,7 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
|||
buf.element("show", mode);
|
||||
}
|
||||
|
||||
buf.append(getExtensions());
|
||||
buf.append(extensions);
|
||||
|
||||
// Add the error sub-packet, if there is one.
|
||||
appendErrorIfExists(buf);
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ExtendedAppendable implements Appendable {
|
||||
|
||||
private final Appendable appendable;
|
||||
|
||||
public ExtendedAppendable(Appendable appendable) {
|
||||
this.appendable = appendable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedAppendable append(CharSequence csq) throws IOException {
|
||||
appendable.append(csq);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedAppendable append(CharSequence csq, int start, int end) throws IOException {
|
||||
appendable.append(csq, start, end);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedAppendable append(char c) throws IOException {
|
||||
appendable.append(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedAppendable append(boolean b) throws IOException {
|
||||
appendable.append(String.valueOf(b));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedAppendable append(int i) throws IOException {
|
||||
appendable.append(String.valueOf(i));
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -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,15 +33,15 @@ public class NumberUtil {
|
|||
/**
|
||||
* Checks if the given long is within the range of an unsigned 32-bit integer, the XML type "xs:unsignedInt".
|
||||
*
|
||||
* @param value TODO javadoc me please
|
||||
* @param value the long to check.
|
||||
* @return the input value.
|
||||
*/
|
||||
public static long requireUInt32(long value) {
|
||||
if (value < 0) {
|
||||
throw new IllegalArgumentException("unsigned 32-bit integers can't be negative");
|
||||
throw new IllegalArgumentException("unsigned 32-bit integers can't be negative: " + value);
|
||||
}
|
||||
if (value > ((1L << 32) - 1)) {
|
||||
throw new IllegalArgumentException("unsigned 32-bit integers can't be greater than 2^32 - 1");
|
||||
throw new IllegalArgumentException("unsigned 32-bit integers can't be greater than 2^32 - 1: " + value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
@ -49,15 +49,15 @@ public class NumberUtil {
|
|||
/**
|
||||
* Checks if the given int is within the range of an unsigned 16-bit integer, the XML type "xs:unsignedShort".
|
||||
*
|
||||
* @param value TODO javadoc me please
|
||||
* @param value the int to check.
|
||||
* @return the input value.
|
||||
*/
|
||||
public static int requireUShort16(int value) {
|
||||
if (value < 0) {
|
||||
throw new IllegalArgumentException("unsigned 16-bit integers can't be negative");
|
||||
throw new IllegalArgumentException("unsigned 16-bit integers can't be negative: " + value);
|
||||
}
|
||||
if (value > ((1 << 16) - 1)) {
|
||||
throw new IllegalArgumentException("unsigned 16-bit integers can't be greater than 2^16 - 1");
|
||||
throw new IllegalArgumentException("unsigned 16-bit integers can't be greater than 2^16 - 1: " + value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
*
|
||||
* 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;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
public interface SslContextFactory {
|
||||
|
||||
SSLContext createSslContext();
|
||||
|
||||
}
|
|
@ -22,12 +22,8 @@ import java.io.FileInputStream;
|
|||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
@ -38,13 +34,9 @@ import java.util.Set;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||
|
@ -132,25 +124,13 @@ public class TLSUtils {
|
|||
*
|
||||
* @param builder a connection configuration builder.
|
||||
* @param <B> Type of the ConnectionConfiguration builder.
|
||||
* @throws NoSuchAlgorithmException if no such algorithm is available.
|
||||
* @throws KeyManagementException if there was a key mangement error.
|
||||
* @return the given builder.
|
||||
*/
|
||||
public static <B extends ConnectionConfiguration.Builder<B, ?>> B acceptAllCertificates(B builder) throws NoSuchAlgorithmException, KeyManagementException {
|
||||
SSLContext context = SSLContext.getInstance(TLS);
|
||||
context.init(null, new TrustManager[] { new AcceptAllTrustManager() }, new SecureRandom());
|
||||
builder.setCustomSSLContext(context);
|
||||
public static <B extends ConnectionConfiguration.Builder<B, ?>> B acceptAllCertificates(B builder) {
|
||||
builder.setCustomX509TrustManager(new AcceptAllTrustManager());
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static final HostnameVerifier DOES_NOT_VERIFY_VERIFIER = new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
// This verifier doesn't verify the hostname, it always returns true.
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Disable the hostname verification of TLS certificates.
|
||||
* <p>
|
||||
|
@ -164,7 +144,9 @@ public class TLSUtils {
|
|||
* @return the given builder.
|
||||
*/
|
||||
public static <B extends ConnectionConfiguration.Builder<B, ?>> B disableHostnameVerificationForTlsCertificates(B builder) {
|
||||
builder.setHostnameVerifier(DOES_NOT_VERIFY_VERIFIER);
|
||||
builder.setHostnameVerifier((hostname, session) -> {
|
||||
return true;
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
@ -274,24 +256,6 @@ public class TLSUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static X509TrustManager getDefaultX509TrustManager(KeyStore keyStore) {
|
||||
String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||
TrustManagerFactory trustManagerFactory;
|
||||
try {
|
||||
trustManagerFactory = TrustManagerFactory.getInstance(defaultAlgorithm);
|
||||
trustManagerFactory.init(keyStore);
|
||||
} catch (NoSuchAlgorithmException | KeyStoreException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
|
||||
if (trustManager instanceof X509TrustManager) {
|
||||
return (X509TrustManager) trustManager;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("No trust manager for the default algorithm " + defaultAlgorithm + " found");
|
||||
}
|
||||
|
||||
private static final File DEFAULT_TRUSTSTORE_PATH;
|
||||
|
||||
static {
|
||||
|
|
|
@ -29,6 +29,7 @@ public class ProviderManagerTest {
|
|||
|
||||
/**
|
||||
* This test should be run in a clean (e.g. forked) VM
|
||||
* @throws Exception if exception.
|
||||
*/
|
||||
@Test
|
||||
public void shouldInitializeSmackTest() throws Exception {
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.junit.Test;
|
|||
|
||||
public class DnsUtilTest {
|
||||
|
||||
@SuppressWarnings("UnnecessaryAnonymousClass")
|
||||
private static final SmackDaneProvider DNS_UTIL_TEST_DANE_PROVIDER = new SmackDaneProvider() {
|
||||
@Override
|
||||
public SmackDaneVerifier newInstance() {
|
||||
|
|
|
@ -150,6 +150,7 @@ public class DummyConnection extends AbstractXMPPConnection {
|
|||
* Returns the first stanza that's sent through {@link #sendStanza(Stanza)}
|
||||
* and that has not been returned by earlier calls to this method.
|
||||
*
|
||||
* @param <P> the top level stream element class.
|
||||
* @return a sent packet.
|
||||
*/
|
||||
public <P extends TopLevelStreamElement> P getSentPacket() {
|
||||
|
@ -162,6 +163,8 @@ public class DummyConnection extends AbstractXMPPConnection {
|
|||
* method will block for up to the specified number of seconds if no packets
|
||||
* have been sent yet.
|
||||
*
|
||||
* @param wait how long to wait in seconds.
|
||||
* @param <P> the top level stream element class.
|
||||
* @return a sent packet.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -222,6 +225,7 @@ public class DummyConnection extends AbstractXMPPConnection {
|
|||
ConnectionConfiguration.Builder<Builder, DummyConnectionConfiguration> {
|
||||
|
||||
private Builder() {
|
||||
setSecurityMode(SecurityMode.disabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.jivesoftware.smackx.carbons.packet;
|
|||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.MessageBuilder;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import org.jivesoftware.smackx.forward.packet.Forwarded;
|
||||
|
@ -158,8 +159,21 @@ public class CarbonExtension implements ExtensionElement {
|
|||
* Marks a message "private", so that it will not be carbon-copied, by adding private packet
|
||||
* extension to the message.
|
||||
*
|
||||
* @param message the message to add the private extension to
|
||||
* @param messageBuilder the message to add the private extension to
|
||||
*/
|
||||
public static void addTo(MessageBuilder messageBuilder) {
|
||||
messageBuilder.addExtension(INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a message "private", so that it will not be carbon-copied, by adding private packet
|
||||
* extension to the message.
|
||||
*
|
||||
* @param message the message to add the private extension to
|
||||
* @deprecated use {@link #addTo(MessageBuilder)} instead.
|
||||
*/
|
||||
// TODO: Remove in Smack 4.6
|
||||
@Deprecated
|
||||
public static void addTo(Message message) {
|
||||
message.addExtension(INSTANCE);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.jivesoftware.smack.AsyncButOrdered;
|
|||
import org.jivesoftware.smack.ConnectionCreationListener;
|
||||
import org.jivesoftware.smack.Manager;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.StanzaListener;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPConnectionRegistry;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
|
@ -68,23 +67,20 @@ public final class FallbackIndicationManager extends Manager {
|
|||
private final StanzaFilter fallbackIndicationElementFilter = new AndFilter(StanzaTypeFilter.MESSAGE,
|
||||
new StanzaExtensionFilter(FallbackIndicationElement.ELEMENT, FallbackIndicationElement.NAMESPACE));
|
||||
|
||||
private final StanzaListener fallbackIndicationElementListener = new StanzaListener() {
|
||||
@Override
|
||||
public void processStanza(Stanza packet) {
|
||||
Message message = (Message) packet;
|
||||
FallbackIndicationElement indicator = FallbackIndicationElement.fromMessage(message);
|
||||
String body = message.getBody();
|
||||
asyncButOrdered.performAsyncButOrdered(message.getFrom().asBareJid(), () -> {
|
||||
for (FallbackIndicationListener l : listeners) {
|
||||
l.onFallbackIndicationReceived(message, indicator, body);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
private void fallbackIndicationElementListener(Stanza packet) {
|
||||
Message message = (Message) packet;
|
||||
FallbackIndicationElement indicator = FallbackIndicationElement.fromMessage(message);
|
||||
String body = message.getBody();
|
||||
asyncButOrdered.performAsyncButOrdered(message.getFrom().asBareJid(), () -> {
|
||||
for (FallbackIndicationListener l : listeners) {
|
||||
l.onFallbackIndicationReceived(message, indicator, body);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private FallbackIndicationManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
connection.addAsyncStanzaListener(fallbackIndicationElementListener, fallbackIndicationElementFilter);
|
||||
connection.addAsyncStanzaListener(this::fallbackIndicationElementListener, fallbackIndicationElementFilter);
|
||||
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(FallbackIndicationElement.NAMESPACE);
|
||||
}
|
||||
|
||||
|
@ -139,7 +135,7 @@ public final class FallbackIndicationManager extends Manager {
|
|||
* @param fallbackMessageBody fallback message body
|
||||
* @return builder with set body and added fallback element
|
||||
*/
|
||||
public MessageBuilder addFallbackIndicationWithBody(MessageBuilder messageBuilder, String fallbackMessageBody) {
|
||||
public static MessageBuilder addFallbackIndicationWithBody(MessageBuilder messageBuilder, String fallbackMessageBody) {
|
||||
return addFallbackIndication(messageBuilder).setBody(fallbackMessageBody);
|
||||
}
|
||||
|
||||
|
@ -149,7 +145,7 @@ public final class FallbackIndicationManager extends Manager {
|
|||
* @param messageBuilder message builder
|
||||
* @return message builder with added fallback element
|
||||
*/
|
||||
public MessageBuilder addFallbackIndication(MessageBuilder messageBuilder) {
|
||||
public static MessageBuilder addFallbackIndication(MessageBuilder messageBuilder) {
|
||||
return messageBuilder.addExtension(new FallbackIndicationElement());
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ import javax.net.ssl.HttpsURLConnection;
|
|||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||
import org.jivesoftware.smack.ConnectionCreationListener;
|
||||
import org.jivesoftware.smack.ConnectionListener;
|
||||
import org.jivesoftware.smack.Manager;
|
||||
|
@ -307,7 +306,9 @@ public final class HttpFileUploadManager extends Manager {
|
|||
public URL uploadFile(InputStream inputStream, String fileName, long fileSize, UploadProgressListener listener) throws XMPPErrorException, InterruptedException, SmackException, IOException {
|
||||
Objects.requireNonNull(inputStream, "Input Stream cannot be null");
|
||||
Objects.requireNonNull(fileName, "Filename Stream cannot be null");
|
||||
Objects.requireNonNull(fileSize, "Filesize Stream cannot be null");
|
||||
if (fileSize < 0) {
|
||||
throw new IllegalArgumentException("File size cannot be negative");
|
||||
}
|
||||
final Slot slot = requestSlot(fileName, fileSize, "application/octet-stream");
|
||||
upload(inputStream, fileSize, slot, listener);
|
||||
return slot.getGetUrl();
|
||||
|
@ -324,8 +325,7 @@ public final class HttpFileUploadManager extends Manager {
|
|||
* supported by the service.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
* @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
|
||||
* @throws SmackException.NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws SmackException.NoResponseException if there was no response from the remote entity.
|
||||
* @throws SmackException if smack exception.
|
||||
*/
|
||||
public Slot requestSlot(String filename, long fileSize) throws InterruptedException,
|
||||
XMPPException.XMPPErrorException, SmackException {
|
||||
|
@ -348,7 +348,7 @@ public final class HttpFileUploadManager extends Manager {
|
|||
* @throws SmackException.NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
* @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
|
||||
* @throws SmackException.NoResponseException if there was no response from the remote entity.
|
||||
* @throws SmackException if smack exception.
|
||||
*/
|
||||
public Slot requestSlot(String filename, long fileSize, String contentType) throws SmackException,
|
||||
InterruptedException, XMPPException.XMPPErrorException {
|
||||
|
@ -427,11 +427,6 @@ public final class HttpFileUploadManager extends Manager {
|
|||
this.tlsSocketFactory = tlsContext.getSocketFactory();
|
||||
}
|
||||
|
||||
public void useTlsSettingsFrom(ConnectionConfiguration connectionConfiguration) {
|
||||
SSLContext sslContext = connectionConfiguration.getCustomSSLContext();
|
||||
setTlsContext(sslContext);
|
||||
}
|
||||
|
||||
private void upload(InputStream iStream, long fileSize, Slot slot, UploadProgressListener listener) throws IOException {
|
||||
final URL putUrl = slot.getPutUrl();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2016 Florian Schmaus and Fernando Ramirez
|
||||
* Copyright © 2016-2020 Florian Schmaus and Fernando Ramirez
|
||||
*
|
||||
* 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 @@ import org.jxmpp.jid.Jid;
|
|||
*/
|
||||
public class MamElements {
|
||||
|
||||
public static final String NAMESPACE = "urn:xmpp:mam:1";
|
||||
public static final String NAMESPACE = "urn:xmpp:mam:2";
|
||||
|
||||
/**
|
||||
* MAM result extension class.
|
||||
|
|
|
@ -102,7 +102,7 @@ public final class MessageRetractionManager extends Manager {
|
|||
* @param retractedMessageId {@link OriginIdElement OriginID} of the message that the user wants to retract
|
||||
* @param carrierMessageBuilder message used to transmit the message retraction to the recipient
|
||||
*/
|
||||
public void addRetractionElementToMessage(OriginIdElement retractedMessageId, MessageBuilder carrierMessageBuilder) {
|
||||
public static void addRetractionElementToMessage(OriginIdElement retractedMessageId, MessageBuilder carrierMessageBuilder) {
|
||||
FasteningElement fasteningElement = FasteningElement.builder()
|
||||
.setOriginId(retractedMessageId)
|
||||
.addWrappedPayload(new RetractElement())
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub
|
||||
* Copyright 2018 Paul Schaub, 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,10 +29,6 @@ import org.jivesoftware.smack.filter.NotFilter;
|
|||
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
|
||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||
import org.jivesoftware.smack.filter.ToTypeFilter;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.MessageBuilder;
|
||||
import org.jivesoftware.smack.util.Consumer;
|
||||
import org.jivesoftware.smack.util.Predicate;
|
||||
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.sid.element.OriginIdElement;
|
||||
|
@ -62,14 +58,8 @@ public final class StableUniqueStanzaIdManager extends Manager {
|
|||
// Filter that filters for messages with an origin id
|
||||
private static final StanzaFilter ORIGIN_ID_FILTER = new StanzaExtensionFilter(OriginIdElement.ELEMENT, NAMESPACE);
|
||||
|
||||
// Listener for outgoing stanzas that adds origin-ids to outgoing stanzas.
|
||||
private static final Consumer<MessageBuilder> ADD_ORIGIN_ID_INTERCEPTOR = mb -> OriginIdElement.addOriginId(mb);
|
||||
|
||||
// We need a filter for outgoing messages that do not carry an origin-id already.
|
||||
private static final StanzaFilter ADD_ORIGIN_ID_FILTER = new AndFilter(OUTGOING_FILTER, new NotFilter(ORIGIN_ID_FILTER));
|
||||
private static final Predicate<Message> ADD_ORIGIN_ID_PREDICATE = m -> {
|
||||
return ADD_ORIGIN_ID_FILTER.accept(m);
|
||||
};
|
||||
|
||||
static {
|
||||
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
|
||||
|
@ -113,7 +103,7 @@ public final class StableUniqueStanzaIdManager extends Manager {
|
|||
* Start appending origin-id elements to outgoing stanzas and add the feature to disco.
|
||||
*/
|
||||
public synchronized void enable() {
|
||||
connection().addMessageInterceptor(ADD_ORIGIN_ID_INTERCEPTOR, ADD_ORIGIN_ID_PREDICATE);
|
||||
connection().addMessageInterceptor(OriginIdElement::addTo, ADD_ORIGIN_ID_FILTER::accept);
|
||||
ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE);
|
||||
}
|
||||
|
||||
|
@ -122,7 +112,7 @@ public final class StableUniqueStanzaIdManager extends Manager {
|
|||
*/
|
||||
public synchronized void disable() {
|
||||
ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE);
|
||||
connection().removeMessageInterceptor(ADD_ORIGIN_ID_INTERCEPTOR);
|
||||
connection().removeMessageInterceptor(OriginIdElement::addTo);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -39,7 +39,7 @@ public class OriginIdElement extends StableAndUniqueIdElement {
|
|||
*
|
||||
* @param message message.
|
||||
* @return the added origin-id element.
|
||||
* @deprecated use {@link #addOriginId(MessageBuilder)} instead.
|
||||
* @deprecated use {@link #addTo(MessageBuilder)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.5.
|
||||
|
@ -57,7 +57,7 @@ public class OriginIdElement extends StableAndUniqueIdElement {
|
|||
* @param messageBuilder the message builder to add an origin ID to.
|
||||
* @return the added origin-id element.
|
||||
*/
|
||||
public static OriginIdElement addOriginId(MessageBuilder messageBuilder) {
|
||||
public static OriginIdElement addTo(MessageBuilder messageBuilder) {
|
||||
OriginIdElement originId = new OriginIdElement();
|
||||
messageBuilder.addExtension(originId);
|
||||
// TODO: Find solution to have both the originIds stanzaId and a nice to look at incremental stanzaID.
|
||||
|
|
|
@ -17,22 +17,22 @@
|
|||
<!-- XEP-0313: Message Archive Management -->
|
||||
<iqProvider>
|
||||
<elementName>prefs</elementName>
|
||||
<namespace>urn:xmpp:mam:1</namespace>
|
||||
<namespace>urn:xmpp:mam:2</namespace>
|
||||
<className>org.jivesoftware.smackx.mam.provider.MamPrefsIQProvider</className>
|
||||
</iqProvider>
|
||||
<iqProvider>
|
||||
<elementName>query</elementName>
|
||||
<namespace>urn:xmpp:mam:1</namespace>
|
||||
<namespace>urn:xmpp:mam:2</namespace>
|
||||
<className>org.jivesoftware.smackx.mam.provider.MamQueryIQProvider</className>
|
||||
</iqProvider>
|
||||
<iqProvider>
|
||||
<elementName>fin</elementName>
|
||||
<namespace>urn:xmpp:mam:1</namespace>
|
||||
<namespace>urn:xmpp:mam:2</namespace>
|
||||
<className>org.jivesoftware.smackx.mam.provider.MamFinIQProvider</className>
|
||||
</iqProvider>
|
||||
<extensionProvider>
|
||||
<elementName>result</elementName>
|
||||
<namespace>urn:xmpp:mam:1</namespace>
|
||||
<namespace>urn:xmpp:mam:2</namespace>
|
||||
<className>org.jivesoftware.smackx.mam.provider.MamResultProvider</className>
|
||||
</extensionProvider>
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.MessageBuilder;
|
||||
import org.jivesoftware.smack.packet.StanzaBuilder;
|
||||
|
||||
import org.jivesoftware.smackx.fallback_indication.element.FallbackIndicationElement;
|
||||
|
||||
|
@ -30,11 +30,11 @@ public class FallbackIndicationTest {
|
|||
|
||||
@Test
|
||||
public void testFallbackIndicationElementFromMessageTest() {
|
||||
Message messageWithoutFallback = MessageBuilder.buildMessage()
|
||||
Message messageWithoutFallback = StanzaBuilder.buildMessage()
|
||||
.build();
|
||||
assertNull(FallbackIndicationElement.fromMessage(messageWithoutFallback));
|
||||
|
||||
Message messageWithFallback = MessageBuilder.buildMessage()
|
||||
Message messageWithFallback = StanzaBuilder.buildMessage()
|
||||
.addExtension(new FallbackIndicationElement())
|
||||
.build();
|
||||
assertNotNull(FallbackIndicationElement.fromMessage(messageWithFallback));
|
||||
|
|
|
@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
public class MamFinProviderTest extends MamTest {
|
||||
|
||||
static final String exmapleMamFinXml = "<fin xmlns='urn:xmpp:mam:1' stable='true'>"
|
||||
static final String exmapleMamFinXml = "<fin xmlns='urn:xmpp:mam:2' stable='true'>"
|
||||
+ "<set xmlns='http://jabber.org/protocol/rsm'>" + "<max>10</max>" + "<after>09af3-cc343-b409f</after>"
|
||||
+ "</set>" + "</fin>";
|
||||
|
||||
|
@ -56,7 +56,7 @@ public class MamFinProviderTest extends MamTest {
|
|||
public void checkQueryLimitedResults() throws Exception {
|
||||
// @formatter:off
|
||||
final String IQ_LIMITED_RESULTS_EXAMPLE = "<iq type='result' id='u29303'>"
|
||||
+ "<fin xmlns='urn:xmpp:mam:1' complete='true'>"
|
||||
+ "<fin xmlns='urn:xmpp:mam:2' complete='true'>"
|
||||
+ "<set xmlns='http://jabber.org/protocol/rsm'>"
|
||||
+ "<first index='0'>23452-4534-1</first>"
|
||||
+ "<last>390-2342-22</last>" + "<count>16</count>"
|
||||
|
|
|
@ -33,19 +33,19 @@ import org.jxmpp.jid.Jid;
|
|||
|
||||
public class MamPrefIQProviderTest extends MamTest {
|
||||
|
||||
private static final String exampleMamPrefsIQ1 = "<iq type='set' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:1' default='roster'>"
|
||||
private static final String exampleMamPrefsIQ1 = "<iq type='set' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:2' default='roster'>"
|
||||
+ "<always>" + "<jid>romeo@montague.lit</jid>" + "</always>" + "<never>"
|
||||
+ "<jid>montague@montague.lit</jid>" + "</never>" + "</prefs>" + "</iq>";
|
||||
|
||||
private static final String exampleMamPrefsIQ2 = "<iq type='set' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:1' default='roster'>"
|
||||
private static final String exampleMamPrefsIQ2 = "<iq type='set' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:2' default='roster'>"
|
||||
+ "<always>" + "<jid>romeo@montague.lit</jid>" + "<jid>montague@montague.lit</jid>" + "</always>"
|
||||
+ "<never>" + "</never>" + "</prefs>" + "</iq>";
|
||||
|
||||
private static final String exampleMamPrefsIQ3 = "<iq type='get' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:1'>" + "</prefs>"
|
||||
private static final String exampleMamPrefsIQ3 = "<iq type='get' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:2'>" + "</prefs>"
|
||||
+ "</iq>";
|
||||
|
||||
private static final String exampleMamPrefsResultIQ = "<iq type='result' id='juliet3'>"
|
||||
+ "<prefs xmlns='urn:xmpp:mam:1' default='roster'>" + "<always>" + "<jid>romeo@montague.lit</jid>"
|
||||
+ "<prefs xmlns='urn:xmpp:mam:2' default='roster'>" + "<always>" + "<jid>romeo@montague.lit</jid>"
|
||||
+ "</always>" + "<never>" + "<jid>sarasa@montague.lit</jid>" + "<jid>montague@montague.lit</jid>"
|
||||
+ "</never>" + "</prefs>" + "</iq>";
|
||||
|
||||
|
|
|
@ -35,18 +35,18 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
public class MamQueryIQProviderTest {
|
||||
|
||||
private static final String exampleMamQueryIQ1 = "<iq type='set' id='query4'>" + "<query xmlns='urn:xmpp:mam:1' queryid='test'>"
|
||||
private static final String exampleMamQueryIQ1 = "<iq type='set' id='query4'>" + "<query xmlns='urn:xmpp:mam:2' queryid='test'>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field type='hidden' var='FORM_TYPE'>"
|
||||
+ "<value>urn:xmpp:mam:1</value>" + "</field>"
|
||||
+ "<value>urn:xmpp:mam:2</value>" + "</field>"
|
||||
+ "<field type='text-single' var='urn:example:xmpp:free-text-search'>"
|
||||
+ "<value>Where arth thou, my Juliet?</value>" + "</field>"
|
||||
+ "<field type='text-single' var='urn:example:xmpp:stanza-content'>"
|
||||
+ "<value>{http://jabber.org/protocol/mood}mood/lonely</value>" + "</field>" + "</x>" + "</query>"
|
||||
+ "</iq>";
|
||||
|
||||
private static final String exampleMamQueryIQ2 = "<iq type='result' id='form1'>" + "<query xmlns='urn:xmpp:mam:1'>"
|
||||
private static final String exampleMamQueryIQ2 = "<iq type='result' id='form1'>" + "<query xmlns='urn:xmpp:mam:2'>"
|
||||
+ "<x xmlns='jabber:x:data' type='form'>" + "<field type='hidden' var='FORM_TYPE'>"
|
||||
+ "<value>urn:xmpp:mam:1</value>" + "</field>" + "<field type='jid-single' var='with'/>"
|
||||
+ "<value>urn:xmpp:mam:2</value>" + "</field>" + "<field type='jid-single' var='with'/>"
|
||||
+ "<field type='text-single' var='start'/>" + "<field type='text-single' var='end'/>"
|
||||
+ "<field type='text-single' var='urn:example:xmpp:free-text-search'/>"
|
||||
+ "<field type='text-single' var='urn:example:xmpp:stanza-content'/>" + "</x>" + "</query>" + "</iq>";
|
||||
|
@ -80,7 +80,7 @@ public class MamQueryIQProviderTest {
|
|||
assertEquals(dataForm2.getType(), DataForm.Type.form);
|
||||
|
||||
List<FormField> fields2 = dataForm2.getFields();
|
||||
assertEquals(fields2.get(0).getValues().get(0).toString(), "urn:xmpp:mam:1");
|
||||
assertEquals(fields2.get(0).getValues().get(0).toString(), "urn:xmpp:mam:2");
|
||||
assertTrue(fields2.get(0).getValues().size() == 1);
|
||||
assertEquals(fields2.get(1).getType(), FormField.Type.jid_single);
|
||||
assertEquals(fields2.get(2).getType(), FormField.Type.text_single);
|
||||
|
|
|
@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
public class MamResultProviderTest {
|
||||
|
||||
private static final String exampleMamResultXml = "<result xmlns='urn:xmpp:mam:1' queryid='f27' id='28482-98726-73623'>"
|
||||
private static final String exampleMamResultXml = "<result xmlns='urn:xmpp:mam:2' queryid='f27' id='28482-98726-73623'>"
|
||||
+ "<forwarded xmlns='urn:xmpp:forward:0'>" + "<delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>"
|
||||
+ "<message xmlns='jabber:client' to='juliet@capulet.lit/balcony' from='romeo@montague.lit/orchard' "
|
||||
+ "type='chat'>"
|
||||
|
@ -42,7 +42,7 @@ public class MamResultProviderTest {
|
|||
+ "</message>" + "</forwarded>" + "</result>";
|
||||
|
||||
private static final String exampleResultMessage = "<message id='aeb213' to='juliet@capulet.lit/chamber'>"
|
||||
+ "<result xmlns='urn:xmpp:mam:1' queryid='f27' id='28482-98726-73623'>"
|
||||
+ "<result xmlns='urn:xmpp:mam:2' queryid='f27' id='28482-98726-73623'>"
|
||||
+ "<forwarded xmlns='urn:xmpp:forward:0'>" + "<delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>"
|
||||
+ "<message xmlns='jabber:client' from='witch@shakespeare.lit' to='macbeth@shakespeare.lit'>"
|
||||
+ "<body>Hail to thee</body>" + "</message>" + "</forwarded>" + "</result>" + "</message>";
|
||||
|
|
|
@ -29,9 +29,9 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
public class PagingTest extends MamTest {
|
||||
|
||||
private static final String pagingStanza = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:1' queryid='testid'>"
|
||||
private static final String pagingStanza = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:2' queryid='testid'>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>"
|
||||
+ "<value>urn:xmpp:mam:1</value>" + "</field>" + "</x>" + "<set xmlns='http://jabber.org/protocol/rsm'>"
|
||||
+ "<value>urn:xmpp:mam:2</value>" + "</field>" + "</x>" + "<set xmlns='http://jabber.org/protocol/rsm'>"
|
||||
+ "<max>10</max>" + "</set>" + "</query>" + "</iq>";
|
||||
|
||||
@Test
|
||||
|
@ -46,7 +46,7 @@ public class PagingTest extends MamTest {
|
|||
mamQueryIQ.addExtension(rsmSet);
|
||||
|
||||
assertEquals(mamQueryIQ.getDataForm(), dataForm);
|
||||
assertEquals(mamQueryIQ.getDataForm().getFields().get(0).getValues().get(0).toString(), "urn:xmpp:mam:1");
|
||||
assertEquals(mamQueryIQ.getDataForm().getFields().get(0).getValues().get(0).toString(), "urn:xmpp:mam:2");
|
||||
assertEquals(pagingStanza, mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
|
||||
}
|
||||
|
||||
|
|
|
@ -40,12 +40,12 @@ import org.jxmpp.jid.impl.JidCreate;
|
|||
|
||||
public class QueryArchiveTest extends MamTest {
|
||||
|
||||
private static final String mamSimpleQueryIQ = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:1' queryid='testid'>"
|
||||
private static final String mamSimpleQueryIQ = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:2' queryid='testid'>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>" + "<value>"
|
||||
+ MamElements.NAMESPACE + "</value>" + "</field>" + "</x>" + "</query>" + "</iq>";
|
||||
|
||||
private static final String mamQueryResultExample = "<message to='hag66@shakespeare.lit/pda' from='coven@chat.shakespeare.lit' id='iasd207'>"
|
||||
+ "<result xmlns='urn:xmpp:mam:1' queryid='g27' id='34482-21985-73620'>"
|
||||
+ "<result xmlns='urn:xmpp:mam:2' queryid='g27' id='34482-21985-73620'>"
|
||||
+ "<forwarded xmlns='urn:xmpp:forward:0'>"
|
||||
+ "<delay xmlns='urn:xmpp:delay' stamp='2002-10-13T23:58:37.000+00:00'/>" + "<message "
|
||||
+ "xmlns='jabber:client' from='coven@chat.shakespeare.lit/firstwitch' " + "id='162BEBB1-F6DB-4D9A-9BD8-CFDCC801A0B2' "
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
public class ResultsLimitTest extends MamTest {
|
||||
|
||||
private static final String resultsLimitStanza = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:1' queryid='testid'>"
|
||||
private static final String resultsLimitStanza = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:2' queryid='testid'>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>" + "<value>"
|
||||
+ MamElements.NAMESPACE + "</value>" + "</field>" + "</x>" + "<set xmlns='http://jabber.org/protocol/rsm'>"
|
||||
+ "<max>10</max>" + "</set>" + "</query>" + "</iq>";
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.util.Arrays;
|
|||
|
||||
import org.jivesoftware.smack.packet.MessageBuilder;
|
||||
import org.jivesoftware.smack.packet.StandardExtensionElement;
|
||||
import org.jivesoftware.smack.packet.StanzaBuilder;
|
||||
import org.jivesoftware.smack.packet.StanzaFactory;
|
||||
import org.jivesoftware.smack.packet.id.StandardStanzaIdSource;
|
||||
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||
|
@ -166,10 +167,10 @@ public class MessageFasteningElementsTest {
|
|||
|
||||
@Test
|
||||
public void hasFasteningElementTest() {
|
||||
MessageBuilder messageBuilderWithFasteningElement = MessageBuilder.buildMessage()
|
||||
MessageBuilder messageBuilderWithFasteningElement = StanzaBuilder.buildMessage()
|
||||
.setBody("Hi!")
|
||||
.addExtension(FasteningElement.builder().setOriginId("origin-id-1").build());
|
||||
MessageBuilder messageBuilderWithoutFasteningElement = MessageBuilder.buildMessage()
|
||||
MessageBuilder messageBuilderWithoutFasteningElement = StanzaBuilder.buildMessage()
|
||||
.setBody("Ho!");
|
||||
|
||||
assertTrue(FasteningElement.hasFasteningElement(messageBuilderWithFasteningElement));
|
||||
|
|
|
@ -78,7 +78,7 @@ public class StableUniqueStanzaIdTest extends SmackTestSuite {
|
|||
assertFalse(OriginIdElement.hasOriginId(message));
|
||||
assertFalse(StanzaIdElement.hasStanzaId(message));
|
||||
|
||||
OriginIdElement.addOriginId(messageBuilder);
|
||||
OriginIdElement.addTo(messageBuilder);
|
||||
|
||||
message = messageBuilder.build();
|
||||
assertTrue(OriginIdElement.hasOriginId(message));
|
||||
|
|
|
@ -33,8 +33,6 @@ import java.util.SortedSet;
|
|||
import java.util.TreeSet;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionCreationListener;
|
||||
import org.jivesoftware.smack.ConnectionListener;
|
||||
|
@ -51,13 +49,11 @@ import org.jivesoftware.smack.filter.PresenceTypeFilter;
|
|||
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
|
||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||
import org.jivesoftware.smack.filter.StanzaTypeFilter;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.packet.PresenceBuilder;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.roster.AbstractPresenceEventListener;
|
||||
import org.jivesoftware.smack.roster.Roster;
|
||||
import org.jivesoftware.smack.util.Consumer;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
|
||||
|
@ -87,7 +83,6 @@ import org.jxmpp.util.cache.LruCache;
|
|||
* @see <a href="http://www.xmpp.org/extensions/xep-0115.html">XEP-0115: Entity Capabilities</a>
|
||||
*/
|
||||
public final class EntityCapsManager extends Manager {
|
||||
private static final Logger LOGGER = Logger.getLogger(EntityCapsManager.class.getName());
|
||||
|
||||
public static final String NAMESPACE = CapsExtension.NAMESPACE;
|
||||
public static final String ELEMENT = CapsExtension.ELEMENT;
|
||||
|
@ -307,7 +302,6 @@ public final class EntityCapsManager extends Manager {
|
|||
|
||||
private boolean entityCapsEnabled;
|
||||
private CapsVersionAndHash currentCapsVersion;
|
||||
private volatile Presence presenceSend;
|
||||
|
||||
/**
|
||||
* The entity node String used by this EntityCapsManager instance.
|
||||
|
@ -317,11 +311,11 @@ public final class EntityCapsManager extends Manager {
|
|||
// Intercept presence packages and add caps data when intended.
|
||||
// XEP-0115 specifies that a client SHOULD include entity capabilities
|
||||
// with every presence notification it sends.
|
||||
private final Consumer<PresenceBuilder> presenceInterceptor = presenceBuilder -> {
|
||||
private void addCapsExtension(PresenceBuilder presenceBuilder) {
|
||||
CapsVersionAndHash capsVersionAndHash = getCapsVersionAndHash();
|
||||
CapsExtension caps = new CapsExtension(entityNode, capsVersionAndHash.version, capsVersionAndHash.hash);
|
||||
presenceBuilder.overrideExtension(caps);
|
||||
};
|
||||
}
|
||||
|
||||
private EntityCapsManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
|
@ -342,11 +336,6 @@ public final class EntityCapsManager extends Manager {
|
|||
// feature, so we try to process it after we are connected and
|
||||
// once after we are authenticated.
|
||||
processCapsStreamFeatureIfAvailable(connection);
|
||||
|
||||
// Reset presenceSend when the connection was not resumed
|
||||
if (!resumed) {
|
||||
presenceSend = null;
|
||||
}
|
||||
}
|
||||
private void processCapsStreamFeatureIfAvailable(XMPPConnection connection) {
|
||||
CapsExtension capsExtension = connection.getFeature(
|
||||
|
@ -359,9 +348,6 @@ public final class EntityCapsManager extends Manager {
|
|||
}
|
||||
});
|
||||
|
||||
// This calculates the local entity caps version
|
||||
updateLocalEntityCaps();
|
||||
|
||||
if (autoEnableEntityCaps)
|
||||
enableEntityCaps();
|
||||
|
||||
|
@ -387,26 +373,16 @@ public final class EntityCapsManager extends Manager {
|
|||
}
|
||||
});
|
||||
|
||||
connection.addStanzaSendingListener(new StanzaListener() {
|
||||
@Override
|
||||
public void processStanza(Stanza packet) {
|
||||
presenceSend = (Presence) packet;
|
||||
}
|
||||
}, PresenceTypeFilter.OUTGOING_PRESENCE_BROADCAST);
|
||||
|
||||
|
||||
enableEntityCaps();
|
||||
|
||||
// It's important to do this as last action. Since it changes the
|
||||
// behavior of the SDM in some ways
|
||||
sdm.addEntityCapabilitiesChangedListener(new EntityCapabilitiesChangedListener() {
|
||||
@Override
|
||||
public void onEntityCapailitiesChanged() {
|
||||
public void onEntityCapabilitiesChanged(DiscoverInfo synthesizedDiscoveryInfo) {
|
||||
if (!entityCapsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateLocalEntityCaps();
|
||||
updateLocalEntityCaps(synthesizedDiscoveryInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -425,13 +401,12 @@ public final class EntityCapsManager extends Manager {
|
|||
}
|
||||
|
||||
public synchronized void enableEntityCaps() {
|
||||
connection().addPresenceInterceptor(presenceInterceptor, p -> {
|
||||
connection().addPresenceInterceptor(this::addCapsExtension, p -> {
|
||||
return PresenceTypeFilter.AVAILABLE.accept(p);
|
||||
});
|
||||
|
||||
// Add Entity Capabilities (XEP-0115) feature node.
|
||||
sdm.addFeature(NAMESPACE);
|
||||
updateLocalEntityCaps();
|
||||
entityCapsEnabled = true;
|
||||
}
|
||||
|
||||
|
@ -439,18 +414,13 @@ public final class EntityCapsManager extends Manager {
|
|||
entityCapsEnabled = false;
|
||||
sdm.removeFeature(NAMESPACE);
|
||||
|
||||
connection().removePresenceInterceptor(presenceInterceptor);
|
||||
connection().removePresenceInterceptor(this::addCapsExtension);
|
||||
}
|
||||
|
||||
public boolean entityCapsEnabled() {
|
||||
return entityCapsEnabled;
|
||||
}
|
||||
|
||||
public void setEntityNode(String entityNode) {
|
||||
this.entityNode = entityNode;
|
||||
updateLocalEntityCaps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a record telling what entity caps node a user has.
|
||||
*
|
||||
|
@ -522,13 +492,10 @@ public final class EntityCapsManager extends Manager {
|
|||
* presence is send to inform others about your new Entity Caps node string.
|
||||
*
|
||||
*/
|
||||
private void updateLocalEntityCaps() {
|
||||
private void updateLocalEntityCaps(DiscoverInfo synthesizedDiscoveryInfo) {
|
||||
XMPPConnection connection = connection();
|
||||
|
||||
DiscoverInfoBuilder discoverInfoBuilder = DiscoverInfo.builder("synthetized-disco-info-response")
|
||||
.ofType(IQ.Type.result);
|
||||
sdm.addDiscoverInfoTo(discoverInfoBuilder);
|
||||
|
||||
DiscoverInfoBuilder discoverInfoBuilder = synthesizedDiscoveryInfo.asBuilder("synthesized-disco-info-result");
|
||||
// getLocalNodeVer() will return a result only after currentCapsVersion is set. Therefore
|
||||
// set it first and then call getLocalNodeVer()
|
||||
currentCapsVersion = generateVerificationString(discoverInfoBuilder);
|
||||
|
@ -564,20 +531,6 @@ public final class EntityCapsManager extends Manager {
|
|||
return packetExtensions;
|
||||
}
|
||||
});
|
||||
|
||||
// Re-send the last sent presence, and let the stanza interceptor
|
||||
// add a <c/> node to it.
|
||||
// See http://xmpp.org/extensions/xep-0115.html#advertise
|
||||
// We only send a presence packet if there was already one send
|
||||
// to respect ConnectionConfiguration.isSendPresence()
|
||||
if (connection != null && connection.isAuthenticated() && presenceSend != null) {
|
||||
try {
|
||||
connection.sendStanza(presenceSend.cloneWithNewId());
|
||||
}
|
||||
catch (InterruptedException | NotConnectedException e) {
|
||||
LOGGER.log(Level.WARNING, "Could could not update presence with caps info", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -517,7 +517,7 @@ public final class AdHocCommandManager extends Manager {
|
|||
|
||||
private boolean sessionSweeperScheduled;
|
||||
|
||||
private final Runnable sessionSweeper = () -> {
|
||||
private void sessionSweeper() {
|
||||
final long currentTime = System.currentTimeMillis();
|
||||
synchronized (this) {
|
||||
for (Iterator<Entry<String, LocalCommand>> it = executingCommands.entrySet().iterator(); it.hasNext();) {
|
||||
|
@ -553,7 +553,7 @@ public final class AdHocCommandManager extends Manager {
|
|||
}
|
||||
|
||||
sessionSweeperScheduled = true;
|
||||
schedule(sessionSweeper, 10, TimeUnit.SECONDS);
|
||||
schedule(this::sessionSweeper, 10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,8 +16,10 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.disco;
|
||||
|
||||
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
||||
|
||||
public interface EntityCapabilitiesChangedListener {
|
||||
|
||||
void onEntityCapailitiesChanged();
|
||||
void onEntityCapabilitiesChanged(DiscoverInfo synthesizedDiscoveryInfo);
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.disco;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -28,20 +29,30 @@ import java.util.Set;
|
|||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionCreationListener;
|
||||
import org.jivesoftware.smack.ConnectionListener;
|
||||
import org.jivesoftware.smack.Manager;
|
||||
import org.jivesoftware.smack.ScheduledAction;
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPConnectionRegistry;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.filter.PresenceTypeFilter;
|
||||
import org.jivesoftware.smack.internal.AbstractStats;
|
||||
import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
|
||||
import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.packet.StanzaError;
|
||||
import org.jivesoftware.smack.util.CollectionUtil;
|
||||
import org.jivesoftware.smack.util.ExtendedAppendable;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
|
||||
|
@ -71,6 +82,8 @@ import org.jxmpp.util.cache.ExpirationCache;
|
|||
*/
|
||||
public final class ServiceDiscoveryManager extends Manager {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(ServiceDiscoveryManager.class.getName());
|
||||
|
||||
private static final String DEFAULT_IDENTITY_NAME = "Smack";
|
||||
private static final String DEFAULT_IDENTITY_CATEGORY = "client";
|
||||
private static final String DEFAULT_IDENTITY_TYPE = "pc";
|
||||
|
@ -91,6 +104,8 @@ public final class ServiceDiscoveryManager extends Manager {
|
|||
private List<DataForm> extendedInfos = new ArrayList<>(2);
|
||||
private final Map<String, NodeInformationProvider> nodeInformationProviders = new ConcurrentHashMap<>();
|
||||
|
||||
private volatile Presence presenceSend;
|
||||
|
||||
// Create a new ServiceDiscoveryManager on every established connection
|
||||
static {
|
||||
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
|
||||
|
@ -190,6 +205,18 @@ public final class ServiceDiscoveryManager extends Manager {
|
|||
return response;
|
||||
}
|
||||
});
|
||||
|
||||
connection.addConnectionListener(new ConnectionListener() {
|
||||
@Override
|
||||
public void authenticated(XMPPConnection connection, boolean resumed) {
|
||||
// Reset presenceSend when the connection was not resumed
|
||||
if (!resumed) {
|
||||
presenceSend = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
connection.addStanzaSendingListener(p -> presenceSend = (Presence) p,
|
||||
PresenceTypeFilter.OUTGOING_PRESENCE_BROADCAST);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -894,13 +921,55 @@ public final class ServiceDiscoveryManager extends Manager {
|
|||
return entityCapabilitiesChangedListeners.add(entityCapabilitiesChangedListener);
|
||||
}
|
||||
|
||||
private static final int RENEW_ENTITY_CAPS_DELAY_MILLIS = 25;
|
||||
|
||||
private ScheduledAction renewEntityCapsScheduledAction;
|
||||
|
||||
private final AtomicInteger renewEntityCapsPerformed = new AtomicInteger();
|
||||
private int renewEntityCapsRequested = 0;
|
||||
private int scheduledRenewEntityCapsAvoided = 0;
|
||||
|
||||
/**
|
||||
* Notify the {@link EntityCapabilitiesChangedListener} about changed capabilities.
|
||||
*/
|
||||
private void renewEntityCapsVersion() {
|
||||
for (EntityCapabilitiesChangedListener entityCapabilitiesChangedListener : entityCapabilitiesChangedListeners) {
|
||||
entityCapabilitiesChangedListener.onEntityCapailitiesChanged();
|
||||
private synchronized void renewEntityCapsVersion() {
|
||||
renewEntityCapsRequested++;
|
||||
if (renewEntityCapsScheduledAction != null) {
|
||||
boolean canceled = renewEntityCapsScheduledAction.cancel();
|
||||
if (canceled) {
|
||||
scheduledRenewEntityCapsAvoided++;
|
||||
}
|
||||
}
|
||||
|
||||
final XMPPConnection connection = connection();
|
||||
|
||||
renewEntityCapsScheduledAction = scheduleBlocking(() -> {
|
||||
renewEntityCapsPerformed.incrementAndGet();
|
||||
|
||||
DiscoverInfoBuilder discoverInfoBuilder = DiscoverInfo.builder("synthetized-disco-info-response")
|
||||
.ofType(IQ.Type.result);
|
||||
addDiscoverInfoTo(discoverInfoBuilder);
|
||||
DiscoverInfo synthesizedDiscoveryInfo = discoverInfoBuilder.build();
|
||||
|
||||
for (EntityCapabilitiesChangedListener entityCapabilitiesChangedListener : entityCapabilitiesChangedListeners) {
|
||||
entityCapabilitiesChangedListener.onEntityCapabilitiesChanged(synthesizedDiscoveryInfo);
|
||||
}
|
||||
|
||||
// Re-send the last sent presence, and let the stanza interceptor
|
||||
// add a <c/> node to it.
|
||||
// See http://xmpp.org/extensions/xep-0115.html#advertise
|
||||
// We only send a presence packet if there was already one send
|
||||
// to respect ConnectionConfiguration.isSendPresence()
|
||||
final Presence presenceSend = this.presenceSend;
|
||||
if (connection.isAuthenticated() && presenceSend != null) {
|
||||
try {
|
||||
connection.sendStanza(presenceSend.cloneWithNewId());
|
||||
}
|
||||
catch (InterruptedException | NotConnectedException e) {
|
||||
LOGGER.log(Level.WARNING, "Could could not update presence with caps info", e);
|
||||
}
|
||||
}
|
||||
}, RENEW_ENTITY_CAPS_DELAY_MILLIS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public static void addDiscoInfoLookupShortcutMechanism(DiscoInfoLookupShortcutMechanism discoInfoLookupShortcutMechanism) {
|
||||
|
@ -915,4 +984,30 @@ public final class ServiceDiscoveryManager extends Manager {
|
|||
discoInfoLookupShortcutMechanisms.remove(discoInfoLookupShortcutMechanism);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Stats getStats() {
|
||||
return new Stats(this);
|
||||
}
|
||||
|
||||
public static final class Stats extends AbstractStats {
|
||||
|
||||
public final int renewEntityCapsRequested;
|
||||
public final int renewEntityCapsPerformed;
|
||||
public final int scheduledRenewEntityCapsAvoided;
|
||||
|
||||
private Stats(ServiceDiscoveryManager serviceDiscoveryManager) {
|
||||
renewEntityCapsRequested = serviceDiscoveryManager.renewEntityCapsRequested;
|
||||
renewEntityCapsPerformed = serviceDiscoveryManager.renewEntityCapsPerformed.get();
|
||||
scheduledRenewEntityCapsAvoided = serviceDiscoveryManager.scheduledRenewEntityCapsAvoided;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendStatsTo(ExtendedAppendable appendable) throws IOException {
|
||||
StringUtils.appendHeading(appendable, "ServiceDiscoveryManager stats", '#').append('\n');
|
||||
appendable.append("renew-entitycaps-requested: ").append(renewEntityCapsRequested).append('\n');
|
||||
appendable.append("renew-entitycaps-performed: ").append(renewEntityCapsPerformed).append('\n');
|
||||
appendable.append("scheduled-renew-entitycaps-avoided: ").append(scheduledRenewEntityCapsAvoided).append('\n');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -299,8 +299,8 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable
|
|||
return containsDuplicateFeatures;
|
||||
}
|
||||
|
||||
public DiscoverInfoBuilder asBuilder() {
|
||||
return new DiscoverInfoBuilder(this);
|
||||
public DiscoverInfoBuilder asBuilder(String stanzaId) {
|
||||
return new DiscoverInfoBuilder(this, stanzaId);
|
||||
}
|
||||
|
||||
// TODO: Deprecate in favor of asBuilder().
|
||||
|
|
|
@ -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.
|
||||
|
@ -48,8 +48,8 @@ public class DiscoverInfoBuilder extends IqBuilder<DiscoverInfoBuilder, Discover
|
|||
super(stanzaId);
|
||||
}
|
||||
|
||||
public DiscoverInfoBuilder(DiscoverInfo discoverInfo) {
|
||||
super(discoverInfo.getStanzaId());
|
||||
public DiscoverInfoBuilder(DiscoverInfo discoverInfo, String stanzaId) {
|
||||
super(discoverInfo, stanzaId);
|
||||
features.addAll(discoverInfo.getFeatures());
|
||||
identities.addAll(discoverInfo.getIdentities());
|
||||
node = discoverInfo.getNode();
|
||||
|
|
|
@ -253,7 +253,10 @@ public final class PepManager extends Manager {
|
|||
*
|
||||
* @param pepListener a roster exchange listener.
|
||||
* @return true if pepListener was added.
|
||||
* @deprecated use {@link #addPepEventListener(String, Class, PepEventListener)} instead.
|
||||
*/
|
||||
// TODO: Remove in Smack 4.5
|
||||
@Deprecated
|
||||
public boolean addPepListener(PepListener pepListener) {
|
||||
return pepListeners.add(pepListener);
|
||||
}
|
||||
|
@ -263,7 +266,10 @@ public final class PepManager extends Manager {
|
|||
*
|
||||
* @param pepListener a roster exchange listener.
|
||||
* @return true, if pepListener was removed.
|
||||
* @deprecated use {@link #removePepEventListener(PepEventListener)} instead.
|
||||
*/
|
||||
// TODO: Remove in Smack 4.5.
|
||||
@Deprecated
|
||||
public boolean removePepListener(PepListener pepListener) {
|
||||
return pepListeners.remove(pepListener);
|
||||
}
|
||||
|
|
|
@ -390,7 +390,7 @@ public final class PingManager extends Manager {
|
|||
int nextPingIn = pingInterval - delta;
|
||||
LOGGER.fine("Scheduling ServerPingTask in " + nextPingIn + " seconds (pingInterval="
|
||||
+ pingInterval + ", delta=" + delta + ")");
|
||||
nextAutomaticPing = schedule(pingServerRunnable, nextPingIn, TimeUnit.SECONDS);
|
||||
nextAutomaticPing = schedule(this::pingServerIfNecessary, nextPingIn, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -467,13 +467,4 @@ public final class PingManager extends Manager {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final Runnable pingServerRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LOGGER.fine("ServerPingTask run()");
|
||||
pingServerIfNecessary();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2013-2014 Georg Lukas, 2015-2019 Florian Schmaus
|
||||
* Copyright 2013-2014 Georg Lukas, 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,11 +38,9 @@ import org.jivesoftware.smack.filter.StanzaExtensionFilter;
|
|||
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||
import org.jivesoftware.smack.filter.StanzaTypeFilter;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.MessageBuilder;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.packet.StanzaBuilder;
|
||||
import org.jivesoftware.smack.roster.Roster;
|
||||
import org.jivesoftware.smack.util.Consumer;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
|
@ -273,8 +271,6 @@ public final class DeliveryReceiptManager extends Manager {
|
|||
);
|
||||
// @formatter:on
|
||||
|
||||
private static final Consumer<MessageBuilder> AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER = mb -> DeliveryReceiptRequest.addTo(mb);
|
||||
|
||||
/**
|
||||
* Enables automatic requests of delivery receipts for outgoing messages of
|
||||
* {@link org.jivesoftware.smack.packet.Message.Type#normal}, {@link org.jivesoftware.smack.packet.Message.Type#chat} or {@link org.jivesoftware.smack.packet.Message.Type#headline}, and
|
||||
|
@ -284,7 +280,7 @@ public final class DeliveryReceiptManager extends Manager {
|
|||
* @see #dontAutoAddDeliveryReceiptRequests()
|
||||
*/
|
||||
public void autoAddDeliveryReceiptRequests() {
|
||||
connection().addMessageInterceptor(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER, m -> {
|
||||
connection().addMessageInterceptor(DeliveryReceiptRequest::addTo, m -> {
|
||||
return MESSAGES_TO_REQUEST_RECEIPTS_FOR.accept(m);
|
||||
});
|
||||
}
|
||||
|
@ -296,7 +292,7 @@ public final class DeliveryReceiptManager extends Manager {
|
|||
* @see #autoAddDeliveryReceiptRequests()
|
||||
*/
|
||||
public void dontAutoAddDeliveryReceiptRequests() {
|
||||
connection().removeMessageInterceptor(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER);
|
||||
connection().removeMessageInterceptor(DeliveryReceiptRequest::addTo);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,18 +16,14 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.softwareinfo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.jivesoftware.smack.Manager;
|
||||
import org.jivesoftware.smack.SmackException.FeatureNotSupportedException;
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
||||
import org.jivesoftware.smackx.softwareinfo.form.SoftwareInfoForm;
|
||||
|
@ -44,11 +40,11 @@ import org.jxmpp.jid.Jid;
|
|||
*/
|
||||
public final class SoftwareInfoManager extends Manager {
|
||||
|
||||
private static final String FEATURE = "http://jabber.org/protocol/disco";
|
||||
private static final Map<XMPPConnection, SoftwareInfoManager> INSTANCES = new WeakHashMap<>();
|
||||
|
||||
private final ServiceDiscoveryManager serviceDiscoveryManager;
|
||||
|
||||
public static synchronized SoftwareInfoManager getInstanceFor (XMPPConnection connection) throws IOException, XmlPullParserException, SmackParsingException {
|
||||
public static synchronized SoftwareInfoManager getInstanceFor (XMPPConnection connection) {
|
||||
SoftwareInfoManager manager = INSTANCES.get(connection);
|
||||
if (manager == null) {
|
||||
manager = new SoftwareInfoManager(connection);
|
||||
|
@ -57,25 +53,11 @@ public final class SoftwareInfoManager extends Manager {
|
|||
return manager;
|
||||
}
|
||||
|
||||
private SoftwareInfoManager(XMPPConnection connection) throws IOException, XmlPullParserException, SmackParsingException {
|
||||
private SoftwareInfoManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the feature is supported by the Jid.
|
||||
* <br>
|
||||
* @param jid Jid to be checked for support
|
||||
* @return boolean
|
||||
* @throws NoResponseException if there was no response from the remote entity
|
||||
* @throws XMPPErrorException if there was an XMPP error returned
|
||||
* @throws NotConnectedException if the XMPP connection is not connected
|
||||
* @throws InterruptedException if the calling thread was interrupted
|
||||
*/
|
||||
public boolean isSupported(Jid jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
return serviceDiscoveryManager.supportsFeatures(jid, FEATURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes the provided {@link SoftwareInfoForm} as an extended info.
|
||||
* <br>
|
||||
|
@ -86,20 +68,16 @@ public final class SoftwareInfoManager extends Manager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get SoftwareInfoForm from Jid provided.
|
||||
* <br>
|
||||
* Get Software Information from the provided XMPP address. Returns <code>null</code> in case the queried entity does not announce that information.
|
||||
*
|
||||
* @param jid jid to get software information from
|
||||
* @return {@link SoftwareInfoForm} Form containing software information
|
||||
* @return {@link SoftwareInfoForm} Form containing software information or <code>null</code>.
|
||||
* @throws NoResponseException if there was no response from the remote entity
|
||||
* @throws XMPPErrorException if there was an XMPP error returned
|
||||
* @throws NotConnectedException if the XMPP connection is not connected
|
||||
* @throws InterruptedException if the calling thread was interrupted
|
||||
* @throws FeatureNotSupportedException if the feature is not supported
|
||||
*/
|
||||
public SoftwareInfoForm fromJid(Jid jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, FeatureNotSupportedException {
|
||||
if (!isSupported(jid)) {
|
||||
throw new FeatureNotSupportedException(SoftwareInfoForm.FORM_TYPE, jid);
|
||||
}
|
||||
public SoftwareInfoForm fromJid(Jid jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||
DiscoverInfo discoverInfo = serviceDiscoveryManager.discoverInfo(jid);
|
||||
DataForm dataForm = DataForm.from(discoverInfo, SoftwareInfoForm.FORM_TYPE);
|
||||
if (dataForm == null) {
|
||||
|
|
|
@ -186,8 +186,8 @@ public final class UserTuneElement implements ExtensionElement {
|
|||
|
||||
/**
|
||||
* Artist is an optional element in UserTuneElement.
|
||||
* @param artist.
|
||||
* @return builder.
|
||||
* @param artist the artist.
|
||||
* @return a reference to this builder.
|
||||
*/
|
||||
public Builder setArtist(String artist) {
|
||||
this.artist = artist;
|
||||
|
@ -196,8 +196,8 @@ public final class UserTuneElement implements ExtensionElement {
|
|||
|
||||
/**
|
||||
* Length is an optional element in UserTuneElement.
|
||||
* @param length.
|
||||
* @return builder.
|
||||
* @param length the length.
|
||||
* @return a reference to this builder.
|
||||
*/
|
||||
public Builder setLength(int length) {
|
||||
return setLength(UInt16.from(length));
|
||||
|
@ -205,8 +205,8 @@ public final class UserTuneElement implements ExtensionElement {
|
|||
|
||||
/**
|
||||
* Length is an optional element in UserTuneElement.
|
||||
* @param length.
|
||||
* @return builder.
|
||||
* @param length the length.
|
||||
* @return a reference to this builder.
|
||||
*/
|
||||
public Builder setLength(UInt16 length) {
|
||||
this.length = length;
|
||||
|
@ -215,8 +215,8 @@ public final class UserTuneElement implements ExtensionElement {
|
|||
|
||||
/**
|
||||
* Rating is an optional element in UserTuneElement.
|
||||
* @param rating.
|
||||
* @return builder.
|
||||
* @param rating the rating.
|
||||
* @return a reference to this builder.
|
||||
*/
|
||||
public Builder setRating(int rating) {
|
||||
this.rating = rating;
|
||||
|
@ -225,8 +225,8 @@ public final class UserTuneElement implements ExtensionElement {
|
|||
|
||||
/**
|
||||
* Source is an optional element in UserTuneElement.
|
||||
* @param source.
|
||||
* @return builder.
|
||||
* @param source the source.
|
||||
* @return a reference to this builder.
|
||||
*/
|
||||
public Builder setSource(String source) {
|
||||
this.source = source;
|
||||
|
@ -235,8 +235,8 @@ public final class UserTuneElement implements ExtensionElement {
|
|||
|
||||
/**
|
||||
* Title is an optional element in UserTuneElement.
|
||||
* @param title.
|
||||
* @return builder.
|
||||
* @param title the title.
|
||||
* @return a reference to this builder.
|
||||
*/
|
||||
public Builder setTitle(String title) {
|
||||
this.title = title;
|
||||
|
@ -245,8 +245,8 @@ public final class UserTuneElement implements ExtensionElement {
|
|||
|
||||
/**
|
||||
* Track is an optional element in UserTuneElement.
|
||||
* @param track.
|
||||
* @return builder.
|
||||
* @param track the track.
|
||||
* @return a reference to this builder.
|
||||
*/
|
||||
public Builder setTrack(String track) {
|
||||
this.track = track;
|
||||
|
@ -255,8 +255,8 @@ public final class UserTuneElement implements ExtensionElement {
|
|||
|
||||
/**
|
||||
* URI is an optional element in UserTuneElement.
|
||||
* @param uri.
|
||||
* @return builder.
|
||||
* @param uri the URI.
|
||||
* @return a reference to this builder.
|
||||
*/
|
||||
public Builder setUri(URI uri) {
|
||||
this.uri = uri;
|
||||
|
|
|
@ -26,6 +26,8 @@ import org.jivesoftware.smack.XMPPConnection;
|
|||
import org.jivesoftware.smack.XMPPConnectionRegistry;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.MessageBuilder;
|
||||
import org.jivesoftware.smack.packet.MessageView;
|
||||
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension;
|
||||
|
@ -57,7 +59,7 @@ public class XHTMLManager {
|
|||
* @param message an XHTML message
|
||||
* @return an Iterator for the bodies in the message or null if none.
|
||||
*/
|
||||
public static List<CharSequence> getBodies(Message message) {
|
||||
public static List<CharSequence> getBodies(MessageView message) {
|
||||
XHTMLExtension xhtmlExtension = XHTMLExtension.from(message);
|
||||
if (xhtmlExtension != null)
|
||||
return xhtmlExtension.getBodies();
|
||||
|
@ -68,9 +70,29 @@ public class XHTMLManager {
|
|||
/**
|
||||
* Adds an XHTML body to the message.
|
||||
*
|
||||
* @param message the message that will receive the XHTML body
|
||||
* @param messageBuilder the message that will receive the XHTML body
|
||||
* @param xhtmlText the string to add as an XHTML body to the message
|
||||
*/
|
||||
public static void addBody(MessageBuilder messageBuilder, XHTMLText xhtmlText) {
|
||||
XHTMLExtension xhtmlExtension = XHTMLExtension.from(messageBuilder);
|
||||
if (xhtmlExtension == null) {
|
||||
// Create an XHTMLExtension and add it to the message
|
||||
xhtmlExtension = new XHTMLExtension();
|
||||
messageBuilder.addExtension(xhtmlExtension);
|
||||
}
|
||||
// Add the required bodies to the message
|
||||
xhtmlExtension.addBody(xhtmlText.toXML());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an XHTML body to the message.
|
||||
*
|
||||
* @param message the message that will receive the XHTML body
|
||||
* @param xhtmlText the string to add as an XHTML body to the message
|
||||
* @deprecated use {@link #addBody(MessageBuilder, XHTMLText)} instead.
|
||||
*/
|
||||
// TODO: Remove in Smack 4.6
|
||||
@Deprecated
|
||||
public static void addBody(Message message, XHTMLText xhtmlText) {
|
||||
XHTMLExtension xhtmlExtension = XHTMLExtension.from(message);
|
||||
if (xhtmlExtension == null) {
|
||||
|
|
|
@ -86,7 +86,7 @@ public class Socks5ProxyTest {
|
|||
* When inserting new network addresses to the proxy the order should remain in the order they
|
||||
* were inserted.
|
||||
*
|
||||
* @throws UnknownHostException
|
||||
* @throws UnknownHostException if unknown host.
|
||||
*/
|
||||
@Test
|
||||
public void shouldPreserveAddressOrderOnInsertions() throws UnknownHostException {
|
||||
|
@ -114,7 +114,7 @@ public class Socks5ProxyTest {
|
|||
* When replacing network addresses of the proxy the order should remain in the order if the
|
||||
* given list.
|
||||
*
|
||||
* @throws UnknownHostException
|
||||
* @throws UnknownHostException if unknown host.
|
||||
*/
|
||||
@Test
|
||||
public void shouldPreserveAddressOrderOnReplace() throws UnknownHostException {
|
||||
|
|
|
@ -44,9 +44,8 @@ public class DataValidationHelperTest {
|
|||
() -> element.checkConsistency(field));
|
||||
assertEquals("Field type 'jid-single' is not consistent with validation method 'basic'.", vce.getMessage());
|
||||
|
||||
IllegalArgumentException iae = assertThrows(IllegalArgumentException.class,
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> new ListRange(-1L, 1L));
|
||||
assertEquals("unsigned 32-bit integers can't be negative", iae.getMessage());
|
||||
|
||||
element.setListRange(new ListRange(10L, 100L));
|
||||
vce = assertThrows(ValidationConsistencyException.class, () -> element.checkConsistency(field));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2013-2015 the original author or authors
|
||||
* Copyright 2013-2015 the original author or authors, 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.
|
||||
|
@ -17,7 +17,6 @@
|
|||
package org.jivesoftware.smack.roster.rosterstore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
|
@ -56,15 +55,10 @@ public final class DirectoryRosterStore implements RosterStore {
|
|||
private static final String STORE_ID = "DEFAULT_ROSTER_STORE";
|
||||
private static final Logger LOGGER = Logger.getLogger(DirectoryRosterStore.class.getName());
|
||||
|
||||
private static final FileFilter rosterDirFilter = new FileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
String name = file.getName();
|
||||
return name.startsWith(ENTRY_PREFIX);
|
||||
}
|
||||
|
||||
};
|
||||
private static boolean rosterDirFilter(File file) {
|
||||
String name = file.getName();
|
||||
return name.startsWith(ENTRY_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseDir TODO javadoc me please
|
||||
|
@ -122,7 +116,7 @@ public final class DirectoryRosterStore implements RosterStore {
|
|||
public List<Item> getEntries() {
|
||||
List<Item> entries = new ArrayList<>();
|
||||
|
||||
for (File file : fileDir.listFiles(rosterDirFilter)) {
|
||||
for (File file : fileDir.listFiles(DirectoryRosterStore::rosterDirFilter)) {
|
||||
Item entry = readEntry(file);
|
||||
if (entry == null) {
|
||||
// Roster directory store corrupt. Abort and signal this by returning null.
|
||||
|
@ -168,7 +162,7 @@ public final class DirectoryRosterStore implements RosterStore {
|
|||
|
||||
@Override
|
||||
public boolean resetEntries(Collection<Item> items, String version) {
|
||||
for (File file : fileDir.listFiles(rosterDirFilter)) {
|
||||
for (File file : fileDir.listFiles(DirectoryRosterStore::rosterDirFilter)) {
|
||||
file.delete();
|
||||
}
|
||||
for (Item item : items) {
|
||||
|
|
|
@ -95,6 +95,8 @@ public class RosterTest extends InitSmackIm {
|
|||
* Test a simple roster initialization according to the example in
|
||||
* <a href="http://xmpp.org/rfcs/rfc3921.html#roster-login"
|
||||
* >RFC3921: Retrieving One's Roster on Login</a>.
|
||||
*
|
||||
* @throws Exception in case of an exception.
|
||||
*/
|
||||
@Test
|
||||
public void testSimpleRosterInitialization() throws Exception {
|
||||
|
@ -131,6 +133,8 @@ public class RosterTest extends InitSmackIm {
|
|||
* Test adding a roster item according to the example in
|
||||
* <a href="http://xmpp.org/rfcs/rfc3921.html#roster-add"
|
||||
* >RFC3921: Adding a Roster Item</a>.
|
||||
*
|
||||
* @throws Throwable in case a throwable is thrown.
|
||||
*/
|
||||
@Test
|
||||
public void testAddRosterItem() throws Throwable {
|
||||
|
@ -203,6 +207,8 @@ public class RosterTest extends InitSmackIm {
|
|||
* Test updating a roster item according to the example in
|
||||
* <a href="http://xmpp.org/rfcs/rfc3921.html#roster-update"
|
||||
* >RFC3921: Updating a Roster Item</a>.
|
||||
*
|
||||
* @throws Throwable in case a throwable is thrown.
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateRosterItem() throws Throwable {
|
||||
|
@ -279,6 +285,7 @@ public class RosterTest extends InitSmackIm {
|
|||
* Test deleting a roster item according to the example in
|
||||
* <a href="http://xmpp.org/rfcs/rfc3921.html#roster-delete"
|
||||
* >RFC3921: Deleting a Roster Item</a>.
|
||||
* @throws Throwable if throwable is thrown.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteRosterItem() throws Throwable {
|
||||
|
@ -327,6 +334,7 @@ public class RosterTest extends InitSmackIm {
|
|||
* Test a simple roster push according to the example in
|
||||
* <a href="http://xmpp.org/internet-drafts/draft-ietf-xmpp-3921bis-03.html#roster-syntax-actions-push"
|
||||
* >RFC3921bis-03: Roster Push</a>.
|
||||
* @throws Throwable in case a throwable is thrown.
|
||||
*/
|
||||
@Test
|
||||
public void testSimpleRosterPush() throws Throwable {
|
||||
|
@ -398,6 +406,7 @@ public class RosterTest extends InitSmackIm {
|
|||
* Test if adding an user with an empty group is equivalent with providing
|
||||
* no group.
|
||||
*
|
||||
* @throws Throwable in case a throwable is thrown.
|
||||
* @see <a href="http://www.igniterealtime.org/issues/browse/SMACK-294">SMACK-294</a>
|
||||
*/
|
||||
@Test(timeout = 5000)
|
||||
|
@ -466,6 +475,7 @@ public class RosterTest extends InitSmackIm {
|
|||
* Test processing a roster push with an empty group is equivalent with providing
|
||||
* no group.
|
||||
*
|
||||
* @throws Throwable in case a throwable is thrown.
|
||||
* @see <a href="http://www.igniterealtime.org/issues/browse/SMACK-294">SMACK-294</a>
|
||||
*/
|
||||
@Test
|
||||
|
|
|
@ -97,6 +97,8 @@ public class RosterVersioningTest {
|
|||
* by all entries of the roster store.
|
||||
* @throws SmackException if Smack detected an exceptional situation.
|
||||
* @throws XMPPException if an XMPP protocol error was received.
|
||||
* @throws InterruptedException if interrupted.
|
||||
* @throws IOException if IO exception.
|
||||
*/
|
||||
@Test(timeout = 300000)
|
||||
public void testEqualVersionStored() throws InterruptedException, IOException, XMPPException, SmackException {
|
||||
|
@ -172,6 +174,7 @@ public class RosterVersioningTest {
|
|||
|
||||
/**
|
||||
* Test roster versioning with roster pushes.
|
||||
* @throws Throwable in case a throwable is thrown.
|
||||
*/
|
||||
@SuppressWarnings("UndefinedEquals")
|
||||
@Test(timeout = 5000)
|
||||
|
|
|
@ -57,6 +57,7 @@ public class DirectoryRosterStoreTest {
|
|||
|
||||
/**
|
||||
* Tests that opening an uninitialized directory fails.
|
||||
* @throws IOException if IO exception.
|
||||
*/
|
||||
@Test
|
||||
public void testStoreUninitialized() throws IOException {
|
||||
|
@ -66,6 +67,7 @@ public class DirectoryRosterStoreTest {
|
|||
|
||||
/**
|
||||
* Tests that an initialized directory is empty.
|
||||
* @throws IOException if IO exception.
|
||||
*/
|
||||
@Test
|
||||
public void testStoreInitializedEmpty() throws IOException {
|
||||
|
@ -80,6 +82,7 @@ public class DirectoryRosterStoreTest {
|
|||
|
||||
/**
|
||||
* Tests adding and removing entries.
|
||||
* @throws IOException if IO exception.
|
||||
*/
|
||||
@Test
|
||||
public void testStoreAddRemove() throws IOException {
|
||||
|
|
|
@ -84,9 +84,9 @@ public abstract class AbstractSmackIntTest {
|
|||
|
||||
protected HttpURLConnection getHttpUrlConnectionFor(URL url) throws IOException {
|
||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
if (sinttestConfiguration.tlsContext != null && urlConnection instanceof HttpsURLConnection) {
|
||||
if (sinttestConfiguration.sslContextFactory != null && urlConnection instanceof HttpsURLConnection) {
|
||||
HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
|
||||
httpsUrlConnection.setSSLSocketFactory(sinttestConfiguration.tlsContext.getSocketFactory());
|
||||
httpsUrlConnection.setSSLSocketFactory(sinttestConfiguration.sslContextFactory.createSslContext().getSocketFactory());
|
||||
}
|
||||
return urlConnection;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,15 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.StanzaListener;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.filter.AndFilter;
|
||||
import org.jivesoftware.smack.filter.FromMatchesFilter;
|
||||
import org.jivesoftware.smack.filter.PresenceTypeFilter;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.util.Async.ThrowingRunnable;
|
||||
|
||||
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
|
||||
|
||||
public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest {
|
||||
|
||||
|
@ -58,4 +66,48 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest
|
|||
connectionsLocal.add(conThree);
|
||||
this.connections = Collections.unmodifiableList(connectionsLocal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform action and wait until conA observes a presence form conB.
|
||||
* <p>
|
||||
* This method is usually used so that 'action' performs an operation that changes one entities
|
||||
* features/nodes/capabilities, and we want to check that another connection is able to observe this change, and use
|
||||
* that new "thing" that was added to the connection.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that this method is a workaround at best and not reliable. Because it is not guaranteed that any XEP-0030
|
||||
* related manager, e.g. EntityCapsManager, already processed the presence when this method returns.
|
||||
* </p>
|
||||
* TODO: Come up with a better solution.
|
||||
*
|
||||
* @param conA the connection to observe the presence on.
|
||||
* @param conB the connection sending the presence
|
||||
* @param action the action to perform.
|
||||
* @throws Exception in case of an exception.
|
||||
*/
|
||||
protected void performActionAndWaitForPresence(XMPPConnection conA, XMPPConnection conB, ThrowingRunnable action)
|
||||
throws Exception {
|
||||
final SimpleResultSyncPoint presenceReceivedSyncPoint = new SimpleResultSyncPoint();
|
||||
final StanzaListener presenceListener = new StanzaListener() {
|
||||
@Override
|
||||
public void processStanza(Stanza packet) {
|
||||
presenceReceivedSyncPoint.signal();
|
||||
}
|
||||
};
|
||||
|
||||
// Add a stanzaListener to listen for incoming presence
|
||||
conA.addAsyncStanzaListener(presenceListener, new AndFilter(
|
||||
PresenceTypeFilter.AVAILABLE,
|
||||
FromMatchesFilter.create(conB.getUser())
|
||||
));
|
||||
|
||||
action.runOrThrow();
|
||||
|
||||
try {
|
||||
// wait for the dummy feature to get sent via presence
|
||||
presenceReceivedSyncPoint.waitForResult(timeout);
|
||||
} finally {
|
||||
conA.removeAsyncStanzaListener(presenceListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ 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.SslContextFactory;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
|
||||
import org.jivesoftware.smackx.debugger.EnhancedDebugger;
|
||||
|
@ -72,7 +73,7 @@ public final class Configuration {
|
|||
|
||||
public final String serviceTlsPin;
|
||||
|
||||
public final SSLContext tlsContext;
|
||||
public final SslContextFactory sslContextFactory;
|
||||
|
||||
public final SecurityMode securityMode;
|
||||
|
||||
|
@ -121,9 +122,10 @@ public final class Configuration {
|
|||
"'service' must be set. Either via 'properties' files or via system property 'sinttest.service'.");
|
||||
serviceTlsPin = builder.serviceTlsPin;
|
||||
if (serviceTlsPin != null) {
|
||||
tlsContext = Java7Pinning.forPin(serviceTlsPin);
|
||||
SSLContext sslContext = Java7Pinning.forPin(serviceTlsPin);
|
||||
sslContextFactory = () -> sslContext;
|
||||
} else {
|
||||
tlsContext = null;
|
||||
sslContextFactory = null;
|
||||
}
|
||||
securityMode = builder.securityMode;
|
||||
if (builder.replyTimeout > 0) {
|
||||
|
@ -168,8 +170,8 @@ public final class Configuration {
|
|||
this.testPackages = builder.testPackages;
|
||||
|
||||
this.configurationApplier = b -> {
|
||||
if (tlsContext != null) {
|
||||
b.setCustomSSLContext(tlsContext);
|
||||
if (sslContextFactory != null) {
|
||||
b.setSslContextFactory(sslContextFactory);
|
||||
}
|
||||
b.setSecurityMode(securityMode);
|
||||
b.setXmppDomain(service);
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
|
|||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||
import org.jivesoftware.smack.StanzaListener;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.filter.AndFilter;
|
||||
|
@ -40,6 +41,7 @@ import org.jivesoftware.smack.filter.PresenceTypeFilter;
|
|||
import org.jivesoftware.smack.filter.StanzaTypeFilter;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.roster.RosterUtil;
|
||||
import org.jivesoftware.smack.util.Async.ThrowingRunnable;
|
||||
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
||||
|
@ -49,7 +51,6 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
|||
import org.igniterealtime.smack.inttest.annotations.AfterClass;
|
||||
import org.igniterealtime.smack.inttest.annotations.BeforeClass;
|
||||
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
|
||||
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
|
||||
|
||||
public class EntityCapsTest extends AbstractSmackIntegrationTest {
|
||||
|
||||
|
@ -124,11 +125,7 @@ public class EntityCapsTest extends AbstractSmackIntegrationTest {
|
|||
/**
|
||||
* Test if entity caps actually prevent a disco info request and reply.
|
||||
*
|
||||
* @throws XMPPException if an XMPP protocol error was received.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws NoResponseException if there was no response from the remote entity.
|
||||
*
|
||||
* @throws Exception if exception.
|
||||
*/
|
||||
@SmackIntegrationTest
|
||||
public void testPreventDiscoInfo() throws Exception {
|
||||
|
@ -143,26 +140,7 @@ public class EntityCapsTest extends AbstractSmackIntegrationTest {
|
|||
|
||||
}, new AndFilter(new StanzaTypeFilter(DiscoverInfo.class), IQTypeFilter.GET));
|
||||
|
||||
final SimpleResultSyncPoint presenceReceivedSyncPoint = new SimpleResultSyncPoint();
|
||||
final StanzaListener presenceListener = new StanzaListener() {
|
||||
@Override
|
||||
public void processStanza(Stanza packet) {
|
||||
presenceReceivedSyncPoint.signal();
|
||||
}
|
||||
};
|
||||
|
||||
// Add a stanzaListener to listen for incoming presence
|
||||
conOne.addAsyncStanzaListener(presenceListener, PresenceTypeFilter.AVAILABLE);
|
||||
|
||||
// add a bogus feature so that con1 ver won't match con0's
|
||||
sdmTwo.addFeature(dummyFeature);
|
||||
|
||||
try {
|
||||
// wait for the dummy feature to get sent via presence
|
||||
presenceReceivedSyncPoint.waitForResult(timeout);
|
||||
} finally {
|
||||
conOne.removeAsyncStanzaListener(presenceListener);
|
||||
}
|
||||
addFeatureAndWaitForPresence(conOne, conTwo, dummyFeature);
|
||||
|
||||
dropCapsCache();
|
||||
// discover that
|
||||
|
@ -181,10 +159,10 @@ public class EntityCapsTest extends AbstractSmackIntegrationTest {
|
|||
}
|
||||
|
||||
@SmackIntegrationTest
|
||||
public void testCapsChanged() {
|
||||
public void testCapsChanged() throws Exception {
|
||||
final String dummyFeature = getNewDummyFeature();
|
||||
String nodeVerBefore = EntityCapsManager.getNodeVersionByJid(conTwo.getUser());
|
||||
sdmTwo.addFeature(dummyFeature);
|
||||
addFeatureAndWaitForPresence(conOne, conTwo, dummyFeature);
|
||||
String nodeVerAfter = EntityCapsManager.getNodeVersionByJid(conTwo.getUser());
|
||||
|
||||
assertFalse(nodeVerBefore.equals(nodeVerAfter));
|
||||
|
@ -229,4 +207,24 @@ public class EntityCapsTest extends AbstractSmackIntegrationTest {
|
|||
private static void dropCapsCache() {
|
||||
EntityCapsManager.CAPS_CACHE.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds 'feature' to conB and waits until conA observes a presence form conB.
|
||||
*
|
||||
* @param conA the connection to observe the presence on.
|
||||
* @param conB the connection to add the feature to.
|
||||
* @param feature the feature to add.
|
||||
* @throws Exception in case of an exception.
|
||||
*/
|
||||
private void addFeatureAndWaitForPresence(XMPPConnection conA, XMPPConnection conB, String feature)
|
||||
throws Exception {
|
||||
final ServiceDiscoveryManager sdmB = ServiceDiscoveryManager.getInstanceFor(conB);
|
||||
ThrowingRunnable action = new ThrowingRunnable() {
|
||||
@Override
|
||||
public void runOrThrow() throws Exception {
|
||||
sdmB.addFeature(feature);
|
||||
}
|
||||
};
|
||||
performActionAndWaitForPresence(conA, conB, action);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import org.jivesoftware.smack.chat2.ChatManager;
|
|||
import org.jivesoftware.smack.packet.Message;
|
||||
|
||||
import org.jivesoftware.smackx.chatstates.ChatState;
|
||||
import org.jivesoftware.smackx.chatstates.ChatStateListener;
|
||||
import org.jivesoftware.smackx.chatstates.ChatStateManager;
|
||||
|
||||
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
|
||||
|
@ -34,25 +33,19 @@ public class ChatStateIntegrationTest extends AbstractSmackIntegrationTest {
|
|||
|
||||
// Listener for composing chat state
|
||||
private final SimpleResultSyncPoint composingSyncPoint = new SimpleResultSyncPoint();
|
||||
private final ChatStateListener composingListener = new ChatStateListener() {
|
||||
@Override
|
||||
public void stateChanged(Chat chat, ChatState state, Message message) {
|
||||
if (state.equals(ChatState.composing)) {
|
||||
composingSyncPoint.signal();
|
||||
}
|
||||
private void composingListener(Chat chat, ChatState state, Message message) {
|
||||
if (state.equals(ChatState.composing)) {
|
||||
composingSyncPoint.signal();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Listener for active chat state
|
||||
private final SimpleResultSyncPoint activeSyncPoint = new SimpleResultSyncPoint();
|
||||
private final ChatStateListener activeListener = new ChatStateListener() {
|
||||
@Override
|
||||
public void stateChanged(Chat chat, ChatState state, Message message) {
|
||||
if (state.equals(ChatState.active)) {
|
||||
activeSyncPoint.signal();
|
||||
}
|
||||
private void activeListener(Chat chat, ChatState state, Message message) {
|
||||
if (state.equals(ChatState.active)) {
|
||||
activeSyncPoint.signal();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public ChatStateIntegrationTest(SmackIntegrationTestEnvironment environment) {
|
||||
|
@ -65,8 +58,8 @@ public class ChatStateIntegrationTest extends AbstractSmackIntegrationTest {
|
|||
ChatStateManager manTwo = ChatStateManager.getInstance(conTwo);
|
||||
|
||||
// Add chatState listeners.
|
||||
manTwo.addChatStateListener(composingListener);
|
||||
manTwo.addChatStateListener(activeListener);
|
||||
manTwo.addChatStateListener(this::composingListener);
|
||||
manTwo.addChatStateListener(this::activeListener);
|
||||
|
||||
Chat chatOne = ChatManager.getInstanceFor(conOne)
|
||||
.chatWith(conTwo.getUser().asEntityBareJid());
|
||||
|
@ -86,7 +79,7 @@ public class ChatStateIntegrationTest extends AbstractSmackIntegrationTest {
|
|||
@AfterClass
|
||||
public void cleanup() {
|
||||
ChatStateManager manTwo = ChatStateManager.getInstance(conTwo);
|
||||
manTwo.removeChatStateListener(composingListener);
|
||||
manTwo.removeChatStateListener(activeListener);
|
||||
manTwo.removeChatStateListener(this::composingListener);
|
||||
manTwo.removeChatStateListener(this::activeListener);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,9 @@ public class HttpFileUploadIntegrationTest extends AbstractSmackIntegrationTest
|
|||
+ " does not accept files of size " + FILE_SIZE
|
||||
+ ". It only accepts files with a maximum size of " + uploadService.getMaxFileSize());
|
||||
}
|
||||
hfumOne.setTlsContext(environment.configuration.tlsContext);
|
||||
if (environment.configuration.sslContextFactory != null) {
|
||||
hfumOne.setTlsContext(environment.configuration.sslContextFactory.createSslContext());
|
||||
}
|
||||
}
|
||||
|
||||
@SmackIntegrationTest
|
||||
|
|
|
@ -16,13 +16,14 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.softwareInfo;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||
import org.jivesoftware.smack.util.Async.ThrowingRunnable;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||
import org.jivesoftware.smackx.mediaelement.element.MediaElement;
|
||||
import org.jivesoftware.smackx.softwareinfo.SoftwareInfoManager;
|
||||
|
@ -54,9 +55,14 @@ public class SoftwareInfoIntegrationTest extends AbstractSmackIntegrationTest {
|
|||
@SmackIntegrationTest
|
||||
public void test() throws Exception {
|
||||
SoftwareInfoForm softwareInfoSent = createSoftwareInfoForm();
|
||||
sim1.publishSoftwareInformationForm(softwareInfoSent);
|
||||
performActionAndWaitForPresence(conTwo, conOne, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void runOrThrow() throws Exception {
|
||||
sim1.publishSoftwareInformationForm(softwareInfoSent);
|
||||
}
|
||||
});
|
||||
SoftwareInfoForm softwareInfoFormReceived = sim2.fromJid(conOne.getUser());
|
||||
assertTrue(softwareInfoFormReceived.equals(softwareInfoSent));
|
||||
assertEquals(softwareInfoSent, softwareInfoFormReceived);
|
||||
}
|
||||
|
||||
private static SoftwareInfoForm createSoftwareInfoForm() throws URISyntaxException {
|
||||
|
|
|
@ -60,7 +60,7 @@ public class SignalOmemoStoreTest extends OmemoStoreTest<IdentityKeyPair, Identi
|
|||
|
||||
/**
|
||||
* We are running this Test with multiple available OmemoStore implementations.
|
||||
* @return
|
||||
* @return the test parameters.
|
||||
* @throws IOException if an I/O error occurred.
|
||||
*/
|
||||
@Parameterized.Parameters
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Paul Schaub
|
||||
* Copyright 2017 Paul Schaub, 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.
|
||||
|
@ -17,7 +17,6 @@
|
|||
package org.jivesoftware.smackx.omemo;
|
||||
|
||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL;
|
||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.PEP_NODE_DEVICE_LIST_NOTIFY;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -37,17 +36,13 @@ import java.util.logging.Logger;
|
|||
import org.jivesoftware.smack.ConnectionListener;
|
||||
import org.jivesoftware.smack.Manager;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.StanzaListener;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.MessageBuilder;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.util.Async;
|
||||
|
||||
import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener;
|
||||
import org.jivesoftware.smackx.carbons.CarbonManager;
|
||||
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
|
@ -74,11 +69,9 @@ import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
|
|||
import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback;
|
||||
import org.jivesoftware.smackx.omemo.trust.TrustState;
|
||||
import org.jivesoftware.smackx.omemo.util.MessageOrOmemoMessage;
|
||||
import org.jivesoftware.smackx.pep.PepListener;
|
||||
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
|
||||
import org.jivesoftware.smackx.pep.PepEventListener;
|
||||
import org.jivesoftware.smackx.pep.PepManager;
|
||||
import org.jivesoftware.smackx.pubsub.EventElement;
|
||||
import org.jivesoftware.smackx.pubsub.ItemsExtension;
|
||||
import org.jivesoftware.smackx.pubsub.PayloadItem;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||
import org.jivesoftware.smackx.pubsub.packet.PubSub;
|
||||
|
||||
|
@ -105,6 +98,8 @@ public final class OmemoManager extends Manager {
|
|||
private final HashSet<OmemoMessageListener> omemoMessageListeners = new HashSet<>();
|
||||
private final HashSet<OmemoMucMessageListener> omemoMucMessageListeners = new HashSet<>();
|
||||
|
||||
private final PepManager pepManager;
|
||||
|
||||
private OmemoTrustCallback trustCallback;
|
||||
|
||||
private BareJid ownJid;
|
||||
|
@ -120,6 +115,7 @@ public final class OmemoManager extends Manager {
|
|||
super(connection);
|
||||
|
||||
service = OmemoService.getInstance();
|
||||
pepManager = PepManager.getInstanceFor(connection);
|
||||
|
||||
this.deviceId = deviceId;
|
||||
|
||||
|
@ -138,9 +134,6 @@ public final class OmemoManager extends Manager {
|
|||
|
||||
// StanzaListeners
|
||||
resumeStanzaAndPEPListeners();
|
||||
|
||||
// Announce OMEMO support
|
||||
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(PEP_NODE_DEVICE_LIST_NOTIFY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -251,7 +244,6 @@ public final class OmemoManager extends Manager {
|
|||
}
|
||||
|
||||
getOmemoService().init(new LoggedInOmemoManager(this));
|
||||
ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(PEP_NODE_DEVICE_LIST_NOTIFY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -893,27 +885,25 @@ public final class OmemoManager extends Manager {
|
|||
* after {@link #stopStanzaAndPEPListeners()} was called.
|
||||
*/
|
||||
public void resumeStanzaAndPEPListeners() {
|
||||
PepManager pepManager = PepManager.getInstanceFor(connection());
|
||||
CarbonManager carbonManager = CarbonManager.getInstanceFor(connection());
|
||||
|
||||
// Remove listeners to avoid them getting added twice
|
||||
connection().removeAsyncStanzaListener(internalOmemoMessageStanzaListener);
|
||||
carbonManager.removeCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
|
||||
pepManager.removePepListener(deviceListUpdateListener);
|
||||
connection().removeAsyncStanzaListener(this::internalOmemoMessageStanzaListener);
|
||||
carbonManager.removeCarbonCopyReceivedListener(this::internalOmemoCarbonCopyListener);
|
||||
|
||||
// Add listeners
|
||||
pepManager.addPepListener(deviceListUpdateListener);
|
||||
connection().addAsyncStanzaListener(internalOmemoMessageStanzaListener, omemoMessageStanzaFilter);
|
||||
carbonManager.addCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
|
||||
pepManager.addPepEventListener(OmemoConstants.PEP_NODE_DEVICE_LIST, OmemoDeviceListElement.class, pepOmemoDeviceListEventListener);
|
||||
connection().addAsyncStanzaListener(this::internalOmemoMessageStanzaListener, OmemoManager::isOmemoMessage);
|
||||
carbonManager.addCarbonCopyReceivedListener(this::internalOmemoCarbonCopyListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove active stanza listeners needed for OMEMO.
|
||||
*/
|
||||
public void stopStanzaAndPEPListeners() {
|
||||
PepManager.getInstanceFor(connection()).removePepListener(deviceListUpdateListener);
|
||||
connection().removeAsyncStanzaListener(internalOmemoMessageStanzaListener);
|
||||
CarbonManager.getInstanceFor(connection()).removeCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
|
||||
pepManager.removePepEventListener(pepOmemoDeviceListEventListener);
|
||||
connection().removeAsyncStanzaListener(this::internalOmemoMessageStanzaListener);
|
||||
CarbonManager.getInstanceFor(connection()).removeCarbonCopyReceivedListener(this::internalOmemoCarbonCopyListener);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -961,127 +951,87 @@ public final class OmemoManager extends Manager {
|
|||
/**
|
||||
* StanzaListener that listens for incoming Stanzas which contain OMEMO elements.
|
||||
*/
|
||||
private final StanzaListener internalOmemoMessageStanzaListener = new StanzaListener() {
|
||||
private void internalOmemoMessageStanzaListener(final Stanza packet) {
|
||||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
getOmemoService().onOmemoMessageStanzaReceived(packet,
|
||||
new LoggedInOmemoManager(OmemoManager.this));
|
||||
} catch (SmackException.NotLoggedInException | IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "Exception while processing OMEMO stanza", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processStanza(final Stanza packet) {
|
||||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
/**
|
||||
* CarbonCopyListener that listens for incoming carbon copies which contain OMEMO elements.
|
||||
*/
|
||||
private void internalOmemoCarbonCopyListener(final CarbonExtension.Direction direction,
|
||||
final Message carbonCopy,
|
||||
final Message wrappingMessage) {
|
||||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isOmemoMessage(carbonCopy)) {
|
||||
try {
|
||||
getOmemoService().onOmemoMessageStanzaReceived(packet,
|
||||
getOmemoService().onOmemoCarbonCopyReceived(direction, carbonCopy, wrappingMessage,
|
||||
new LoggedInOmemoManager(OmemoManager.this));
|
||||
} catch (SmackException.NotLoggedInException | IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "Exception while processing OMEMO stanza", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CarbonCopyListener that listens for incoming carbon copies which contain OMEMO elements.
|
||||
*/
|
||||
private final CarbonCopyReceivedListener internalOmemoCarbonCopyListener = new CarbonCopyReceivedListener() {
|
||||
@Override
|
||||
public void onCarbonCopyReceived(final CarbonExtension.Direction direction,
|
||||
final Message carbonCopy,
|
||||
final Message wrappingMessage) {
|
||||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (omemoMessageStanzaFilter.accept(carbonCopy)) {
|
||||
try {
|
||||
getOmemoService().onOmemoCarbonCopyReceived(direction, carbonCopy, wrappingMessage,
|
||||
new LoggedInOmemoManager(OmemoManager.this));
|
||||
} catch (SmackException.NotLoggedInException | IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "Exception while processing OMEMO stanza", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@SuppressWarnings("UnnecessaryLambda")
|
||||
private final PepEventListener<OmemoDeviceListElement> pepOmemoDeviceListEventListener =
|
||||
(from, receivedDeviceList, id, message) -> {
|
||||
// Device List <list>
|
||||
OmemoCachedDeviceList deviceList;
|
||||
try {
|
||||
getOmemoService().getOmemoStoreBackend().mergeCachedDeviceList(getOwnDevice(), from,
|
||||
receivedDeviceList);
|
||||
|
||||
/**
|
||||
* PEPListener that listens for OMEMO deviceList updates.
|
||||
*/
|
||||
private final PepListener deviceListUpdateListener = new PepListener() {
|
||||
@Override
|
||||
public void eventReceived(EntityBareJid from, EventElement event, Message message) {
|
||||
|
||||
// Unknown sender, no more work to do.
|
||||
if (from == null) {
|
||||
// TODO: This DOES happen for some reason. Figure out when...
|
||||
if (!from.asBareJid().equals(getOwnJid())) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ExtensionElement items : event.getExtensions()) {
|
||||
if (!(items instanceof ItemsExtension)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (ExtensionElement item : ((ItemsExtension) items).getExtensions()) {
|
||||
if (!(item instanceof PayloadItem<?>)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PayloadItem<?> payloadItem = (PayloadItem<?>) item;
|
||||
|
||||
if (!(payloadItem.getPayload() instanceof OmemoDeviceListElement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Device List <list>
|
||||
OmemoCachedDeviceList deviceList;
|
||||
OmemoDeviceListElement receivedDeviceList = (OmemoDeviceListElement) payloadItem.getPayload();
|
||||
try {
|
||||
getOmemoService().getOmemoStoreBackend().mergeCachedDeviceList(getOwnDevice(), from,
|
||||
receivedDeviceList);
|
||||
|
||||
if (!from.asBareJid().equals(getOwnJid())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
deviceList = getOmemoService().cleanUpDeviceList(getOwnDevice());
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE,
|
||||
"IOException while processing OMEMO PEP device updates. Message: " + message,
|
||||
deviceList = getOmemoService().cleanUpDeviceList(getOwnDevice());
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE,
|
||||
"IOException while processing OMEMO PEP device updates. Message: " + message,
|
||||
e);
|
||||
continue;
|
||||
}
|
||||
final OmemoDeviceListElement_VAxolotl newDeviceList = new OmemoDeviceListElement_VAxolotl(deviceList);
|
||||
return;
|
||||
}
|
||||
final OmemoDeviceListElement_VAxolotl newDeviceList = new OmemoDeviceListElement_VAxolotl(deviceList);
|
||||
|
||||
if (!newDeviceList.copyDeviceIds().equals(receivedDeviceList.copyDeviceIds())) {
|
||||
LOGGER.log(Level.FINE, "Republish deviceList due to changes:" +
|
||||
" Received: " + Arrays.toString(receivedDeviceList.copyDeviceIds().toArray()) +
|
||||
" Published: " + Arrays.toString(newDeviceList.copyDeviceIds().toArray()));
|
||||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
OmemoService.publishDeviceList(connection(), newDeviceList);
|
||||
} catch (InterruptedException | XMPPException.XMPPErrorException |
|
||||
SmackException.NotConnectedException | SmackException.NoResponseException | PubSubException.NotALeafNodeException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not publish our deviceList upon an received update.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!newDeviceList.copyDeviceIds().equals(receivedDeviceList.copyDeviceIds())) {
|
||||
LOGGER.log(Level.FINE, "Republish deviceList due to changes:" +
|
||||
" Received: " + Arrays.toString(receivedDeviceList.copyDeviceIds().toArray()) +
|
||||
" Published: " + Arrays.toString(newDeviceList.copyDeviceIds().toArray()));
|
||||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
OmemoService.publishDeviceList(connection(), newDeviceList);
|
||||
} catch (InterruptedException | XMPPException.XMPPErrorException |
|
||||
SmackException.NotConnectedException | SmackException.NoResponseException | PubSubException.NotALeafNodeException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not publish our deviceList upon an received update.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* StanzaFilter that filters messages containing a OMEMO element.
|
||||
*/
|
||||
private final StanzaFilter omemoMessageStanzaFilter = new StanzaFilter() {
|
||||
@Override
|
||||
public boolean accept(Stanza stanza) {
|
||||
return stanza instanceof Message && OmemoManager.stanzaContainsOmemoElement(stanza);
|
||||
}
|
||||
};
|
||||
private static boolean isOmemoMessage(Stanza stanza) {
|
||||
return stanza instanceof Message && OmemoManager.stanzaContainsOmemoElement(stanza);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard class which ensures that the wrapped OmemoManager knows its BareJid.
|
||||
|
|
|
@ -31,7 +31,6 @@ public final class OmemoConstants {
|
|||
|
||||
// PubSub Node names
|
||||
public static final String PEP_NODE_DEVICE_LIST = OMEMO_NAMESPACE_V_AXOLOTL + ".devicelist";
|
||||
public static final String PEP_NODE_DEVICE_LIST_NOTIFY = PEP_NODE_DEVICE_LIST + "+notify";
|
||||
public static final String PEP_NODE_BUNDLES = OMEMO_NAMESPACE_V_AXOLOTL + ".bundles";
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017-2019 Florian Schmaus, 2018 Paul Schaub.
|
||||
* Copyright 2017-2020 Florian Schmaus, 2018 Paul Schaub.
|
||||
*
|
||||
* 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,6 @@ import org.jivesoftware.smack.XMPPConnection;
|
|||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.chat2.Chat;
|
||||
import org.jivesoftware.smack.chat2.ChatManager;
|
||||
import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.util.Async;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
|
@ -69,12 +68,10 @@ import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore;
|
|||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
|
||||
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
|
||||
import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper;
|
||||
import org.jivesoftware.smackx.pep.PepEventListener;
|
||||
import org.jivesoftware.smackx.pep.PepListener;
|
||||
import org.jivesoftware.smackx.pep.PepManager;
|
||||
import org.jivesoftware.smackx.pubsub.EventElement;
|
||||
import org.jivesoftware.smackx.pubsub.ItemsExtension;
|
||||
import org.jivesoftware.smackx.pubsub.LeafNode;
|
||||
import org.jivesoftware.smackx.pubsub.PayloadItem;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubFeature;
|
||||
|
||||
|
@ -173,6 +170,9 @@ public final class OpenPgpManager extends Manager {
|
|||
private final Set<SignElementReceivedListener> signElementReceivedListeners = new HashSet<>();
|
||||
private final Set<CryptElementReceivedListener> cryptElementReceivedListeners = new HashSet<>();
|
||||
|
||||
@SuppressWarnings("UnnecessaryLambda")
|
||||
private final PepEventListener<PublicKeysListElement> pepPublicKeyListElementListener = (from, listElement, id, message) -> processPublicKeysListElement(from, listElement);;
|
||||
|
||||
/**
|
||||
* Private constructor to avoid instantiation without putting the object into {@code INSTANCES}.
|
||||
*
|
||||
|
@ -180,7 +180,7 @@ public final class OpenPgpManager extends Manager {
|
|||
*/
|
||||
private OpenPgpManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
ChatManager.getInstanceFor(connection).addIncomingListener(incomingOpenPgpMessageListener);
|
||||
ChatManager.getInstanceFor(connection).addIncomingListener(this::incomingChatMessageListener);
|
||||
pepManager = PepManager.getInstanceFor(connection);
|
||||
}
|
||||
|
||||
|
@ -279,7 +279,7 @@ public final class OpenPgpManager extends Manager {
|
|||
publishPublicKey(pepManager, pubkeyElement, primaryFingerprint);
|
||||
|
||||
// Subscribe to public key changes
|
||||
PepManager.getInstanceFor(connection()).addPepListener(metadataListener);
|
||||
pepManager.addPepEventListener(PEP_NODE_PUBLIC_KEYS, PublicKeysListElement.class, pepPublicKeyListElementListener);
|
||||
ServiceDiscoveryManager.getInstanceFor(connection())
|
||||
.addFeature(PEP_NODE_PUBLIC_KEYS_NOTIFY);
|
||||
}
|
||||
|
@ -381,7 +381,7 @@ public final class OpenPgpManager extends Manager {
|
|||
* Remove the metadata listener. This method is mainly used in tests.
|
||||
*/
|
||||
public void stopMetadataListener() {
|
||||
PepManager.getInstanceFor(connection()).removePepListener(metadataListener);
|
||||
pepManager.removePepEventListener(pepPublicKeyListElementListener);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -497,31 +497,6 @@ public final class OpenPgpManager extends Manager {
|
|||
Private stuff.
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@link PepListener} that listens for changes to the OX public keys metadata node.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#pubsub-notifications">XEP-0373 §4.4</a>
|
||||
*/
|
||||
private final PepListener metadataListener = new PepListener() {
|
||||
@Override
|
||||
public void eventReceived(final EntityBareJid from, final EventElement event, final Message message) {
|
||||
if (PEP_NODE_PUBLIC_KEYS.equals(event.getEvent().getNode())) {
|
||||
final BareJid contact = from.asBareJid();
|
||||
LOGGER.log(Level.INFO, "Received OpenPGP metadata update from " + contact);
|
||||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ItemsExtension items = (ItemsExtension) event.getExtensions().get(0);
|
||||
PayloadItem<?> payload = (PayloadItem<?>) items.getItems().get(0);
|
||||
PublicKeysListElement listElement = (PublicKeysListElement) payload.getPayload();
|
||||
|
||||
processPublicKeysListElement(from, listElement);
|
||||
}
|
||||
}, "ProcessOXMetadata");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void processPublicKeysListElement(BareJid contact, PublicKeysListElement listElement) {
|
||||
OpenPgpContact openPgpContact = getOpenPgpContact(contact.asEntityBareJidIfPossible());
|
||||
try {
|
||||
|
@ -548,62 +523,60 @@ public final class OpenPgpManager extends Manager {
|
|||
return provider.decryptAndOrVerify(element, getOpenPgpSelf(), sender);
|
||||
}
|
||||
|
||||
private final IncomingChatMessageListener incomingOpenPgpMessageListener =
|
||||
new IncomingChatMessageListener() {
|
||||
@Override
|
||||
public void newIncomingMessage(final EntityBareJid from, final Message message, Chat chat) {
|
||||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
OpenPgpElement element = message.getExtension(OpenPgpElement.class);
|
||||
if (element == null) {
|
||||
// Message does not contain an OpenPgpElement -> discard
|
||||
return;
|
||||
}
|
||||
|
||||
OpenPgpContact contact = getOpenPgpContact(from);
|
||||
|
||||
OpenPgpMessage decrypted = null;
|
||||
OpenPgpContentElement contentElement = null;
|
||||
try {
|
||||
decrypted = decryptOpenPgpElement(element, contact);
|
||||
contentElement = decrypted.getOpenPgpContentElement();
|
||||
} catch (PGPException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not decrypt incoming OpenPGP encrypted message", e);
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Invalid XML content of incoming OpenPGP encrypted message", e);
|
||||
} catch (SmackException.NotLoggedInException e) {
|
||||
LOGGER.log(Level.WARNING, "Cannot determine our JID, since we are not logged in.", e);
|
||||
}
|
||||
|
||||
if (contentElement instanceof SigncryptElement) {
|
||||
for (SigncryptElementReceivedListener l : signcryptElementReceivedListeners) {
|
||||
l.signcryptElementReceived(contact, message, (SigncryptElement) contentElement, decrypted.getMetadata());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentElement instanceof SignElement) {
|
||||
for (SignElementReceivedListener l : signElementReceivedListeners) {
|
||||
l.signElementReceived(contact, message, (SignElement) contentElement, decrypted.getMetadata());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentElement instanceof CryptElement) {
|
||||
for (CryptElementReceivedListener l : cryptElementReceivedListeners) {
|
||||
l.cryptElementReceived(contact, message, (CryptElement) contentElement, decrypted.getMetadata());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
else {
|
||||
throw new AssertionError("Invalid element received: " + contentElement.getClass().getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
private void incomingChatMessageListener(final EntityBareJid from, final Message message, Chat chat) {
|
||||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
OpenPgpElement element = message.getExtension(OpenPgpElement.class);
|
||||
if (element == null) {
|
||||
// Message does not contain an OpenPgpElement -> discard
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
OpenPgpContact contact = getOpenPgpContact(from);
|
||||
|
||||
OpenPgpMessage decrypted = null;
|
||||
OpenPgpContentElement contentElement = null;
|
||||
try {
|
||||
decrypted = decryptOpenPgpElement(element, contact);
|
||||
contentElement = decrypted.getOpenPgpContentElement();
|
||||
} catch (PGPException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not decrypt incoming OpenPGP encrypted message", e);
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Invalid XML content of incoming OpenPGP encrypted message", e);
|
||||
} catch (SmackException.NotLoggedInException e) {
|
||||
LOGGER.log(Level.WARNING, "Cannot determine our JID, since we are not logged in.", e);
|
||||
}
|
||||
|
||||
if (contentElement instanceof SigncryptElement) {
|
||||
for (SigncryptElementReceivedListener l : signcryptElementReceivedListeners) {
|
||||
l.signcryptElementReceived(contact, message, (SigncryptElement) contentElement,
|
||||
decrypted.getMetadata());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentElement instanceof SignElement) {
|
||||
for (SignElementReceivedListener l : signElementReceivedListeners) {
|
||||
l.signElementReceived(contact, message, (SignElement) contentElement, decrypted.getMetadata());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentElement instanceof CryptElement) {
|
||||
for (CryptElementReceivedListener l : cryptElementReceivedListeners) {
|
||||
l.cryptElementReceived(contact, message, (CryptElement) contentElement,
|
||||
decrypted.getMetadata());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
else {
|
||||
throw new AssertionError("Invalid element received: " + contentElement.getClass().getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link PubkeyElement} which contains the OpenPGP public key of {@code owner} which belongs to
|
||||
|
|
|
@ -44,7 +44,6 @@ import org.jivesoftware.smackx.ox.crypto.OpenPgpElementAndMetadata;
|
|||
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
import org.jivesoftware.smackx.ox.listener.SigncryptElementReceivedListener;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
@ -127,7 +126,7 @@ public final class OXInstantMessagingManager extends Manager {
|
|||
private OXInstantMessagingManager(final XMPPConnection connection) {
|
||||
super(connection);
|
||||
openPgpManager = OpenPgpManager.getInstanceFor(connection);
|
||||
openPgpManager.registerSigncryptReceivedListener(signcryptElementReceivedListener);
|
||||
openPgpManager.registerSigncryptReceivedListener(this::signcryptElementReceivedListener);
|
||||
announceSupportForOxInstantMessaging();
|
||||
}
|
||||
|
||||
|
@ -358,12 +357,9 @@ public final class OXInstantMessagingManager extends Manager {
|
|||
message.setBody("This message is encrypted using XEP-0374: OpenPGP for XMPP: Instant Messaging.");
|
||||
}
|
||||
|
||||
private final SigncryptElementReceivedListener signcryptElementReceivedListener = new SigncryptElementReceivedListener() {
|
||||
@Override
|
||||
public void signcryptElementReceived(OpenPgpContact contact, Message originalMessage, SigncryptElement signcryptElement, OpenPgpMetadata metadata) {
|
||||
for (OxMessageListener listener : oxMessageListeners) {
|
||||
listener.newIncomingOxMessage(contact, originalMessage, signcryptElement, metadata);
|
||||
}
|
||||
private void signcryptElementReceivedListener(OpenPgpContact contact, Message originalMessage, SigncryptElement signcryptElement, OpenPgpMetadata metadata) {
|
||||
for (OxMessageListener listener : oxMessageListeners) {
|
||||
listener.newIncomingOxMessage(contact, originalMessage, signcryptElement, metadata);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ plugins {
|
|||
id "com.github.alisiikh.scalastyle_2.12" version "2.0.2"
|
||||
}
|
||||
|
||||
description = """\
|
||||
A REPL (Read-Eval-Print-Loop) for Smack, or, in other words, a CLI (Command Line Interface) for Smack."""
|
||||
|
||||
apply plugin: 'scala'
|
||||
apply plugin: 'com.github.alisiikh.scalastyle_2.12'
|
||||
|
||||
|
|
|
@ -42,6 +42,8 @@ import org.jivesoftware.smack.packet.Message;
|
|||
import org.jivesoftware.smack.sm.StreamManagementModuleDescriptor;
|
||||
import org.jivesoftware.smack.tcp.XmppTcpTransportModuleDescriptor;
|
||||
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
|
||||
import org.jxmpp.util.XmppDateTime;
|
||||
|
||||
public class Nio {
|
||||
|
@ -128,9 +130,10 @@ public class Nio {
|
|||
connection.disconnect();
|
||||
|
||||
ModularXmppClientToServerConnection.Stats connectionStats = connection.getStats();
|
||||
ServiceDiscoveryManager.Stats serviceDiscoveryManagerStats = ServiceDiscoveryManager.getInstanceFor(connection).getStats();
|
||||
|
||||
// CHECKSTYLE:OFF
|
||||
System.out.println("NIO successfully finished, yeah!\n" + connectionStats);
|
||||
System.out.println("NIO successfully finished, yeah!\n" + connectionStats + '\n' + serviceDiscoveryManagerStats);
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ public class TlsTest {
|
|||
|
||||
if (StringUtils.isNotEmpty(tlsPin)) {
|
||||
SSLContext sslContext = Java7Pinning.forPin(tlsPin);
|
||||
builder.setCustomSSLContext(sslContext);
|
||||
builder.setSslContextFactory(() -> sslContext);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -81,6 +81,7 @@ 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.internal.SmackTlsContext;
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
|
|
|
@ -21,13 +21,9 @@ import java.net.InetSocketAddress;
|
|||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
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;
|
||||
|
@ -49,7 +45,6 @@ import javax.net.ssl.SSLEngineResult;
|
|||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
import org.jivesoftware.smack.AbstractXMPPConnection.SmackTlsContext;
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.SmackException.ConnectionException;
|
||||
|
@ -61,7 +56,6 @@ import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
|
|||
import org.jivesoftware.smack.SmackException.SmackWrappedException;
|
||||
import org.jivesoftware.smack.SmackFuture;
|
||||
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
|
||||
import org.jivesoftware.smack.SmackReactor.ChannelSelectedCallback;
|
||||
import org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment;
|
||||
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
||||
import org.jivesoftware.smack.XmppInputOutputFilter;
|
||||
|
@ -75,6 +69,7 @@ import org.jivesoftware.smack.debugger.SmackDebugger;
|
|||
import org.jivesoftware.smack.fsm.State;
|
||||
import org.jivesoftware.smack.fsm.StateDescriptor;
|
||||
import org.jivesoftware.smack.fsm.StateTransitionResult;
|
||||
import org.jivesoftware.smack.internal.SmackTlsContext;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.packet.StartTls;
|
||||
import org.jivesoftware.smack.packet.StreamOpen;
|
||||
|
@ -280,294 +275,292 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
|||
}
|
||||
};
|
||||
|
||||
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;
|
||||
private void onChannelSelected(SelectableChannel selectedChannel, SelectionKey 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();
|
||||
if (!channelSelectedCallbackLock.tryLock()) {
|
||||
rejectedChannelSelectedCallbacks.incrementAndGet();
|
||||
return;
|
||||
}
|
||||
|
||||
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 || !connectionInternal.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<TopLevelStreamElement> sendElements = bufferToElementMap.remove(prunedBuffer);
|
||||
if (sendElements == null) {
|
||||
continue;
|
||||
}
|
||||
for (TopLevelStreamElement elementJustSend : sendElements) {
|
||||
connectionInternal.fireFirstLevelElementSendListeners(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<XmppInputOutputFilter> it = connectionInternal.getXmppInputOutputFilterBeginIterator(); it.hasNext();) {
|
||||
XmppInputOutputFilter inputOutputFilter = it.next();
|
||||
XmppInputOutputFilter.OutputResult outputResult;
|
||||
try {
|
||||
outputResult = inputOutputFilter.output(outputFilterInputData, isLastPartOfElement,
|
||||
destinationAddressChanged, moreDataAvailable);
|
||||
} catch (IOException e) {
|
||||
connectionInternal.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;
|
||||
}
|
||||
|
||||
final SmackDebugger debugger = connectionInternal.smackDebugger;
|
||||
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 (!connectionInternal.outgoingElementsQueue.isEmpty()) {
|
||||
currentlyOutgonigTopLevelStreamElement = connectionInternal.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;
|
||||
XmlEnvironment outgoingStreamXmlEnvironment = connectionInternal.getOutgoingStreamXmlEnvironment();
|
||||
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;
|
||||
}
|
||||
|
||||
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 || !connectionInternal.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<TopLevelStreamElement> sendElements = bufferToElementMap.remove(prunedBuffer);
|
||||
if (sendElements == null) {
|
||||
continue;
|
||||
}
|
||||
for (TopLevelStreamElement elementJustSend : sendElements) {
|
||||
connectionInternal.fireFirstLevelElementSendListeners(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<XmppInputOutputFilter> it = connectionInternal.getXmppInputOutputFilterBeginIterator(); it.hasNext();) {
|
||||
XmppInputOutputFilter inputOutputFilter = it.next();
|
||||
XmppInputOutputFilter.OutputResult outputResult;
|
||||
try {
|
||||
outputResult = inputOutputFilter.output(outputFilterInputData, isLastPartOfElement,
|
||||
destinationAddressChanged, moreDataAvailable);
|
||||
} catch (IOException e) {
|
||||
connectionInternal.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;
|
||||
}
|
||||
|
||||
final SmackDebugger debugger = connectionInternal.smackDebugger;
|
||||
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 (!connectionInternal.outgoingElementsQueue.isEmpty()) {
|
||||
currentlyOutgonigTopLevelStreamElement = connectionInternal.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;
|
||||
XmlEnvironment outgoingStreamXmlEnvironment = connectionInternal.getOutgoingStreamXmlEnvironment();
|
||||
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<XmppInputOutputFilter> it = connectionInternal.getXmppInputOutputFilterEndIterator(); it.hasPrevious();) {
|
||||
filteredIncomingBuffer.flip();
|
||||
|
||||
ByteBuffer newFilteredIncomingBuffer;
|
||||
try {
|
||||
newFilteredIncomingBuffer = it.previous().input(filteredIncomingBuffer);
|
||||
} catch (IOException e) {
|
||||
connectionInternal.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) {
|
||||
connectionInternal.notifyConnectionError(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
totalBytesWritten += callbackBytesWritten;
|
||||
totalBytesRead += callbackBytesRead;
|
||||
|
||||
channelSelectedCallbackLock.unlock();
|
||||
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;
|
||||
}
|
||||
|
||||
// Indicate that there is no reactor thread racing towards handling this selection key.
|
||||
final SelectionKeyAttachment selectionKeyAttachment = this.selectionKeyAttachment;
|
||||
if (selectionKeyAttachment != null) {
|
||||
selectionKeyAttachment.resetReactorThreadRacing();
|
||||
if (!pendingInputFilterData) {
|
||||
if (bytesRead == 0) {
|
||||
// Nothing more to read.
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
pendingInputFilterData = false;
|
||||
}
|
||||
|
||||
// Check the queue again to prevent lost wakeups caused by elements inserted before we
|
||||
// called resetReactorThreadRacing() a few lines above.
|
||||
if (!connectionInternal.outgoingElementsQueue.isEmpty()) {
|
||||
setWriteInterestAfterChannelSelectedCallback.incrementAndGet();
|
||||
// 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;
|
||||
}
|
||||
|
||||
connectionInternal.setInterestOps(selectionKey, newInterestedOps);
|
||||
};
|
||||
callbackBytesRead += bytesRead;
|
||||
|
||||
ByteBuffer filteredIncomingBuffer = incomingBuffer;
|
||||
for (ListIterator<XmppInputOutputFilter> it = connectionInternal.getXmppInputOutputFilterEndIterator(); it.hasPrevious();) {
|
||||
filteredIncomingBuffer.flip();
|
||||
|
||||
ByteBuffer newFilteredIncomingBuffer;
|
||||
try {
|
||||
newFilteredIncomingBuffer = it.previous().input(filteredIncomingBuffer);
|
||||
} catch (IOException e) {
|
||||
connectionInternal.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) {
|
||||
connectionInternal.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 (!connectionInternal.outgoingElementsQueue.isEmpty()) {
|
||||
setWriteInterestAfterChannelSelectedCallback.incrementAndGet();
|
||||
newInterestedOps |= SelectionKey.OP_WRITE;
|
||||
}
|
||||
|
||||
connectionInternal.setInterestOps(selectionKey, newInterestedOps);
|
||||
}
|
||||
|
||||
private void handleReadWriteIoException(IOException e) {
|
||||
if (e instanceof ClosedChannelException && !tcpNioTransport.isConnected()) {
|
||||
|
@ -677,7 +670,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stats getStats() {
|
||||
public XmppTcpTransportModule.Stats getStats() {
|
||||
return XmppTcpTransportModule.this.getStats();
|
||||
}
|
||||
|
||||
|
@ -774,7 +767,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
|||
remoteAddress = (InetSocketAddress) socketChannel.socket().getRemoteSocketAddress();
|
||||
|
||||
selectionKey = connectionInternal.registerWithSelector(socketChannel, SelectionKey.OP_READ,
|
||||
channelSelectedCallback);
|
||||
XmppTcpTransportModule.this::onChannelSelected);
|
||||
selectionKeyAttachment = (SelectionKeyAttachment) selectionKey.attachment();
|
||||
|
||||
connectionInternal.setTransport(tcpNioTransport);
|
||||
|
@ -869,13 +862,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
|||
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
|
||||
connectionInternal.sendAndWaitForResponse(StartTls.INSTANCE, TlsProceed.class, TlsFailure.class);
|
||||
|
||||
SmackTlsContext smackTlsContext;
|
||||
try {
|
||||
smackTlsContext = connectionInternal.getSmackTlsContext();
|
||||
} catch (KeyManagementException | UnrecoverableKeyException | NoSuchAlgorithmException
|
||||
| CertificateException | KeyStoreException | NoSuchProviderException e) {
|
||||
throw new SmackWrappedException(e);
|
||||
}
|
||||
SmackTlsContext smackTlsContext = connectionInternal.getSmackTlsContext();
|
||||
|
||||
tlsState = new TlsState(smackTlsContext);
|
||||
connectionInternal.addXmppInputOutputFilter(tlsState);
|
||||
|
@ -1316,7 +1303,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
|||
pendingOutputFilterData = true;
|
||||
}
|
||||
|
||||
channelSelectedCallback.onChannelSelected(channel, key);
|
||||
onChannelSelected(channel, key);
|
||||
} finally {
|
||||
channelSelectedCallbackLock.unlock();
|
||||
}
|
||||
|
@ -1347,7 +1334,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
|||
return CollectionUtil.removeUntil(buffers, b -> b.hasRemaining());
|
||||
}
|
||||
|
||||
public Stats getStats() {
|
||||
public XmppTcpTransportModule.Stats getStats() {
|
||||
return new Stats(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,18 +16,14 @@
|
|||
*/
|
||||
package org.jivesoftware.smack.tcp.rce;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
|
||||
import org.jivesoftware.smack.datatypes.UInt16;
|
||||
import org.jivesoftware.smack.util.rce.SingleAddressRemoteConnectionEndpoint;
|
||||
|
||||
import org.minidns.record.A;
|
||||
import org.minidns.record.AAAA;
|
||||
import org.minidns.record.InternetAddressRR;
|
||||
|
||||
public final class IpTcpRemoteConnectionEndpoint<IARR extends InternetAddressRR>
|
||||
public final class IpTcpRemoteConnectionEndpoint<IARR extends InternetAddressRR<?>>
|
||||
implements Rfc6120TcpRemoteConnectionEndpoint, SingleAddressRemoteConnectionEndpoint {
|
||||
|
||||
private final CharSequence host;
|
||||
|
@ -42,17 +38,11 @@ public final class IpTcpRemoteConnectionEndpoint<IARR extends InternetAddressRR>
|
|||
this.internetAddressResourceRecord = internetAddressResourceRecord;
|
||||
}
|
||||
|
||||
public static IpTcpRemoteConnectionEndpoint<InternetAddressRR> from(CharSequence host, int port,
|
||||
public static IpTcpRemoteConnectionEndpoint<InternetAddressRR<?>> from(CharSequence host, UInt16 port,
|
||||
InetAddress inetAddress) {
|
||||
InternetAddressRR internetAddressResourceRecord;
|
||||
// TODO: Use InternetAddressRR.from(InetAddress) once MiniDNS is updated.
|
||||
if (inetAddress instanceof Inet4Address) {
|
||||
internetAddressResourceRecord = new A((Inet4Address) inetAddress);
|
||||
} else {
|
||||
internetAddressResourceRecord = new AAAA((Inet6Address) inetAddress);
|
||||
}
|
||||
InternetAddressRR<?> internetAddressResourceRecord = InternetAddressRR.from(inetAddress);
|
||||
|
||||
return new IpTcpRemoteConnectionEndpoint<InternetAddressRR>(host, UInt16.from(port),
|
||||
return new IpTcpRemoteConnectionEndpoint<InternetAddressRR<?>>(host, port,
|
||||
internetAddressResourceRecord);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.logging.Logger;
|
|||
|
||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
|
||||
import org.jivesoftware.smack.datatypes.UInt16;
|
||||
import org.jivesoftware.smack.util.DNSUtil;
|
||||
import org.jivesoftware.smack.util.dns.DNSResolver;
|
||||
import org.jivesoftware.smack.util.rce.RemoteConnectionEndpoint;
|
||||
|
@ -61,7 +62,7 @@ public class RemoteXmppTcpConnectionEndpoints {
|
|||
if (hostAddress != null) {
|
||||
lookupFailures = Collections.emptyList();
|
||||
|
||||
IpTcpRemoteConnectionEndpoint<InternetAddressRR> connectionEndpoint = IpTcpRemoteConnectionEndpoint.from(
|
||||
IpTcpRemoteConnectionEndpoint<InternetAddressRR<?>> connectionEndpoint = IpTcpRemoteConnectionEndpoint.from(
|
||||
hostAddress.toString(), config.getPort(), hostAddress);
|
||||
discoveredRemoteConnectionEndpoints = Collections.singletonList(connectionEndpoint);
|
||||
} else if (host != null) {
|
||||
|
@ -72,9 +73,9 @@ public class RemoteXmppTcpConnectionEndpoints {
|
|||
|
||||
if (hostAddresses != null) {
|
||||
discoveredRemoteConnectionEndpoints = new ArrayList<>(hostAddresses.size());
|
||||
int port = config.getPort();
|
||||
UInt16 port = config.getPort();
|
||||
for (InetAddress inetAddress : hostAddresses) {
|
||||
IpTcpRemoteConnectionEndpoint<InternetAddressRR> connectionEndpoint = IpTcpRemoteConnectionEndpoint.from(
|
||||
IpTcpRemoteConnectionEndpoint<InternetAddressRR<?>> connectionEndpoint = IpTcpRemoteConnectionEndpoint.from(
|
||||
host, port, inetAddress);
|
||||
discoveredRemoteConnectionEndpoints.add(connectionEndpoint);
|
||||
}
|
||||
|
@ -198,13 +199,13 @@ public class RemoteXmppTcpConnectionEndpoints {
|
|||
LOGGER.info("Could not resolve DNS SRV resource records for " + srvDomain + ". Consider adding those.");
|
||||
}
|
||||
|
||||
int defaultPort;
|
||||
UInt16 defaultPort;
|
||||
switch (domainType) {
|
||||
case client:
|
||||
defaultPort = 5222;
|
||||
defaultPort = UInt16.from(5222);
|
||||
break;
|
||||
case server:
|
||||
defaultPort = 5269;
|
||||
defaultPort = UInt16.from(5269);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
|
@ -214,7 +215,7 @@ public class RemoteXmppTcpConnectionEndpoints {
|
|||
List<InetAddress> hostAddresses = dnsResolver.lookupHostAddress(domain, lookupFailures, dnssecMode);
|
||||
if (hostAddresses != null) {
|
||||
for (InetAddress inetAddress : hostAddresses) {
|
||||
IpTcpRemoteConnectionEndpoint<InternetAddressRR> endpoint = IpTcpRemoteConnectionEndpoint.from(domain, defaultPort, inetAddress);
|
||||
IpTcpRemoteConnectionEndpoint<InternetAddressRR<?>> endpoint = IpTcpRemoteConnectionEndpoint.from(domain, defaultPort, inetAddress);
|
||||
endpoints.add(endpoint);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,12 +52,12 @@ public class PacketWriterTest {
|
|||
* interrupt.
|
||||
*
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
* @throws BrokenBarrierException
|
||||
* @throws BrokenBarrierException in case of a broken barrier.
|
||||
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||
* @throws XmppStringprepException if the provided string is invalid.
|
||||
* @throws SecurityException if there was a security violation.
|
||||
* @throws NoSuchFieldException
|
||||
* @throws IllegalAccessException
|
||||
* @throws NoSuchFieldException if there is no such field.
|
||||
* @throws IllegalAccessException if there was an illegal access.
|
||||
* @throws IllegalArgumentException if an illegal argument was given.
|
||||
*/
|
||||
@Test
|
||||
|
|
2
version
2
version
|
@ -1 +1 @@
|
|||
4.4.0-alpha3-SNAPSHOT
|
||||
4.4.0-alpha4-SNAPSHOT
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue