1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-29 23:42:06 +01:00

Compare commits

..

42 commits

Author SHA1 Message Date
Florian Schmaus
870756997f Improve exception message of XmppConnectionStressTest 2019-04-24 21:25:22 +02:00
Guus der Kinderen
fc70484cf6 More specific Pubsub integration tests.
XEP-0060 prohibits publishing a request that contains an item to a node that
is both 'notification-only' and 'transient' (section 7.1.3.6)

In commit 8ed872ca63 the existing pubsub publication
test was modified (to resolve a different issue) to operate on a node that's both
'notification-only' and 'transient'. This resulted in a test that should return
an error, even though the test implementation didn't expect one.

This commit explicitly verifies that publishing an item to such a node causes
an error to be returned. A new test is added that verifies that publishing an
event notification does succeed.
2019-04-17 21:52:01 +02:00
Florian Schmaus
2dbc32340d
Merge pull request #311 from guusdk/sint-accountmanager-disconnect
Admin should be disconnected after tests.
2019-04-17 21:32:42 +02:00
Florian Schmaus
d97fb126a1 Add PubSubManager.getInstanceFor() just like all other Managers
and deprecate PubSubManager.getInstance().
2019-04-16 11:21:22 +02:00
Florian Schmaus
99bf8316f5 Add javadoc about callbacks to XMPPConnection 2019-04-16 09:33:07 +02:00
Florian Schmaus
9c30e4f3cc Merge branch '4.3' 2019-04-16 09:27:48 +02:00
Florian Schmaus
11775ed6b0 Add checkstyle rule for 'synchronized' on Manager.getInstanceFor() 2019-04-15 09:58:53 +02:00
Florian Schmaus
488055948d Add missing 'synchronized' keywords to Manager.getInstanceFor()
Fixes SMACK-865.
2019-04-15 09:48:52 +02:00
Florian Schmaus
7fd0676ff4 Add unit test for smack-resolver-minidns-dox 2019-04-14 22:34:15 +02:00
Florian Schmaus
6076a9dfa5 Introduce asyncGoLimited()
which limits the number of threads created for asynchronous
operations.

Fixes SMACK-864.
2019-04-14 21:40:09 +02:00
Florian Schmaus
a1c88f1fad Add DNSSEC support to DnsOverXmppMiniDnsResolver 2019-04-09 21:00:08 +02:00
Florian Schmaus
fc45e1b905 DoX: Ensure that the response message ID matches the query ID 2019-04-09 17:02:56 +02:00
Florian Schmaus
0ec7e84cbc Update link to XMPP Registry for Service Discovery Identities 2019-04-09 14:26:15 +02:00
Florian Schmaus
9ad162af6e Use correct field in ServiceDiscoveryManager.getIdentities()
Fixes SMACK-863.
2019-04-09 12:14:12 +02:00
Florian Schmaus
474ea68d4a Remove unused method in Smack's Base64 API 2019-04-09 10:56:47 +02:00
Florian Schmaus
62fd897cf7 Add support for XEP-0418: DNS Queries over XMPP (DoX)
Fixes SMACK-862.
2019-04-09 10:56:47 +02:00
Florian Schmaus
75b1d8ce13 Add AbstractError.Builder.setDescriptiveEnText(String, Exception) 2019-04-09 09:33:32 +02:00
Florian Schmaus
1122bf394c Add ExceptionUtil 2019-04-09 09:33:32 +02:00
Florian Schmaus
3075430713 Use IQ.isRequestIQ() in IQ 2019-04-09 09:33:32 +02:00
Florian Schmaus
d1f2631771 Use IQ.isResponseIQ() in AbstractXMPPConnection 2019-04-09 09:33:32 +02:00
Florian Schmaus
87e0ac9ba1 Add IQ.isResponseIQ() 2019-04-09 09:33:32 +02:00
Florian Schmaus
7d5274dad1 Use java.util.Base64 and remove build-in Base64 API 2019-04-09 09:33:32 +02:00
Guus der Kinderen
a9673408cc Admin should be disconnected after tests.
The Smack Integration tests can use an admin account to provision
accounts that are used by the tests. This admin account uses an XMPP
connection to interact with the server-under-test.

When the tests are over, this account should be disconnected
explicitly, to prevent stream management from keeping it alive longer
than it needs to.
2019-04-08 16:10:29 +02:00
Florian Schmaus
0d17f195b0 Use EntityFullJid as JID type in MultiUserChat.getJoinedRooms()
Got a user who put in a EntityBareJid and wondered why the returned
list was empty.
2019-04-07 22:13:38 +02:00
Florian Schmaus
d6b6fdca17 Add ConnectionConfiguration.setXmppAddressAndPassword() 2019-04-07 16:44:04 +02:00
Florian Schmaus
38384a1eed Improve javadoc of ConnectionConfiguration 2019-04-07 16:43:48 +02:00
Florian Schmaus
af0fb7543c
Merge pull request #309 from vanitasvitae/rosterListenerDoc
Add missing Roster documentation
2019-04-06 15:19:59 +02:00
Florian Schmaus
fea7362293
Merge pull request #306 from Alameyo/doc-fix-stanza-type-filter-to-uppercase
Fix in documentation. StanzaTypeFilter to uppercase as in code.
2019-04-06 15:17:58 +02:00
af88227919
Add missing Roster documentation 2019-04-06 02:36:32 +02:00
Florian Schmaus
d10319f1a0 Merge branch '4.3' 2019-04-05 10:22:12 +02:00
Florian Schmaus
c499556d07 Remove dead code from OmemoService 2019-04-02 16:00:18 +02:00
Florian Schmaus
7d7fbe6828 Do not explicity select the (crypto) Provider in smack-omemo
This makes the system select the "best" available provider.

Also the 'BC' provider in newer Android version does not longer
implement certain Ciphers, which causes an NoSuchAlgorithmException if
the Cipher is requested explicitly by the 'BC' provider:

E/XmppService: XmppServiceConnection - Error while sending pending messages
  org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException: java.security.NoSuchAlgorithmException: The BC provider no longer provides an implementation for Cipher.AES/GCM/NoPadding.  Please see https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html for more details.
      at org.jivesoftware.smackx.omemo.OmemoService.encrypt(OmemoService.java:375)
      at org.jivesoftware.smackx.omemo.OmemoService.createOmemoMessage(OmemoService.java:537)
      at org.jivesoftware.smackx.omemo.OmemoManager.encrypt(OmemoManager.java:341)
      at org.jivesoftware.smackx.omemo.OmemoManager.encrypt(OmemoManager.java:314)
      at es.iecisa.xmppservice.XmppServiceConnection.lambda$sendMessage$0(XmppServiceConnection.java:516)
      at es.iecisa.xmppservice.-$$Lambda$XmppServiceConnection$aBU_80chagvypMTSd-aSm7pRQRY.run(Unknown Source:4)
      at java.lang.Thread.run(Thread.java:764)
   Caused by: java.security.NoSuchAlgorithmException: The BC provider no longer provides an implementation for Cipher.AES/GCM/NoPadding.  Please see https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html for more details.
      at sun.security.jca.Providers.checkBouncyCastleDeprecation(Providers.java:563)
      at sun.security.jca.Providers.checkBouncyCastleDeprecation(Providers.java:346)
      at javax.crypto.Cipher.createCipher(Cipher.java:722)
      at javax.crypto.Cipher.getInstance(Cipher.java:717)
      at javax.crypto.Cipher.getInstance(Cipher.java:674)
      at org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder.setMessage(OmemoMessageBuilder.java:169)
      at org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder.<init>(OmemoMessageBuilder.java:116)
      at org.jivesoftware.smackx.omemo.OmemoService.encrypt(OmemoService.java:372)
      at org.jivesoftware.smackx.omemo.OmemoService.createOmemoMessage(OmemoService.java:537)
      at org.jivesoftware.smackx.omemo.OmemoManager.encrypt(OmemoManager.java:341)
      at org.jivesoftware.smackx.omemo.OmemoManager.encrypt(OmemoManager.java:314)
      at es.iecisa.xmppservice.XmppServiceConnection.lambda$sendMessage$0(XmppServiceConnection.java:516)
      at es.iecisa.xmppservice.-$$Lambda$XmppServiceConnection$aBU_80chagvypMTSd-aSm7pRQRY.run(Unknown Source:4)
      at java.lang.Thread.run(Thread.java:764)
2019-04-02 15:58:34 +02:00
Florian Schmaus
9f8d13b8cd Do not explicitly select the Provider in HashManager
Note that we still setup the BouncyCastleProvider so all requested
MessageDigest instances should be avaialble.
2019-04-02 15:58:34 +02:00
Florian Schmaus
d6a90942a4 Unify Bouncy Castle versions: Add bouncyCastleVersion variable 2019-04-02 15:58:34 +02:00
Florian Schmaus
9be498c440 Fix NPE in Roster's presence listeners if 'from' is not set
The NPE is caused by an inbound presence stanza without the 'from'
attribute set. The stacktrace of the NPE is:

FATAL EXCEPTION: Smack Cached Executor
Process: de.fhg.ivi.senetz.mobile.android.mbk.debug, PID: 13365
java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.Object.hashCode()' on a null object reference
    at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:944)
    at org.jivesoftware.smack.roster.Roster.getPresencesInternal(Roster.java:374)
    at org.jivesoftware.smack.roster.Roster.getOrCreatePresencesInternal(Roster.java:388)
    at org.jivesoftware.smack.roster.Roster.access$1100(Roster.java:94)
    at org.jivesoftware.smack.roster.Roster$PresencePacketListener$1.run(Roster.java:1519)
    at org.jivesoftware.smack.AsyncButOrdered$Handler.run(AsyncButOrdered.java:121)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
    at java.lang.Thread.run(Thread.java:764)

Thanks to Marcel Heckel for reporting this.

Fixes SMACK-861.
2019-04-02 14:28:41 +02:00
Florian Schmaus
25b3f35421 Ensure that shutdown() terminates reader/writer threads
In case an exception happens in connect()/login() the
'disconnectedButResumable' boolean may (still) be set. Which causes
only one of the reader and writer threads to exit, typically the
reader thread, because shutdown() will bail out very early. This
leaves a dangling (writer) thread causing memory leaks and deadlocks
on a subsequent connect()/login().
2019-03-28 17:58:25 +01:00
Alameyo
7f542e403f Adjust in documentation StanzaTypeFilter to uppercase as in code 2019-03-27 00:12:58 +01:00
Florian Schmaus
5d46e281fc XMPPTCPConnection log when reader/writer threads start and exit 2019-03-26 17:23:27 +01:00
Florian Schmaus
ab7d81e7b5 Use type parameter bounds for the 'to' set in OpenPgpgContentElement 2019-03-25 18:58:58 +01:00
Florian Schmaus
89c0fa4b99 Let StringUtils.(insecure)randomString() return empty string
in case length is zero. Also do throw a NegativeArraySizeException if
length is negative instead of returning null.

This fixes the following sporadic test issue:

org.jivesoftware.smackx.ox.PainlessOpenPgpProviderTest > encryptDecryptTest FAILED
java.lang.AssertionError
at org.jivesoftware.smack.util.XmlStringBuilder.escape(XmlStringBuilder.java:425)
at org.jivesoftware.smackx.ox.element.EncryptedOpenPgpContentElement.addCommonXml(EncryptedOpenPgpContentElement.java:65)
at org.jivesoftware.smackx.ox.element.CryptElement.toXML(CryptElement.java:51)
at org.jivesoftware.smackx.ox.element.CryptElement.toXML(CryptElement.java:31)
at org.jivesoftware.smack.packet.Element.toXML(Element.java:41)
at org.jivesoftware.smackx.ox.element.OpenPgpContentElement.toInputStream(OpenPgpContentElement.java:186)
at org.jivesoftware.smackx.ox.crypto.PainlessOpenPgpProvider.encrypt(PainlessOpenPgpProvider.java:136)
at org.jivesoftware.smackx.ox.PainlessOpenPgpProviderTest.encryptDecryptTest(PainlessOpenPgpProviderTest.java:155)

because EncryptedOpenPgpContentElement rpad field was sometimes
'null' in case the random function returned '0' as length.
2019-03-25 18:58:58 +01:00
Florian Schmaus
14f288a763 Introduce RandomUtil
and use it in EncryptedOpenPgpContentElement
2019-03-25 18:58:58 +01:00
Florian Schmaus
927eb5e7d7 Add MemoryLeakTest(Util)
to check for the correct operation of what was implemented with
SMACK-383.
2019-03-25 18:58:58 +01:00
77 changed files with 1560 additions and 2064 deletions

