mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-25 05:22:06 +01:00
Add support for XEP-0198: Stream Management
- De-duplicate code by moving it into AbstractXMPPConnection - Introduce TopLevelStreamElement as superclass for all XMPP stream elements. - Add SynchronizationPoint, ParserUtils - Add ParserUtils Fixes SMACK-333 and SMACK-521
This commit is contained in:
parent
07c10a7444
commit
fc51f3df48
69 changed files with 3277 additions and 1083 deletions
|
@ -33,7 +33,7 @@ allprojects {
|
|||
// build, causing unnecessary rebuilds.
|
||||
builtDate = (new java.text.SimpleDateFormat("yyyy-MM-dd")).format(new Date())
|
||||
oneLineDesc = 'An Open Source XMPP (Jabber) client library'
|
||||
jxmppVersion = "0.2.0"
|
||||
jxmppVersion = "0.3.0"
|
||||
}
|
||||
group = 'org.igniterealtime.smack'
|
||||
sourceCompatibility = 1.7
|
||||
|
|
|
@ -9,6 +9,14 @@ for many of the protocol extensions.
|
|||
This manual provides details about each of the "smackx" extensions, including
|
||||
what it is, how to use it, and some simple example code.
|
||||
|
||||
Currently supported XEPs of smack-tcp
|
||||
-------------------------------------
|
||||
|
||||
| Name | XEP | Description |
|
||||
|---------------------------------------------|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
|
||||
| [Stream Management](streammanagement.html) | [XEP-0198](http://xmpp.org/extensions/xep-0198.html) | Allows active management of an XML Stream between two XMPP entities (stanza acknowledgement, stream resumption). |
|
||||
|
||||
|
||||
Smack Extensions and currently supported XEPs by Smack (smack-extensions)
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
|
|
37
documentation/extensions/streammanagement.md
Normal file
37
documentation/extensions/streammanagement.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
Stream Management
|
||||
=================
|
||||
|
||||
XMPPTCPConnection comes with support for Stream Management (SM).
|
||||
|
||||
**XEP related:** [XEP-0198](http://xmpp.org/extensions/xep-0198.html)
|
||||
|
||||
Known interoperability issues
|
||||
-----------------------------
|
||||
|
||||
- SM resumption failes on prosody when compression in sync flush mode is used with prosody. See [Prosody issue #433](https://code.google.com/p/lxmppd/issues/detail?id=433).
|
||||
|
||||
Enabling stream management
|
||||
------------------------
|
||||
|
||||
TODO
|
||||
|
||||
Getting notifications about acknowledges stanzas
|
||||
------------------------------------------------
|
||||
|
||||
TODO
|
||||
|
||||
Requisting stanza acknowledgements from the server
|
||||
--------------------------------------------------
|
||||
|
||||
### By using predicates
|
||||
|
||||
TODO
|
||||
|
||||
### Manually
|
||||
|
||||
TODO
|
||||
|
||||
Enable stream resumption
|
||||
------------------------
|
||||
|
||||
TODO
|
25
resources/getCopyright.sh
Executable file
25
resources/getCopyright.sh
Executable file
|
@ -0,0 +1,25 @@
|
|||
#!/bin/bash
|
||||
|
||||
SCRIPTDIR="$(dirname ${BASH_SOURCE[0]})"
|
||||
BASEDIR=${SCRIPTDIR}/..
|
||||
|
||||
cd $BASEDIR
|
||||
SUBPROJECTS=$(grep -oP "\'.*\'" settings.gradle | sed "s;';;g")
|
||||
for p in $SUBPROJECTS; do
|
||||
echo "Copyright notices for $p"
|
||||
# Find all .java files in the project
|
||||
find $p/src -type f -name "*.java" -print0 | \
|
||||
# Get the project string
|
||||
xargs -0 grep -ohP '^.*\* Copyright \K.*' | \
|
||||
# Sort the output
|
||||
sort | \
|
||||
# Remove duplicates
|
||||
uniq | \
|
||||
# Split multi Copyright statemtents, e.g. "2001-2013 FooBar, 2014 Baz"
|
||||
tr ',' '\n' | \
|
||||
# Remove whitespaces resulting from the previous split
|
||||
sed "s/^[ \t]*//" | \
|
||||
# Remove dots at the end and '©' at the beginning
|
||||
sed "s/^© //" | sed "s/\.$//" | sed "s/^(C) //"
|
||||
echo -ne "\n"
|
||||
done
|
|
@ -20,9 +20,8 @@ package org.jivesoftware.smack.bosh;
|
|||
import java.io.StringReader;
|
||||
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Challenge;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
@ -86,8 +85,6 @@ public class BOSHPacketReader implements BOSHClientResponseListener {
|
|||
final String challengeData = parser.nextText();
|
||||
connection.getSASLAuthentication()
|
||||
.challengeReceived(challengeData);
|
||||
connection.processPacket(new Challenge(
|
||||
challengeData));
|
||||
} else if (parser.getName().equals("success")) {
|
||||
connection.send(ComposableBody.builder()
|
||||
.setNamespaceDefinition("xmpp", XMPPBOSHConnection.XMPP_BOSH_NS)
|
||||
|
@ -100,14 +97,12 @@ public class BOSHPacketReader implements BOSHClientResponseListener {
|
|||
.build());
|
||||
Success success = new Success(parser.nextText());
|
||||
connection.getSASLAuthentication().authenticated(success);
|
||||
connection.processPacket(success);
|
||||
} else if (parser.getName().equals("features")) {
|
||||
parseFeatures(parser);
|
||||
} else if (parser.getName().equals("failure")) {
|
||||
if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) {
|
||||
final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser);
|
||||
connection.getSASLAuthentication().authenticationFailed(failure);
|
||||
connection.processPacket(failure);
|
||||
}
|
||||
} else if (parser.getName().equals("error")) {
|
||||
throw new StreamErrorException(PacketParserUtils.parseStreamError(parser));
|
||||
|
@ -123,41 +118,7 @@ public class BOSHPacketReader implements BOSHClientResponseListener {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and setup the XML stream features.
|
||||
*
|
||||
* @param parser the XML parser, positioned at the start of a message packet.
|
||||
* @throws Exception if an exception occurs while parsing the packet.
|
||||
*/
|
||||
private void parseFeatures(XmlPullParser parser) throws Exception {
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
int eventType = parser.next();
|
||||
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
if (parser.getName().equals("mechanisms")) {
|
||||
// The server is reporting available SASL mechanisms. Store
|
||||
// this information
|
||||
// which will be used later while logging (i.e.
|
||||
// authenticating) into
|
||||
// the server
|
||||
connection.getSASLAuthentication().setAvailableSASLMethods(
|
||||
PacketParserUtils.parseMechanisms(parser));
|
||||
} else if (parser.getName().equals("bind")) {
|
||||
// The server requires the client to bind a resource to the
|
||||
// stream
|
||||
connection.serverRequiresBinding();
|
||||
} else if (parser.getName().equals("session")) {
|
||||
// The server supports sessions
|
||||
connection.serverSupportsSession();
|
||||
} else if (parser.getName().equals("register")) {
|
||||
connection.serverSupportsAccountCreation();
|
||||
}
|
||||
} else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (parser.getName().equals("features")) {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
connection.parseFeatures0(parser);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,9 +38,12 @@ import org.jivesoftware.smack.ConnectionCreationListener;
|
|||
import org.jivesoftware.smack.ConnectionListener;
|
||||
import org.jivesoftware.smack.Roster;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.packet.Presence.Type;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.igniterealtime.jbosh.BOSHClient;
|
||||
import org.igniterealtime.jbosh.BOSHClientConfig;
|
||||
import org.igniterealtime.jbosh.BOSHClientConnEvent;
|
||||
|
@ -85,7 +88,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
// Some flags which provides some info about the current state.
|
||||
private boolean connected = false;
|
||||
private boolean authenticated = false;
|
||||
private boolean anonymous = false;
|
||||
private boolean isFirstInitialization = true;
|
||||
private boolean wasAuthenticated = false;
|
||||
private boolean done = false;
|
||||
|
@ -104,11 +106,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
*/
|
||||
protected String sessionID = null;
|
||||
|
||||
/**
|
||||
* The full JID of the authenticated user.
|
||||
*/
|
||||
private String user = null;
|
||||
|
||||
/**
|
||||
* Create a HTTP Binding connection to a XMPP server.
|
||||
*
|
||||
|
@ -220,10 +217,6 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
return user;
|
||||
}
|
||||
|
||||
public boolean isAnonymous() {
|
||||
return anonymous;
|
||||
}
|
||||
|
||||
public boolean isAuthenticated() {
|
||||
return authenticated;
|
||||
}
|
||||
|
@ -250,6 +243,10 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
if (authenticated) {
|
||||
throw new AlreadyLoggedInException();
|
||||
}
|
||||
|
||||
// Wait with SASL auth until the SASL mechanisms have been received
|
||||
saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
|
||||
|
||||
// Do partial version of nameprep on the username.
|
||||
username = username.toLowerCase(Locale.US).trim();
|
||||
|
||||
|
@ -264,41 +261,11 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
throw new SaslException("No non-anonymous SASL authentication mechanism available");
|
||||
}
|
||||
|
||||
String response = bindResourceAndEstablishSession(resource);
|
||||
// Set the user.
|
||||
if (response != null) {
|
||||
this.user = response;
|
||||
// Update the serviceName with the one returned by the server
|
||||
setServiceName(response);
|
||||
} else {
|
||||
this.user = username + "@" + getServiceName();
|
||||
if (resource != null) {
|
||||
this.user += "/" + resource;
|
||||
}
|
||||
}
|
||||
bindResourceAndEstablishSession(resource);
|
||||
|
||||
// Indicate that we're now authenticated.
|
||||
authenticated = true;
|
||||
anonymous = false;
|
||||
|
||||
// Stores the autentication for future reconnection
|
||||
// Stores the authentication for future reconnection
|
||||
setLoginInfo(username, password, resource);
|
||||
|
||||
// If debugging is enabled, change the the debug window title to include
|
||||
// the
|
||||
// name we are now logged-in as.l
|
||||
if (config.isDebuggerEnabled() && debugger != null) {
|
||||
debugger.userHasLogged(user);
|
||||
}
|
||||
callConnectionAuthenticatedListener();
|
||||
|
||||
// Set presence to online. It is important that this is done after
|
||||
// callConnectionAuthenticatedListener(), as this call will also
|
||||
// eventually load the roster. And we should load the roster before we
|
||||
// send the initial presence.
|
||||
if (config.isSendPresence()) {
|
||||
sendPacket(new Presence(Presence.Type.available));
|
||||
}
|
||||
afterSuccessfulLogin(false, false);
|
||||
}
|
||||
|
||||
public void loginAnonymously() throws XMPPException, SmackException, IOException {
|
||||
|
@ -309,6 +276,9 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
throw new AlreadyLoggedInException();
|
||||
}
|
||||
|
||||
// Wait with SASL auth until the SASL mechanisms have been received
|
||||
saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
|
||||
|
||||
if (saslAuthentication.hasAnonymousAuthentication()) {
|
||||
saslAuthentication.authenticateAnonymously();
|
||||
}
|
||||
|
@ -317,38 +287,27 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
throw new SaslException("No anonymous SASL authentication mechanism available");
|
||||
}
|
||||
|
||||
String response = bindResourceAndEstablishSession(null);
|
||||
// Set the user value.
|
||||
this.user = response;
|
||||
// Update the serviceName with the one returned by the server
|
||||
setServiceName(response);
|
||||
bindResourceAndEstablishSession(null);
|
||||
|
||||
// Set presence to online.
|
||||
if (config.isSendPresence()) {
|
||||
sendPacket(new Presence(Presence.Type.available));
|
||||
afterSuccessfulLogin(true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(PlainStreamElement element) throws NotConnectedException {
|
||||
if (done) {
|
||||
throw new NotConnectedException();
|
||||
}
|
||||
|
||||
// Indicate that we're now authenticated.
|
||||
authenticated = true;
|
||||
anonymous = true;
|
||||
|
||||
// If debugging is enabled, change the the debug window title to include the
|
||||
// name we are now logged-in as.
|
||||
// If DEBUG_ENABLED was set to true AFTER the connection was created the debugger
|
||||
// will be null
|
||||
if (config.isDebuggerEnabled() && debugger != null) {
|
||||
debugger.userHasLogged(user);
|
||||
}
|
||||
callConnectionAuthenticatedListener();
|
||||
sendElement(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendPacketInternal(Packet packet) throws NotConnectedException {
|
||||
if (done) {
|
||||
throw new NotConnectedException();
|
||||
}
|
||||
sendElement(packet);
|
||||
}
|
||||
|
||||
private void sendElement(Element element) {
|
||||
try {
|
||||
send(ComposableBody.builder().setPayloadXML(packet.toXML().toString()).build());
|
||||
send(ComposableBody.builder().setPayloadXML(element.toXML().toString()).build());
|
||||
}
|
||||
catch (BOSHException e) {
|
||||
LOGGER.log(Level.SEVERE, "BOSHException in sendPacketInternal", e);
|
||||
|
@ -524,19 +483,8 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
return super.getSASLAuthentication();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serverRequiresBinding() {
|
||||
super.serverRequiresBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serverSupportsSession() {
|
||||
super.serverSupportsSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serverSupportsAccountCreation() {
|
||||
super.serverSupportsAccountCreation();
|
||||
void parseFeatures0(XmlPullParser parser) throws Exception {
|
||||
parseFeatures(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.io.IOException;
|
|||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
@ -28,27 +29,43 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.SmackException.ConnectionException;
|
||||
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
|
||||
import org.jivesoftware.smack.SmackException.SecurityRequiredException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.compress.packet.Compress;
|
||||
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
|
||||
import org.jivesoftware.smack.debugger.SmackDebugger;
|
||||
import org.jivesoftware.smack.filter.IQReplyFilter;
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.filter.PacketIDFilter;
|
||||
import org.jivesoftware.smack.packet.Bind;
|
||||
import org.jivesoftware.smack.packet.CapsExtension;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Mechanisms;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.packet.Registration;
|
||||
import org.jivesoftware.smack.packet.RosterVer;
|
||||
import org.jivesoftware.smack.packet.Session;
|
||||
import org.jivesoftware.smack.packet.StartTls;
|
||||
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||
import org.jivesoftware.smack.rosterstore.RosterStore;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.jxmpp.util.XmppStringUtils;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
|
||||
public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractXMPPConnection.class.getName());
|
||||
|
@ -105,6 +122,15 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
protected final Map<PacketInterceptor, InterceptorWrapper> interceptors =
|
||||
new ConcurrentHashMap<PacketInterceptor, InterceptorWrapper>();
|
||||
|
||||
protected final Lock connectionLock = new ReentrantLock();
|
||||
|
||||
protected final Map<String, PacketExtension> streamFeatures = new HashMap<String, PacketExtension>();
|
||||
|
||||
/**
|
||||
* The full JID of the authenticated user.
|
||||
*/
|
||||
protected String user;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -125,7 +151,21 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
protected Writer writer;
|
||||
|
||||
/**
|
||||
* Set to success if the last features stanza from the server has been parsed. A XMPP connection
|
||||
* handshake can invoke multiple features stanzas, e.g. when TLS is activated a second feature
|
||||
* stanza is send by the server. This is set to true once the last feature stanza has been
|
||||
* parsed.
|
||||
*/
|
||||
protected final SynchronizationPoint<Exception> lastFeaturesReceived = new SynchronizationPoint<Exception>(
|
||||
AbstractXMPPConnection.this);
|
||||
|
||||
/**
|
||||
* Set to success if the sasl feature has been received.
|
||||
*/
|
||||
protected final SynchronizationPoint<SmackException> saslFeatureReceived = new SynchronizationPoint<SmackException>(
|
||||
AbstractXMPPConnection.this);
|
||||
|
||||
/**
|
||||
* The SASLAuthentication manager that is responsible for authenticating with the server.
|
||||
*/
|
||||
|
@ -142,21 +182,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
protected final ConnectionConfiguration config;
|
||||
|
||||
/**
|
||||
* Holds the Caps Node information for the used XMPP service (i.e. the XMPP server)
|
||||
*/
|
||||
private String serviceCapsNode;
|
||||
|
||||
/**
|
||||
* Defines how the from attribute of outgoing stanzas should be handled.
|
||||
*/
|
||||
private FromMode fromMode = FromMode.OMITTED;
|
||||
|
||||
/**
|
||||
* Stores whether the server supports rosterVersioning
|
||||
*/
|
||||
private boolean rosterVersioningSupported = false;
|
||||
|
||||
protected XMPPInputOutputStream compressionHandler;
|
||||
|
||||
/**
|
||||
|
@ -200,22 +230,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
protected int port;
|
||||
|
||||
/**
|
||||
* Set to true if the server requires the connection to be binded in order to continue.
|
||||
* <p>
|
||||
* Note that we use AtomicBoolean here because it allows us to set the Boolean *object*, which
|
||||
* we also use as synchronization object. A plain non-atomic Boolean object would be newly created
|
||||
* for every change of the boolean value, which makes it useless as object for wait()/notify().
|
||||
*/
|
||||
private AtomicBoolean bindingRequired = new AtomicBoolean(false);
|
||||
|
||||
private boolean sessionSupported;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private Exception connectionException;
|
||||
|
||||
/**
|
||||
* Flag that indicates if the user is currently authenticated with the server.
|
||||
*/
|
||||
|
@ -227,6 +241,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
protected boolean wasAuthenticated = false;
|
||||
|
||||
private boolean anonymous = false;
|
||||
|
||||
/**
|
||||
* Create a new XMPPConnection to a XMPP server.
|
||||
*
|
||||
|
@ -267,14 +283,14 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
@Override
|
||||
public abstract boolean isAuthenticated();
|
||||
|
||||
@Override
|
||||
public abstract boolean isAnonymous();
|
||||
|
||||
@Override
|
||||
public abstract boolean isSecureConnection();
|
||||
|
||||
protected abstract void sendPacketInternal(Packet packet) throws NotConnectedException;
|
||||
|
||||
@Override
|
||||
public abstract void send(PlainStreamElement element) throws NotConnectedException;
|
||||
|
||||
@Override
|
||||
public abstract boolean isUsingCompression();
|
||||
|
||||
|
@ -292,22 +308,16 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
public void connect() throws SmackException, IOException, XMPPException {
|
||||
saslAuthentication.init();
|
||||
bindingRequired.set(false);
|
||||
sessionSupported = false;
|
||||
connectionException = null;
|
||||
saslFeatureReceived.init();
|
||||
lastFeaturesReceived.init();
|
||||
connectInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method that concrete subclasses of XMPPConnection need to implement to perform their
|
||||
* way of XMPP connection establishment. Implementations must guarantee that this method will
|
||||
* block until the last features stanzas has been parsed and the features have been reported
|
||||
* back to XMPPConnection (e.g. by calling @{link {@link AbstractXMPPConnection#serverRequiresBinding()}
|
||||
* and such).
|
||||
* <p>
|
||||
* Also implementations are required to perform an automatic login if the previous connection
|
||||
* state was logged (authenticated).
|
||||
*
|
||||
* way of XMPP connection establishment. Implementations are required to perform an automatic
|
||||
* login if the previous connection state was logged (authenticated).
|
||||
*
|
||||
* @throws SmackException
|
||||
* @throws IOException
|
||||
* @throws XMPPException
|
||||
|
@ -383,44 +393,20 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
public abstract void loginAnonymously() throws XMPPException, SmackException, IOException;
|
||||
|
||||
/**
|
||||
* Notification message saying that the server requires the client to bind a
|
||||
* resource to the stream.
|
||||
*/
|
||||
protected void serverRequiresBinding() {
|
||||
synchronized (bindingRequired) {
|
||||
bindingRequired.set(true);
|
||||
bindingRequired.notify();
|
||||
}
|
||||
}
|
||||
protected void bindResourceAndEstablishSession(String resource) throws XMPPErrorException,
|
||||
IOException, SmackException {
|
||||
|
||||
/**
|
||||
* Notification message saying that the server supports sessions. When a server supports
|
||||
* sessions the client needs to send a Session packet after successfully binding a resource
|
||||
* for the session.
|
||||
*/
|
||||
protected void serverSupportsSession() {
|
||||
sessionSupported = true;
|
||||
}
|
||||
// Wait until either:
|
||||
// - the servers last features stanza has been parsed
|
||||
// - the timeout occurs
|
||||
LOGGER.finer("Waiting for last features to be received before continuing with resource binding");
|
||||
lastFeaturesReceived.checkIfSuccessOrWait();
|
||||
|
||||
protected String bindResourceAndEstablishSession(String resource) throws XMPPErrorException,
|
||||
ResourceBindingNotOfferedException, NoResponseException, NotConnectedException {
|
||||
|
||||
synchronized (bindingRequired) {
|
||||
if (!bindingRequired.get()) {
|
||||
try {
|
||||
bindingRequired.wait(getPacketReplyTimeout());
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
if (!bindingRequired.get()) {
|
||||
// Server never offered resource binding, which is REQURIED in XMPP client and
|
||||
// server
|
||||
// implementations as per RFC6120 7.2
|
||||
throw new ResourceBindingNotOfferedException();
|
||||
}
|
||||
}
|
||||
if (!hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
|
||||
// Server never offered resource binding, which is REQURIED in XMPP client and
|
||||
// server implementations as per RFC6120 7.2
|
||||
throw new ResourceBindingNotOfferedException();
|
||||
}
|
||||
|
||||
// Resource binding, see RFC6120 7.
|
||||
|
@ -435,9 +421,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
throw e;
|
||||
}
|
||||
Bind response = packetCollector.nextResultOrThrow();
|
||||
String userJID = response.getJid();
|
||||
user = response.getJid();
|
||||
setServiceName(XmppStringUtils.parseDomain(user));
|
||||
|
||||
if (sessionSupported && !getConfiguration().isLegacySessionDisabled()) {
|
||||
if (hasFeature(Session.ELEMENT, Session.NAMESPACE) && !getConfiguration().isLegacySessionDisabled()) {
|
||||
Session session = new Session();
|
||||
packetCollector = createPacketCollector(new PacketIDFilter(session));
|
||||
try {
|
||||
|
@ -448,58 +435,59 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
}
|
||||
packetCollector.nextResultOrThrow();
|
||||
}
|
||||
return userJID;
|
||||
}
|
||||
|
||||
protected void setConnectionException(Exception e) {
|
||||
connectionException = e;
|
||||
}
|
||||
protected void afterSuccessfulLogin(final boolean anonymous, final boolean resumed) throws NotConnectedException {
|
||||
// Indicate that we're now authenticated.
|
||||
this.authenticated = true;
|
||||
this.anonymous = anonymous;
|
||||
|
||||
protected void throwConnectionExceptionOrNoResponse() throws IOException, NoResponseException, SmackException {
|
||||
if (connectionException != null) {
|
||||
if (connectionException instanceof IOException) {
|
||||
throw (IOException) connectionException;
|
||||
} else if (connectionException instanceof SmackException) {
|
||||
throw (SmackException) connectionException;
|
||||
} else {
|
||||
throw new SmackException(connectionException);
|
||||
}
|
||||
} else {
|
||||
throw new NoResponseException();
|
||||
// If debugging is enabled, change the the debug window title to include the
|
||||
// name we are now logged-in as.
|
||||
// If DEBUG_ENABLED was set to true AFTER the connection was created the debugger
|
||||
// will be null
|
||||
if (config.isDebuggerEnabled() && debugger != null) {
|
||||
debugger.userHasLogged(user);
|
||||
}
|
||||
callConnectionAuthenticatedListener();
|
||||
|
||||
// Set presence to online. It is important that this is done after
|
||||
// callConnectionAuthenticatedListener(), as this call will also
|
||||
// eventually load the roster. And we should load the roster before we
|
||||
// send the initial presence.
|
||||
if (config.isSendPresence() && !resumed) {
|
||||
sendPacket(new Presence(Presence.Type.available));
|
||||
}
|
||||
}
|
||||
|
||||
protected Reader getReader() {
|
||||
return reader;
|
||||
}
|
||||
|
||||
protected Writer getWriter() {
|
||||
return writer;
|
||||
@Override
|
||||
public boolean isAnonymous() {
|
||||
return anonymous;
|
||||
}
|
||||
|
||||
protected void setServiceName(String serviceName) {
|
||||
config.setServiceName(serviceName);
|
||||
}
|
||||
|
||||
|
||||
protected void setLoginInfo(String username, String password, String resource) {
|
||||
config.setLoginInfo(username, password, resource);
|
||||
}
|
||||
|
||||
protected void serverSupportsAccountCreation() {
|
||||
AccountManager.getInstance(this).setSupportsAccountCreation(true);
|
||||
}
|
||||
|
||||
protected void maybeResolveDns() throws Exception {
|
||||
config.maybeResolveDns();
|
||||
}
|
||||
|
||||
protected Lock getConnectionLock() {
|
||||
return connectionLock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Packet packet) throws NotConnectedException {
|
||||
if (!isConnected()) {
|
||||
throw new NotConnectedException();
|
||||
}
|
||||
if (packet == null) {
|
||||
throw new NullPointerException("Packet is null.");
|
||||
throw new IllegalArgumentException("Packet must not be null");
|
||||
}
|
||||
switch (fromMode) {
|
||||
case OMITTED:
|
||||
|
@ -800,35 +788,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the servers Entity Caps node
|
||||
*
|
||||
* XMPPConnection holds this information in order to avoid a dependency to
|
||||
* smack-extensions where EntityCapsManager lives from smack.
|
||||
*
|
||||
* @param node
|
||||
*/
|
||||
protected void setServiceCapsNode(String node) {
|
||||
serviceCapsNode = node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceCapsNode() {
|
||||
return serviceCapsNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRosterVersioningSupported() {
|
||||
return rosterVersioningSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the server supports roster versioning as defined in XEP-0237.
|
||||
*/
|
||||
protected void setRosterVersioningSupported() {
|
||||
rosterVersioningSupported = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPacketReplyTimeout() {
|
||||
return packetReplyTimeout;
|
||||
|
@ -1055,6 +1014,112 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
return config.isRosterLoadedAtLogin();
|
||||
}
|
||||
|
||||
protected final void parseFeatures(XmlPullParser parser) throws XmlPullParserException,
|
||||
IOException, SecurityRequiredException, NotConnectedException {
|
||||
streamFeatures.clear();
|
||||
final int initialDepth = parser.getDepth();
|
||||
while (true) {
|
||||
int eventType = parser.next();
|
||||
|
||||
if (eventType == XmlPullParser.START_TAG && parser.getDepth() == initialDepth + 1) {
|
||||
String name = parser.getName();
|
||||
String namespace = parser.getNamespace();
|
||||
switch (name) {
|
||||
case StartTls.ELEMENT:
|
||||
StartTls startTls = PacketParserUtils.parseStartTlsFeature(parser);
|
||||
addStreamFeature(startTls);
|
||||
break;
|
||||
case Mechanisms.ELEMENT:
|
||||
Mechanisms mechanisms = new Mechanisms(PacketParserUtils.parseMechanisms(parser));
|
||||
addStreamFeature(mechanisms);
|
||||
break;
|
||||
case Bind.ELEMENT:
|
||||
addStreamFeature(Bind.Feature.INSTANCE);
|
||||
break;
|
||||
case CapsExtension.ELEMENT:
|
||||
// Set the entity caps node for the server if one is send
|
||||
// See http://xmpp.org/extensions/xep-0115.html#stream
|
||||
String node = parser.getAttributeValue(null, "node");
|
||||
String ver = parser.getAttributeValue(null, "ver");
|
||||
String hash = parser.getAttributeValue(null, "hash");
|
||||
CapsExtension capsExtension = new CapsExtension(node, ver, hash);
|
||||
addStreamFeature(capsExtension);
|
||||
break;
|
||||
case Session.ELEMENT:
|
||||
addStreamFeature(Session.Feature.INSTANCE);
|
||||
break;
|
||||
case RosterVer.ELEMENT:
|
||||
if(namespace.equals(RosterVer.NAMESPACE)) {
|
||||
addStreamFeature(RosterVer.INSTANCE);
|
||||
}
|
||||
else {
|
||||
LOGGER.severe("Unkown Roster Versioning Namespace: "
|
||||
+ namespace
|
||||
+ ". Roster versioning not enabled");
|
||||
}
|
||||
break;
|
||||
case Compress.Feature.ELEMENT:
|
||||
Compress.Feature compression = PacketParserUtils.parseCompressionFeature(parser);
|
||||
addStreamFeature(compression);
|
||||
break;
|
||||
case Registration.Feature.ELEMENT:
|
||||
addStreamFeature(Registration.Feature.INSTANCE);
|
||||
break;
|
||||
default:
|
||||
parseFeaturesSubclass(name, namespace, parser);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (eventType == XmlPullParser.END_TAG && parser.getDepth() == initialDepth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE)) {
|
||||
// Only proceed with SASL auth if TLS is disabled or if the server doesn't announce it
|
||||
if (!hasFeature(StartTls.ELEMENT, StartTls.NAMESPACE)
|
||||
|| config.getSecurityMode() == SecurityMode.disabled) {
|
||||
saslFeatureReceived.reportSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
// If the server reported the bind feature then we are that that we did SASL and maybe
|
||||
// STARTTLS. We can then report that the last 'stream:features' have been parsed
|
||||
if (hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
|
||||
if (!hasFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE)
|
||||
|| !config.isCompressionEnabled()) {
|
||||
// This was was last features from the server is either it did not contain
|
||||
// compression or if we disabled it
|
||||
lastFeaturesReceived.reportSuccess();
|
||||
}
|
||||
}
|
||||
afterFeaturesReceived();
|
||||
}
|
||||
|
||||
protected void parseFeaturesSubclass (String name, String namespace, XmlPullParser parser) {
|
||||
// Default implementation does nothing
|
||||
}
|
||||
|
||||
protected void afterFeaturesReceived() throws SecurityRequiredException, NotConnectedException {
|
||||
// Default implementation does nothing
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <F extends PacketExtension> F getFeature(String element, String namespace) {
|
||||
return (F) streamFeatures.get(XmppStringUtils.generateKey(element, namespace));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFeature(String element, String namespace) {
|
||||
return getFeature(element, namespace) != null;
|
||||
}
|
||||
|
||||
protected void addStreamFeature(PacketExtension feature) {
|
||||
String key = XmppStringUtils.generateKey(feature.getElementName(), feature.getNamespace());
|
||||
streamFeatures.put(key, feature);
|
||||
}
|
||||
|
||||
private final ScheduledExecutorService removeCallbacksService = new ScheduledThreadPoolExecutor(1,
|
||||
new SmackExecutorThreadFactory(connectionCounterValue));
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
|
|||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.filter.AndFilter;
|
||||
import org.jivesoftware.smack.filter.IQReplyFilter;
|
||||
import org.jivesoftware.smack.filter.IQTypeFilter;
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
||||
|
@ -45,6 +44,7 @@ import org.jivesoftware.smack.packet.IQ;
|
|||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.packet.RosterPacket;
|
||||
import org.jivesoftware.smack.packet.RosterVer;
|
||||
import org.jivesoftware.smack.packet.RosterPacket.Item;
|
||||
import org.jivesoftware.smack.rosterstore.RosterStore;
|
||||
import org.jxmpp.util.XmppStringUtils;
|
||||
|
@ -227,12 +227,15 @@ public class Roster {
|
|||
}
|
||||
|
||||
RosterPacket packet = new RosterPacket();
|
||||
if (rosterStore != null && connection.isRosterVersioningSupported()) {
|
||||
if (rosterStore != null && isRosterVersioningSupported()) {
|
||||
packet.setVersion(rosterStore.getRosterVersion());
|
||||
}
|
||||
PacketFilter filter = new IQReplyFilter(packet, connection);
|
||||
connection.addPacketListener(new RosterResultListener(), filter);
|
||||
connection.sendPacket(packet);
|
||||
connection.sendIqWithResponseCallback(packet, new RosterResultListener(), new ExceptionCallback() {
|
||||
@Override
|
||||
public void processException(Exception exception) {
|
||||
LOGGER.log(Level.SEVERE, "Exception reloading roster" , exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -791,6 +794,10 @@ public class Roster {
|
|||
|| item.getItemType().equals(RosterPacket.ItemType.both);
|
||||
}
|
||||
|
||||
private boolean isRosterVersioningSupported() {
|
||||
return connection.hasFeature(RosterVer.ELEMENT, RosterVer.NAMESPACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* An enumeration for the subscription mode options.
|
||||
*/
|
||||
|
@ -939,23 +946,13 @@ public class Roster {
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles the case of the empty IQ-result for roster versioning.
|
||||
*
|
||||
* Intended to listen for a concrete roster result and deregisters
|
||||
* itself after a processed packet.
|
||||
* Handles roster reults as described in RFC 6121 2.1.4
|
||||
*/
|
||||
private class RosterResultListener implements PacketListener {
|
||||
|
||||
@Override
|
||||
public void processPacket(Packet packet) {
|
||||
connection.removePacketListener(this);
|
||||
|
||||
IQ result = (IQ)packet;
|
||||
if (!result.getType().equals(IQ.Type.result)) {
|
||||
LOGGER.severe("Roster result IQ not of type result. Packet: " + result.toXML());
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.fine("RosterResultListener received stanza");
|
||||
Collection<String> addedEntries = new ArrayList<String>();
|
||||
Collection<String> updatedEntries = new ArrayList<String>();
|
||||
Collection<String> deletedEntries = new ArrayList<String>();
|
||||
|
|
|
@ -19,17 +19,17 @@ package org.jivesoftware.smack;
|
|||
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.packet.Mechanisms;
|
||||
import org.jivesoftware.smack.sasl.SASLAnonymous;
|
||||
import org.jivesoftware.smack.sasl.SASLErrorException;
|
||||
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Success;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -128,13 +128,12 @@ public class SASLAuthentication {
|
|||
}
|
||||
|
||||
private final AbstractXMPPConnection connection;
|
||||
private Collection<String> serverMechanisms = new ArrayList<String>();
|
||||
private SASLMechanism currentMechanism = null;
|
||||
|
||||
/**
|
||||
* Boolean indicating if SASL negotiation has finished and was successful.
|
||||
*/
|
||||
private boolean saslNegotiated;
|
||||
private boolean authenticationSuccessful;
|
||||
|
||||
/**
|
||||
* Either of type {@link SmackException} or {@link SASLErrorException}
|
||||
|
@ -152,7 +151,7 @@ public class SASLAuthentication {
|
|||
* @return true if the server offered ANONYMOUS SASL as a way to authenticate users.
|
||||
*/
|
||||
public boolean hasAnonymousAuthentication() {
|
||||
return serverMechanisms.contains("ANONYMOUS");
|
||||
return serverMechanisms().contains("ANONYMOUS");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -161,7 +160,7 @@ public class SASLAuthentication {
|
|||
* @return true if the server offered SASL authentication besides ANONYMOUS SASL.
|
||||
*/
|
||||
public boolean hasNonAnonymousAuthentication() {
|
||||
return !serverMechanisms.isEmpty() && (serverMechanisms.size() != 1 || !hasAnonymousAuthentication());
|
||||
return !serverMechanisms().isEmpty() && (serverMechanisms().size() != 1 || !hasAnonymousAuthentication());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -197,7 +196,7 @@ public class SASLAuthentication {
|
|||
|
||||
maybeThrowException();
|
||||
|
||||
if (!saslNegotiated) {
|
||||
if (!authenticationSuccessful) {
|
||||
throw new NoResponseException();
|
||||
}
|
||||
}
|
||||
|
@ -244,7 +243,7 @@ public class SASLAuthentication {
|
|||
|
||||
maybeThrowException();
|
||||
|
||||
if (!saslNegotiated) {
|
||||
if (!authenticationSuccessful) {
|
||||
throw new NoResponseException();
|
||||
}
|
||||
}
|
||||
|
@ -283,7 +282,7 @@ public class SASLAuthentication {
|
|||
|
||||
maybeThrowException();
|
||||
|
||||
if (!saslNegotiated) {
|
||||
if (!authenticationSuccessful) {
|
||||
throw new NoResponseException();
|
||||
}
|
||||
}
|
||||
|
@ -299,17 +298,6 @@ public class SASLAuthentication {
|
|||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets the available SASL mechanism reported by the server. The server will report the
|
||||
* available SASL mechanism once the TLS negotiation was successful. This information is
|
||||
* stored and will be used when doing the authentication for logging in the user.
|
||||
*
|
||||
* @param mechanisms collection of strings with the available SASL mechanism reported
|
||||
* by the server.
|
||||
*/
|
||||
public void setAvailableSASLMethods(Collection<String> mechanisms) {
|
||||
this.serverMechanisms = mechanisms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #challengeReceived(String, boolean)}, with <code>finalChallenge</code> set
|
||||
|
@ -355,7 +343,7 @@ public class SASLAuthentication {
|
|||
if (success.getData() != null) {
|
||||
challengeReceived(success.getData(), true);
|
||||
}
|
||||
saslNegotiated = true;
|
||||
authenticationSuccessful = true;
|
||||
// Wake up the thread that is waiting in the #authenticate method
|
||||
synchronized (this) {
|
||||
notify();
|
||||
|
@ -381,13 +369,17 @@ public class SASLAuthentication {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean authenticationSuccessful() {
|
||||
return authenticationSuccessful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the internal state in order to be able to be reused. The authentication
|
||||
* is used by the connection at the first login and then reused after the connection
|
||||
* is disconnected and then reconnected.
|
||||
*/
|
||||
protected void init() {
|
||||
saslNegotiated = false;
|
||||
authenticationSuccessful = false;
|
||||
saslException = null;
|
||||
}
|
||||
|
||||
|
@ -404,7 +396,7 @@ public class SASLAuthentication {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
if (serverMechanisms.contains(mechanismName)) {
|
||||
if (serverMechanisms().contains(mechanismName)) {
|
||||
// Create a new instance of the SASLMechanism for every authentication attempt.
|
||||
selectedMechanism = mechanism.instanceForAuthentication(connection);
|
||||
break;
|
||||
|
@ -412,4 +404,9 @@ public class SASLAuthentication {
|
|||
}
|
||||
return selectedMechanism;
|
||||
}
|
||||
|
||||
private List<String> serverMechanisms() {
|
||||
Mechanisms mechanisms = connection.getFeature(Mechanisms.ELEMENT, Mechanisms.NAMESPACE);
|
||||
return mechanisms.getMechanisms();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,6 +89,17 @@ public class SmackException extends Exception {
|
|||
}
|
||||
}
|
||||
|
||||
public static class AlreadyConnectedException extends SmackException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 5011416918049135231L;
|
||||
|
||||
public AlreadyConnectedException() {
|
||||
}
|
||||
}
|
||||
|
||||
public static class NotConnectedException extends SmackException {
|
||||
|
||||
/**
|
||||
|
@ -120,6 +131,10 @@ public class SmackException extends Exception {
|
|||
|
||||
public SecurityRequiredException() {
|
||||
}
|
||||
|
||||
public SecurityRequiredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecurityNotPossibleException extends SmackException {
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||
|
||||
public class SynchronizationPoint<E extends Exception> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(SynchronizationPoint.class.getName());
|
||||
|
||||
private final AbstractXMPPConnection connection;
|
||||
private final Lock connectionLock;
|
||||
private final Condition condition;
|
||||
|
||||
private State state;
|
||||
private E failureException;
|
||||
|
||||
public SynchronizationPoint(AbstractXMPPConnection connection) {
|
||||
this.connection = connection;
|
||||
this.connectionLock = connection.getConnectionLock();
|
||||
this.condition = connection.getConnectionLock().newCondition();
|
||||
init();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
state = State.Initial;
|
||||
failureException = null;
|
||||
}
|
||||
|
||||
public void sendAndWaitForResponse(TopLevelStreamElement request) throws NoResponseException,
|
||||
NotConnectedException {
|
||||
assert (state == State.Initial);
|
||||
connectionLock.lock();
|
||||
try {
|
||||
if (request != null) {
|
||||
if (request instanceof Packet) {
|
||||
connection.sendPacket((Packet) request);
|
||||
}
|
||||
else if (request instanceof PlainStreamElement){
|
||||
connection.send((PlainStreamElement) request);
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported element type");
|
||||
}
|
||||
state = State.RequestSent;
|
||||
}
|
||||
waitForConditionOrTimeout();
|
||||
}
|
||||
finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
checkForResponse();
|
||||
}
|
||||
|
||||
public void sendAndWaitForResponseOrThrow(PlainStreamElement request) throws E, NoResponseException,
|
||||
NotConnectedException {
|
||||
sendAndWaitForResponse(request);
|
||||
switch (state) {
|
||||
case Failure:
|
||||
if (failureException != null) {
|
||||
throw failureException;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Success, do nothing
|
||||
}
|
||||
}
|
||||
|
||||
public void checkIfSuccessOrWaitOrThrow() throws NoResponseException, E {
|
||||
checkIfSuccessOrWait();
|
||||
if (state == State.Failure) {
|
||||
throw failureException;
|
||||
}
|
||||
}
|
||||
|
||||
public void checkIfSuccessOrWait() throws NoResponseException {
|
||||
connectionLock.lock();
|
||||
try {
|
||||
if (state == State.Success) {
|
||||
// Return immediately
|
||||
return;
|
||||
}
|
||||
waitForConditionOrTimeout();
|
||||
} finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
checkForResponse();
|
||||
}
|
||||
|
||||
public void reportSuccess() {
|
||||
connectionLock.lock();
|
||||
try {
|
||||
state = State.Success;
|
||||
condition.signal();
|
||||
}
|
||||
finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void reportFailure() {
|
||||
reportFailure(null);
|
||||
}
|
||||
|
||||
public void reportFailure(E failureException) {
|
||||
connectionLock.lock();
|
||||
try {
|
||||
state = State.Failure;
|
||||
this.failureException = failureException;
|
||||
condition.signal();
|
||||
}
|
||||
finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return state == State.Success;
|
||||
}
|
||||
|
||||
public boolean requestSent() {
|
||||
return state == State.RequestSent;
|
||||
}
|
||||
|
||||
private void waitForConditionOrTimeout() {
|
||||
long remainingWait = TimeUnit.MILLISECONDS.toNanos(connection.getPacketReplyTimeout());
|
||||
while (state == State.RequestSent || state == State.Initial) {
|
||||
try {
|
||||
remainingWait = condition.awaitNanos(
|
||||
remainingWait);
|
||||
if (remainingWait <= 0) {
|
||||
state = State.NoResponse;
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.log(Level.FINE, "was interrupted while waiting, this should not happen", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a response and throw a {@link NoResponseException} if there was none.
|
||||
* <p>
|
||||
* The exception is thrown, if state is one of 'Initial', 'NoResponse' or 'RequestSent'
|
||||
* </p>
|
||||
* @throws NoResponseException
|
||||
*/
|
||||
private void checkForResponse() throws NoResponseException {
|
||||
switch (state) {
|
||||
case Initial:
|
||||
case NoResponse:
|
||||
case RequestSent:
|
||||
throw new NoResponseException();
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private enum State {
|
||||
Initial,
|
||||
RequestSent,
|
||||
NoResponse,
|
||||
Success,
|
||||
Failure,
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@ import org.jivesoftware.smack.filter.IQReplyFilter;
|
|||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||
import org.jivesoftware.smack.rosterstore.RosterStore;
|
||||
|
||||
/**
|
||||
|
@ -151,6 +153,13 @@ public interface XMPPConnection {
|
|||
*/
|
||||
public void sendPacket(Packet packet) throws NotConnectedException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param element
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
public void send(PlainStreamElement element) throws NotConnectedException;
|
||||
|
||||
/**
|
||||
* Returns the roster for the user.
|
||||
* <p>
|
||||
|
@ -272,23 +281,6 @@ public interface XMPPConnection {
|
|||
*/
|
||||
public void removePacketInterceptor(PacketInterceptor packetInterceptor);
|
||||
|
||||
/**
|
||||
* Retrieve the servers Entity Caps node
|
||||
*
|
||||
* XMPPConnection holds this information in order to avoid a dependency to
|
||||
* smackx where EntityCapsManager lives from smack.
|
||||
*
|
||||
* @return the servers entity caps node
|
||||
*/
|
||||
public String getServiceCapsNode();
|
||||
|
||||
/**
|
||||
* Returns true if the server supports roster versioning as defined in XEP-0237.
|
||||
*
|
||||
* @return true if the server supports roster versioning
|
||||
*/
|
||||
public boolean isRosterVersioningSupported();
|
||||
|
||||
/**
|
||||
* Returns the current value of the reply timeout in milliseconds for request for this
|
||||
* XMPPConnection instance.
|
||||
|
@ -354,13 +346,33 @@ public interface XMPPConnection {
|
|||
|
||||
/**
|
||||
* Returns true if the roster will be loaded from the server when logging in. This
|
||||
* is the common behaviour for clients but sometimes clients may want to differ this
|
||||
* is the common behavior for clients but sometimes clients may want to differ this
|
||||
* or just never do it if not interested in rosters.
|
||||
*
|
||||
* @return true if the roster will be loaded from the server when logging in.
|
||||
* @see <a href="http://xmpp.org/rfcs/rfc6121.html#roster-login">RFC 6121 2.2 - Retrieving the Roster on Login</a>
|
||||
*/
|
||||
public boolean isRosterLoadedAtLogin();
|
||||
|
||||
/**
|
||||
* Get the feature packet extensions for a given stream feature of the
|
||||
* server, or <code>null</code> if the server doesn't support that feature.
|
||||
*
|
||||
* @param element
|
||||
* @param namespace
|
||||
* @return a packet extensions of the feature or <code>null</code>
|
||||
*/
|
||||
public <F extends PacketExtension> F getFeature(String element, String namespace);
|
||||
|
||||
/**
|
||||
* Return true if the server supports the given stream feature.
|
||||
*
|
||||
* @param element
|
||||
* @param namespace
|
||||
* @return
|
||||
*/
|
||||
public boolean hasFeature(String element, String namespace);
|
||||
|
||||
/**
|
||||
* Send a stanza and wait asynchronously for a response by using <code>replyFilter</code>.
|
||||
* <p>
|
||||
|
@ -464,4 +476,5 @@ public interface XMPPConnection {
|
|||
* @return the timestamp in milliseconds
|
||||
*/
|
||||
public long getLastStanzaReceived();
|
||||
|
||||
}
|
||||
|
|
|
@ -55,7 +55,6 @@ public abstract class XMPPException extends Exception {
|
|||
super(message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new XMPPException with a description of the exception and the
|
||||
* Throwable that was the root cause of the exception.
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.compress.packet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.packet.FullStreamElement;
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
public class Compress extends FullStreamElement {
|
||||
|
||||
public static final String ELEMENT = "compress";
|
||||
public static final String NAMESPACE = "http://jabber.org/protocol/compress";
|
||||
|
||||
public final String method;
|
||||
|
||||
public Compress(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.rightAngleBracket();
|
||||
xml.element("method", method);
|
||||
xml.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
|
||||
public static class Feature implements PacketExtension {
|
||||
public static final String ELEMENT = "compression";
|
||||
|
||||
public final List<String> methods;
|
||||
|
||||
public Feature(List<String> methods) {
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
public List<String> getMethods() {
|
||||
return Collections.unmodifiableList(methods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.rightAngleBracket();
|
||||
for (String method : methods) {
|
||||
xml.element("method", method);
|
||||
}
|
||||
xml.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.compress.packet;
|
||||
|
||||
import org.jivesoftware.smack.packet.FullStreamElement;
|
||||
|
||||
public class Compressed extends FullStreamElement {
|
||||
|
||||
public static final String ELEMENT = "compressed";
|
||||
public static final String NAMESPACE = Compress.NAMESPACE;
|
||||
|
||||
public static final Compressed INSTANCE = new Compressed();
|
||||
|
||||
private Compressed() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toXML() {
|
||||
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
|
||||
}
|
||||
}
|
|
@ -110,6 +110,7 @@ public class Java7ZlibInputOutputStream extends XMPPInputOutputStream {
|
|||
flushMethodInt = FULL_FLUSH_INT;
|
||||
}
|
||||
return new DeflaterOutputStream(outputStream, new Deflater(compressionLevel)) {
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
if (!supported) {
|
||||
super.flush();
|
||||
|
|
|
@ -176,10 +176,11 @@ public class ConsoleDebugger implements SmackDebugger {
|
|||
}
|
||||
|
||||
public void userHasLogged(String user) {
|
||||
boolean isAnonymous = "".equals(XmppStringUtils.parseLocalpart(user));
|
||||
String localpart = XmppStringUtils.parseLocalpart(user);
|
||||
boolean isAnonymous = "".equals(localpart);
|
||||
String title =
|
||||
"User logged (" + connection.hashCode() + "): "
|
||||
+ (isAnonymous ? "" : XmppStringUtils.parseBareAddress(user))
|
||||
+ (isAnonymous ? "" : localpart)
|
||||
+ "@"
|
||||
+ connection.getServiceName()
|
||||
+ ":"
|
||||
|
|
|
@ -51,18 +51,6 @@ public class Bind extends IQ {
|
|||
return jid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder getChildElementXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder();
|
||||
|
||||
xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngleBracket();
|
||||
xml.optElement("resource", resource);
|
||||
xml.optElement("jid", jid);
|
||||
xml.closeElement(ELEMENT);
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
public static Bind newSet(String resource) {
|
||||
Bind bind = new Bind(resource, null);
|
||||
bind.setType(IQ.Type.set);
|
||||
|
@ -72,4 +60,38 @@ public class Bind extends IQ {
|
|||
public static Bind newResult(String jid) {
|
||||
return new Bind(null, jid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder getChildElementXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder();
|
||||
xml.halfOpenElement(ELEMENT).xmlnsAttribute(NAMESPACE).rightAngleBracket();
|
||||
xml.optElement("resource", resource);
|
||||
xml.optElement("jid", jid);
|
||||
xml.closeElement(ELEMENT);
|
||||
return xml;
|
||||
}
|
||||
|
||||
public static class Feature implements PacketExtension {
|
||||
|
||||
public static final Feature INSTANCE = new Feature();
|
||||
|
||||
private Feature() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toXML() {
|
||||
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,22 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.caps.packet;
|
||||
package org.jivesoftware.smack.packet;
|
||||
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
import org.jivesoftware.smackx.caps.EntityCapsManager;
|
||||
|
||||
/**
|
||||
* A XEP-0115 Entity Capabilities extension.
|
||||
* <p>
|
||||
* Note that this is currently in smack-core as it's a potential stream feature.
|
||||
* TODO: In feature versions of Smack, it should be possible to register
|
||||
* "providers" for stream features too, so that this class can be moved back to
|
||||
* smack-extensions.
|
||||
* </p>
|
||||
*/
|
||||
public class CapsExtension implements PacketExtension {
|
||||
public static final String NAMESPACE = "http://jabber.org/protocol/caps";
|
||||
public static final String ELEMENT = "c";
|
||||
|
||||
private final String node, ver, hash;
|
||||
|
||||
|
@ -31,11 +40,11 @@ public class CapsExtension implements PacketExtension {
|
|||
}
|
||||
|
||||
public String getElementName() {
|
||||
return EntityCapsManager.ELEMENT;
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return EntityCapsManager.NAMESPACE;
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
public String getNode() {
|
||||
|
@ -50,17 +59,24 @@ public class CapsExtension implements PacketExtension {
|
|||
return hash;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* <pre>
|
||||
* <c xmlns='http://jabber.org/protocol/caps'
|
||||
* hash='sha-1'
|
||||
* node='http://code.google.com/p/exodus'
|
||||
* ver='QgayPKawpkPSDYmwT/WM94uAlu0='/>
|
||||
* hash='sha-1'
|
||||
* node='http://code.google.com/p/exodus'
|
||||
* ver='QgayPKawpkPSDYmwT/WM94uAlu0='/>
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public CharSequence toXML() {
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.attribute("hash", hash).attribute("node", node).attribute("ver", ver);
|
||||
xml.closeEmptyElement();
|
||||
return xml;
|
||||
}
|
||||
|
||||
public static CapsExtension from(Packet stanza) {
|
||||
return stanza.getExtension(ELEMENT, NAMESPACE);
|
||||
}
|
||||
}
|
|
@ -24,14 +24,7 @@ package org.jivesoftware.smack.packet;
|
|||
public interface Element {
|
||||
|
||||
/**
|
||||
* Returns the root element name.
|
||||
*
|
||||
* @return the element name.
|
||||
*/
|
||||
public String getElementName();
|
||||
|
||||
/**
|
||||
* Returns the XML representation of the PacketExtension.
|
||||
* Returns the XML representation of this Element.
|
||||
*
|
||||
* @return the packet extension as XML.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.packet;
|
||||
|
||||
/**
|
||||
* Base class for Stream elements. Everything that is not a stanza (RFC 6120 8.), ie. message,
|
||||
* presence and iq, should sublcass this class instead of {@link Packet}.
|
||||
*
|
||||
* @author Florian Schmaus
|
||||
*/
|
||||
public abstract class FullStreamElement extends PlainStreamElement implements PacketExtension {
|
||||
|
||||
}
|
|
@ -40,6 +40,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
|
|||
*/
|
||||
public abstract class IQ extends Packet {
|
||||
|
||||
public static final String ELEMENT = "iq";
|
||||
public static final String QUERY_ELEMENT = "query";
|
||||
|
||||
private Type type = Type.get;
|
||||
|
@ -78,7 +79,7 @@ public abstract class IQ extends Packet {
|
|||
@Override
|
||||
public CharSequence toXML() {
|
||||
XmlStringBuilder buf = new XmlStringBuilder();
|
||||
buf.halfOpenElement("iq");
|
||||
buf.halfOpenElement(ELEMENT);
|
||||
addCommonAttributes(buf);
|
||||
if (type == null) {
|
||||
buf.attribute("type", "get");
|
||||
|
@ -94,7 +95,7 @@ public abstract class IQ extends Packet {
|
|||
if (error != null) {
|
||||
buf.append(error.toXML());
|
||||
}
|
||||
buf.closeElement("iq");
|
||||
buf.closeElement(ELEMENT);
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.packet;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
public class Mechanisms implements PacketExtension {
|
||||
|
||||
public static final String ELEMENT = "mechanisms";
|
||||
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl";
|
||||
|
||||
public final List<String> mechanisms = new LinkedList<String>();
|
||||
|
||||
public Mechanisms(String mechanism) {
|
||||
mechanisms.add(mechanism);
|
||||
}
|
||||
|
||||
public Mechanisms(Collection<String> mechanisms) {
|
||||
this.mechanisms.addAll(mechanisms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
public List<String> getMechanisms() {
|
||||
return Collections.unmodifiableList(mechanisms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.rightAngleBracket();
|
||||
for (String mechanism : mechanisms) {
|
||||
xml.element("mechanism", mechanism);
|
||||
}
|
||||
xml.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
|
||||
}
|
|
@ -53,6 +53,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
|
|||
*/
|
||||
public class Message extends XmlLangStanza {
|
||||
|
||||
public static final String ELEMENT = "message";
|
||||
public static final String BODY = "body";
|
||||
|
||||
private Type type = Type.normal;
|
||||
|
@ -394,7 +395,7 @@ public class Message extends XmlLangStanza {
|
|||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder buf = new XmlStringBuilder();
|
||||
buf.halfOpenElement("message");
|
||||
buf.halfOpenElement(ELEMENT);
|
||||
buf.xmllangAttribute(language);
|
||||
addCommonAttributes(buf);
|
||||
if (type != Type.normal) {
|
||||
|
@ -440,7 +441,7 @@ public class Message extends XmlLangStanza {
|
|||
}
|
||||
// Add packet extensions, if any are defined.
|
||||
buf.append(getExtensionsXML());
|
||||
buf.closeElement("message");
|
||||
buf.closeElement(ELEMENT);
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.packet;
|
||||
|
||||
/**
|
||||
* Interface to represent a XML element. This is similar to {@link PacketExtension}, but does not
|
||||
* carry a namespace and is usually included as child element of an packet extension.
|
||||
*/
|
||||
public interface NamedElement extends Element {
|
||||
|
||||
/**
|
||||
* Returns the root element name.
|
||||
*
|
||||
* @return the element name.
|
||||
*/
|
||||
public String getElementName();
|
||||
|
||||
}
|
|
@ -34,7 +34,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||
*
|
||||
* @author Matt Tucker
|
||||
*/
|
||||
public abstract class Packet {
|
||||
public abstract class Packet extends TopLevelStreamElement {
|
||||
|
||||
public static final String TEXT = "text";
|
||||
public static final String ITEM = "item";
|
||||
|
@ -246,15 +246,6 @@ public abstract class Packet {
|
|||
packetExtensions.remove(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the packet as XML. Every concrete extension of Packet must implement
|
||||
* this method. In addition to writing out packet-specific data, every sub-class
|
||||
* should also write out the error and the extensions data if they are defined.
|
||||
*
|
||||
* @return the XML format of the packet as a String.
|
||||
*/
|
||||
public abstract CharSequence toXML();
|
||||
|
||||
/**
|
||||
* Returns the extension sub-packets (including properties data) as an XML
|
||||
* String, or the Empty String if there are no packet extensions.
|
||||
|
|
|
@ -28,7 +28,7 @@ package org.jivesoftware.smack.packet;
|
|||
* @see org.jivesoftware.smack.provider.PacketExtensionProvider
|
||||
* @author Matt Tucker
|
||||
*/
|
||||
public interface PacketExtension extends Element {
|
||||
public interface PacketExtension extends NamedElement {
|
||||
|
||||
/**
|
||||
* Returns the root element XML namespace.
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.packet;
|
||||
|
||||
/**
|
||||
* Plain stream elements, ie. everything that is <b>not a stanza</b> as defined
|
||||
* RFC 6120 8. Stanzas are {@link Message}, {@link Presence} and {@link IQ}.
|
||||
* Everything else should sublcass this class instead of {@link Packet}.
|
||||
* <p>
|
||||
* It is important to cleanly distinguish between stanzas and non-stanzas. For
|
||||
* example plain stream elements don't count into the stanza count of XEP-198
|
||||
* Stream Management.
|
||||
* </p>
|
||||
*
|
||||
* @author Florian Schmaus
|
||||
*/
|
||||
public abstract class PlainStreamElement extends TopLevelStreamElement {
|
||||
|
||||
}
|
|
@ -46,6 +46,8 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
|
|||
*/
|
||||
public class Registration extends IQ {
|
||||
|
||||
public static final String NAMESPACE = "jabber:iq:register";
|
||||
|
||||
private String instructions = null;
|
||||
private Map<String, String> attributes = null;
|
||||
|
||||
|
@ -90,8 +92,8 @@ public class Registration extends IQ {
|
|||
@Override
|
||||
public XmlStringBuilder getChildElementXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder();
|
||||
xml.halfOpenElement("query");
|
||||
xml.xmlnsAttribute("jabber:iq:register");
|
||||
xml.halfOpenElement(QUERY_ELEMENT);
|
||||
xml.xmlnsAttribute(NAMESPACE);
|
||||
xml.rightAngleBracket();
|
||||
xml.optElement("instructions", instructions);
|
||||
if (attributes != null && attributes.size() > 0) {
|
||||
|
@ -102,7 +104,33 @@ public class Registration extends IQ {
|
|||
}
|
||||
// Add packet extensions, if any are defined.
|
||||
xml.append(getExtensionsXML());
|
||||
xml.closeElement("query");
|
||||
xml.closeElement(QUERY_ELEMENT);
|
||||
return xml;
|
||||
}
|
||||
|
||||
public static class Feature implements PacketExtension {
|
||||
|
||||
public static final String ELEMENT = "register";
|
||||
public static final String NAMESPACE = "http://jabber.org/features/iq-register";
|
||||
public static final Feature INSTANCE = new Registration.Feature();
|
||||
|
||||
private Feature() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toXML() {
|
||||
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.packet;
|
||||
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
public class RosterVer implements PacketExtension {
|
||||
|
||||
public static final String ELEMENT = "ver";
|
||||
public static final String NAMESPACE = "urn:xmpp:features:rosterver";
|
||||
|
||||
public static final RosterVer INSTANCE = new RosterVer();
|
||||
|
||||
private RosterVer() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.closeEmptyElement();
|
||||
return xml;
|
||||
}
|
||||
|
||||
}
|
|
@ -32,12 +32,40 @@ package org.jivesoftware.smack.packet;
|
|||
*/
|
||||
public class Session extends IQ {
|
||||
|
||||
public static final String ELEMENT = "session";
|
||||
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-session";
|
||||
|
||||
private static final String SESSION = '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
|
||||
|
||||
public Session() {
|
||||
setType(IQ.Type.set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getChildElementXML() {
|
||||
return "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>";
|
||||
public String getChildElementXML() {
|
||||
return SESSION;
|
||||
}
|
||||
|
||||
public static class Feature implements PacketExtension {
|
||||
|
||||
public static final Session.Feature INSTANCE = new Feature();
|
||||
|
||||
private Feature() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toXML() {
|
||||
return SESSION;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.packet;
|
||||
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
public class StartTls extends FullStreamElement {
|
||||
|
||||
public static final String ELEMENT = "starttls";
|
||||
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls";
|
||||
|
||||
private final boolean required;
|
||||
|
||||
public StartTls() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public StartTls(boolean required) {
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public boolean required() {
|
||||
return required;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.rightAngleBracket();
|
||||
xml.condEmptyElement(required, "required");
|
||||
xml.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.packet;
|
||||
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class StreamOpen extends FullStreamElement {
|
||||
|
||||
public static final String ELEMENT = "stream:stream";
|
||||
public static final String NAMESPACE = "jabber:client";
|
||||
public static final String VERSION = "1.0";
|
||||
|
||||
private final String service;
|
||||
|
||||
public StreamOpen(String service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.attribute("to", service);
|
||||
xml.attribute("xmlns:stream", "http://etherx.jabber.org/streams");
|
||||
xml.attribute("version", VERSION);
|
||||
xml.rightAngleBracket();
|
||||
return xml;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.packet;
|
||||
|
||||
/**
|
||||
* A XMPP top level stream element. This is either a stanza ({@link Packet}) or
|
||||
* just a plain stream element ({@link PlainStreamElement}).
|
||||
*/
|
||||
public abstract class TopLevelStreamElement implements Element {
|
||||
|
||||
}
|
|
@ -72,6 +72,10 @@ public class XMPPError {
|
|||
private String message;
|
||||
private List<PacketExtension> applicationExtensions = null;
|
||||
|
||||
public XMPPError(String condition) {
|
||||
this(new Condition(condition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new error with the specified condition inferring the type.
|
||||
* If the Condition is predefined, client code should be like:
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.Map;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jxmpp.util.XmppStringUtils;
|
||||
|
||||
/**
|
||||
* Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of
|
||||
|
@ -111,13 +112,13 @@ public final class ProviderManager {
|
|||
public static void addLoader(ProviderLoader loader) {
|
||||
if (loader.getIQProviderInfo() != null) {
|
||||
for (IQProviderInfo info : loader.getIQProviderInfo()) {
|
||||
iqProviders.put(getProviderKey(info.getElementName(), info.getNamespace()), info.getProvider());
|
||||
iqProviders.put(getKey(info.getElementName(), info.getNamespace()), info.getProvider());
|
||||
}
|
||||
}
|
||||
|
||||
if (loader.getExtensionProviderInfo() != null) {
|
||||
for (ExtensionProviderInfo info : loader.getExtensionProviderInfo()) {
|
||||
extensionProviders.put(getProviderKey(info.getElementName(), info.getNamespace()), info.getProvider());
|
||||
extensionProviders.put(getKey(info.getElementName(), info.getNamespace()), info.getProvider());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +144,7 @@ public final class ProviderManager {
|
|||
* @return the IQ provider.
|
||||
*/
|
||||
public static Object getIQProvider(String elementName, String namespace) {
|
||||
String key = getProviderKey(elementName, namespace);
|
||||
String key = getKey(elementName, namespace);
|
||||
return iqProviders.get(key);
|
||||
}
|
||||
|
||||
|
@ -176,7 +177,7 @@ public final class ProviderManager {
|
|||
throw new IllegalArgumentException("Provider must be an IQProvider " +
|
||||
"or a Class instance sublcassing IQ.");
|
||||
}
|
||||
String key = getProviderKey(elementName, namespace);
|
||||
String key = getKey(elementName, namespace);
|
||||
iqProviders.put(key, provider);
|
||||
}
|
||||
|
||||
|
@ -189,7 +190,7 @@ public final class ProviderManager {
|
|||
* @param namespace the XML namespace.
|
||||
*/
|
||||
public static void removeIQProvider(String elementName, String namespace) {
|
||||
String key = getProviderKey(elementName, namespace);
|
||||
String key = getKey(elementName, namespace);
|
||||
iqProviders.remove(key);
|
||||
}
|
||||
|
||||
|
@ -213,7 +214,7 @@ public final class ProviderManager {
|
|||
* @return the extenion provider.
|
||||
*/
|
||||
public static Object getExtensionProvider(String elementName, String namespace) {
|
||||
String key = getProviderKey(elementName, namespace);
|
||||
String key = getKey(elementName, namespace);
|
||||
return extensionProviders.get(key);
|
||||
}
|
||||
|
||||
|
@ -233,7 +234,7 @@ public final class ProviderManager {
|
|||
throw new IllegalArgumentException("Provider must be a PacketExtensionProvider " +
|
||||
"or a Class instance.");
|
||||
}
|
||||
String key = getProviderKey(elementName, namespace);
|
||||
String key = getKey(elementName, namespace);
|
||||
extensionProviders.put(key, provider);
|
||||
}
|
||||
|
||||
|
@ -246,7 +247,7 @@ public final class ProviderManager {
|
|||
* @param namespace the XML namespace.
|
||||
*/
|
||||
public static void removeExtensionProvider(String elementName, String namespace) {
|
||||
String key = getProviderKey(elementName, namespace);
|
||||
String key = getKey(elementName, namespace);
|
||||
extensionProviders.remove(key);
|
||||
}
|
||||
|
||||
|
@ -261,14 +262,7 @@ public final class ProviderManager {
|
|||
return Collections.unmodifiableCollection(extensionProviders.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a String key for a given element name and namespace.
|
||||
*
|
||||
* @param elementName the element name.
|
||||
* @param namespace the namespace.
|
||||
* @return a unique key for the element name and namespace pair.
|
||||
*/
|
||||
private static String getProviderKey(String elementName, String namespace) {
|
||||
return elementName + '#' + namespace;
|
||||
private static String getKey(String elementName, String namespace) {
|
||||
return XmppStringUtils.generateKey(elementName, namespace);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||
|
||||
public class SASLErrorException extends XMPPException {
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@ package org.jivesoftware.smack.sasl;
|
|||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.AuthMechanism;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.Response;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
|
||||
|
@ -181,7 +181,7 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
|||
authenticationText = "=";
|
||||
}
|
||||
// Send the authentication to the server
|
||||
connection.sendPacket(new AuthMechanism(getName(), authenticationText));
|
||||
connection.send(new AuthMechanism(getName(), authenticationText));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -218,7 +218,7 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
|||
}
|
||||
|
||||
// Send the authentication to the server
|
||||
connection.sendPacket(responseStanza);
|
||||
connection.send(responseStanza);
|
||||
}
|
||||
|
||||
protected byte[] evaluateChallenge(byte[] challenge) throws SmackException {
|
||||
|
|
|
@ -16,18 +16,18 @@
|
|||
*/
|
||||
package org.jivesoftware.smack.sasl.packet;
|
||||
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||
import org.jivesoftware.smack.sasl.SASLError;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
public class SaslStanzas {
|
||||
public class SaslStreamElements {
|
||||
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-sasl";
|
||||
|
||||
/**
|
||||
* Initiating SASL authentication by select a mechanism.
|
||||
*/
|
||||
public static class AuthMechanism extends Packet {
|
||||
public static class AuthMechanism extends PlainStreamElement {
|
||||
public static final String ELEMENT = "auth";
|
||||
|
||||
private final String mechanism;
|
||||
|
@ -55,9 +55,9 @@ public class SaslStanzas {
|
|||
}
|
||||
|
||||
/**
|
||||
* A SASL challenge stanza.
|
||||
* A SASL challenge stream element.
|
||||
*/
|
||||
public static class Challenge extends Packet {
|
||||
public static class Challenge extends PlainStreamElement {
|
||||
public static final String ELEMENT = "challenge";
|
||||
|
||||
private final String data;
|
||||
|
@ -77,9 +77,9 @@ public class SaslStanzas {
|
|||
}
|
||||
|
||||
/**
|
||||
* A SASL response stanza.
|
||||
* A SASL response stream element.
|
||||
*/
|
||||
public static class Response extends Packet {
|
||||
public static class Response extends PlainStreamElement {
|
||||
public static final String ELEMENT = "response";
|
||||
|
||||
private final String authenticationText;
|
||||
|
@ -103,15 +103,15 @@ public class SaslStanzas {
|
|||
}
|
||||
|
||||
/**
|
||||
* A SASL success stanza.
|
||||
* A SASL success stream element.
|
||||
*/
|
||||
public static class Success extends Packet {
|
||||
public static class Success extends PlainStreamElement {
|
||||
public static final String ELEMENT = "success";
|
||||
|
||||
final private String data;
|
||||
|
||||
/**
|
||||
* Construct a new SASL success stanza with optional additional data for the SASL layer
|
||||
* Construct a new SASL success stream element with optional additional data for the SASL layer
|
||||
* (RFC6120 6.3.10)
|
||||
*
|
||||
* @param data additional data for the SASL layer or <code>null</code>
|
||||
|
@ -140,9 +140,9 @@ public class SaslStanzas {
|
|||
}
|
||||
|
||||
/**
|
||||
* A SASL failure stanza.
|
||||
* A SASL failure stream element.
|
||||
*/
|
||||
public static class SASLFailure extends Packet {
|
||||
public static class SASLFailure extends PlainStreamElement {
|
||||
public static final String ELEMENT = "failure";
|
||||
|
||||
private final SASLError saslError;
|
|
@ -123,15 +123,20 @@ public class DNSUtil {
|
|||
} else {
|
||||
srvDomain = domain;
|
||||
}
|
||||
List<SRVRecord> srvRecords = dnsResolver.lookupSRVRecords(srvDomain);
|
||||
if (LOGGER.isLoggable(Level.FINE)) {
|
||||
String logMessage = "Resolved SRV RR for " + srvDomain + ":";
|
||||
for (SRVRecord r : srvRecords)
|
||||
logMessage += " " + r;
|
||||
LOGGER.fine(logMessage);
|
||||
try {
|
||||
List<SRVRecord> srvRecords = dnsResolver.lookupSRVRecords(srvDomain);
|
||||
if (LOGGER.isLoggable(Level.FINE)) {
|
||||
String logMessage = "Resolved SRV RR for " + srvDomain + ":";
|
||||
for (SRVRecord r : srvRecords)
|
||||
logMessage += " " + r;
|
||||
LOGGER.fine(logMessage);
|
||||
}
|
||||
List<HostAddress> sortedRecords = sortSRVRecords(srvRecords);
|
||||
addresses.addAll(sortedRecords);
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.log(Level.WARNING, "Exception while resolving SRV records for " + domain, e);
|
||||
}
|
||||
List<HostAddress> sortedRecords = sortSRVRecords(srvRecords);
|
||||
addresses.addAll(sortedRecords);
|
||||
|
||||
// Step two: Add the hostname to the end of the list
|
||||
addresses.add(new HostAddress(domain));
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.lang.reflect.InvocationTargetException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -30,6 +31,7 @@ import java.util.logging.Level;
|
|||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.compress.packet.Compress;
|
||||
import org.jivesoftware.smack.packet.Bind;
|
||||
import org.jivesoftware.smack.packet.DefaultPacketExtension;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
|
@ -39,12 +41,13 @@ import org.jivesoftware.smack.packet.PacketExtension;
|
|||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.packet.Registration;
|
||||
import org.jivesoftware.smack.packet.RosterPacket;
|
||||
import org.jivesoftware.smack.packet.StartTls;
|
||||
import org.jivesoftware.smack.packet.StreamError;
|
||||
import org.jivesoftware.smack.packet.XMPPError;
|
||||
import org.jivesoftware.smack.provider.IQProvider;
|
||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||
import org.jivesoftware.smack.provider.ProviderManager;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStanzas.SASLFailure;
|
||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
@ -63,9 +66,7 @@ public class PacketParserUtils {
|
|||
}
|
||||
|
||||
public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
|
||||
XmlPullParser parser = newXmppParser();
|
||||
parser.setInput(reader);
|
||||
|
||||
XmlPullParser parser = newXmppParser(reader);
|
||||
// Wind the parser forward to the first start tag
|
||||
int event = parser.getEventType();
|
||||
while (event != XmlPullParser.START_TAG) {
|
||||
|
@ -116,20 +117,17 @@ public class PacketParserUtils {
|
|||
* @throws Exception
|
||||
*/
|
||||
public static Packet parseStanza(XmlPullParser parser, XMPPConnection connection) throws Exception {
|
||||
final int eventType = parser.getEventType();
|
||||
if (eventType != XmlPullParser.START_TAG) {
|
||||
throw new IllegalArgumentException("Parser not at start tag");
|
||||
}
|
||||
assert(parser.getEventType() == XmlPullParser.START_TAG);
|
||||
final String name = parser.getName();
|
||||
switch (name) {
|
||||
case "message":
|
||||
case Message.ELEMENT:
|
||||
return parseMessage(parser);
|
||||
case "iq":
|
||||
case IQ.ELEMENT:
|
||||
return parseIQ(parser, connection);
|
||||
case "presence":
|
||||
case Presence.ELEMENT:
|
||||
return parsePresence(parser);
|
||||
default:
|
||||
return null;
|
||||
throw new IllegalArgumentException("Can only parse message, iq or presence, not " + name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,6 +149,25 @@ public class PacketParserUtils {
|
|||
return parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
|
||||
* FEATURE_PROCESS_NAMESPACES is enabled.
|
||||
* <p>
|
||||
* Note that not all XmlPullParser implementations will return a String on
|
||||
* <code>getText()</code> if the parser is on START_TAG or END_TAG. So you must not rely on this
|
||||
* behavior when using the parser.
|
||||
* </p>
|
||||
*
|
||||
* @param reader
|
||||
* @return A suitable XmlPullParser for XMPP parsing
|
||||
* @throws XmlPullParserException
|
||||
*/
|
||||
public static XmlPullParser newXmppParser(Reader reader) throws XmlPullParserException {
|
||||
XmlPullParser parser = newXmppParser();
|
||||
parser.setInput(reader);
|
||||
return parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a message packet.
|
||||
*
|
||||
|
@ -525,8 +542,7 @@ public class PacketParserUtils {
|
|||
else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) {
|
||||
iqPacket = parseRegistration(parser);
|
||||
}
|
||||
else if (elementName.equals("bind") &&
|
||||
namespace.equals("urn:ietf:params:xml:ns:xmpp-bind")) {
|
||||
else if (elementName.equals(Bind.ELEMENT) && namespace.equals(Bind.NAMESPACE)) {
|
||||
iqPacket = parseResourceBinding(parser);
|
||||
}
|
||||
// Otherwise, see if there is a registered provider for
|
||||
|
@ -688,25 +704,35 @@ public class PacketParserUtils {
|
|||
return registration;
|
||||
}
|
||||
|
||||
private static Bind parseResourceBinding(XmlPullParser parser) throws IOException,
|
||||
XmlPullParserException {
|
||||
public static Bind parseResourceBinding(XmlPullParser parser) throws IOException,
|
||||
XmlPullParserException {
|
||||
assert (parser.getEventType() == XmlPullParser.START_TAG);
|
||||
int initalDepth = parser.getDepth();
|
||||
String name;
|
||||
Bind bind = null;
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
outerloop: while (true) {
|
||||
int eventType = parser.next();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
if (parser.getName().equals("resource")) {
|
||||
switch (eventType) {
|
||||
case XmlPullParser.START_TAG:
|
||||
name = parser.getName();
|
||||
switch (name) {
|
||||
case "resource":
|
||||
bind = Bind.newSet(parser.nextText());
|
||||
}
|
||||
else if (parser.getName().equals("jid")) {
|
||||
break;
|
||||
case "jid":
|
||||
bind = Bind.newResult(parser.nextText());
|
||||
break;
|
||||
}
|
||||
} else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (parser.getName().equals(Bind.ELEMENT)) {
|
||||
done = true;
|
||||
break;
|
||||
case XmlPullParser.END_TAG:
|
||||
name = parser.getName();
|
||||
if (name.equals(Bind.ELEMENT) && parser.getDepth() == initalDepth) {
|
||||
break outerloop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert (parser.getEventType() == XmlPullParser.END_TAG);
|
||||
return bind;
|
||||
}
|
||||
|
||||
|
@ -715,9 +741,11 @@ public class PacketParserUtils {
|
|||
*
|
||||
* @param parser the XML parser, positioned at the start of the mechanisms stanza.
|
||||
* @return a collection of Stings with the mechanisms included in the mechanisms stanza.
|
||||
* @throws Exception if an exception occurs while parsing the stanza.
|
||||
* @throws IOException
|
||||
* @throws XmlPullParserException
|
||||
*/
|
||||
public static Collection<String> parseMechanisms(XmlPullParser parser) throws Exception {
|
||||
public static Collection<String> parseMechanisms(XmlPullParser parser)
|
||||
throws XmlPullParserException, IOException {
|
||||
List<String> mechanisms = new ArrayList<String>();
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
|
@ -739,32 +767,42 @@ public class PacketParserUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse the available compression methods reported from the server.
|
||||
* Parse the Compression Feature reported from the server.
|
||||
*
|
||||
* @param parser the XML parser, positioned at the start of the compression stanza.
|
||||
* @return a collection of Stings with the methods included in the compression stanza.
|
||||
* @return The CompressionFeature stream element
|
||||
* @throws XmlPullParserException if an exception occurs while parsing the stanza.
|
||||
*/
|
||||
public static Collection<String> parseCompressionMethods(XmlPullParser parser)
|
||||
throws IOException, XmlPullParserException {
|
||||
List<String> methods = new ArrayList<String>();
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
public static Compress.Feature parseCompressionFeature(XmlPullParser parser)
|
||||
throws IOException, XmlPullParserException {
|
||||
assert (parser.getEventType() == XmlPullParser.START_TAG);
|
||||
String name;
|
||||
final int initialDepth = parser.getDepth();
|
||||
List<String> methods = new LinkedList<String>();
|
||||
outerloop: while (true) {
|
||||
int eventType = parser.next();
|
||||
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String elementName = parser.getName();
|
||||
if (elementName.equals("method")) {
|
||||
switch (eventType) {
|
||||
case XmlPullParser.START_TAG:
|
||||
name = parser.getName();
|
||||
switch (name) {
|
||||
case "method":
|
||||
methods.add(parser.nextText());
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (parser.getName().equals("compression")) {
|
||||
done = true;
|
||||
break;
|
||||
case XmlPullParser.END_TAG:
|
||||
name = parser.getName();
|
||||
switch (name) {
|
||||
case Compress.Feature.ELEMENT:
|
||||
if (parser.getDepth() == initialDepth) {
|
||||
break outerloop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
assert (parser.getEventType() == XmlPullParser.END_TAG);
|
||||
assert (parser.getDepth() == initialDepth);
|
||||
return new Compress.Feature(methods);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -840,21 +878,16 @@ public class PacketParserUtils {
|
|||
* @throws Exception if an exception occurs while parsing the packet.
|
||||
*/
|
||||
public static XMPPError parseError(XmlPullParser parser) throws Exception {
|
||||
final String errorNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
||||
String type = null;
|
||||
String message = null;
|
||||
String condition = null;
|
||||
List<PacketExtension> extensions = new ArrayList<PacketExtension>();
|
||||
|
||||
// Parse the error header
|
||||
for (int i=0; i<parser.getAttributeCount(); i++) {
|
||||
if (parser.getAttributeName(i).equals("type")) {
|
||||
type = parser.getAttributeValue("", "type");
|
||||
}
|
||||
}
|
||||
boolean done = false;
|
||||
type = parser.getAttributeValue("", "type");
|
||||
// Parse the text and condition tags
|
||||
while (!done) {
|
||||
outerloop:
|
||||
while (true) {
|
||||
int eventType = parser.next();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
if (parser.getName().equals(Packet.TEXT)) {
|
||||
|
@ -864,7 +897,7 @@ public class PacketParserUtils {
|
|||
// Condition tag, it can be xmpp error or an application defined error.
|
||||
String elementName = parser.getName();
|
||||
String namespace = parser.getNamespace();
|
||||
if (errorNamespace.equals(namespace)) {
|
||||
if (namespace.equals(XMPPError.NAMESPACE)) {
|
||||
condition = elementName;
|
||||
}
|
||||
else {
|
||||
|
@ -874,7 +907,7 @@ public class PacketParserUtils {
|
|||
}
|
||||
else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (parser.getName().equals("error")) {
|
||||
done = true;
|
||||
break outerloop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -942,6 +975,33 @@ public class PacketParserUtils {
|
|||
return extension;
|
||||
}
|
||||
|
||||
public static StartTls parseStartTlsFeature(XmlPullParser parser)
|
||||
throws XmlPullParserException, IOException {
|
||||
assert (parser.getEventType() == XmlPullParser.START_TAG);
|
||||
assert (parser.getNamespace().equals(StartTls.NAMESPACE));
|
||||
int initalDepth = parser.getDepth();
|
||||
boolean required = false;
|
||||
outerloop: while (true) {
|
||||
int event = parser.next();
|
||||
switch (event) {
|
||||
case XmlPullParser.START_TAG:
|
||||
String name = parser.getName();
|
||||
switch (name) {
|
||||
case "required":
|
||||
required = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case XmlPullParser.END_TAG:
|
||||
if (parser.getDepth() == initalDepth) {
|
||||
break outerloop;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(parser.getEventType() == XmlPullParser.END_TAG);
|
||||
return new StartTls(required);
|
||||
}
|
||||
|
||||
private static String getLanguageAttribute(XmlPullParser parser) {
|
||||
for (int i = 0; i < parser.getAttributeCount(); i++) {
|
||||
String attributeName = parser.getAttributeName(i);
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 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.util.Locale;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
public class ParserUtils {
|
||||
public static void assertAtStartTag(XmlPullParser parser) throws XmlPullParserException {
|
||||
assert(parser.getEventType() == XmlPullParser.START_TAG);
|
||||
}
|
||||
|
||||
public static void assertAtEndTag(XmlPullParser parser) throws XmlPullParserException {
|
||||
assert(parser.getEventType() == XmlPullParser.END_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the boolean value of an argument.
|
||||
*
|
||||
* @param parser
|
||||
* @param name
|
||||
* @return the boolean value or null of no argument of the given name exists
|
||||
*/
|
||||
public static Boolean getBooleanAttribute(XmlPullParser parser, String name) {
|
||||
String valueString = parser.getAttributeValue("", name);
|
||||
if (valueString == null)
|
||||
return null;
|
||||
valueString = valueString.toLowerCase(Locale.US);
|
||||
if (valueString.equals("true") || valueString.equals("0")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean getBooleanAttribute(XmlPullParser parser, String name,
|
||||
boolean defaultValue) {
|
||||
Boolean bool = getBooleanAttribute(parser, name);
|
||||
if (bool == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
else {
|
||||
return bool;
|
||||
}
|
||||
}
|
||||
|
||||
public static Integer getIntegerAttribute(XmlPullParser parser, String name) {
|
||||
String valueString = parser.getAttributeValue("", name);
|
||||
if (valueString == null)
|
||||
return null;
|
||||
return Integer.valueOf(valueString);
|
||||
}
|
||||
|
||||
public static int getIntegerAttribute(XmlPullParser parser, String name, int defaultValue) {
|
||||
Integer integer = getIntegerAttribute(parser, name);
|
||||
if (integer == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
else {
|
||||
return integer;
|
||||
}
|
||||
}
|
||||
|
||||
public static Long getLongAttribute(XmlPullParser parser, String name) {
|
||||
String valueString = parser.getAttributeValue("", name);
|
||||
if (valueString == null)
|
||||
return null;
|
||||
return Long.valueOf(valueString);
|
||||
}
|
||||
|
||||
public static long getLongAttribute(XmlPullParser parser, String name, long defaultValue) {
|
||||
Long l = getLongAttribute(parser, name);
|
||||
if (l == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
else {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package org.jivesoftware.smack.util;
|
||||
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.NamedElement;
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
|
||||
public class XmlStringBuilder implements Appendable, CharSequence {
|
||||
|
@ -33,7 +34,7 @@ public class XmlStringBuilder implements Appendable, CharSequence {
|
|||
prelude(pe);
|
||||
}
|
||||
|
||||
public XmlStringBuilder(Element e) {
|
||||
public XmlStringBuilder(NamedElement e) {
|
||||
this();
|
||||
halfOpenElement(e.getElementName());
|
||||
}
|
||||
|
@ -101,7 +102,7 @@ public class XmlStringBuilder implements Appendable, CharSequence {
|
|||
return this;
|
||||
}
|
||||
|
||||
public XmlStringBuilder closeElement(Element e) {
|
||||
public XmlStringBuilder closeElement(NamedElement e) {
|
||||
closeElement(e.getElementName());
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -23,9 +23,10 @@ import java.util.concurrent.BlockingQueue;
|
|||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||
|
||||
/**
|
||||
* A dummy implementation of {@link XMPPConnection}, intended to be used during
|
||||
|
@ -53,7 +54,7 @@ public class DummyConnection extends AbstractXMPPConnection {
|
|||
private String connectionID;
|
||||
private Roster roster;
|
||||
|
||||
private final BlockingQueue<Packet> queue = new LinkedBlockingQueue<Packet>();
|
||||
private final BlockingQueue<Element> queue = new LinkedBlockingQueue<Element>();
|
||||
|
||||
public DummyConnection() {
|
||||
this(new ConnectionConfiguration("example.com"));
|
||||
|
@ -178,6 +179,14 @@ public class DummyConnection extends AbstractXMPPConnection {
|
|||
authenticated = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(PlainStreamElement element) {
|
||||
if (SmackConfiguration.DEBUG_ENABLED) {
|
||||
System.out.println("[SEND]: " + element.toXML());
|
||||
}
|
||||
queue.add(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendPacketInternal(Packet packet) {
|
||||
if (SmackConfiguration.DEBUG_ENABLED) {
|
||||
|
@ -204,7 +213,7 @@ public class DummyConnection extends AbstractXMPPConnection {
|
|||
* @throws InterruptedException
|
||||
*/
|
||||
public Packet getSentPacket() throws InterruptedException {
|
||||
return queue.poll();
|
||||
return (Packet) queue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -217,7 +226,7 @@ public class DummyConnection extends AbstractXMPPConnection {
|
|||
* @throws InterruptedException
|
||||
*/
|
||||
public Packet getSentPacket(int wait) throws InterruptedException {
|
||||
return queue.poll(wait, TimeUnit.SECONDS);
|
||||
return (Packet) queue.poll(wait, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -69,8 +69,6 @@ public class RosterVersioningTest {
|
|||
connection = new DummyConnection(conf);
|
||||
connection.connect();
|
||||
|
||||
connection.setRosterVersioningSupported();
|
||||
|
||||
connection.login("rostertest", "secret");
|
||||
}
|
||||
|
||||
|
|
|
@ -47,8 +47,7 @@ final public class TestUtils {
|
|||
public static XmlPullParser getParser(Reader reader, String startTag) {
|
||||
XmlPullParser parser;
|
||||
try {
|
||||
parser = PacketParserUtils.newXmppParser();
|
||||
parser.setInput(reader);
|
||||
parser = PacketParserUtils.newXmppParser(reader);
|
||||
if (startTag == null) {
|
||||
return parser;
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.NamedElement;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
/**
|
||||
|
@ -265,7 +265,7 @@ public class Bytestream extends IQ {
|
|||
*
|
||||
* @author Alexander Wenckus
|
||||
*/
|
||||
public static class StreamHost implements Element {
|
||||
public static class StreamHost implements NamedElement {
|
||||
|
||||
public static String ELEMENTNAME = "streamhost";
|
||||
|
||||
|
@ -343,7 +343,7 @@ public class Bytestream extends IQ {
|
|||
*
|
||||
* @author Alexander Wenckus
|
||||
*/
|
||||
public static class StreamHostUsed implements Element {
|
||||
public static class StreamHostUsed implements NamedElement {
|
||||
|
||||
public static String ELEMENTNAME = "streamhost-used";
|
||||
|
||||
|
@ -385,7 +385,7 @@ public class Bytestream extends IQ {
|
|||
*
|
||||
* @author Alexander Wenckus
|
||||
*/
|
||||
public static class Activate implements Element {
|
||||
public static class Activate implements NamedElement {
|
||||
|
||||
public static String ELEMENTNAME = "activate";
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.jivesoftware.smack.PacketInterceptor;
|
|||
import org.jivesoftware.smack.PacketListener;
|
||||
import org.jivesoftware.smack.XMPPConnectionRegistry;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.packet.CapsExtension;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
|
@ -38,7 +39,6 @@ import org.jivesoftware.smack.filter.PacketExtensionFilter;
|
|||
import org.jivesoftware.smack.util.Cache;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache;
|
||||
import org.jivesoftware.smackx.caps.packet.CapsExtension;
|
||||
import org.jivesoftware.smackx.disco.NodeInformationProvider;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
||||
|
@ -74,8 +74,8 @@ import java.security.NoSuchAlgorithmException;
|
|||
public class EntityCapsManager extends Manager {
|
||||
private static final Logger LOGGER = Logger.getLogger(EntityCapsManager.class.getName());
|
||||
|
||||
public static final String NAMESPACE = "http://jabber.org/protocol/caps";
|
||||
public static final String ELEMENT = "c";
|
||||
public static final String NAMESPACE = CapsExtension.NAMESPACE;
|
||||
public static final String ELEMENT = CapsExtension.ELEMENT;
|
||||
|
||||
private static final Map<String, MessageDigest> SUPPORTED_HASHES = new HashMap<String, MessageDigest>();
|
||||
private static String DEFAULT_ENTITY_NODE = "http://www.igniterealtime.org/projects/smack";
|
||||
|
@ -239,6 +239,17 @@ public class EntityCapsManager extends Manager {
|
|||
CAPS_CACHE.clear();
|
||||
}
|
||||
|
||||
private static void addCapsExtensionInfo(String from, CapsExtension capsExtension) {
|
||||
String hash = capsExtension.getHash().toLowerCase(Locale.US);
|
||||
if (!SUPPORTED_HASHES.containsKey(hash))
|
||||
return;
|
||||
|
||||
String node = capsExtension.getNode();
|
||||
String ver = capsExtension.getVer();
|
||||
|
||||
JID_TO_NODEVER_CACHE.put(from, new NodeVerHash(node, ver, hash));
|
||||
}
|
||||
|
||||
private final Queue<String> lastLocalCapsVersions = new ConcurrentLinkedQueue<String>();
|
||||
|
||||
private final ServiceDiscoveryManager sdm;
|
||||
|
@ -258,6 +269,20 @@ public class EntityCapsManager extends Manager {
|
|||
instances.put(connection, this);
|
||||
|
||||
connection.addConnectionListener(new AbstractConnectionListener() {
|
||||
@Override
|
||||
public void connected(XMPPConnection connection) {
|
||||
// It's not clear when a server would report the caps stream
|
||||
// feature, so we try to process it after we are connected and
|
||||
// once after we are authenticated.
|
||||
processCapsStreamFeatureIfAvailable(connection);
|
||||
}
|
||||
@Override
|
||||
public void authenticated(XMPPConnection connection) {
|
||||
// It's not clear when a server would report the caps stream
|
||||
// feature, so we try to process it after we are connected and
|
||||
// once after we are authenticated.
|
||||
processCapsStreamFeatureIfAvailable(connection);
|
||||
}
|
||||
@Override
|
||||
public void connectionClosed() {
|
||||
presenceSend = false;
|
||||
|
@ -266,6 +291,16 @@ public class EntityCapsManager extends Manager {
|
|||
public void connectionClosedOnError(Exception e) {
|
||||
presenceSend = false;
|
||||
}
|
||||
|
||||
private void processCapsStreamFeatureIfAvailable(XMPPConnection connection) {
|
||||
CapsExtension capsExtension = connection.getFeature(
|
||||
CapsExtension.ELEMENT, CapsExtension.NAMESPACE);
|
||||
if (capsExtension == null) {
|
||||
return;
|
||||
}
|
||||
String from = connection.getServiceName();
|
||||
addCapsExtensionInfo(from, capsExtension);
|
||||
}
|
||||
});
|
||||
|
||||
// This calculates the local entity caps version
|
||||
|
@ -282,18 +317,9 @@ public class EntityCapsManager extends Manager {
|
|||
if (!entityCapsEnabled())
|
||||
return;
|
||||
|
||||
CapsExtension ext = (CapsExtension) packet.getExtension(EntityCapsManager.ELEMENT,
|
||||
EntityCapsManager.NAMESPACE);
|
||||
|
||||
String hash = ext.getHash().toLowerCase(Locale.US);
|
||||
if (!SUPPORTED_HASHES.containsKey(hash))
|
||||
return;
|
||||
|
||||
CapsExtension capsExtension = CapsExtension.from(packet);
|
||||
String from = packet.getFrom();
|
||||
String node = ext.getNode();
|
||||
String ver = ext.getVer();
|
||||
|
||||
JID_TO_NODEVER_CACHE.put(from, new NodeVerHash(node, ver, hash));
|
||||
addCapsExtensionInfo(from, capsExtension);
|
||||
}
|
||||
|
||||
}, PRESENCES_WITH_CAPS);
|
||||
|
|
|
@ -19,10 +19,10 @@ package org.jivesoftware.smackx.caps.provider;
|
|||
import java.io.IOException;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.packet.CapsExtension;
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||
import org.jivesoftware.smackx.caps.EntityCapsManager;
|
||||
import org.jivesoftware.smackx.caps.packet.CapsExtension;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.jxmpp.util.XmppDateTime;
|
|||
*/
|
||||
public class DelayInformationProvider extends AbstractDelayInformationProvider {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
protected Date parseDate(String string) throws ParseException {
|
||||
return XmppDateTime.parseXEP0082Date(string);
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.muc.packet;
|
||||
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.NamedElement;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
/**
|
||||
|
@ -26,7 +26,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
|
|||
*
|
||||
* @author Gaston Dombiak
|
||||
*/
|
||||
public class Destroy implements Element {
|
||||
public class Destroy implements NamedElement {
|
||||
public static final String ELEMENT = "destroy";
|
||||
|
||||
private String reason;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.jivesoftware.smackx.muc.packet;
|
||||
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.NamedElement;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
@ -119,7 +119,7 @@ public class MUCInitialPresence implements PacketExtension {
|
|||
*
|
||||
* @author Gaston Dombiak
|
||||
*/
|
||||
public static class History implements Element {
|
||||
public static class History implements NamedElement {
|
||||
|
||||
public static final String ELEMENT = "history";
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.muc.packet;
|
||||
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.NamedElement;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
import org.jivesoftware.smackx.muc.MUCAffiliation;
|
||||
import org.jivesoftware.smackx.muc.MUCRole;
|
||||
|
@ -27,7 +27,7 @@ import org.jivesoftware.smackx.muc.MUCRole;
|
|||
*
|
||||
* @author Gaston Dombiak
|
||||
*/
|
||||
public class MUCItem implements Element {
|
||||
public class MUCItem implements NamedElement {
|
||||
public static final String ELEMENT = IQ.ITEM;
|
||||
|
||||
private final MUCAffiliation affiliation;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.jivesoftware.smackx.muc.packet;
|
||||
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.NamedElement;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -218,7 +218,7 @@ public class MUCUser implements PacketExtension {
|
|||
*
|
||||
* @author Gaston Dombiak
|
||||
*/
|
||||
public static class Invite implements Element {
|
||||
public static class Invite implements NamedElement {
|
||||
public static final String ELEMENT ="invite";
|
||||
|
||||
private String reason;
|
||||
|
@ -304,7 +304,7 @@ public class MUCUser implements PacketExtension {
|
|||
*
|
||||
* @author Gaston Dombiak
|
||||
*/
|
||||
public static class Decline implements Element {
|
||||
public static class Decline implements NamedElement {
|
||||
public static final String ELEMENT = "decline";
|
||||
|
||||
private String reason;
|
||||
|
@ -390,7 +390,7 @@ public class MUCUser implements PacketExtension {
|
|||
*
|
||||
* @author Gaston Dombiak
|
||||
*/
|
||||
public static class Status implements Element {
|
||||
public static class Status implements NamedElement {
|
||||
public static final String ELEMENT = "status";
|
||||
|
||||
private static final Map<Integer, Status> statusMap = new HashMap<Integer, Status>(8);
|
||||
|
|
|
@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
|
|||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
@ -27,6 +28,8 @@ import java.util.GregorianCalendar;
|
|||
import java.util.Properties;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.xml.parsers.FactoryConfigurationError;
|
||||
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.jxmpp.util.XmppDateTime;
|
||||
|
@ -35,14 +38,17 @@ import org.jivesoftware.smackx.delay.DelayInformationManager;
|
|||
import org.jivesoftware.smackx.delay.packet.DelayInformation;
|
||||
import org.junit.Test;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import com.jamesmurty.utils.XMLBuilder;
|
||||
|
||||
public class DelayInformationTest extends InitExtensions {
|
||||
|
||||
private static final Calendar calendar = new GregorianCalendar(2002, 9 - 1, 10, 23, 8, 25);
|
||||
private static Properties outputProperties = new Properties();
|
||||
static {
|
||||
outputProperties.put(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -95,8 +101,6 @@ public class DelayInformationTest extends InitExtensions {
|
|||
DelayInformationProvider p = new DelayInformationProvider();
|
||||
DelayInformation delayInfo;
|
||||
String control;
|
||||
GregorianCalendar calendar = new GregorianCalendar(2002, 9 - 1, 10, 23, 8, 25);
|
||||
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
// XEP-0082 date format
|
||||
control = XMLBuilder.create("delay")
|
||||
|
@ -132,6 +136,14 @@ public class DelayInformationTest extends InitExtensions {
|
|||
delayInfo = (DelayInformation) p.parseExtension(PacketParserUtils.getParserFor(control));
|
||||
|
||||
assertEquals(calendar.getTime(), delayInfo.getStamp());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyDateFormatsTest() throws FactoryConfigurationError, XmlPullParserException, IOException, Exception {
|
||||
LegacyDelayInformationProvider p = new LegacyDelayInformationProvider();
|
||||
DelayInformation delayInfo;
|
||||
|
||||
String control;
|
||||
|
||||
// XEP-0091 date format
|
||||
control = XMLBuilder.create("x")
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.sm;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class SMUtils {
|
||||
|
||||
private static long MASK_32_BIT = BigInteger.ONE.shiftLeft(32).subtract(BigInteger.ONE).longValue();
|
||||
|
||||
/**
|
||||
* Quoting XEP-198 4.:
|
||||
* "In the unlikely case that the number of stanzas handled during a stream management session exceeds the number
|
||||
* of digits that can be represented by the unsignedInt datatype as specified in XML Schema Part 2 [10]
|
||||
* (i.e., 2^32), the value of 'h' SHALL be reset from 2^32-1 back to zero (rather than being incremented to 2^32)."
|
||||
*
|
||||
* @param height
|
||||
* @return the incremented height
|
||||
*/
|
||||
public static long incrementHeight(long height) {
|
||||
return ++height & MASK_32_BIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the delta of the last known stanza handled count and the new
|
||||
* reported stanza handled count while considering that the new value may be
|
||||
* wrapped after 2^32-1.
|
||||
*
|
||||
* @param reportedHandledCount
|
||||
* @param lastKnownHandledCount
|
||||
* @return the delta
|
||||
*/
|
||||
public static long calculateDelta(long reportedHandledCount, long lastKnownHandledCount) {
|
||||
return (reportedHandledCount - lastKnownHandledCount) & MASK_32_BIT;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.sm;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
|
||||
public abstract class StreamManagementException extends SmackException {
|
||||
|
||||
public StreamManagementException() {
|
||||
}
|
||||
|
||||
public StreamManagementException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 3767590115788821101L;
|
||||
|
||||
public static class StreamManagementNotEnabledException extends StreamManagementException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 2624821584352571307L;
|
||||
|
||||
}
|
||||
|
||||
public static class StreamIdDoesNotMatchException extends StreamManagementException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1191073341336559621L;
|
||||
|
||||
public StreamIdDoesNotMatchException(String expected, String got) {
|
||||
super("Stream IDs do not match. Expected '" + expected + "', but got '" + got + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,344 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.sm.packet;
|
||||
|
||||
import org.jivesoftware.smack.packet.FullStreamElement;
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
import org.jivesoftware.smack.packet.XMPPError;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
public class StreamManagement {
|
||||
public static final String NAMESPACE = "urn:xmpp:sm:3";
|
||||
|
||||
public static class StreamManagementFeature implements PacketExtension {
|
||||
|
||||
public static final String ELEMENT = "sm";
|
||||
public static final StreamManagementFeature INSTANCE = new StreamManagementFeature();
|
||||
|
||||
private StreamManagementFeature() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.closeEmptyElement();
|
||||
return xml;
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class AbstractEnable extends FullStreamElement {
|
||||
|
||||
/**
|
||||
* Preferred maximum resumption time in seconds (optional).
|
||||
*/
|
||||
protected int max = -1;
|
||||
|
||||
protected boolean resume = false;
|
||||
|
||||
protected void maybeAddResumeAttributeTo(XmlStringBuilder xml) {
|
||||
if (resume) {
|
||||
// XEP 198 never mentions the case where resume='false', it's either set to true or
|
||||
// not set at all. We reflect this in this code part
|
||||
xml.attribute("resume", "true");
|
||||
}
|
||||
}
|
||||
|
||||
protected void maybeAddMaxAttributeTo(XmlStringBuilder xml) {
|
||||
if (max > 0) {
|
||||
xml.attribute("max", Integer.toString(max));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isResumeSet() {
|
||||
return resume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the max resumption time in seconds.
|
||||
* @return the max resumption time in seconds
|
||||
*/
|
||||
public int getMaxResumptionTime() {
|
||||
return max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Enable extends AbstractEnable {
|
||||
public static final String ELEMENT = "enable";
|
||||
|
||||
public static final Enable INSTANCE = new Enable();
|
||||
|
||||
private Enable() {
|
||||
}
|
||||
|
||||
public Enable(boolean resume) {
|
||||
this.resume = resume;
|
||||
}
|
||||
|
||||
public Enable(boolean resume, int max) {
|
||||
this(resume);
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
maybeAddResumeAttributeTo(xml);
|
||||
maybeAddMaxAttributeTo(xml);
|
||||
xml.closeEmptyElement();
|
||||
return xml;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Stream Management 'enabled' element.
|
||||
* <p>
|
||||
* Here is a full example, all attributes besides 'xmlns' are optional.
|
||||
* </p>
|
||||
* <pre>
|
||||
* {@code
|
||||
* <enabled xmlns='urn:xmpp:sm:3'
|
||||
* id='some-long-sm-id'
|
||||
* location='[2001:41D0:1:A49b::1]:9222'
|
||||
* resume='true'/>
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public static class Enabled extends AbstractEnable {
|
||||
public static final String ELEMENT = "enabled";
|
||||
|
||||
/**
|
||||
* The stream id ("SM-ID")
|
||||
*/
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* The location where the server prefers reconnection.
|
||||
*/
|
||||
private final String location;
|
||||
|
||||
public Enabled(String id, boolean resume) {
|
||||
this(id, resume, null, -1);
|
||||
}
|
||||
|
||||
public Enabled(String id, boolean resume, String location, int max) {
|
||||
this.id = id;
|
||||
this.resume = resume;
|
||||
this.location = location;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.optAttribute("id", id);
|
||||
maybeAddResumeAttributeTo(xml);
|
||||
xml.optAttribute("location", location);
|
||||
maybeAddMaxAttributeTo(xml);
|
||||
xml.closeEmptyElement();
|
||||
return xml;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Failed extends FullStreamElement {
|
||||
public static final String ELEMENT = "failed";
|
||||
|
||||
private XMPPError error;
|
||||
|
||||
public Failed() {
|
||||
}
|
||||
|
||||
public Failed(XMPPError error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public XMPPError getXMPPError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
if (error != null) {
|
||||
xml.rightAngleBracket();
|
||||
xml.append(error.toXML());
|
||||
xml.closeElement(ELEMENT);
|
||||
}
|
||||
else {
|
||||
xml.closeEmptyElement();
|
||||
}
|
||||
return xml;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static abstract class AbstractResume extends FullStreamElement {
|
||||
|
||||
private final long handledCount;
|
||||
private final String previd;
|
||||
|
||||
public AbstractResume(long handledCount, String previd) {
|
||||
this.handledCount = handledCount;
|
||||
this.previd = previd;
|
||||
}
|
||||
|
||||
public long getHandledCount() {
|
||||
return handledCount;
|
||||
}
|
||||
|
||||
public String getPrevId() {
|
||||
return previd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.attribute("h", Long.toString(handledCount));
|
||||
xml.attribute("previd", previd);
|
||||
xml.closeEmptyElement();
|
||||
return xml;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Resume extends AbstractResume {
|
||||
public static final String ELEMENT = "resume";
|
||||
|
||||
public Resume(long handledCount, String previd) {
|
||||
super(handledCount, previd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Resumed extends AbstractResume {
|
||||
public static final String ELEMENT = "resumed";
|
||||
|
||||
public Resumed(long handledCount, String previd) {
|
||||
super(handledCount, previd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AckAnswer extends FullStreamElement {
|
||||
public static final String ELEMENT = "a";
|
||||
|
||||
private final long handledCount;
|
||||
|
||||
public AckAnswer(long handledCount) {
|
||||
this.handledCount = handledCount;
|
||||
}
|
||||
|
||||
public long getHandledCount() {
|
||||
return handledCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.attribute("h", Long.toString(handledCount));
|
||||
xml.closeEmptyElement();
|
||||
return xml;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AckRequest extends FullStreamElement {
|
||||
public static final String ELEMENT = "r";
|
||||
public static final AckRequest INSTANCE = new AckRequest();
|
||||
|
||||
private AckRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toXML() {
|
||||
return '<' + ELEMENT + " xmlns='" + NAMESPACE + "'/>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.sm.predicates;
|
||||
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
|
||||
public class AfterXStanzas implements PacketFilter {
|
||||
|
||||
final int count;
|
||||
int currentCount;
|
||||
|
||||
public AfterXStanzas(int count) {
|
||||
this.count = count;
|
||||
currentCount = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean accept(Packet packet) {
|
||||
currentCount++;
|
||||
if (currentCount == count) {
|
||||
resetCounter();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public synchronized void resetCounter() {
|
||||
currentCount = 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.sm.predicates;
|
||||
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
|
||||
public class ForEveryMessage implements PacketFilter {
|
||||
|
||||
public static final ForEveryMessage INSTANCE = new ForEveryMessage();
|
||||
|
||||
private ForEveryMessage() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Packet packet) {
|
||||
if (packet instanceof Message) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.sm.predicates;
|
||||
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
|
||||
public class ForEveryStanza implements PacketFilter {
|
||||
|
||||
public static final ForEveryStanza INSTANCE = new ForEveryStanza();
|
||||
|
||||
private ForEveryStanza() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Packet packet) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.sm.predicates;
|
||||
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
|
||||
public class ForMatchingPredicateOrAfterXStanzas implements PacketFilter {
|
||||
|
||||
private final PacketFilter predicate;
|
||||
private final AfterXStanzas afterXStanzas;
|
||||
|
||||
public ForMatchingPredicateOrAfterXStanzas(PacketFilter predicate, int count) {
|
||||
this.predicate = predicate;
|
||||
this.afterXStanzas = new AfterXStanzas(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Packet packet) {
|
||||
if (predicate.accept(packet)) {
|
||||
afterXStanzas.resetCounter();
|
||||
return true;
|
||||
}
|
||||
return afterXStanzas.accept(packet);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.sm.predicates;
|
||||
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
|
||||
public class OnceForThisStanza implements PacketFilter {
|
||||
|
||||
private final String id;
|
||||
private final XMPPTCPConnection connection;
|
||||
|
||||
public static void setup(XMPPTCPConnection connection, Packet packet) {
|
||||
PacketFilter packetFilter = new OnceForThisStanza(connection, packet);
|
||||
connection.addRequestAckPredicate(packetFilter);
|
||||
}
|
||||
|
||||
private OnceForThisStanza(XMPPTCPConnection connection, Packet packet) {
|
||||
this.connection = connection;
|
||||
this.id = packet.getPacketID();
|
||||
if (StringUtils.isNullOrEmpty(id)) {
|
||||
throw new IllegalArgumentException("Stanza ID must be set");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Packet packet) {
|
||||
String otherId = packet.getPacketID();
|
||||
if (StringUtils.isNullOrEmpty(otherId)) {
|
||||
return false;
|
||||
}
|
||||
if (id.equals(otherId)) {
|
||||
connection.removeRequestAckPredicate(this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.sm.predicates;
|
||||
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
|
||||
public class Predicate {
|
||||
|
||||
public static PacketFilter forMessagesOrAfter5Stanzas() {
|
||||
return new ForMatchingPredicateOrAfterXStanzas(ForEveryMessage.INSTANCE, 5);
|
||||
}
|
||||
|
||||
public static AfterXStanzas after5Stanzas() {
|
||||
return new AfterXStanzas(5);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.sm.predicates;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
|
||||
public class ShortcutPredicates implements PacketFilter {
|
||||
|
||||
private final Set<PacketFilter> predicates = new LinkedHashSet<PacketFilter>();
|
||||
|
||||
public ShortcutPredicates() {
|
||||
}
|
||||
|
||||
public ShortcutPredicates(Collection<? extends PacketFilter> predicates) {
|
||||
this.predicates.addAll(predicates);
|
||||
}
|
||||
|
||||
public boolean addPredicate(PacketFilter predicate) {
|
||||
return predicates.add(predicate);
|
||||
}
|
||||
|
||||
public boolean removePredicate(PacketFilter prediacte) {
|
||||
return predicates.remove(prediacte);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Packet packet) {
|
||||
for (PacketFilter predicate : predicates) {
|
||||
if (predicate.accept(packet)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack.tcp.sm.provider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jivesoftware.smack.packet.XMPPError;
|
||||
import org.jivesoftware.smack.tcp.sm.packet.StreamManagement.AckAnswer;
|
||||
import org.jivesoftware.smack.tcp.sm.packet.StreamManagement.Enabled;
|
||||
import org.jivesoftware.smack.tcp.sm.packet.StreamManagement.Failed;
|
||||
import org.jivesoftware.smack.tcp.sm.packet.StreamManagement.Resumed;
|
||||
import org.jivesoftware.smack.util.ParserUtils;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
public class ParseStreamManagement {
|
||||
|
||||
public static Enabled enabled(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
ParserUtils.assertAtStartTag(parser);
|
||||
boolean resume = ParserUtils.getBooleanAttribute(parser, "resume", false);
|
||||
String id = parser.getAttributeValue("", "id");
|
||||
String location = parser.getAttributeValue("", "location");
|
||||
int max = ParserUtils.getIntegerAttribute(parser, "max", -1);
|
||||
parser.next();
|
||||
ParserUtils.assertAtEndTag(parser);
|
||||
return new Enabled(id, resume, location, max);
|
||||
}
|
||||
|
||||
public static Failed failed(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
ParserUtils.assertAtStartTag(parser);
|
||||
String name;
|
||||
String condition = "unknown";
|
||||
outerloop:
|
||||
while(true) {
|
||||
int event = parser.next();
|
||||
switch (event) {
|
||||
case XmlPullParser.START_TAG:
|
||||
name = parser.getName();
|
||||
String namespace = parser.getNamespace();
|
||||
if (XMPPError.NAMESPACE.equals(namespace)) {
|
||||
condition = name;
|
||||
}
|
||||
break;
|
||||
case XmlPullParser.END_TAG:
|
||||
name = parser.getName();
|
||||
if (Failed.ELEMENT.equals(name)) {
|
||||
break outerloop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
ParserUtils.assertAtEndTag(parser);
|
||||
XMPPError error = new XMPPError(condition);
|
||||
return new Failed(error);
|
||||
}
|
||||
|
||||
public static Resumed resumed(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
ParserUtils.assertAtStartTag(parser);
|
||||
long h = ParserUtils.getLongAttribute(parser, "h");
|
||||
String previd = parser.getAttributeValue("", "previd");
|
||||
parser.next();
|
||||
ParserUtils.assertAtEndTag(parser);
|
||||
return new Resumed(h, previd);
|
||||
}
|
||||
|
||||
public static AckAnswer ackAnswer(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
ParserUtils.assertAtStartTag(parser);
|
||||
long h = ParserUtils.getLongAttribute(parser, "h");
|
||||
parser.next();
|
||||
ParserUtils.assertAtEndTag(parser);
|
||||
return new AckAnswer(h);
|
||||
}
|
||||
|
||||
}
|
|
@ -40,19 +40,22 @@ public class PacketWriterTest {
|
|||
*
|
||||
* @throws InterruptedException
|
||||
* @throws BrokenBarrierException
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
@SuppressWarnings("javadoc")
|
||||
@Test
|
||||
public void shouldBlockAndUnblockTest() throws InterruptedException, BrokenBarrierException, NotConnectedException {
|
||||
XMPPTCPConnection connection = new XMPPTCPConnection("foobar.com");
|
||||
final PacketWriter pw = connection.new PacketWriter();
|
||||
pw.setWriter(new BlockingStringWriter());
|
||||
pw.startup();
|
||||
connection.packetWriter = pw;
|
||||
connection.packetReader = connection.new PacketReader();
|
||||
connection.setWriter(new BlockingStringWriter());
|
||||
pw.init();
|
||||
|
||||
for (int i = 0; i < XMPPTCPConnection.PacketWriter.QUEUE_SIZE; i++) {
|
||||
pw.sendPacket(new Message());
|
||||
pw.sendStreamElement(new Message());
|
||||
}
|
||||
|
||||
|
||||
final CyclicBarrier barrier = new CyclicBarrier(2);
|
||||
shutdown = false;
|
||||
prematureUnblocked = false;
|
||||
|
@ -61,7 +64,7 @@ public class PacketWriterTest {
|
|||
public void run() {
|
||||
try {
|
||||
barrier.await();
|
||||
pw.sendPacket(new Message());
|
||||
pw.sendStreamElement(new Message());
|
||||
// should only return after the pw was interrupted
|
||||
if (!shutdown) {
|
||||
prematureUnblocked = true;
|
||||
|
@ -85,9 +88,9 @@ public class PacketWriterTest {
|
|||
Thread.sleep(250);
|
||||
|
||||
// Set to true for testing purposes, so that shutdown() won't wait packet writer
|
||||
pw.shutdownDone.set(true);
|
||||
pw.shutdownDone.reportSuccess();
|
||||
// Shutdown the packetwriter
|
||||
pw.shutdown();
|
||||
pw.shutdown(false);
|
||||
shutdown = true;
|
||||
barrier.await();
|
||||
if (prematureUnblocked) {
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2014 Vyacheslav Blinov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.jivesoftware.smack.tcp.sm.provider;
|
||||
|
||||
import com.jamesmurty.utils.XMLBuilder;
|
||||
import org.jivesoftware.smack.packet.XMPPError;
|
||||
import org.jivesoftware.smack.tcp.sm.packet.StreamManagement;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.junit.Test;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class ParseStreamManagementTest {
|
||||
private static final Properties outputProperties = initOutputProperties();
|
||||
|
||||
@Test
|
||||
public void testParseEnabled() throws Exception {
|
||||
String stanzaID = "zid615d9";
|
||||
boolean resume = true;
|
||||
String location = "test";
|
||||
int max = 42;
|
||||
|
||||
String enabledStanza = XMLBuilder.create("enabled")
|
||||
.a("xmlns", "urn:xmpp:sm:3")
|
||||
.a("id", "zid615d9")
|
||||
.a("resume", String.valueOf(resume))
|
||||
.a("location", location)
|
||||
.a("max", String.valueOf(max))
|
||||
.asString(outputProperties);
|
||||
|
||||
StreamManagement.Enabled enabledPacket = ParseStreamManagement.enabled(
|
||||
PacketParserUtils.getParserFor(enabledStanza));
|
||||
|
||||
assertThat(enabledPacket, is(notNullValue()));
|
||||
assertThat(enabledPacket.getId(), equalTo(stanzaID));
|
||||
assertThat(enabledPacket.getLocation(), equalTo(location));
|
||||
assertThat(enabledPacket.isResumeSet(), equalTo(resume));
|
||||
assertThat(enabledPacket.getMaxResumptionTime(), equalTo(max));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testParseEnabledInvariant() throws XmlPullParserException, IOException {
|
||||
String enabledString = (new StreamManagement.Enabled("stream-id", false)).toXML().toString();
|
||||
XmlPullParser parser = PacketParserUtils.getParserFor(enabledString);
|
||||
StreamManagement.Enabled enabled = ParseStreamManagement.enabled(parser);
|
||||
|
||||
assertEquals(enabledString, enabled.toXML().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseFailed() throws Exception {
|
||||
String failedStanza = XMLBuilder.create("failed")
|
||||
.a("xmlns", "urn:xmpp:sm:3")
|
||||
.asString(outputProperties);
|
||||
|
||||
StreamManagement.Failed failedPacket = ParseStreamManagement.failed(
|
||||
PacketParserUtils.getParserFor(failedStanza));
|
||||
|
||||
assertThat(failedPacket, is(notNullValue()));
|
||||
XMPPError error = failedPacket.getXMPPError();
|
||||
|
||||
assertThat(error, is(notNullValue()));
|
||||
assertThat(error.getCondition(), equalTo("unknown"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseFailedError() throws Exception {
|
||||
String errorCondition = "failure";
|
||||
|
||||
String failedStanza = XMLBuilder.create("failed")
|
||||
.a("xmlns", "urn:xmpp:sm:3")
|
||||
.element(errorCondition, XMPPError.NAMESPACE)
|
||||
.asString(outputProperties);
|
||||
|
||||
System.err.println(failedStanza);
|
||||
|
||||
StreamManagement.Failed failedPacket = ParseStreamManagement.failed(
|
||||
PacketParserUtils.getParserFor(failedStanza));
|
||||
|
||||
assertThat(failedPacket, is(notNullValue()));
|
||||
XMPPError error = failedPacket.getXMPPError();
|
||||
|
||||
assertThat(error, is(notNullValue()));
|
||||
assertThat(error.getCondition(), equalTo(errorCondition));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseResumed() throws Exception {
|
||||
long handledPackets = 42;
|
||||
String previousID = "zid615d9";
|
||||
|
||||
String resumedStanza = XMLBuilder.create("resumed")
|
||||
.a("xmlns", "urn:xmpp:sm:3")
|
||||
.a("h", String.valueOf(handledPackets))
|
||||
.a("previd", previousID)
|
||||
.asString(outputProperties);
|
||||
|
||||
StreamManagement.Resumed resumedPacket = ParseStreamManagement.resumed(
|
||||
PacketParserUtils.getParserFor(resumedStanza));
|
||||
|
||||
assertThat(resumedPacket, is(notNullValue()));
|
||||
assertThat(resumedPacket.getHandledCount(), equalTo(handledPackets));
|
||||
assertThat(resumedPacket.getPrevId(), equalTo(previousID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseAckAnswer() throws Exception {
|
||||
long handledPackets = 42 + 42;
|
||||
|
||||
String ackStanza = XMLBuilder.create("a")
|
||||
.a("xmlns", "urn:xmpp:sm:3")
|
||||
.a("h", String.valueOf(handledPackets))
|
||||
.asString(outputProperties);
|
||||
|
||||
StreamManagement.AckAnswer acknowledgementPacket = ParseStreamManagement.ackAnswer(
|
||||
PacketParserUtils.getParserFor(ackStanza));
|
||||
|
||||
assertThat(acknowledgementPacket, is(notNullValue()));
|
||||
assertThat(acknowledgementPacket.getHandledCount(), equalTo(handledPackets));
|
||||
}
|
||||
|
||||
|
||||
private static Properties initOutputProperties() {
|
||||
Properties properties = new Properties();
|
||||
properties.put(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
return properties;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue