mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-22 12:02:05 +01:00
Compare commits
42 commits
0cd3318b12
...
870756997f
Author | SHA1 | Date | |
---|---|---|---|
|
870756997f | ||
|
fc70484cf6 | ||
|
2dbc32340d | ||
|
d97fb126a1 | ||
|
99bf8316f5 | ||
|
9c30e4f3cc | ||
|
11775ed6b0 | ||
|
488055948d | ||
|
7fd0676ff4 | ||
|
6076a9dfa5 | ||
|
a1c88f1fad | ||
|
fc45e1b905 | ||
|
0ec7e84cbc | ||
|
9ad162af6e | ||
|
474ea68d4a | ||
|
62fd897cf7 | ||
|
75b1d8ce13 | ||
|
1122bf394c | ||
|
3075430713 | ||
|
d1f2631771 | ||
|
87e0ac9ba1 | ||
|
7d5274dad1 | ||
|
a9673408cc | ||
|
0d17f195b0 | ||
|
d6b6fdca17 | ||
|
38384a1eed | ||
|
af0fb7543c | ||
|
fea7362293 | ||
af88227919 | |||
|
d10319f1a0 | ||
|
c499556d07 | ||
|
7d7fbe6828 | ||
|
9f8d13b8cd | ||
|
d6a90942a4 | ||
|
9be498c440 | ||
|
25b3f35421 | ||
|
7f542e403f | ||
|
5d46e281fc | ||
|
ab7d81e7b5 | ||
|
89c0fa4b99 | ||
|
14f288a763 | ||
|
927eb5e7d7 |
77 changed files with 1560 additions and 2064 deletions
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
--------------------------
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -240,6 +240,10 @@ public class XmppConnectionManager<DC extends AbstractXMPPConnection> {
|
|||
}
|
||||
|
||||
connections.clear();
|
||||
|
||||
if (accountRegistrationConnection != null) {
|
||||
accountRegistrationConnection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 <bad-request/> error
|
||||
* and a pubsub-specific error condition of <item-forbidden/>.
|
||||
* </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);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
8
smack-resolver-minidns-dox/build.gradle
Normal file
8
smack-resolver-minidns-dox/build.gradle
Normal 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")
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue