1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-09-10 17:49:38 +02:00

Compare commits

...

41 commits

Author SHA1 Message Date
Florian Schmaus
ccc785062e [extensions] Deprecate old-style PepManager PEP listeners 2020-05-25 20:32:47 +02:00
Florian Schmaus
15499ad1f8 [openpgp] Remove info log "Received OpenPPG metadata update from…" 2020-05-25 16:16:56 +02:00
Florian Schmaus
f5448c5faa [core] Rework TLS logic
This moves the logic in AbstractXMPPConnection.getSmackTlsContext()
into the ConnectionConfiguration constructor.

Also introduce SslContextFactory and use it in
ConnectionConfiguration.
2020-05-25 15:41:57 +02:00
Florian Schmaus
7156849c77 [core] Set default SecurityMode to 'required' in ConnectionConfiguration 2020-05-25 14:44:35 +02:00
Florian Schmaus
70188dbe57 [travis] Only run javadocAll on Java 11 or higher
It appears that 'javadoc' from older JREs is not able to consume the
javadoc generated by newer JREs:

javadoc: warning - Error fetching URL: https://jxmpp.org/releases/0.7.0-alpha6/javadoc/
javadoc: warning - Error fetching URL: https://minidns.org/releases/0.4.0-alpha5/javadoc/
2020-05-25 11:14:47 +02:00
Florian Schmaus
d65f2c932e Bump Error Prone version to 2.3.4 and fix new bug patterns 2020-05-24 21:10:01 +02:00
Florian Schmaus
1c0bdfae40 [experimental] Delcare methods as static when possible 2020-05-24 18:01:00 +02:00
Florian Schmaus
f3207e8c27 Bump junit version to 5.6.2 2020-05-24 13:14:26 +02:00
Florian Schmaus
f750c79392 Bump jxmpp Version to 0.7.0-alpha6 2020-05-24 13:14:14 +02:00
Florian Schmaus
c7c5b10c41 Bump MiniDNS version to 0.4.0-alpha5 2020-05-24 13:11:50 +02:00
Florian Schmaus
cac874bdc7 [core] Use UInt16 for ConnectionConfiguration 'port' 2020-05-24 13:08:03 +02:00
Florian Schmaus
9a8ee3c8e3 [core] Improve NumberUtil's exception message and fix javadoc 2020-05-24 13:08:03 +02:00
Florian Schmaus
f045c0dd08 Update Message Archive Management (XEP-0313) support to urn:xmpp:mam:2
Fixes SMACK-890.
2020-05-24 12:42:58 +02:00
Florian Schmaus
a51663448b [checkstyle] Bump to 8.27
We can not bump to the latest version, due
https://github.com/checkstyle/checkstyle/issues/8248
2020-05-23 23:36:58 +02:00
Florian Schmaus
65aa543c57 [checkstyle] Enable JavadocMethod also for protected methods 2020-05-23 22:49:44 +02:00
Florian Schmaus
ebe5c49e92 [checkstyle] Tighten JavadocMethod checkstyle rule 2020-05-23 22:43:29 +02:00
Florian Schmaus
5bfe789e08 [sinttest] Add unreliable workaround for XEP-0030 based operations 2020-05-22 15:44:23 +02:00
Florian Schmaus
a137944e50 [disco/caps] Fix DiscoverInfo.asBuilder()
The method would not copy the extensions elements, which would lead to
a false calculation of the caps hash by EntityCapsManager.
2020-05-22 15:38:16 +02:00
Florian Schmaus
0db6406262 [softwareinfo] Separate static and non-static fields by empty line 2020-05-22 15:36:39 +02:00
Florian Schmaus
54d6bc8658 [softwareinfo] Remove SoftwareInfoManager.isSupported(Jid)
SoftwareInfoManager.fromJid(Jid) will return 'null' if the jid in
question does not announce or support this.
2020-05-22 15:35:56 +02:00
Florian Schmaus
027358fc63 [softwareinfo] Remove unnecessary throws declarations 2020-05-22 15:34:27 +02:00
Florian Schmaus
68a453d3b3 [core] Fix deprecation javadoc in Presence
It should hint towards the StanzaFactory not SocketFactory.
2020-05-22 09:25:20 +02:00
Florian Schmaus
054fd9ae44 [core] Replace 'packet' with 'stanza' in Presence's javadoc 2020-05-22 09:25:15 +02:00
Florian Schmaus
962071762a [core] Optimize Presence.toXML() for empty element 2020-05-22 09:20:02 +02:00
Florian Schmaus
b3c57ef918 [xhtmlim] Use stanza builder API, deprecate old 2020-05-21 22:45:28 +02:00
Florian Schmaus
ca85b8326a [test] Use correct class StanzaBuilder for static call to buildMessage() 2020-05-21 22:44:51 +02:00
Florian Schmaus
c4ad857c0d [carbons] Add CarbonExtension.Private.addTo(MessageBuilder), deprecate old 2020-05-21 22:44:14 +02:00
Florian Schmaus
33720e8d97 [core] Fix potential NPE in Java7ZlibInputOutputStream
The field XMPPInputOutputStream.flushMethod was not initialized, which
could cause an NPE in the "switch (flushMethod)" found in
Java7ZlibInputOutputStream.getOutputStream().
2020-05-21 12:47:55 +02:00
Florian Schmaus
cbcf1d15f5 Smack 4.4.0-alpha4-SNAPSHOT 2020-05-21 10:35:16 +02:00
Florian Schmaus
f97397f5ee Smack 4.4.0-alpha3 2020-05-21 09:48:30 +02:00
Florian Schmaus
d69f62d012 [repl] Add project description 2020-05-21 09:48:29 +02:00
Florian Schmaus
2679c72f0f [disco] Move logic that was previously in EntityCapsManager in SDM 2020-05-18 09:15:15 +02:00
Florian Schmaus
87591655ad [core] Add StanzaFilter.asPredicate(Class) 2020-05-18 09:15:14 +02:00
Florian Schmaus
4239dac440 [gradle] Fix project description in POM 2020-05-18 09:15:14 +02:00
Florian Schmaus
46ba273689 [disco] Delay the entity caps renewal
This avoids the calculation of the caps hash while the managers become
registered with the connection and add their features.
2020-05-18 09:15:14 +02:00
Florian Schmaus
72c5dc5886 [core] Introduce ScheduledAction.Kind for blocking and non-blocking actions 2020-05-18 09:15:14 +02:00
Florian Schmaus
4e5536e227 [core] Make ScheduledAction.cancel() return boolean and add javadoc 2020-05-18 09:15:14 +02:00
Florian Schmaus
6daf19dbd3 [core] Add javadoc to SmackReactor.cancel(ScheduledAction) 2020-05-18 09:15:14 +02:00
Florian Schmaus
dfdd0acf91 Introduce AbstractStats 2020-05-18 09:15:14 +02:00
Florian Schmaus
b79ee8f6a9
Merge pull request #394 from vanitasvitae/tlsClose
Prevent NPE when reading trust store stream
2020-05-17 18:14:15 +02:00
dc64a43f12
Prevent NPE when closing trust store stream 2020-05-17 17:01:42 +02:00
82 changed files with 1338 additions and 1198 deletions

View file

@ -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)

View file

@ -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'

View file

@ -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"/>

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}

View file

@ -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();
}
}
}

View file

@ -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)

View file

@ -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() {

View file

@ -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);
};
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,21 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Smack internal classes and interfaces.
*/
package org.jivesoftware.smack.internal;

View file

@ -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;

View file

@ -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);
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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() {

View file

@ -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

View file

@ -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);
}

View file

@ -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());
}

View file

@ -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();

View file

@ -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.

View file

@ -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())

View file

@ -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);
}
/**

View file

@ -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.

View file

@ -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>

View file

@ -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));

View file

@ -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>"

View file

@ -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>";

View file

@ -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);

View file

@ -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>";

View file

@ -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());
}

View file

@ -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' "

View file

@ -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>";

View file

@ -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));

View file

@ -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));

View file

@ -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);
}
}
}
/**

View file

@ -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);
}
/**

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus.
* Copyright 2018-2020 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -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);
}

View file

@ -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');
}
}
}

View file

@ -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().

View file

@ -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();

View file

@ -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);
}

View file

@ -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();
}
};
}

View file

@ -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);
}
/**

View file

@ -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) {

View file

@ -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;

View file

@ -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) {

View file

@ -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 {

View file

@ -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));

View file

@ -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) {

View file

@ -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

View file

@ -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)

View file

@ -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 {

View file

@ -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;
}

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -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.

View file

@ -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";
/**

View file

@ -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

View file

@ -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);
}
};
}
}

View file

@ -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'

View file

@ -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
}

View file

@ -89,7 +89,7 @@ public class TlsTest {
if (StringUtils.isNotEmpty(tlsPin)) {
SSLContext sslContext = Java7Pinning.forPin(tlsPin);
builder.setCustomSSLContext(sslContext);
builder.setSslContextFactory(() -> sslContext);
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -1 +1 @@
4.4.0-alpha3-SNAPSHOT
4.4.0-alpha4-SNAPSHOT