View file

@ -6,6 +6,11 @@
<module name="SuppressionFilter">
<property name="file" value="config/suppressions.xml"/>
</module>
<module name="SuppressWithPlainTextCommentFilter">
<property name="offCommentFormat" value="CHECKSTYLE\:OFF\:(\w+)"/>
<property name="onCommentFormat" value="CHECKSTYLE\:ON\:(\w+)"/>
<property name="checkFormat" value="$1"/>
</module>
<module name="Header">
<property name="headerFile" value="config/${checkstyleLicenseHeader}.txt"/>
<property name="ignoreLines" value="3"/>
@ -61,6 +66,14 @@
<property name="format" value="^\s*//[^\s]"/>
<property name="message" value="Comment start ('//') followed by non-space character. You would not continue after a punctuation without a space, would you?"/>
</module>
<!-- Check for synchronized keyword on Manager's static
getInstanceFor() method. Note that if XMPPConnection is every
replaced with something else, then we need to change it here
too. -->
<module name="RegexpSingleline">
<property name="format" value="^\s*public(?!.*synchronized).*getInstanceFor\(XMPPConnection.*$"/>
<property name="message" value="getInstanceFor() should be synchronized"/>
</module>
<module name="JavadocPackage"/>
<module name="TreeWalker">
<module name="SuppressionCommentFilter"/>

View file

