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:
Florian Schmaus 2014-09-11 09:49:16 +02:00
parent 07c10a7444
commit fc51f3df48
69 changed files with 3277 additions and 1083 deletions

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 + "'/>";
}
}

View File

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

View File

@ -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()
+ ":"

View File

@ -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 + "'/>";
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -69,8 +69,6 @@ public class RosterVersioningTest {
connection = new DummyConnection(conf);
connection.connect();
connection.setRosterVersioningSupported();
connection.login("rostertest", "secret");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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