@ -103,6 +103,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
| [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-0384](https://xmpp.org/extensions/xep-0384.html) | n/a | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). |
| [Consistent Color Generation](consistent_colors.md) | [XEP-0392](https://xmpp.org/extensions/xep-0392.html) | 0.4.0 | Generate consistent colors for identifiers like usernames to provide a consistent user experience. |
| [Message Markup](messagemarkup.md) | [XEP-0394](https://xmpp.org/extensions/xep-0394.html) | 0.1.0 | Style message bodies while keeping body and markup information separated. |
| DNS Queries over XMPP (DoX) | [XEP-0418](https://xmpp.org/extensions/xep-0418.html) | 0.1.0 | Send DNS queries and responses over XMPP. |
Unofficial XMPP Extensions
--------------------------

View file

@ -20,7 +20,7 @@ and a stanza listener:
```
// Create a stanza filter to listen for new messages from a particular
// user. We use an AndFilter to combine two other filters._
StanzaFilter filter = new AndFilter(StanzaTypeFilter.Message, FromMatchesFilter.create("mary@jivesoftware.com"));
StanzaFilter filter = new AndFilter(StanzaTypeFilter.MESSAGE, FromMatchesFilter.create("mary@jivesoftware.com"));
// Assume we've created an XMPPConnection named "connection".
// First, register a stanza collector using the filter we created.

View file

@ -12,6 +12,7 @@ include 'smack-core',
'smack-debug-slf4j',
'smack-resolver-dnsjava',
'smack-resolver-minidns',
'smack-resolver-minidns-dox',
'smack-resolver-javax',
'smack-sasl-javax',
'smack-sasl-provided',

View file

@ -1,6 +1,6 @@
/**
*
* Copyright © 2014 Florian Schmaus
* Copyright © 2014-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -42,18 +42,17 @@ public final class AndroidBase64Encoder implements org.jivesoftware.smack.util.s
}
@Override
public byte[] decode(byte[] input, int offset, int len) {
return Base64.decode(input, offset, len, 0);
public String encodeToString(byte[] input) {
return Base64.encodeToString(input, BASE64_ENCODER_FLAGS);
}
@Override
public String encodeToString(byte[] input, int offset, int len) {
return Base64.encodeToString(input, offset, len, BASE64_ENCODER_FLAGS);
public String encodeToStringWithoutPadding(byte[] input) {
return Base64.encodeToString(input, BASE64_ENCODER_FLAGS | Base64.NO_PADDING);
}
@Override
public byte[] encode(byte[] input, int offset, int len) {
return Base64.encode(input, offset, len, BASE64_ENCODER_FLAGS);
public byte[] encode(byte[] input) {
return Base64.encode(input, BASE64_ENCODER_FLAGS);
}
}

View file

@ -20,7 +20,6 @@ dependencies {
testCompile "org.powermock:powermock-module-junit4-rule:$powerMockVersion"
testCompile "org.powermock:powermock-api-mockito2:$powerMockVersion"
testCompile 'com.jamesmurty.utils:java-xmlbuilder:1.2'
testCompile 'net.iharder:base64:2.3.8'
}
class CreateFileTask extends DefaultTask {

View file

@ -41,6 +41,7 @@ import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;
@ -343,6 +344,17 @@ 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
*/
@ -1336,7 +1348,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
executorService = ASYNC_BUT_ORDERED.asExecutorFor(this);
break;
case async:
executorService = CACHED_EXECUTOR_SERVICE;
executorService = limitedExcutor;
break;
}
final IQRequestHandler finalIqRequestHandler = iqRequestHandler;
@ -1353,7 +1365,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return;
}
assert (response.getType() == IQ.Type.result || response.getType() == IQ.Type.error);
assert response.isResponseIQ();
response.setTo(iqRequest.getFrom());
response.setStanzaId(iqRequest.getStanzaId());
@ -1379,7 +1391,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
final Collection<StanzaListener> listenersToNotify = new LinkedList<>();
extractMatchingListeners(packet, asyncRecvListeners, listenersToNotify);
for (final StanzaListener listener : listenersToNotify) {
asyncGo(new Runnable() {
asyncGoLimited(new Runnable() {
@Override
public void run() {
try {
@ -1875,6 +1887,75 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return getClass().getSimpleName() + '[' + localEndpointString + "] (" + getConnectionCounter() + ')';
}
/**
* A queue of deferred runnables that where not executed immediately because {@link #currentAsyncRunnables} reached
* {@link #maxAsyncRunnables}. Note that we use a {@code LinkedList} in order to avoid space blowups in case the
* list ever becomes very big and shrinks again.
*/
private final Queue<Runnable> deferredAsyncRunnables = new LinkedList<>();
private int deferredAsyncRunnablesCount;
private int deferredAsyncRunnablesCountPrevious;
private int maxAsyncRunnables = SmackConfiguration.getDefaultConcurrencyLevelLimit();
private int currentAsyncRunnables;
protected void asyncGoLimited(final Runnable runnable) {
Runnable wrappedRunnable = new Runnable() {
@Override
public void run() {
runnable.run();
synchronized (deferredAsyncRunnables) {
Runnable defferredRunnable = deferredAsyncRunnables.poll();
if (defferredRunnable == null) {
currentAsyncRunnables--;
} else {
deferredAsyncRunnablesCount--;
asyncGo(defferredRunnable);
}
}
}
};
synchronized (deferredAsyncRunnables) {
if (currentAsyncRunnables < maxAsyncRunnables) {
currentAsyncRunnables++;
asyncGo(wrappedRunnable);
} else {
deferredAsyncRunnablesCount++;
deferredAsyncRunnables.add(wrappedRunnable);
}
final int HIGH_WATERMARK = 100;
final int INFORM_WATERMARK = 20;
final int deferredAsyncRunnablesCount = this.deferredAsyncRunnablesCount;
if (deferredAsyncRunnablesCount >= HIGH_WATERMARK
&& deferredAsyncRunnablesCountPrevious < HIGH_WATERMARK) {
LOGGER.log(Level.WARNING, "High watermark of " + HIGH_WATERMARK + " simultaneous executing runnables reached");
} else if (deferredAsyncRunnablesCount >= INFORM_WATERMARK
&& deferredAsyncRunnablesCountPrevious < INFORM_WATERMARK) {
LOGGER.log(Level.INFO, INFORM_WATERMARK + " simultaneous executing runnables reached");
}
deferredAsyncRunnablesCountPrevious = deferredAsyncRunnablesCount;
}
}
public void setMaxAsyncOperations(int maxAsyncOperations) {
if (maxAsyncOperations < 1) {
throw new IllegalArgumentException("Max async operations must be greater than 0");
}
synchronized (deferredAsyncRunnables) {
maxAsyncRunnables = maxAsyncOperations;
}
}
protected static void asyncGo(Runnable runnable) {
CACHED_EXECUTOR_SERVICE.execute(runnable);
}

View file

@ -55,6 +55,16 @@ public class AsyncButOrdered<K> {
private final Map<K, Boolean> threadActiveMap = new WeakHashMap<>();
private final Executor executor;
public AsyncButOrdered() {
this(null);
}
public AsyncButOrdered(Executor executor) {
this.executor = executor;
}
/**
* Invoke the given {@link Runnable} asynchronous but ordered in respect to the given key.
*
@ -86,7 +96,11 @@ public class AsyncButOrdered<K> {
if (newHandler) {
Handler handler = new Handler(keyQueue, key);
threadActiveMap.put(key, true);
AbstractXMPPConnection.asyncGo(handler);
if (executor == null) {
AbstractXMPPConnection.asyncGo(handler);
} else {
executor.execute(handler);
}
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2017-2018 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2017-2019 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -49,9 +49,29 @@ import org.minidns.dnsname.DnsName;
import org.minidns.util.InetAddressUtil;
/**
* Configuration to use while establishing the connection to the server.
* The connection configuration used for XMPP client-to-server connections. A well configured XMPP service will
* typically only require you to provide two parameters: The XMPP address, also known as the JID, of the user and the
* password. All other configuration parameters could ideally be determined automatically by Smack. Hence it is often
* enough to call {@link Builder#setXmppAddressAndPassword(CharSequence, String)}.
* <p>
* Technically there are typically at least two parameters required: Some kind of credentials for authentication. And
* the XMPP service domain. The credentials often consists of a username and password use for the SASL authentication.
* But there are also other authentication mechanisms, like client side certificates, which do not require a particular
* username and password.
* </p>
* <p>
* There are some misconceptions about XMPP client-to-server parameters: The first is that the username used for
* authentication will be equal to the localpart of the bound XMPP address after authentication. While this is usually
* true, it is not required. Technically the username used for authentication and the resulting XMPP address are
* completely independent from each other. The second common misconception steers from the terms "XMPP host" and "XMPP
* service domain": An XMPP service host is a system which hosts one or multiple XMPP domains. The "XMPP service domain"
* will be usually the domainpart of the bound JID. This domain is used to verify the remote endpoint, typically using
* TLS. This third misconception is that the XMPP service domain is required to become the domainpart of the bound JID.
* Again, while this is very common to be true, it is not strictly required.
* </p>
*
* @author Gaston Dombiak
* @author Florian Schmaus
*/
public abstract class ConnectionConfiguration {
@ -534,6 +554,39 @@ public abstract class ConnectionConfiguration {
}
}
/**
* Convenience method to configure the username, password and XMPP service domain.
*
* @param jid the XMPP address of the user.
* @param password the password of the user.
* @return a reference to this builder.
* @throws XmppStringprepException in case the XMPP address is not valid.
* @see #setXmppAddressAndPassword(EntityBareJid, String)
* @since 4.4.0
*/
public B setXmppAddressAndPassword(CharSequence jid, String password) throws XmppStringprepException {
return setXmppAddressAndPassword(JidCreate.entityBareFrom(jid), password);
}
/**
* Convenience method to configure the username, password and XMPP service domain. The localpart of the provided
* JID is used as username and the domanipart is used as XMPP service domain.
* <p>
* Please note that this does and can not configure the client XMPP address. XMPP services are not required to
* assign bound JIDs where the localpart matches the username and the domainpart matches the verified domainpart.
* Although most services will follow that pattern.
* </p>
*
* @param jid
* @param password
* @return a reference to this builder.
* @since 4.4.0
*/
public B setXmppAddressAndPassword(EntityBareJid jid, String password) {
setUsernameAndPassword(jid.getLocalpart(), password);
return setXmppDomain(jid.asDomainBareJid());
}
/**
* Set the XMPP entities username and password.
* <p>

View file

@ -365,4 +365,19 @@ public final class SmackConfiguration {
public static void setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode unknownIqRequestReplyMode) {
SmackConfiguration.unknownIqRequestReplyMode = Objects.requireNonNull(unknownIqRequestReplyMode, "Must set mode");
}
private static final int defaultConcurrencyLevelLimit;
static {
int availableProcessors = Runtime.getRuntime().availableProcessors();
if (availableProcessors < 8) {
defaultConcurrencyLevelLimit = 8;
} else {
defaultConcurrencyLevelLimit = (int) (availableProcessors * 1.1);
}
}
public static int getDefaultConcurrencyLevelLimit() {
return defaultConcurrencyLevelLimit;
}
}

View file

@ -70,6 +70,27 @@ import org.jxmpp.jid.EntityFullJid;
* disconnected and then connected again. Listeners of the XMPPConnection will be retained across
* connections.
* </p>
* <h2>Incoming Stanza Listeners</h2>
* Most callbacks (listeners, handlers, ) than you can add to a connection come in three different variants:
* <ul>
* <li>standard</li>
* <li>async (asynchronous)</li>
* <li>sync (synchronous)</li>
* </ul>
* <p>
* Standard callbacks are invoked concurrently, but it is ensured that the same callback is never run concurrently.
* The callback's identity is used as key for that. The events delivered to the callback preserve the order of the
* causing events of the connection.
* </p>
* <p>
* Asynchronous callbacks are run decoupled from the connections main event loop. Hence a callback triggered by
* stanza B may (appear to) invoked before a callback triggered by stanza A, even though stanza A arrived before B.
* </p>
* <p>
* Synchronous callbacks are run synchronous to the main event loop of a connection. Hence they are invoked in the
* exact order of how events happen there, most importantly the arrival order of incoming stanzas. You should only
* use synchronous callbacks in rare situations.
* </p>
*
* @author Matt Tucker
* @author Guenther Niess

View file

@ -16,12 +16,11 @@
*/
package org.jivesoftware.smack.debugger;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.util.ExceptionUtil;
/**
* Very simple debugger that prints to the console (stdout) the sent and received stanzas. Use
@ -55,12 +54,8 @@ public class ConsoleDebugger extends AbstractDebugger {
@Override
protected void log(String logMessage, Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
// CHECKSTYLE:OFF
throwable.printStackTrace(pw);
// CHECKSTYLE:ON
log(logMessage + sw);
String stacktrace = ExceptionUtil.getStackTrace(throwable);
log(logMessage + '\n' + stacktrace);
}
public static final class Factory implements SmackDebuggerFactory {

View file

@ -23,6 +23,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.jivesoftware.smack.util.ExceptionUtil;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketUtil;
import org.jivesoftware.smack.util.XmlStringBuilder;
@ -150,6 +151,17 @@ public class AbstractError {
return getThis();
}
public B setDescriptiveEnText(String descriptiveEnText, Exception exception) {
StringBuilder sb = new StringBuilder(512);
sb.append(descriptiveEnText)
.append('\n');
String stacktrace = ExceptionUtil.getStackTrace(exception);
sb.append(stacktrace);
return setDescriptiveEnText(sb.toString());
}
public B setTextNamespace(String textNamespace) {
this.textNamespace = textNamespace;
return getThis();

View file

@ -105,6 +105,16 @@ public abstract class IQ extends Stanza {
}
}
/**
* Return true if this IQ is a request, i.e. an IQ of type {@link Type#result} or {@link Type#error}.
*
* @return true if IQ type is 'result' or 'error', false otherwise.
* @since 4.4
*/
public boolean isResponseIQ() {
return !isRequestIQ();
}
public final String getChildElementName() {
return childElementName;
}
@ -287,7 +297,7 @@ public abstract class IQ extends Stanza {
* @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ.
*/
public static ErrorIQ createErrorResponse(final IQ request, final StanzaError.Builder error) {
if (!(request.getType() == Type.get || request.getType() == Type.set)) {
if (!request.isRequestIQ()) {
throw new IllegalArgumentException(
"IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML());
}

View file

@ -0,0 +1,38 @@
/**
*
* Copyright 2019 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util;
import java.io.PrintWriter;
import java.io.StringWriter;
public class ExceptionUtil {
public static String getStackTrace(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
// CHECKSTYLE:OFF
throwable.printStackTrace(printWriter);
// CHECKSTYLE:ON
printWriter.flush();
StringBuffer stringBuffer = stringWriter.getBuffer();
return stringBuffer.toString();
}
}

View file

@ -0,0 +1,50 @@
/**
*
* Copyright 2003-2007 Jive Software, 2016-2019 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util;
import java.security.SecureRandom;
import java.util.Random;
public class RandomUtil {
static final ThreadLocal<SecureRandom> SECURE_RANDOM = new ThreadLocal<SecureRandom>() {
@Override
protected SecureRandom initialValue() {
return new SecureRandom();
}
};
/**
* Pseudo-random number generator object for use with randomString().
* The Random class is not considered to be cryptographically secure, so
* only use these random Strings for low to medium security applications.
*/
static final ThreadLocal<Random> RANDOM = new ThreadLocal<Random>() {
@Override
protected Random initialValue() {
return new Random();
}
};
public static int nextSecureRandomInt(int bound) {
return SECURE_RANDOM.get().nextInt(bound);
}
public static int nextSecureRandomInt() {
return SECURE_RANDOM.get().nextInt();
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2016-2018 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2016-2019 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,7 +18,6 @@
package org.jivesoftware.smack.util;
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;
@ -253,18 +252,6 @@ public class StringUtils {
}
}
/**
* Pseudo-random number generator object for use with randomString().
* The Random class is not considered to be cryptographically secure, so
* only use these random Strings for low to medium security applications.
*/
private static final ThreadLocal<Random> randGen = new ThreadLocal<Random>() {
@Override
protected Random initialValue() {
return new Random();
}
};
/**
* Array of numbers and letters of mixed case. Numbers appear in the list
* twice so that there is a more equal chance that a number will be picked.
@ -288,23 +275,16 @@ public class StringUtils {
* @return a random String of numbers and letters of the specified length.
*/
public static String insecureRandomString(int length) {
return randomString(length, randGen.get());
return randomString(length, RandomUtil.RANDOM.get());
}
private static final ThreadLocal<SecureRandom> SECURE_RANDOM = new ThreadLocal<SecureRandom>() {
@Override
protected SecureRandom initialValue() {
return new SecureRandom();
}
};
public static String randomString(final int length) {
return randomString(length, SECURE_RANDOM.get());
return randomString(length, RandomUtil.SECURE_RANDOM.get());
}
public static String randomString(final int length, Random random) {
if (length < 1) {
return null;
if (length == 0) {
return "";
}
byte[] randomBytes = new byte[length];

View file

@ -1,6 +1,6 @@
/**
*
* Copyright © 2014-2015 Florian Schmaus
* Copyright © 2014-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -39,29 +39,19 @@ public class Base64 {
}
public static final String encodeToString(byte[] input) {
byte[] bytes = encode(input);
try {
return new String(bytes, StringUtils.USASCII);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
return base64encoder.encodeToString(input);
}
public static final String encodeToString(byte[] input, int offset, int len) {
byte[] bytes = encode(input, offset, len);
try {
return new String(bytes, StringUtils.USASCII);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
return encodeToString(slice(input, offset, len));
}
public static final String encodeToStringWithoutPadding(byte[] input) {
return base64encoder.encodeToStringWithoutPadding(input);
}
public static final byte[] encode(byte[] input) {
return encode(input, 0, input.length);
}
public static final byte[] encode(byte[] input, int offset, int len) {
return base64encoder.encode(input, offset, len);
return base64encoder.encode(input);
}
public static final String decodeToString(String string) {
@ -73,34 +63,43 @@ public class Base64 {
}
}
public static final String decodeToString(byte[] input, int offset, int len) {
byte[] bytes = decode(input, offset, len);
// TODO: We really should not mask the IllegalArgumentException. But some unit test depend on this behavior, like
// ibb.packet.DataPacketExtension.shouldReturnNullIfDataIsInvalid().
public static final byte[] decode(String string) {
try {
return new String(bytes, StringUtils.UTF8);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 not supported", e);
return base64encoder.decode(string);
} catch (IllegalArgumentException e) {
return null;
}
}
public static final byte[] decode(String string) {
return base64encoder.decode(string);
}
public static final byte[] decode(byte[] input) {
return base64encoder.decode(input, 0, input.length);
String string;
try {
string = new String(input, StringUtils.USASCII);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
return decode(string);
}
public static final byte[] decode(byte[] input, int offset, int len) {
return base64encoder.decode(input, offset, len);
private static byte[] slice(byte[] input, int offset, int len) {
if (offset == 0 && len == input.length) {
return input;
}
byte[] res = new byte[len];
System.arraycopy(input, offset, res, 0, len);
return res;
}
public interface Encoder {
byte[] decode(String string);
byte[] decode(byte[] input, int offset, int len);
String encodeToString(byte[] input);
String encodeToString(byte[] input, int offset, int len);
String encodeToStringWithoutPadding(byte[] input);
byte[] encode(byte[] input, int offset, int len);
byte[] encode(byte[] input);
}
}

View file

@ -64,6 +64,11 @@ public class DummyConnection extends AbstractXMPPConnection {
this(getDummyConfigurationBuilder().build());
}
public DummyConnection(CharSequence username, String password, String serviceName) throws XmppStringprepException {
this(getDummyConfigurationBuilder().setUsernameAndPassword(username, password).setXmppDomain(
JidCreate.domainBareFrom(serviceName)).build());
}
private EntityFullJid getUserJid() {
try {
return JidCreate.entityFullFrom(config.getUsername()

View file

@ -1,6 +1,6 @@
/**
*
* Copyright © 2014 Florian Schmaus
* Copyright © 2014-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,14 +16,10 @@
*/
package org.jivesoftware.smack.test.util;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.stringencoder.Base64.Encoder;
import net.iharder.Base64;
/**
* The SmackTestSuite takes care of initializing Smack for the unit tests. For example the Base64
* encoder is configured.
@ -35,53 +31,24 @@ public class SmackTestSuite {
@Override
public byte[] decode(String string) {
try {
return Base64.decode(string);
}
catch (IllegalArgumentException e) {
// Expected by e.g. the unit test.
// " Base64-encoded string must have at least four characters, but length specified was 1",
// should not cause an exception, but instead null should be returned. Maybe
// this should be changed in a later Smack release, so that the actual exception
// is handled.
return null;
}
catch (IOException e) {
throw new IllegalStateException(e);
}
return Base64.getDecoder().decode(string);
}
@Override
public byte[] decode(byte[] input, int offset, int len) {
try {
return Base64.decode(input, offset, len, 0);
}
catch (IllegalArgumentException e) {
// Expected by e.g. the unit test.
// " Base64-encoded string must have at least four characters, but length specified was 1",
// should not cause an exception, but instead null should be returned. Maybe
// this should be changed in a later Smack release, so that the actual exception
// is handled.
return null;
} catch (IOException e) {
throw new IllegalStateException(e);
}
public String encodeToString(byte[] input) {
return Base64.getEncoder().encodeToString(input);
}
@Override
public String encodeToString(byte[] input, int offset, int len) {
return Base64.encodeBytes(input, offset, len);
public String encodeToStringWithoutPadding(byte[] input) {
return Base64.getEncoder().withoutPadding().encodeToString(input);
}
@Override
public byte[] encode(byte[] input, int offset, int len) {
String string = encodeToString(input, offset, len);
try {
return string.getBytes(StringUtils.USASCII);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
public byte[] encode(byte[] input) {
return Base64.getEncoder().encode(input);
}
});
}
}

View file

@ -0,0 +1,140 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Logger;
import org.jivesoftware.smack.DummyConnection;
import org.jivesoftware.smack.Manager;
import org.jxmpp.stringprep.XmppStringprepException;
/**
* Utility class to test for memory leaks caused by Smack.
* <p>
* Note that this test is based on the assumption that it is possible to trigger a full garbage collection run, which is
* not the case. See also this
* <a href="https://stackoverflow.com/questions/1481178/how-to-force-garbage-collection-in-java">stackoverflow
* question</a>. Hence the {@link #triggerGarbageCollection()} method defined in this class is not portable and depends
* on implementation depended Java Virtual Machine behavior.
* </p>
*
* @see <a href="https://issues.igniterealtime.org/browse/SMACK-383">SMACK-383 Jira Issue</a>
*/
public class MemoryLeakTestUtil {
private static final Logger LOGGER = Logger.getLogger(MemoryLeakTestUtil.class.getName());
public static <M extends Manager> void noResourceLeakTest(Function<DummyConnection, M> managerSupplier)
throws XmppStringprepException, IllegalArgumentException, InterruptedException {
final int numConnections = 10;
ReferenceQueue<DummyConnection> connectionsReferenceQueue = new ReferenceQueue<>();
ReferenceQueue<Manager> managerReferenceQueue = new ReferenceQueue<>();
// Those two sets ensure that we hold a strong reference to the created PhantomReferences until the end of the
// test.
@SuppressWarnings("ModifiedButNotUsed")
Set<PhantomReference<DummyConnection>> connectionsPhantomReferences = new HashSet<>();
@SuppressWarnings("ModifiedButNotUsed")
Set<PhantomReference<Manager>> managersPhantomReferences = new HashSet<>();
List<DummyConnection> connections = new ArrayList<>(numConnections);
for (int i = 0; i < numConnections; i++) {
DummyConnection connection = new DummyConnection("foo" + i, "bar", "baz");
PhantomReference<DummyConnection> connectionPhantomReference = new PhantomReference<>(connection, connectionsReferenceQueue);
connectionsPhantomReferences.add(connectionPhantomReference);
Manager manager = managerSupplier.apply(connection);
PhantomReference<Manager> managerPhantomReference = new PhantomReference<Manager>(manager, managerReferenceQueue);
managersPhantomReferences.add(managerPhantomReference);
connections.add(connection);
}
// Clear the only references to the created connections.
connections = null;
triggerGarbageCollection();
// Now the connections should have been gc'ed, but not managers not yet.
assertReferencesQueueSize(connectionsReferenceQueue, numConnections);
assertReferencesQueueIsEmpty(managerReferenceQueue);
// We new create another connection and explicitly a new Manager. This will trigger the cleanup mechanism in the
// WeakHashMaps used by the Manager's iNSTANCE field. This should clean up all references to the Managers.
DummyConnection connection = new DummyConnection("last", "bar", "baz");
@SuppressWarnings("unused")
Manager manager = managerSupplier.apply(connection);
// The previous Managers should now be reclaimable by the garbage collector. First trigger a GC run.
triggerGarbageCollection();
// Now the Managers should have been freed and this means we should see their phantom references in the
// reference queue.
assertReferencesQueueSize(managerReferenceQueue, numConnections);
}
private static void assertReferencesQueueSize(ReferenceQueue<?> referenceQueue, int expectedSize) throws IllegalArgumentException, InterruptedException {
final int timeout = 60000;
for (int itemsRemoved = 0; itemsRemoved < expectedSize; ++itemsRemoved) {
Reference<?> reference = referenceQueue.remove(timeout);
assertNotNull("No reference found after " + timeout + "ms", reference);
reference.clear();
}
Reference<?> reference = referenceQueue.poll();
assertNull("Reference queue is not empty when it should be", reference);
}
private static void assertReferencesQueueIsEmpty(ReferenceQueue<?> referenceQueue) {
Reference<?> reference = referenceQueue.poll();
assertNull(reference);
}
private static void triggerGarbageCollection() {
Object object = new Object();
WeakReference<Object> weakReference = new WeakReference<>(object);
object = null;
int gcCalls = 0;
do {
if (gcCalls > 1000) {
throw new AssertionError("No observed gargabe collection after " + gcCalls + " calls of System.gc()");
}
System.gc();
gcCalls++;
} while (weakReference.get() != null);
// Note that this is no guarantee that a *full* garbage collection run has been made, which is what we actually
// need here in order to prevent false negatives.
LOGGER.finer("Observed garbage collection after " + gcCalls + " calls of System.gc()");
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software.
* Copyright 2003-2007 Jive Software, 2019 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -88,13 +88,7 @@ public class StringUtilsTest {
@Test
public void testRandomString() {
// Boundary test
String result = StringUtils.randomString(-1);
assertNull(result);
// Zero length string test
result = StringUtils.randomString(0);
assertNull(result);
String result;
// Test various lengths - make sure the same length is returned
result = StringUtils.randomString(4);
@ -104,4 +98,17 @@ public class StringUtilsTest {
result = StringUtils.randomString(128);
assertTrue(result.length() == 128);
}
@Test(expected = NegativeArraySizeException.class)
public void testNegativeArraySizeException() {
// Boundary test
StringUtils.randomString(-1);
}
@Test
public void testZeroLengthRandomString() {
// Zero length string test
String result = StringUtils.randomString(0);
assertEquals("", result);
}
}

View file

@ -11,5 +11,5 @@ dependencies {
testCompile project(path: ":smack-core", configuration: "archives")
testCompile project(path: ":smack-extensions", configuration: "testRuntime")
compile "org.bouncycastle:bcprov-jdk15on:1.57"
compile "org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion"
}

View file

@ -0,0 +1,169 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.dox;
import java.io.IOException;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import org.jivesoftware.smack.Manager;
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.iqrequest.AbstractIqRequestHandler;
import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.packet.StanzaError.Condition;
import org.jivesoftware.smack.packet.StanzaError.Type;
import org.jivesoftware.smack.util.RandomUtil;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.dox.element.DnsIq;
import org.jxmpp.jid.Jid;
import org.minidns.dnsmessage.DnsMessage;
import org.minidns.dnsmessage.Question;
public final class DnsOverXmppManager extends Manager {
private static final Logger LOGGER = Logger.getLogger(DnsOverXmppManager.class.getName());
private static final Map<XMPPConnection, DnsOverXmppManager> INSTANCES = new WeakHashMap<>();
public static synchronized DnsOverXmppManager getInstanceFor(XMPPConnection connection) {
DnsOverXmppManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new DnsOverXmppManager(connection);
INSTANCES.put(connection, manager);
}
return manager;
}
private static final String NAMESPACE = DnsIq.NAMESPACE;
private static DnsOverXmppResolver defaultResolver;
public void setDefaultDnsOverXmppResolver(DnsOverXmppResolver resolver) {
defaultResolver = resolver;
}
private final ServiceDiscoveryManager serviceDiscoveryManager;
private DnsOverXmppResolver resolver = defaultResolver;
private boolean enabled;
private final AbstractIqRequestHandler dnsIqRequestHandler = new AbstractIqRequestHandler(
DnsIq.ELEMENT, DnsIq.NAMESPACE, IQ.Type.get, Mode.async) {
@Override
public IQ handleIQRequest(IQ iqRequest) {
DnsOverXmppResolver resolver = DnsOverXmppManager.this.resolver;
if (resolver == null) {
LOGGER.info("Resolver was null while attempting to handle " + iqRequest);
return null;
}
DnsIq dnsIqRequest = (DnsIq) iqRequest;
DnsMessage query = dnsIqRequest.getDnsMessage();
DnsMessage response;
try {
response = resolver.resolve(query);
} catch (IOException exception) {
StanzaError.Builder errorBuilder = StanzaError.getBuilder()
.setType(Type.CANCEL)
.setCondition(Condition.internal_server_error)
.setDescriptiveEnText("Exception while resolving your DNS query", exception)
;
IQ errorResponse = IQ.createErrorResponse(iqRequest, errorBuilder);
return errorResponse;
}
if (query.id != response.id) {
// The ID may not match because the resolver returned a cached result.
response = response.asBuilder().setId(query.id).build();
}
DnsIq dnsIqResult = new DnsIq(response);
dnsIqResult.setType(IQ.Type.result);
return dnsIqResult;
}
};
private DnsOverXmppManager(XMPPConnection connection) {
super(connection);
this.serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
}
public synchronized void setDnsOverXmppResolver(DnsOverXmppResolver resolver) {
this.resolver = resolver;
if (resolver == null) {
disable();
}
}
public synchronized void enable() {
if (enabled) return;
if (resolver == null) {
throw new IllegalStateException("No DnsOverXmppResolver configured");
}
XMPPConnection connection = connection();
if (connection == null) return;
connection.registerIQRequestHandler(dnsIqRequestHandler);
serviceDiscoveryManager.addFeature(NAMESPACE);
}
public synchronized void disable() {
if (!enabled) return;
XMPPConnection connection = connection();
if (connection == null) return;
serviceDiscoveryManager.removeFeature(NAMESPACE);
connection.unregisterIQRequestHandler(dnsIqRequestHandler);
}
public boolean isSupported(Jid jid)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return serviceDiscoveryManager.supportsFeature(jid, NAMESPACE);
}
public DnsMessage query(Jid jid, Question question) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
DnsMessage queryMessage = DnsMessage.builder()
.addQuestion(question)
.setId(RandomUtil.nextSecureRandomInt())
.setRecursionDesired(true)
.build();
return query(jid, queryMessage);
}
public DnsMessage query(Jid jid, DnsMessage query)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
DnsIq queryIq = new DnsIq(query, jid);
DnsIq responseIq = connection().sendIqRequestAndWaitForResponse(queryIq);
return responseIq.getDnsMessage();
}
}

View file

@ -0,0 +1,27 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.dox;
import java.io.IOException;
import org.minidns.dnsmessage.DnsMessage;
public interface DnsOverXmppResolver {
DnsMessage resolve(DnsMessage query) throws IOException;
}

View file

@ -0,0 +1,80 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.dox.element;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jxmpp.jid.Jid;
import org.minidns.dnsmessage.DnsMessage;
public class DnsIq extends IQ {
public static final String ELEMENT = "dns";
public static final String NAMESPACE = "urn:xmpp:dox:0";
private final DnsMessage dnsMessage;
private String base64DnsMessage;
public DnsIq(String base64DnsMessage) throws IOException {
this(Base64.decode(base64DnsMessage));
this.base64DnsMessage = base64DnsMessage;
}
public DnsIq(byte[] dnsMessage) throws IOException {
this(new DnsMessage(dnsMessage));
}
public DnsIq(DnsMessage dnsQuery, Jid to) {
this(dnsQuery);
setTo(to);
setType(Type.get);
}
public DnsIq(DnsMessage dnsMessage) {
super(ELEMENT, NAMESPACE);
this.dnsMessage = dnsMessage;
}
public DnsMessage getDnsMessage() {
return dnsMessage;
}
@SuppressWarnings("ByteBufferBackingArray")
public String getDnsMessageBase64Encoded() {
if (base64DnsMessage == null) {
ByteBuffer byteBuffer = dnsMessage.getInByteBuffer();
byte[] bytes = byteBuffer.array();
base64DnsMessage = Base64.encodeToStringWithoutPadding(bytes);
}
return base64DnsMessage;
}
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
xml.rightAngleBracket();
xml.escape(getDnsMessageBase64Encoded());
return xml;
}
}

View file

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

View file

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

View file

@ -0,0 +1,38 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.dox.provider;
import java.io.IOException;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smackx.dox.element.DnsIq;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
public class DnsIqProvider extends IQProvider<DnsIq> {
@Override
public DnsIq parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
throws XmlPullParserException, IOException, SmackParsingException {
String base64DnsMessage = parser.nextText();
return new DnsIq(base64DnsMessage);
}
}

View file

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

View file

@ -1,6 +1,6 @@
/**
*
* Copyright © 2017 Paul Schaub
* Copyright © 2017 Paul Schaub, 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -35,7 +35,6 @@ import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.SHA_512;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.util.Arrays;
import java.util.Collections;
@ -61,7 +60,6 @@ public final class HashManager extends Manager {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static final String PROVIDER = "BC";
public static final String PREFIX_NS_ALGO = "urn:xmpp:hash-function-text-names:";
@ -224,52 +222,52 @@ public final class HashManager extends Manager {
try {
switch (algorithm) {
case MD5:
md = MessageDigest.getInstance("MD5", PROVIDER);
md = MessageDigest.getInstance("MD5");
break;
case SHA_1:
md = MessageDigest.getInstance("SHA-1", PROVIDER);
md = MessageDigest.getInstance("SHA-1");
break;
case SHA_224:
md = MessageDigest.getInstance("SHA-224", PROVIDER);
md = MessageDigest.getInstance("SHA-224");
break;
case SHA_256:
md = MessageDigest.getInstance("SHA-256", PROVIDER);
md = MessageDigest.getInstance("SHA-256");
break;
case SHA_384:
md = MessageDigest.getInstance("SHA-384", PROVIDER);
md = MessageDigest.getInstance("SHA-384");
break;
case SHA_512:
md = MessageDigest.getInstance("SHA-512", PROVIDER);
md = MessageDigest.getInstance("SHA-512");
break;
case SHA3_224:
md = MessageDigest.getInstance("SHA3-224", PROVIDER);
md = MessageDigest.getInstance("SHA3-224");
break;
case SHA3_256:
md = MessageDigest.getInstance("SHA3-256", PROVIDER);
md = MessageDigest.getInstance("SHA3-256");
break;
case SHA3_384:
md = MessageDigest.getInstance("SHA3-384", PROVIDER);
md = MessageDigest.getInstance("SHA3-384");
break;
case SHA3_512:
md = MessageDigest.getInstance("SHA3-512", PROVIDER);
md = MessageDigest.getInstance("SHA3-512");
break;
case BLAKE2B160:
md = MessageDigest.getInstance("BLAKE2b-160", PROVIDER);
md = MessageDigest.getInstance("BLAKE2b-160");
break;
case BLAKE2B256:
md = MessageDigest.getInstance("BLAKE2b-256", PROVIDER);
md = MessageDigest.getInstance("BLAKE2b-256");
break;
case BLAKE2B384:
md = MessageDigest.getInstance("BLAKE2b-384", PROVIDER);
md = MessageDigest.getInstance("BLAKE2b-384");
break;
case BLAKE2B512:
md = MessageDigest.getInstance("BLAKE2b-512", PROVIDER);
md = MessageDigest.getInstance("BLAKE2b-512");
break;
default:
throw new AssertionError("Invalid enum value: " + algorithm);
}
return md;
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}

View file

@ -32,7 +32,7 @@ public final class JingleFileTransferManager extends Manager {
super(connection);
}
public static JingleFileTransferManager getInstanceFor(XMPPConnection connection) {
public static synchronized JingleFileTransferManager getInstanceFor(XMPPConnection connection) {
JingleFileTransferManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new JingleFileTransferManager(connection);

View file

@ -178,7 +178,9 @@ public final class MamManager extends Manager {
* @param connection the XMPP connection to get the archive for.
* @return the instance of MamManager.
*/
// CHECKSTYLE:OFF:RegexpSingleline
public static MamManager getInstanceFor(XMPPConnection connection) {
// CHECKSTYLE:ON:RegexpSingleline
return getInstanceFor(connection, (Jid) null);
}

View file

@ -51,7 +51,7 @@ public final class ReferenceManager extends Manager {
* @param connection xmpp connection
* @return reference manager instance
*/
public static ReferenceManager getInstanceFor(XMPPConnection connection) {
public static synchronized ReferenceManager getInstanceFor(XMPPConnection connection) {
ReferenceManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new ReferenceManager(connection);

View file

@ -78,7 +78,7 @@ public final class StableUniqueStanzaIdManager extends Manager {
* @param connection xmpp-connection
* @return manager instance for the connection
*/
public static StableUniqueStanzaIdManager getInstanceFor(XMPPConnection connection) {
public static synchronized StableUniqueStanzaIdManager getInstanceFor(XMPPConnection connection) {
StableUniqueStanzaIdManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new StableUniqueStanzaIdManager(connection);

View file

@ -61,7 +61,7 @@ public final class SpoilerManager extends Manager {
* @param connection xmpp connection
* @return SpoilerManager
*/
public static SpoilerManager getInstanceFor(XMPPConnection connection) {
public static synchronized SpoilerManager getInstanceFor(XMPPConnection connection) {
SpoilerManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new SpoilerManager(connection);

View file

@ -317,4 +317,11 @@
<className>org.jivesoftware.smackx.message_markup.provider.MarkupElementProvider</className>
</extensionProvider>
<!-- XEP-0418: DNS Queries over XMPP (DoX) -->
<iqProvider>
<elementName>dns</elementName>
<namespace>urn:xmpp:dox:0</namespace>
<className>org.jivesoftware.smackx.dox.provider.DnsIqProvider</className>
</iqProvider>
</smackProviders>

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2003-2007 Jive Software, 2018 Florian Schmaus.
* Copyright 2003-2007 Jive Software, 2018-2019 Florian Schmaus.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -227,7 +227,7 @@ public final class ServiceDiscoveryManager extends Manager {
/**
* Returns the type of client that will be returned when asked for the client identity in a
* disco request. The valid types are defined by the category client. Follow this link to learn
* the possible types: <a href="http://xmpp.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
* the possible types: <a href="https://xmpp.org/registrar/disco-categories.html">XMPP Registry for Service Discovery Identities</a>
*
* @return the type of client that will be returned when asked for the client identity in a
* disco request.
@ -271,8 +271,8 @@ public final class ServiceDiscoveryManager extends Manager {
*/
public Set<DiscoverInfo.Identity> getIdentities() {
Set<Identity> res = new HashSet<>(identities);
// Add the default identity that must exist
res.add(defaultIdentity);
// Add the main identity that must exist
res.add(identity);
return Collections.unmodifiableSet(res);
}

View file

@ -263,7 +263,7 @@ public class DiscoverInfo extends IQ implements TypedCloneable<DiscoverInfo> {
* Represents the identity of a given XMPP entity. An entity may have many identities but all
* the identities SHOULD have the same name.<p>
*
* Refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
* Refer to <a href="https://xmpp.org/registrar/disco-categories.html">XMPP Registry for Service Discovery Identities</a>
* in order to get the official registry of values for the <i>category</i> and <i>type</i>
* attributes.
*
@ -327,7 +327,7 @@ public class DiscoverInfo extends IQ implements TypedCloneable<DiscoverInfo> {
/**
* Returns the entity's category. To get the official registry of values for the
* 'category' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
* 'category' attribute refer to <a href="https://xmpp.org/registrar/disco-categories.html">XMPP Registry for Service Discovery Identities</a>.
*
* @return the entity's category.
*/
@ -346,7 +346,7 @@ public class DiscoverInfo extends IQ implements TypedCloneable<DiscoverInfo> {
/**
* Returns the entity's type. To get the official registry of values for the
* 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
* 'type' attribute refer to <a href="https://xmpp.org/registrar/disco-categories.html">XMPP Registry for Service Discovery Identities</a>.
*
* @return the entity's type.
*/

View file

@ -124,7 +124,7 @@ public final class GeoLocationManager extends Manager {
private LeafNode getNode()
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotALeafNodeException {
return PubSubManager.getInstance(connection()).getOrCreateLeafNode(GeoLocation.NAMESPACE);
return PubSubManager.getInstanceFor(connection()).getOrCreateLeafNode(GeoLocation.NAMESPACE);
}
}

View file

@ -48,7 +48,7 @@ public final class JingleTransportMethodManager extends Manager {
super(connection);
}
public static JingleTransportMethodManager getInstanceFor(XMPPConnection connection) {
public static synchronized JingleTransportMethodManager getInstanceFor(XMPPConnection connection) {
JingleTransportMethodManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new JingleTransportMethodManager(connection);

View file

@ -38,7 +38,7 @@ public final class JingleIBBTransportManager extends JingleTransportManager<Jing
JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleIBBTransportProvider());
}
public static JingleIBBTransportManager getInstanceFor(XMPPConnection connection) {
public static synchronized JingleIBBTransportManager getInstanceFor(XMPPConnection connection) {
JingleIBBTransportManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new JingleIBBTransportManager(connection);

View file

@ -63,7 +63,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager<Jing
JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleS5BTransportProvider());
}
public static JingleS5BTransportManager getInstanceFor(XMPPConnection connection) {
public static synchronized JingleS5BTransportManager getInstanceFor(XMPPConnection connection) {
JingleS5BTransportManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new JingleS5BTransportManager(connection);

View file

@ -102,7 +102,7 @@ public final class MoodManager extends Manager {
});
}
public static MoodManager getInstanceFor(XMPPConnection connection) {
public static synchronized MoodManager getInstanceFor(XMPPConnection connection) {
MoodManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new MoodManager(connection);
@ -147,7 +147,7 @@ public final class MoodManager extends Manager {
throws SmackException.NotLoggedInException, InterruptedException, PubSubException.NotALeafNodeException,
XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
if (pubSubManager == null) {
pubSubManager = PubSubManager.getInstance(getAuthenticatedConnectionOrThrow(), connection().getUser().asBareJid());
pubSubManager = PubSubManager.getInstanceFor(getAuthenticatedConnectionOrThrow(), connection().getUser().asBareJid());
}
LeafNode node = pubSubManager.getOrCreateLeafNode(MOOD_NODE);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright © 2014-2018 Florian Schmaus
* Copyright © 2014-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -59,6 +59,7 @@ import org.jivesoftware.smackx.muc.packet.MUCUser;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.EntityJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.parts.Resourcepart;
@ -304,7 +305,7 @@ public final class MultiUserChatManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
*/
public List<EntityBareJid> getJoinedRooms(EntityJid user) throws NoResponseException, XMPPErrorException,
public List<EntityBareJid> getJoinedRooms(EntityFullJid user) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException {
// Send the disco packet to the user
DiscoverItems result = serviceDiscoveryManager.discoverItems(user, DISCO_NODE);

View file

@ -120,7 +120,7 @@ public final class PepManager extends Manager {
// TODO Add filter to check if from supports PubSub as per xep163 2 2.4
connection.addSyncStanzaListener(packetListener, FROM_BARE_JID_WITH_EVENT_EXTENSION_FILTER);
pepPubSubManager = PubSubManager.getInstance(connection, null);
pepPubSubManager = PubSubManager.getInstanceFor(connection, null);
}
public PubSubManager getPepPubSubManager() {

View file

@ -88,7 +88,9 @@ public final class PubSubManager extends Manager {
* @param connection
* @return the default PubSub manager.
*/
public static PubSubManager getInstance(XMPPConnection connection) {
// CHECKSTYLE:OFF:RegexpSingleline
public static PubSubManager getInstanceFor(XMPPConnection connection) {
// CHECKSTYLE:ON:RegexpSingleline
DomainBareJid pubSubService = null;
if (connection.isAuthenticated()) {
try {
@ -110,7 +112,7 @@ public final class PubSubManager extends Manager {
throw new RuntimeException(e);
}
}
return getInstance(connection, pubSubService);
return getInstanceFor(connection, pubSubService);
}
/**
@ -121,7 +123,9 @@ public final class PubSubManager extends Manager {
* @param pubSubService the PubSub service, may be <code>null</code>.
* @return a PubSub manager for the connection and service.
*/
public static PubSubManager getInstance(XMPPConnection connection, BareJid pubSubService) {
// CHECKSTYLE:OFF:RegexpSingleline
public static PubSubManager getInstanceFor(XMPPConnection connection, BareJid pubSubService) {
// CHECKSTYLE:ON:RegexpSingleline
if (pubSubService != null && connection.isAuthenticated() && connection.getUser().asBareJid().equals(pubSubService)) {
// PEP service.
pubSubService = null;
@ -147,6 +151,28 @@ public final class PubSubManager extends Manager {
return pubSubManager;
}
/**
* Deprecated.
*
* @deprecated use {@link #getInstanceFor(XMPPConnection)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public static PubSubManager getInstance(XMPPConnection connection) {
return getInstanceFor(connection);
}
/**
* Deprecated.
*
* @deprecated use {@link #getInstanceFor(XMPPConnection, BareJid)} instead.
*/
@Deprecated
// TODO: Remove in Smack 4.5.
public static PubSubManager getInstance(XMPPConnection connection, BareJid pubSubService) {
return getInstanceFor(connection, pubSubService);
}
/**
* Create a pubsub manager associated to the specified connection where
* the pubsub requests require a specific to address for packets.

View file

@ -0,0 +1,32 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.muc;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smack.util.MemoryLeakTestUtil;
import org.junit.Test;
import org.jxmpp.stringprep.XmppStringprepException;
public class MucMemoryLeakTest extends SmackTestSuite {
@Test
public void mucMemoryLeakTest() throws XmppStringprepException, IllegalArgumentException, InterruptedException {
MemoryLeakTestUtil.noResourceLeakTest((c) -> MultiUserChatManager.getInstanceFor(c));
}
}

View file

@ -560,7 +560,8 @@ public final class Roster extends Manager {
}
/**
* Add a roster loaded listener.
* Add a roster loaded listener. Roster loaded listeners are invoked once the {@link Roster}
* was successfully loaded.
*
* @param rosterLoadedListener the listener to add.
* @return true if the listener was not already added.
@ -587,6 +588,20 @@ public final class Roster extends Manager {
}
}
/**
* Add a {@link PresenceEventListener}. Such a listener will be fired whenever certain
* presence events happen.<p>
* Among those events are:
* <ul>
* <li> 'available' presence received
* <li> 'unavailable' presence received
* <li> 'error' presence received
* <li> 'subscribed' presence received
* <li> 'unsubscribed' presence received
* </ul>
* @param presenceEventListener listener to add.
* @return true if the listener was not already added.
*/
public boolean addPresenceEventListener(PresenceEventListener presenceEventListener) {
return presenceEventListeners.add(presenceEventListener);
}
@ -1516,7 +1531,29 @@ public final class Roster extends Manager {
final Presence presence = (Presence) packet;
final Jid from = presence.getFrom();
final BareJid key = from != null ? from.asBareJid() : null;
final BareJid key;
if (from != null) {
key = from.asBareJid();
} else {
XMPPConnection connection = connection();
if (connection == null) {
LOGGER.finest("Connection was null while trying to handle exotic presence stanza: " + presence);
return;
}
// Assume the presence come "from the users account on the server" since no from was set (RFC 6120 §
// 8.1.2.1 4.). Note that getUser() may return null, but should never return null in this case as where
// connected.
EntityFullJid myJid = connection.getUser();
if (myJid == null) {
LOGGER.info(
"Connection had no local address in Roster's presence listener."
+ " Possibly we received a presence without from before being authenticated."
+ " Presence: " + presence);
return;
}
LOGGER.info("Exotic presence stanza without from received: " + presence);
key = myJid.asBareJid();
}
asyncButOrdered.performAsyncButOrdered(key, new Runnable() {
@Override

View file

@ -151,18 +151,37 @@ public class XmppConnectionStressTest {
// Sanity check: All markers before must be true, all markers including the messageNumber marker must be false.
for (int i = 0; i < fromMarkers.length; i++) {
if ((i < messageNumber && !fromMarkers[i])
|| (i >= messageNumber && fromMarkers[i])) {
// TODO: Better exception.
Exception exception = new Exception("out of order");
receiveExceptions.put(connection, exception);
// TODO: Current Smack design does not guarantee that the listener won't be invoked again.
// This is because the decission to invoke a sync listeners is done at a different place
// then invoking the listener.
connection.removeSyncStanzaListener(this);
receivedSemaphore.release();
return;
final String inOrderViolation;
if (i < messageNumber && !fromMarkers[i]) {
// A previous message was missing.
inOrderViolation = "not yet message #";
} else if (i >= messageNumber && fromMarkers[i]) {
// We already received a new message.
// TODO: Can it ever happen that this is taken? Wouldn't we prior run into the "a previous
// message is missing" case?
inOrderViolation = "we already received a later (or the same) message #";
} else {
continue;
}
StringBuilder exceptionMessage = new StringBuilder();
exceptionMessage.append("We received message #").append(messageNumber).append(" but ");
exceptionMessage.append(inOrderViolation);
exceptionMessage.append(i);
exceptionMessage.append("\nMessage with id ").append(stanza.getStanzaId())
.append(" from ").append(from)
.append(" to ").append(stanza.getTo());
Exception exception = new Exception(exceptionMessage.toString());
receiveExceptions.put(connection, exception);
// TODO: Current Smack design does not guarantee that the listener won't be invoked again.
// This is because the decission to invoke a sync listeners is done at a different place
// then invoking the listener.
connection.removeSyncStanzaListener(this);
receivedSemaphore.release();
// TODO: Do not return here?
return;
}
fromMarkers[messageNumber] = true;

View file

@ -240,6 +240,10 @@ public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
}
connections.clear();
if (accountRegistrationConnection != null) {
accountRegistrationConnection.disconnect();
}
}

View file

@ -125,7 +125,7 @@ public class OmemoManagerSetupHelper {
}
public static void cleanUpPubSub(OmemoManager omemoManager) {
PubSubManager pm = PubSubManager.getInstance(omemoManager.getConnection(),omemoManager.getOwnJid());
PubSubManager pm = PubSubManager.getInstanceFor(omemoManager.getConnection(),omemoManager.getOwnJid());
try {
omemoManager.requestDeviceListUpdateFor(omemoManager.getOwnJid());
} catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException e) {

View file

@ -17,12 +17,16 @@
package org.jivesoftware.smackx.pubsub;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.List;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.StanzaError;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
@ -42,33 +46,70 @@ public class PubSubIntegrationTest extends AbstractSmackIntegrationTest {
if (pubSubService == null) {
throw new TestNotPossibleException("No PubSub service found");
}
pubSubManagerOne = PubSubManager.getInstance(conOne, pubSubService);
pubSubManagerOne = PubSubManager.getInstanceFor(conOne, pubSubService);
if (!pubSubManagerOne.canCreateNodesAndPublishItems()) {
throw new TestNotPossibleException("PubSub service does not allow node creation");
}
}
/**
* Asserts that an event notification (publication without item) can be published to
* a node that is both 'notification-only' as well as 'transient'.
*/
@SmackIntegrationTest
public void simplePubSubNodeTest() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
final String nodename = "sinttest-simple-nodename-" + testRunId;
final String itemId = "sintest-simple-itemid-" + testRunId;
public void transientNotificationOnlyNodeWithoutItemTest() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
final String nodename = "sinttest-transient-notificationonly-withoutitem-nodename-" + testRunId;
ConfigureForm defaultConfiguration = pubSubManagerOne.getDefaultConfiguration();
ConfigureForm config = new ConfigureForm(defaultConfiguration.createAnswerForm());
// Configure the node as "Notification-Only Node", which in turn means that
// items do not need payload, to prevent payload-required error responses when
// publishing the item.
// Configure the node as "Notification-Only Node".
config.setDeliverPayloads(false);
// Set persistent_items to 'false' (was previously 'true') as workaround for ejabberd issue #2799
// (https://github.com/processone/ejabberd/issues/2799).
// Configure the node as "transient" (set persistent_items to 'false')
config.setPersistentItems(false);
Node node = pubSubManagerOne.createNode(nodename, config);
try {
LeafNode leafNode = (LeafNode) node;
leafNode.publish();
List<Item> items = leafNode.getItems();
assertTrue(items.isEmpty());
}
finally {
pubSubManagerOne.deleteNode(nodename);
}
}
/**
* Asserts that an error is returned when a publish request to a node that is both
* 'notification-only' as well as 'transient' contains an item element.
*
* <p>From XEP-0060 § 7.1.3.6:</p>
* <blockquote>
* If the event type is notification + transient and the publisher provides an item,
* the service MUST bounce the publication request with a &lt;bad-request/&gt; error
* and a pubsub-specific error condition of &lt;item-forbidden/&gt;.
* </blockquote>
*
* @see <a href="https://xmpp.org/extensions/xep-0060.html#publisher-publish-error-badrequest">
* 7.1.3.6 Request Does Not Match Configuration</a>
*/
@SmackIntegrationTest
public void transientNotificationOnlyNodeWithItemTest() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
final String nodename = "sinttest-transient-notificationonly-withitem-nodename-" + testRunId;
final String itemId = "sinttest-transient-notificationonly-withitem-itemid-" + testRunId;
ConfigureForm defaultConfiguration = pubSubManagerOne.getDefaultConfiguration();
ConfigureForm config = new ConfigureForm(defaultConfiguration.createAnswerForm());
// Configure the node as "Notification-Only Node".
config.setDeliverPayloads(false);
// Configure the node as "transient" (set persistent_items to 'false')
config.setPersistentItems(false);
Node node = pubSubManagerOne.createNode(nodename, config);
try {
LeafNode leafNode = (LeafNode) node;
leafNode.publish(new Item(itemId));
List<Item> items = leafNode.getItems();
assertEquals(1, items.size());
Item item = items.get(0);
assertEquals(itemId, item.getId());
fail("An exception should have been thrown.");
}
catch (XMPPErrorException e) {
assertEquals(StanzaError.Type.MODIFY, e.getStanzaError().getType());
assertNotNull(e.getStanzaError().getExtension("item-forbidden", "http://jabber.org/protocol/pubsub#errors"));
}
finally {
pubSubManagerOne.deleteNode(nodename);

View file

@ -16,9 +16,7 @@
*/
package org.jivesoftware.smack.util.stringencoder.java7;
import java.io.UnsupportedEncodingException;
import org.jivesoftware.smack.util.StringUtils;
import java.util.Base64;
/**
* A Base 64 encoding implementation.
@ -28,10 +26,15 @@ public final class Java7Base64Encoder implements org.jivesoftware.smack.util.str
private static final Java7Base64Encoder instance = new Java7Base64Encoder();
private static final int BASE64_ENCODER_FLAGS = Base64.DONT_BREAK_LINES;
private final Base64.Encoder encoder;
private final Base64.Encoder encoderWithoutPadding;
private final Base64.Decoder decoder;
private Java7Base64Encoder() {
// Use getInstance()
encoder = Base64.getEncoder();
encoderWithoutPadding = encoder.withoutPadding();
decoder = Base64.getDecoder();
}
public static Java7Base64Encoder getInstance() {
@ -40,27 +43,21 @@ public final class Java7Base64Encoder implements org.jivesoftware.smack.util.str
@Override
public byte[] decode(String string) {
return Base64.decode(string);
return decoder.decode(string);
}
@Override
public byte[] decode(byte[] input, int offset, int len) {
return Base64.decode(input, offset, len, 0);
public String encodeToString(byte[] input) {
return encoder.encodeToString(input);
}
@Override
public String encodeToString(byte[] input, int offset, int len) {
return Base64.encodeBytes(input, offset, len, BASE64_ENCODER_FLAGS);
public String encodeToStringWithoutPadding(byte[] input) {
return encoderWithoutPadding.encodeToString(input);
}
@Override
public byte[] encode(byte[] input, int offset, int len) {
String string = encodeToString(input, offset, len);
try {
return string.getBytes(StringUtils.USASCII);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
public byte[] encode(byte[] input) {
return encoder.encode(input);
}
}

View file

@ -17,6 +17,7 @@
package org.jivesoftware.smack.util.stringencoder.java7;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.stringencoder.StringEncoder;
@ -37,10 +38,12 @@ public final class Java7Base64UrlSafeEncoder implements StringEncoder<String> {
private static final Java7Base64UrlSafeEncoder instance = new Java7Base64UrlSafeEncoder();
private static final int BASE64_ENCODER_FLAGS = Base64.URL_SAFE | Base64.DONT_BREAK_LINES;
private final Base64.Encoder encoder;
private final Base64.Decoder decoder;
private Java7Base64UrlSafeEncoder() {
// Use getInstance()
encoder = Base64.getUrlEncoder();
decoder = Base64.getUrlDecoder();
}
public static Java7Base64UrlSafeEncoder getInstance() {
@ -56,13 +59,14 @@ public final class Java7Base64UrlSafeEncoder implements StringEncoder<String> {
catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
return Base64.encodeBytes(bytes, BASE64_ENCODER_FLAGS);
return encoder.encodeToString(bytes);
}
@Override
public String decode(String s) {
byte[] bytes = decoder.decode(s);
try {
return new String(Base64.decode(s, BASE64_ENCODER_FLAGS), StringUtils.UTF8);
return new String(bytes, StringUtils.UTF8);
}
catch (UnsupportedEncodingException e) {
throw new AssertionError(e);

View file

@ -620,7 +620,7 @@ public final class JingleSession extends JingleNegotiator implements MediaReceiv
* A XMPP connection
* @return a Jingle session
*/
public static JingleSession getInstanceFor(XMPPConnection con) {
public static synchronized JingleSession getInstanceFor(XMPPConnection con) {
if (con == null) {
throw new IllegalArgumentException("XMPPConnection cannot be null");
}

View file

@ -3,7 +3,7 @@ dependencies {
compile project(":smack-extensions")
compile project(":smack-experimental")
compile "org.bouncycastle:bcprov-jdk15on:1.60"
compile "org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion"
testCompile project(path: ":smack-core", configuration: "testRuntime")
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Paul Schaub
* Copyright 2017 Paul Schaub, 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -23,7 +23,6 @@ import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.util.ArrayList;
import java.util.Collection;
@ -308,7 +307,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
try {
builder = new OmemoMessageBuilder<>(userDevice, gullibleTrustCallback, getOmemoRatchet(manager),
messageKey, iv, null);
} catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | BadPaddingException | UnsupportedEncodingException | NoSuchProviderException | IllegalBlockSizeException e) {
} catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | BadPaddingException | UnsupportedEncodingException | IllegalBlockSizeException e) {
throw new CryptoFailedException(e);
}
@ -370,7 +369,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
try {
builder = new OmemoMessageBuilder<>(
userDevice, manager.getTrustCallback(), getOmemoRatchet(managerGuard.get()), messageKey, iv, message);
} catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException | NoSuchProviderException |
} catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException |
NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException e) {
throw new CryptoFailedException(e);
}
@ -557,7 +556,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException,
PubSubException.NotAPubSubNodeException {
PubSubManager pm = PubSubManager.getInstance(connection, contactsDevice.getJid());
PubSubManager pm = PubSubManager.getInstanceFor(connection, contactsDevice.getJid());
LeafNode node = pm.getLeafNode(contactsDevice.getBundleNodeName());
if (node == null) {
@ -585,7 +584,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
static void publishBundle(XMPPConnection connection, OmemoDevice userDevice, OmemoBundleElement bundle)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {
PubSubManager pm = PubSubManager.getInstance(connection, connection.getUser().asBareJid());
PubSubManager pm = PubSubManager.getInstanceFor(connection, connection.getUser().asBareJid());
pm.tryToPublishAndPossibleAutoCreate(userDevice.getBundleNodeName(), new PayloadItem<>(bundle));
}
@ -607,7 +606,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
SmackException.NotConnectedException, XMPPException.XMPPErrorException,
PubSubException.NotAPubSubNodeException {
PubSubManager pm = PubSubManager.getInstance(connection, contact);
PubSubManager pm = PubSubManager.getInstanceFor(connection, contact);
String nodeName = OmemoConstants.PEP_NODE_DEVICE_LIST;
LeafNode node = pm.getLeafNode(nodeName);
@ -637,7 +636,7 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
SmackException.NoResponseException {
PubSubManager.getInstance(connection, connection.getUser().asBareJid())
PubSubManager.getInstanceFor(connection, connection.getUser().asBareJid())
.tryToPublishAndPossibleAutoCreate(OmemoConstants.PEP_NODE_DEVICE_LIST, new PayloadItem<>(deviceList));
}
@ -786,33 +785,6 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
processBundle(omemoManager, randomPreKeyBundle, contactsDevice);
}
/**
* Build OMEMO sessions with all devices of the contact, we haven't had sessions with before.
* This method returns a set of OmemoDevices. This set contains all devices, with which we either had sessions
* before, plus those devices with which we just built sessions.
*
* @param connection authenticated XMPP connection.
* @param userDevice our OmemoDevice
* @param contact the BareJid of the contact with whom we want to build sessions with.
* @return set of devices with a session.
* @throws SmackException.NotConnectedException
* @throws InterruptedException
* @throws SmackException.NoResponseException
*/
private Set<OmemoDevice> buildMissingSessionsWithContact(XMPPConnection connection,
OmemoDevice userDevice,
BareJid contact)
throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
OmemoCachedDeviceList contactsDeviceIds = getOmemoStoreBackend().loadCachedDeviceList(userDevice, contact);
Set<OmemoDevice> contactsDevices = new HashSet<>();
for (int deviceId : contactsDeviceIds.getActiveDevices()) {
contactsDevices.add(new OmemoDevice(contact, deviceId));
}
return buildMissingSessionsWithDevices(connection, userDevice, contactsDevices);
}
/**
* Build sessions with all devices from the set, we don't have a session with yet.
* Return the set of all devices we have a session with afterwards.
@ -854,34 +826,6 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
return devicesWithSession;
}
/**
* Build OMEMO sessions with all devices of the contacts, we haven't had sessions with before.
* This method returns a set of OmemoDevices. This set contains all devices, with which we either had sessions
* before, plus those devices with which we just built sessions.
*
* @param connection authenticated XMPP connection.
* @param userDevice our OmemoDevice
* @param contacts set of BareJids of contacts, we want to build sessions with.
* @return set of devices, we have sessions with.
* @throws SmackException.NotConnectedException
* @throws InterruptedException
* @throws SmackException.NoResponseException
*/
private Set<OmemoDevice> buildMissingSessionsWithContacts(XMPPConnection connection,
OmemoDevice userDevice,
Set<BareJid> contacts)
throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
Set<OmemoDevice> devicesWithSessions = new HashSet<>();
for (BareJid contact : contacts) {
Set<OmemoDevice> devices = buildMissingSessionsWithContact(connection, userDevice, contact);
devicesWithSessions.addAll(devices);
}
return devicesWithSessions;
}
/**
* Return a set of all devices from the provided set, which trust level is undecided.
* A device is also considered undecided, if its fingerprint cannot be loaded.
@ -914,37 +858,6 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
return undecidedDevices;
}
/**
* Return a set of all devices from the provided set, which are untrusted.
* A device is also considered untrusted, if its fingerprint cannot be loaded.
*
* @param userDevice our own OmemoDevice
* @param trustCallback OmemoTrustCallback to query trust decisions from
* @param devices set of OmemoDevices
* @return set of OmemoDevices from devices, which contains all devices which are untrusted
*/
private Set<OmemoDevice> getUntrustedDeviced(OmemoDevice userDevice, OmemoTrustCallback trustCallback, Set<OmemoDevice> devices) {
Set<OmemoDevice> untrustedDevices = new HashSet<>();
for (OmemoDevice device : devices) {
OmemoFingerprint fingerprint;
try {
fingerprint = getOmemoStoreBackend().getFingerprint(userDevice, device);
} catch (CorruptedOmemoKeyException | NoIdentityKeyException e) {
// TODO: Best solution?
untrustedDevices.add(device);
continue;
}
if (trustCallback.getTrust(device, fingerprint) == TrustState.untrusted) {
untrustedDevices.add(device);
}
}
return untrustedDevices;
}
/**
* Return true, if the OmemoManager of userDevice has a session with the contactsDevice.
*

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Paul Schaub
* Copyright 2017 Paul Schaub, 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,11 +18,9 @@ package org.jivesoftware.smackx.omemo.internal;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.CIPHERMODE;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.PROVIDER;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
@ -50,13 +48,13 @@ public class CipherAndAuthTag {
Cipher cipher;
try {
cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
cipher = Cipher.getInstance(CIPHERMODE);
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
} catch (NoSuchAlgorithmException | java.security.InvalidKeyException |
InvalidAlgorithmParameterException |
NoSuchPaddingException | NoSuchProviderException e) {
NoSuchPaddingException e) {
throw new CryptoFailedException(e);
}

View file

@ -58,6 +58,5 @@ public final class OmemoConstants {
public static final String KEYTYPE = "AES";
public static final int KEYLENGTH = 128;
public static final String CIPHERMODE = "AES/GCM/NoPadding";
public static final String PROVIDER = "BC";
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Paul Schaub
* Copyright 2017 Paul Schaub, 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,7 +19,6 @@ package org.jivesoftware.smackx.omemo.util;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.CIPHERMODE;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.PROVIDER;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
@ -96,7 +95,6 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
* @throws NoSuchAlgorithmException
* @throws IllegalBlockSizeException
* @throws UnsupportedEncodingException
* @throws NoSuchProviderException
* @throws InvalidAlgorithmParameterException
*/
public OmemoMessageBuilder(OmemoDevice userDevice,
@ -107,7 +105,7 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
String message)
throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
IllegalBlockSizeException,
UnsupportedEncodingException, NoSuchProviderException, InvalidAlgorithmParameterException {
UnsupportedEncodingException, InvalidAlgorithmParameterException {
this.userDevice = userDevice;
this.trustCallback = callback;
this.ratchet = ratchet;
@ -130,7 +128,6 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
* @throws NoSuchAlgorithmException
* @throws IllegalBlockSizeException
* @throws UnsupportedEncodingException
* @throws NoSuchProviderException
* @throws InvalidAlgorithmParameterException
*/
public OmemoMessageBuilder(OmemoDevice userDevice,
@ -138,7 +135,7 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> ratchet,
String message)
throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException,
UnsupportedEncodingException, NoSuchProviderException, InvalidAlgorithmParameterException {
UnsupportedEncodingException, InvalidAlgorithmParameterException {
this(userDevice, callback, ratchet, generateKey(KEYTYPE, KEYLENGTH), generateIv(), message);
}
@ -150,7 +147,6 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
*
* @param message plaintext message
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
@ -158,7 +154,7 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/
private void setMessage(String message) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
private void setMessage(String message) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
if (message == null) {
return;
}
@ -166,7 +162,7 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
// Encrypt message body
SecretKey secretKey = new SecretKeySpec(messageKey, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);
Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
Cipher cipher = Cipher.getInstance(CIPHERMODE);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] body;

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Florian Schmaus, 2018 Paul Schaub.
* Copyright 2017-2019 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.
@ -189,7 +189,7 @@ public final class OpenPgpManager extends Manager {
* @param connection xmpp connection.
* @return instance of the manager.
*/
public static OpenPgpManager getInstanceFor(XMPPConnection connection) {
public static synchronized OpenPgpManager getInstanceFor(XMPPConnection connection) {
OpenPgpManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new OpenPgpManager(connection);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Florian Schmaus, 2018 Paul Schaub.
* Copyright 2017-2019 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.
@ -32,11 +32,11 @@ public class CryptElement extends EncryptedOpenPgpContentElement {
public static final String ELEMENT = "crypt";
public CryptElement(Set<Jid> to, String rpad, Date timestamp, List<ExtensionElement> payload) {
public CryptElement(Set<? extends Jid> to, String rpad, Date timestamp, List<ExtensionElement> payload) {
super(to, rpad, timestamp, payload);
}
public CryptElement(Set<Jid> to, List<ExtensionElement> payload) {
public CryptElement(Set<? extends Jid> to, List<ExtensionElement> payload) {
super(to, payload);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Florian Schmaus, 2018 Paul Schaub.
* Copyright 2017-2019 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.
@ -16,13 +16,13 @@
*/
package org.jivesoftware.smackx.ox.element;
import java.security.SecureRandom;
import java.util.Date;
import java.util.List;
import java.util.Set;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.RandomUtil;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
@ -38,14 +38,14 @@ public abstract class EncryptedOpenPgpContentElement extends OpenPgpContentEleme
private final String rpad;
protected EncryptedOpenPgpContentElement(Set<Jid> to, String rpad, Date timestamp, List<ExtensionElement> payload) {
protected EncryptedOpenPgpContentElement(Set<? extends Jid> to, String rpad, Date timestamp, List<ExtensionElement> payload) {
super(Objects.requireNonNullNorEmpty(
to, "Encrypted OpenPGP content elements must have at least one 'to' attribute."),
timestamp, payload);
this.rpad = Objects.requireNonNull(rpad);
}
protected EncryptedOpenPgpContentElement(Set<Jid> to, List<ExtensionElement> payload) {
protected EncryptedOpenPgpContentElement(Set<? extends Jid> to, List<ExtensionElement> payload) {
super(Objects.requireNonNullNorEmpty(
to, "Encrypted OpenPGP content elements must have at least one 'to' attribute."),
new Date(), payload);
@ -53,8 +53,7 @@ public abstract class EncryptedOpenPgpContentElement extends OpenPgpContentEleme
}
private static String createRandomPadding() {
SecureRandom secRan = new SecureRandom();
int len = secRan.nextInt(256);
int len = RandomUtil.nextSecureRandomInt(256);
return StringUtils.randomString(len);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Florian Schmaus, 2018 Paul Schaub.
* Copyright 2017-2019 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.
@ -48,13 +48,13 @@ public abstract class OpenPgpContentElement implements ExtensionElement {
public static final String ATTR_STAMP = "stamp";
public static final String ELEM_PAYLOAD = "payload";
private final Set<Jid> to;
private final Set<? extends Jid> to;
private final Date timestamp;
private final MultiMap<String, ExtensionElement> payload;
private String timestampString;
protected OpenPgpContentElement(Set<Jid> to, Date timestamp, List<ExtensionElement> payload) {
protected OpenPgpContentElement(Set<? extends Jid> to, Date timestamp, List<ExtensionElement> payload) {
this.to = to;
this.timestamp = Objects.requireNonNull(timestamp);
this.payload = new MultiMap<>();
@ -68,7 +68,7 @@ public abstract class OpenPgpContentElement implements ExtensionElement {
*
* @return recipients.
*/
public final Set<Jid> getTo() {
public final Set<? extends Jid> getTo() {
return to;
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Florian Schmaus, 2018 Paul Schaub.
* Copyright 2017-2019 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.
@ -35,11 +35,11 @@ public class SigncryptElement extends EncryptedOpenPgpContentElement {
public static final String ELEMENT = "signcrypt";
public SigncryptElement(Set<Jid> to, String rpad, Date timestamp, List<ExtensionElement> payload) {
public SigncryptElement(Set<? extends Jid> to, String rpad, Date timestamp, List<ExtensionElement> payload) {
super(to, rpad, timestamp, payload);
}
public SigncryptElement(Set<Jid> to, List<ExtensionElement> payload) {
public SigncryptElement(Set<? extends Jid> to, List<ExtensionElement> payload) {
super(to, payload);
}

View file

@ -204,7 +204,7 @@ public class OpenPgpPubSubUtil {
public static PublicKeysListElement fetchPubkeysList(XMPPConnection connection, BareJid contact)
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NoResponseException,
PubSubException.NotALeafNodeException, SmackException.NotConnectedException, PubSubException.NotAPubSubNodeException {
PubSubManager pm = PubSubManager.getInstance(connection, contact);
PubSubManager pm = PubSubManager.getInstanceFor(connection, contact);
LeafNode node = getLeafNode(pm, PEP_NODE_PUBLIC_KEYS);
List<PayloadItem<PublicKeysListElement>> list = node.getItems(1);
@ -274,7 +274,7 @@ public class OpenPgpPubSubUtil {
public static PubkeyElement fetchPubkey(XMPPConnection connection, BareJid contact, OpenPgpV4Fingerprint v4_fingerprint)
throws InterruptedException, XMPPException.XMPPErrorException, PubSubException.NotAPubSubNodeException,
PubSubException.NotALeafNodeException, SmackException.NotConnectedException, SmackException.NoResponseException {
PubSubManager pm = PubSubManager.getInstance(connection, contact);
PubSubManager pm = PubSubManager.getInstanceFor(connection, contact);
String nodeName = PEP_NODE_PUBLIC_KEY(v4_fingerprint);
LeafNode node = getLeafNode(pm, nodeName);

View file

@ -135,7 +135,7 @@ public final class OXInstantMessagingManager extends Manager {
* @param connection XMPP connection
* @return manager instance
*/
public static OXInstantMessagingManager getInstanceFor(XMPPConnection connection) {
public static synchronized OXInstantMessagingManager getInstanceFor(XMPPConnection connection) {
OXInstantMessagingManager manager = INSTANCES.get(connection);
if (manager == null) {

View file

@ -14,6 +14,7 @@ dependencies {
compile project(':smack-bosh')
compile project(':smack-java7')
compile project(':smack-resolver-minidns')
compile project(':smack-resolver-minidns-dox')
compile project(':smack-extensions')
compile project(':smack-experimental')
compile project(':smack-legacy')

View file

@ -0,0 +1,78 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.igniterealtime.smack.smackrepl;
import java.io.IOException;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smackx.dox.DnsOverXmppManager;
import org.jivesoftware.smackx.dox.resolver.minidns.DnsOverXmppMiniDnsResolver;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.minidns.dnsmessage.DnsMessage;
import org.minidns.dnsmessage.Question;
import org.minidns.record.Record;
public class DoX {
public static void main(String[] args) throws XMPPException, SmackException, IOException, InterruptedException {
SmackConfiguration.DEBUG = true;
XMPPTCPConnection connection = new XMPPTCPConnection(args[0], args[1]);
connection.setReplyTimeout(60000);
connection.connect().login();
DnsOverXmppManager dox = DnsOverXmppManager.getInstanceFor(connection);
Jid target = JidCreate.from("dns@moparisthebest.com/listener");
Question question = new Question("geekplace.eu", Record.TYPE.A);
DnsMessage response = dox.query(target, question);
// CHECKSTYLE:OFF
System.out.println(response);
// CHECKSTYLE:ON
connection.disconnect();
}
public static XMPPTCPConnection runDoxResolver(String jid, String password)
throws XMPPException, SmackException, IOException, InterruptedException {
XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
.setXmppAddressAndPassword(jid, password)
.setResource("dns")
.setDebuggerFactory(ConsoleDebugger.Factory.INSTANCE)
.build();
XMPPTCPConnection connection = new XMPPTCPConnection(config);
connection.connect().login();
DnsOverXmppManager dox = DnsOverXmppManager.getInstanceFor(connection);
dox.setDnsOverXmppResolver(DnsOverXmppMiniDnsResolver.INSTANCE);
dox.enable();
return connection;
}
}

View file

@ -0,0 +1,8 @@
description = """\
DNS over XMPP (DoX) support using MiniDNS."""
dependencies {
compile project(path: ':smack-resolver-minidns')
compile project(path: ':smack-experimental')
testCompile project(path: ":smack-core", configuration: "testRuntime")
}

View file

@ -0,0 +1,57 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.dox.resolver.minidns;
import java.io.IOException;
import org.jivesoftware.smackx.dox.DnsOverXmppResolver;
import org.minidns.DnsClient;
import org.minidns.dnsmessage.DnsMessage;
import org.minidns.dnsmessage.Question;
import org.minidns.dnsqueryresult.DnsQueryResult;
import org.minidns.dnssec.DnssecClient;
import org.minidns.dnssec.DnssecQueryResult;
public final class DnsOverXmppMiniDnsResolver implements DnsOverXmppResolver {
public static final DnsOverXmppMiniDnsResolver INSTANCE = new DnsOverXmppMiniDnsResolver(new DnsClient(), new DnssecClient());
private final DnsClient dnsClient;
private final DnssecClient dnssecClient;
DnsOverXmppMiniDnsResolver(DnsClient dnsClient, DnssecClient dnssecClient) {
this.dnsClient = dnsClient;
this.dnssecClient = dnssecClient;
}
@Override
public DnsMessage resolve(DnsMessage query) throws IOException {
Question question = query.getQuestion();
final DnsQueryResult dnsQueryResult;
if (query.isDnssecOk()) {
DnssecQueryResult dnssecQueryResult = dnssecClient.queryDnssec(question);
dnsQueryResult = dnssecQueryResult.dnsQueryResult;
} else {
dnsQueryResult = dnsClient.query(question);
}
return dnsQueryResult.response;
}
}

View file

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

View file

@ -0,0 +1,167 @@
/**
*
* Copyright 2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.dox.resolver.minidns;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.net.InetAddress;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.minidns.DnsCache;
import org.minidns.DnsClient;
import org.minidns.MiniDnsFuture;
import org.minidns.dnsmessage.DnsMessage;
import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;
import org.minidns.dnsmessage.Question;
import org.minidns.dnsname.DnsName;
import org.minidns.dnsqueryresult.CachedDnsQueryResult;
import org.minidns.dnsqueryresult.DnsQueryResult;
import org.minidns.dnssec.DnssecClient;
import org.minidns.dnssec.DnssecValidationFailedException;
import org.minidns.record.Record.TYPE;
import org.minidns.source.DnsDataSource;
public final class DnsOverXmppMiniDnsResolverTest {
@Test
public void dnsOverXmppMiniDnsResolverTest() throws IOException {
TestDnsDataSource dnsSource = new TestDnsDataSource();
TestDnsDataSource dnssecSource = new TestDnsDataSource();
DnsClient dnsClient = new DnsClient(NoopDnsCache.INSTANCE);
dnsClient.setDataSource(dnsSource);
DnssecClient dnssecClient = new DnssecClient(NoopDnsCache.INSTANCE);
dnssecClient.setDataSource(dnssecSource);
DnsOverXmppMiniDnsResolver doxResolver = new DnsOverXmppMiniDnsResolver(dnsClient, dnssecClient);
Question question = new Question("example.org", TYPE.A);
{
DnsMessage nondnssecQuery = question.asQueryMessage();
doxResolver.resolve(nondnssecQuery);
assertTrue(dnsSource.getAndResetWasQueried());
assertFalse(dnssecSource.getAndResetWasQueried());
}
{
DnsMessage.Builder dnssecQueryBuilder = question.asMessageBuilder();
dnssecQueryBuilder.getEdnsBuilder().setDnssecOk();
DnsMessage dnssecQuery = dnssecQueryBuilder.build();
DnssecValidationFailedException dnssecValidationFailedException = null;
try {
doxResolver.resolve(dnssecQuery);
} catch (DnssecValidationFailedException e) {
dnssecValidationFailedException = e;
}
// This exception is expected since we don't have a realy DNS source.
assertNotNull(dnssecValidationFailedException);
assertFalse(dnsSource.getAndResetWasQueried());
assertTrue(dnssecSource.getAndResetWasQueried());
}
}
public static class TestDnsDataSource implements DnsDataSource {
private final AtomicBoolean wasQueried = new AtomicBoolean();
public boolean getAndResetWasQueried() {
return wasQueried.getAndSet(false);
}
private void setWasQueried() {
wasQueried.set(true);
}
@Override
public DnsQueryResult query(DnsMessage query, InetAddress address, int port) throws IOException {
setWasQueried();
return new TestDnsQueryResult(query);
}
@Override
public MiniDnsFuture<DnsQueryResult, IOException> queryAsync(DnsMessage query, InetAddress address, int port,
OnResponseCallback onResponseCallback) {
setWasQueried();
DnsQueryResult result = new TestDnsQueryResult(query);
return MiniDnsFuture.from(result);
}
@Override
public int getUdpPayloadSize() {
return 0;
}
@Override
public int getTimeout() {
return 0;
}
@Override
public void setTimeout(int timeout) {
}
private static class TestDnsQueryResult extends DnsQueryResult {
protected TestDnsQueryResult(DnsMessage query) {
super(QueryMethod.testWorld, query, createNxDomainAnswerFor(query));
}
private static DnsMessage createNxDomainAnswerFor(DnsMessage query) {
Question question = query.getQuestion();
DnsMessage response = DnsMessage.builder()
.setQuestion(question)
.setRecursionAvailable(true)
.setResponseCode(RESPONSE_CODE.NX_DOMAIN)
.build();
return response;
}
}
}
// TODO: Workaround for NPE-if-no-cache-set bug in MiniDNS. Remove we use a MiniDNS version where this is fixed,
// i.e. one that has 864fbb5 ("Fix NPE in AbstractDnsClient if cache is 'null'")
private static class NoopDnsCache extends DnsCache {
private static final NoopDnsCache INSTANCE = new NoopDnsCache();
@Override
protected void putNormalized(DnsMessage normalizedQuery, DnsQueryResult result) {
}
@Override
public void offer(DnsMessage query, DnsQueryResult result, DnsName authoritativeZone) {
}
@Override
protected CachedDnsQueryResult getNormalized(DnsMessage normalizedQuery) {
return null;
}
}
}

View file

@ -121,7 +121,6 @@ import org.jivesoftware.smack.util.dns.HostAddress;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart;
import org.jxmpp.stringprep.XmppStringprepException;
import org.jxmpp.util.XmppStringUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@ -319,7 +318,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
* @throws XmppStringprepException
*/
public XMPPTCPConnection(CharSequence jid, String password) throws XmppStringprepException {
this(XmppStringUtils.parseLocalpart(jid.toString()), password, XmppStringUtils.parseDomain(jid.toString()));
this(XMPPTCPConnectionConfiguration.builder().setXmppAddressAndPassword(jid, password).build());
}
/**
@ -482,10 +481,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
private void shutdown(boolean instant) {
if (disconnectedButResumeable) {
return;
}
// First shutdown the writer, this will result in a closing stream element getting send to
// the server
LOGGER.finer("PacketWriter shutdown()");
@ -503,6 +498,15 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
CloseableUtil.maybeClose(socket, LOGGER);
setWasAuthenticated();
// Wait for reader and writer threads to be terminated.
readerWriterSemaphore.acquireUninterruptibly(2);
readerWriterSemaphore.release(2);
if (disconnectedButResumeable) {
return;
}
// If we are able to resume the stream, then don't set
// connected/authenticated/usingTLS to false since we like behave like we are still
// connected (e.g. sendStanza should not throw a NotConnectedException).
@ -523,10 +527,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
writer = null;
initState();
// Wait for reader and writer threads to be terminated.
readerWriterSemaphore.acquireUninterruptibly(2);
readerWriterSemaphore.release(2);
}
@Override
@ -860,6 +860,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
protected class PacketReader {
private final String threadName = "Smack Reader (" + getConnectionCounter() + ')';
XmlPullParser parser;
private volatile boolean done;
@ -874,13 +876,15 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
Async.go(new Runnable() {
@Override
public void run() {
LOGGER.finer(threadName + " start");
try {
parsePackets();
} finally {
LOGGER.finer(threadName + " exit");
XMPPTCPConnection.this.readerWriterSemaphore.release();
}
}
}, "Smack Reader (" + getConnectionCounter() + ")");
}, threadName);
}
/**
@ -1128,6 +1132,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
protected class PacketWriter {
public static final int QUEUE_SIZE = XMPPTCPConnection.QUEUE_SIZE;
private final String threadName = "Smack Writer (" + getConnectionCounter() + ')';
private final ArrayBlockingQueueWithShutdown<Element> queue = new ArrayBlockingQueueWithShutdown<>(
QUEUE_SIZE, true);
@ -1173,13 +1179,15 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
Async.go(new Runnable() {
@Override
public void run() {
LOGGER.finer(threadName + " start");
try {
writePackets();
} finally {
LOGGER.finer(threadName + " exit");
XMPPTCPConnection.this.readerWriterSemaphore.release();
}
}
}, "Smack Writer (" + getConnectionCounter() + ")");
}, threadName);
}
private boolean done() {

View file

@ -10,6 +10,7 @@ allprojects {
// - https://issues.igniterealtime.org/browse/SMACK-858
jxmppVersion = '0.7.0-alpha5'
miniDnsVersion = '0.4.0-alpha3'
bouncyCastleVersion = '1.61'
smackMinAndroidSdk = 19
}
}