summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTarun Gupta <tarun1995gupta@gmail.com>2015-07-26 07:47:40 (GMT)
committerTarun Gupta <tarun1995gupta@gmail.com>2015-08-17 13:21:01 (GMT)
commitf56f245c1f7e768caf356a0e7b57f428cf8cc6da (patch)
treef0935ab00c73e86163abe72fc9c20098121fff4f
parent2533374644704040ca67aba4e1240a9d6ea450c8 (diff)
downloadstroke-f56f245c1f7e768caf356a0e7b57f428cf8cc6da.zip
stroke-f56f245c1f7e768caf356a0e7b57f428cf8cc6da.tar.bz2
Update Client.
Updates all bits of Client to be in parity with Swiften. Some part of code which depends on Network is commented. License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details. Test-Information: Tests added for: ClientBlockListManager, ClientSession and NickResolver. All tests pass. Change-Id: I5b9b0e5ae5b2df58202e2349ba24e4544d9a4614
-rw-r--r--src/com/isode/stroke/client/BlockList.java40
-rw-r--r--src/com/isode/stroke/client/BlockListImpl.java91
-rw-r--r--src/com/isode/stroke/client/Client.java119
-rw-r--r--src/com/isode/stroke/client/ClientBlockListManager.java176
-rw-r--r--src/com/isode/stroke/client/ClientError.java13
-rw-r--r--src/com/isode/stroke/client/ClientOptions.java78
-rw-r--r--src/com/isode/stroke/client/ClientSession.java115
-rw-r--r--src/com/isode/stroke/client/ClientSessionStanzaChannel.java18
-rw-r--r--src/com/isode/stroke/client/ClientXMLTracer.java72
-rw-r--r--src/com/isode/stroke/client/CoreClient.java272
-rw-r--r--src/com/isode/stroke/client/DummyNickManager.java22
-rw-r--r--src/com/isode/stroke/client/IDGenerator.java22
-rw-r--r--src/com/isode/stroke/client/MemoryStorages.java10
-rw-r--r--src/com/isode/stroke/client/NickManagerImpl.java7
-rw-r--r--src/com/isode/stroke/client/NickResolver.java16
-rw-r--r--src/com/isode/stroke/client/StanzaChannel.java2
-rw-r--r--src/com/isode/stroke/client/Storages.java4
-rw-r--r--src/com/isode/stroke/client/XMLBeautifier.java147
-rw-r--r--src/com/isode/stroke/crypto/JavaCryptoProvider.java2
-rw-r--r--src/com/isode/stroke/disco/ClientDiscoManager.java2
-rw-r--r--test/com/isode/stroke/client/ClientBlockListManagerTest.java191
-rw-r--r--test/com/isode/stroke/client/ClientSessionTest.java871
-rw-r--r--test/com/isode/stroke/client/NickResolverTest.java155
23 files changed, 2315 insertions, 130 deletions
diff --git a/src/com/isode/stroke/client/BlockList.java b/src/com/isode/stroke/client/BlockList.java
new file mode 100644
index 0000000..bf72f45
--- /dev/null
+++ b/src/com/isode/stroke/client/BlockList.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2011-2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.client;
+
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+
+public abstract class BlockList {
+
+ public enum State {
+ Init,
+ Requesting,
+ Available,
+ Error
+ };
+
+ public final Signal onStateChanged = new Signal();
+ public final Signal1<JID> onItemAdded = new Signal1<JID>();
+ public final Signal1<JID> onItemRemoved = new Signal1<JID>();
+
+ public abstract State getState();
+
+ public abstract Vector<JID> getItems();
+
+ public boolean isBlocked(final JID jid) {
+ final Vector<JID> items = getItems();
+ return (items.contains(jid.toBare()) || items.contains(new JID(jid.getDomain())) || items.contains(jid));
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/client/BlockListImpl.java b/src/com/isode/stroke/client/BlockListImpl.java
new file mode 100644
index 0000000..a85f86c
--- /dev/null
+++ b/src/com/isode/stroke/client/BlockListImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2011 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.client;
+
+import com.isode.stroke.client.BlockList;
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+
+public class BlockListImpl extends BlockList {
+
+ private State state;
+ private Vector<JID> items = new Vector<JID>();
+
+ public BlockListImpl() {
+ this.state = State.Init;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public void setState(State state) {
+ if (!(this.state.equals(state))) {
+ this.state = state;
+ onStateChanged.emit();
+ }
+ }
+
+ public Vector<JID> getItems() {
+ return items;
+ }
+
+ public void setItems(final Vector<JID> items) {
+ for (final JID jid : this.items) {
+ if(items.contains(jid)) {
+ onItemRemoved.emit(jid);
+ }
+ }
+
+ for (final JID jid : items) {
+ if(this.items.contains(jid)) {
+ onItemAdded.emit(jid);
+ }
+ }
+ this.items = items;
+ }
+
+ public void addItem(final JID item) {
+ if(!(items.contains(item))) {
+ items.add(item);
+ onItemAdded.emit(item);
+ }
+ }
+
+ public void removeItem(final JID item) {
+ int oldSize = items.size();
+ while(items.contains(item)) {
+ items.remove(item);
+ }
+ if (items.size() != oldSize) {
+ onItemRemoved.emit(item);
+ }
+ }
+
+ public void addItems(final Vector<JID> items) {
+ Vector<JID> itemsToAdd = new Vector<JID>(items); //Have to do this to avoid ConcurrentModificationException.
+ for (final JID item : itemsToAdd) {
+ addItem(item);
+ }
+ }
+
+ public void removeItems(final Vector<JID> items) {
+ Vector<JID> itemsToRemove = new Vector<JID>(items); //Have to do this to avoid ConcurrentModificationException.
+ for (final JID item : itemsToRemove) {
+ removeItem(item);
+ }
+ }
+
+ public void removeAllItems() {
+ removeItems(items);
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/client/Client.java b/src/com/isode/stroke/client/Client.java
index 1d137c5..464e062 100644
--- a/src/com/isode/stroke/client/Client.java
+++ b/src/com/isode/stroke/client/Client.java
@@ -29,6 +29,7 @@ import com.isode.stroke.roster.XMPPRosterImpl;
import com.isode.stroke.signals.Signal1;
import com.isode.stroke.vcards.VCardManager;
import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.tls.BlindCertificateTrustChecker;
/**
* Provides the core functionality for writing XMPP client software.
@@ -44,7 +45,7 @@ public class Client extends CoreClient {
private final DirectedPresenceSender directedPresenceSender; //NOPMD, this is not better as a local variable
private final StanzaChannelPresenceSender stanzaChannelPresenceSender; //NOPMD, this is not better as a local variable
private final SoftwareVersionResponder softwareVersionResponder;
- private final PubSubManager pubSubManager;
+ private final PubSubManager pubsubManager;
private final XMPPRosterImpl roster;
private final XMPPRosterController rosterController;
private final PresenceOracle presenceOracle;
@@ -58,8 +59,13 @@ public class Client extends CoreClient {
private final SubscriptionManager subscriptionManager;
private final ClientDiscoManager discoManager;
private final AvatarManager avatarManager;
+ //private final JingleSessionManager jingleSessionManager;
+ //private final FileTransferManager fileTransferManager;
+ private final BlindCertificateTrustChecker blindCertificateTrustChecker;
+ //private final WhiteboardSessionManager whiteboardSessionManager;
+ private final ClientBlockListManager blockListManager;
- final Signal1<Presence> onPresenceChange = new Signal1<Presence>();
+ public final Signal1<Presence> onPresenceChange = new Signal1<Presence>();
/**
* Constructor.
@@ -109,9 +115,45 @@ public class Client extends CoreClient {
nickManager = new NickManagerImpl(jid.toBare(), vcardManager);
nickResolver = new NickResolver(jid.toBare(), roster, vcardManager, mucRegistry);
- pubSubManager = new PubSubManagerImpl(getStanzaChannel(), getIQRouter());
+ blindCertificateTrustChecker = new BlindCertificateTrustChecker();
+
+ //TO PORT
+ //jingleSessionManager = new JingleSessionManager(getIQRouter());
+ blockListManager = new ClientBlockListManager(getIQRouter());
+
+ /*whiteboardSessionManager = NULL;
+ #ifdef SWIFT_EXPERIMENTAL_WB
+ whiteboardSessionManager = new WhiteboardSessionManager(getIQRouter(), getStanzaChannel(), presenceOracle, getEntityCapsProvider());
+ #endif*/
+
+ pubsubManager = new PubSubManagerImpl(getStanzaChannel(), getIQRouter());
+
+ /*#ifdef SWIFT_EXPERIMENTAL_FT
+ fileTransferManager = new FileTransferManagerImpl(
+ getJID(),
+ jingleSessionManager,
+ getIQRouter(),
+ getEntityCapsProvider(),
+ presenceOracle,
+ getNetworkFactories()->getConnectionFactory(),
+ getNetworkFactories()->getConnectionServerFactory(),
+ getNetworkFactories()->getTimerFactory(),
+ getNetworkFactories()->getDomainNameResolver(),
+ getNetworkFactories()->getNetworkEnvironment(),
+ getNetworkFactories()->getNATTraverser(),
+ getNetworkFactories()->getCryptoProvider());
+ #else
+ fileTransferManager = new DummyFileTransferManager();
+ #endif*/
}
-
+
+ /**
+ * Constructs a client for the given JID with the given password.
+ *
+ * \param storages The interfaces for storing cache information etc. If
+ * this is NULL,
+ * all data will be stored in memory (and be lost on shutdown)
+ */
public Client(final JID jid, final SafeByteArray password, final NetworkFactories networkFactories) {
this(jid, password, networkFactories, null);
}
@@ -137,13 +179,32 @@ public class Client extends CoreClient {
* @return PubSub manager, not null
*/
public PubSubManager getPubSubManager() {
- return pubSubManager;
+ return pubsubManager;
}
-
+
+ /**
+ * Returns a representation of the roster.
+ *
+ * The roster is initially empty. To populate it, call requestRoster(),
+ * which will request the roster from the server. When the roster has
+ * been requested, it will also be kept up to date when it is updated on
+ * the server side.
+ *
+ * This pointer remains the same across the lifetime of Client. All
+ * changes to the roster (e.g. after the initial roster request, or after
+ * subsequent roster updates) are notified through the XMPPRoster's
+ * signals.
+ *
+ * \see requestRoster()
+ */
public XMPPRoster getRoster() {
return roster;
}
-
+
+ public void setSoftwareVersion(final String name, final String version) {
+ setSoftwareVersion(name, version, "");
+ }
+
/**
* Sets the software version of the client.
*
@@ -153,7 +214,11 @@ public class Client extends CoreClient {
softwareVersionResponder.setVersion(name, version, os);
}
-
+ /**
+ * Requests the roster from the server.
+ *
+ * \see getRoster()
+ */
public void requestRoster() {
// FIXME: We should set this once when the session is finished, but there
// is currently no callback for this
@@ -190,7 +255,37 @@ public class Client extends CoreClient {
public ClientDiscoManager getDiscoManager() {
return discoManager;
}
-
+
+ public ClientBlockListManager getClientBlockListManager() {
+ return blockListManager;
+ }
+
+ /**
+ * Returns a FileTransferManager for the client. This is only available after the onConnected
+ * signal has been fired.
+ *
+ * WARNING: File transfer will only work if Swiften is built in 'experimental' mode.
+ */
+ //TO PORT
+ /*public FileTransferManager getFileTransferManager() {
+ return fileTransferManager;
+ }*/
+
+ /**
+ * Configures the client to always trust a non-validating
+ * TLS certificate from the server.
+ * This is equivalent to setting a BlindCertificateTrustChecker
+ * using setCertificateTrustChecker().
+ */
+ public void setAlwaysTrustCertificates() {
+ setCertificateTrustChecker(blindCertificateTrustChecker);
+ }
+
+ //TO PORT
+ /*public WhiteboardSessionManager getWhiteboardSessionManager() {
+ return whiteboardSessionManager;
+ }*/
+
public VCardManager getVCardManager() {
return vcardManager;
}
@@ -205,7 +300,11 @@ public class Client extends CoreClient {
}
return memoryStorages;
}
-
+
+ protected void handleConnected() {
+ discoManager.handleConnected();
+ }
+
public PresenceSender getPresenceSender() {
return discoManager.getPresenceSender();
}
diff --git a/src/com/isode/stroke/client/ClientBlockListManager.java b/src/com/isode/stroke/client/ClientBlockListManager.java
new file mode 100644
index 0000000..b370f63
--- /dev/null
+++ b/src/com/isode/stroke/client/ClientBlockListManager.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2011-2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.client;
+
+import com.isode.stroke.elements.BlockPayload;
+import com.isode.stroke.elements.BlockListPayload;
+import com.isode.stroke.elements.UnblockPayload;
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.signals.Slot2;
+import com.isode.stroke.queries.SetResponder;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.client.BlockList;
+import com.isode.stroke.client.BlockListImpl;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+
+public class ClientBlockListManager {
+
+ private IQRouter iqRouter;
+ private SetResponder<BlockPayload> blockResponder;
+ private SetResponder<UnblockPayload> unblockResponder;
+ private BlockListImpl blockList;
+
+ class BlockResponder extends SetResponder<BlockPayload> {
+
+ public BlockResponder(BlockListImpl blockList, IQRouter iqRouter) {
+ super(new BlockPayload(), iqRouter);
+ this.blockList = blockList;
+ }
+
+ public boolean handleSetRequest(final JID from, final JID to, final String id, BlockPayload payload) {
+ if (getIQRouter().isAccountJID(from)) {
+ if (payload != null) {
+ blockList.addItems(payload.getItems());
+ }
+ sendResponse(from, id, null);
+ }
+ else {
+ sendError(from, id, ErrorPayload.Condition.NotAuthorized, ErrorPayload.Type.Cancel);
+ }
+ return true;
+ }
+
+ private BlockListImpl blockList;
+ };
+
+ class UnblockResponder extends SetResponder<UnblockPayload> {
+
+ public UnblockResponder(BlockListImpl blockList, IQRouter iqRouter) {
+ super(new UnblockPayload(), iqRouter);
+ this.blockList = blockList;
+ }
+
+ public boolean handleSetRequest(final JID from, final JID to, final String id, UnblockPayload payload) {
+ if (getIQRouter().isAccountJID(from)) {
+ if (payload != null) {
+ if (payload.getItems().isEmpty()) {
+ blockList.removeAllItems();
+ }
+ else {
+ blockList.removeItems(payload.getItems());
+ }
+ }
+ sendResponse(from, id, null);
+ }
+ else {
+ sendError(from, id, ErrorPayload.Condition.NotAuthorized, ErrorPayload.Type.Cancel);
+ }
+ return true;
+ }
+
+ private BlockListImpl blockList;
+ };
+
+ public ClientBlockListManager(IQRouter iqRouter) {
+ this.iqRouter = iqRouter;
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ if (blockList != null && BlockList.State.Available.equals(blockList.getState())) {
+ unblockResponder.stop();
+ blockResponder.stop();
+ }
+ }
+ finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Returns the blocklist.
+ */
+ public BlockList getBlockList() {
+ if (blockList == null) {
+ blockList = new BlockListImpl();
+ blockList.setState(BlockList.State.Init);
+ }
+ return blockList;
+ }
+
+ /**
+ * Get the blocklist from the server.
+ */
+ public BlockList requestBlockList() {
+ if (blockList == null) {
+ blockList = new BlockListImpl();
+ }
+ blockList.setState(BlockList.State.Requesting);
+ GenericRequest<BlockListPayload> getRequest = new GenericRequest<BlockListPayload>(IQ.Type.Get, new JID(), new BlockListPayload(), iqRouter);
+ getRequest.onResponse.connect(new Slot2<BlockListPayload, ErrorPayload>() {
+ @Override
+ public void call(BlockListPayload p, ErrorPayload e) {
+ handleBlockListReceived(p, e);
+ }
+ });
+ getRequest.send();
+ return blockList;
+ }
+
+ public GenericRequest<BlockPayload> createBlockJIDRequest(final JID jid) {
+ Vector<JID> vec = new Vector<JID>();
+ vec.add(jid);
+ return createBlockJIDsRequest(vec);
+ }
+
+ public GenericRequest<BlockPayload> createBlockJIDsRequest(final Vector<JID> jids) {
+ BlockPayload payload = new BlockPayload(jids);
+ return new GenericRequest<BlockPayload>(IQ.Type.Set, new JID(), payload, iqRouter);
+ }
+
+ public GenericRequest<UnblockPayload> createUnblockJIDRequest(final JID jid) {
+ Vector<JID> vec = new Vector<JID>();
+ vec.add(jid);
+ return createUnblockJIDsRequest(vec);
+ }
+
+ public GenericRequest<UnblockPayload> createUnblockJIDsRequest(final Vector<JID> jids) {
+ UnblockPayload payload = new UnblockPayload(jids);
+ return new GenericRequest<UnblockPayload>(IQ.Type.Set, new JID(), payload, iqRouter);
+ }
+
+ public GenericRequest<UnblockPayload> createUnblockAllRequest() {
+ return createUnblockJIDsRequest(new Vector<JID>());
+ }
+
+ private void handleBlockListReceived(BlockListPayload payload, ErrorPayload error) {
+ if (error != null || payload == null) {
+ blockList.setState(BlockList.State.Error);
+ }
+ else {
+ blockList.setItems(payload.getItems());
+ blockList.setState(BlockList.State.Available);
+ if (blockResponder == null) {
+ blockResponder = new BlockResponder(blockList, iqRouter);
+ blockResponder.start();
+ }
+ if (unblockResponder == null) {
+ unblockResponder = new UnblockResponder(blockList, iqRouter);
+ unblockResponder.start();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/client/ClientError.java b/src/com/isode/stroke/client/ClientError.java
index de2db49..cfcf12f 100644
--- a/src/com/isode/stroke/client/ClientError.java
+++ b/src/com/isode/stroke/client/ClientError.java
@@ -30,9 +30,14 @@ public class ClientError {
UnexpectedElementError,
ResourceBindError,
SessionStartError,
+ StreamError,
TLSError,
ClientCertificateLoadError,
ClientCertificateError,
+
+ // Certifate on smartcard was removed
+ CertificateCardRemoved,
+
// Certificate verification errors
UnknownCertificateError,
CertificateExpiredError,
@@ -45,9 +50,15 @@ public class ClientError {
InvalidCertificateSignatureError,
InvalidCAError,
InvalidServerIdentityError,
+ RevokedError,
+ RevocationCheckFailedError
};
- ClientError(final Type type) {
+ public ClientError() {
+ type_ = Type.UnknownError;
+ }
+
+ public ClientError(final Type type) {
type_ = type;
}
diff --git a/src/com/isode/stroke/client/ClientOptions.java b/src/com/isode/stroke/client/ClientOptions.java
index b410094..c2d9e3f 100644
--- a/src/com/isode/stroke/client/ClientOptions.java
+++ b/src/com/isode/stroke/client/ClientOptions.java
@@ -10,6 +10,8 @@ package com.isode.stroke.client;
import com.isode.stroke.tls.TLSOptions;
+import com.isode.stroke.base.URL;
+import com.isode.stroke.base.SafeByteArray;
/**
* Options for a client connection
@@ -40,7 +42,17 @@ public class ClientOptions {
* Default: false
*/
public boolean useStreamResumption;
-
+
+ /**
+ * Forget the password once it's used.
+ * This makes the Client useless after the first login attempt.
+ *
+ * FIXME: This is a temporary workaround.
+ *
+ * Default: false
+ */
+ public boolean forgetPassword;
+
/**
* Use XEP-0198 acks in the stream when available.
* Default: true
@@ -48,6 +60,12 @@ public class ClientOptions {
public boolean useAcks;
/**
+ * Use Single Sign On.
+ * Default: false
+ */
+ public boolean singleSignOn;
+
+ /**
* The hostname to connect to.
* Leave this empty for standard XMPP connection, based on the JID domain.
*/
@@ -60,6 +78,48 @@ public class ClientOptions {
*/
public int manualPort;
+ /**
+ * The type of proxy to use for connecting to the XMPP
+ * server.
+ */
+ public ProxyType proxyType;
+
+ /**
+ * Override the system-configured proxy hostname.
+ */
+ public String manualProxyHostname;
+
+ /**
+ * Override the system-configured proxy port.
+ */
+ public int manualProxyPort;
+
+ /**
+ * If non-empty, use BOSH instead of direct TCP, with the given URL.
+ * Default: empty (no BOSH)
+ */
+ public URL boshURL = new URL();
+
+ /**
+ * If non-empty, BOSH connections will try to connect over this HTTP CONNECT
+ * proxy instead of directly.
+ * Default: empty (no proxy)
+ */
+ public URL boshHTTPConnectProxyURL = new URL();
+
+ /**
+ * If this and matching Password are non-empty, BOSH connections over
+ * HTTP CONNECT proxies will use these credentials for proxy access.
+ * Default: empty (no authentication needed by the proxy)
+ */
+ public SafeByteArray boshHTTPConnectProxyAuthID;
+ public SafeByteArray boshHTTPConnectProxyAuthPassword;
+
+ /**
+ * This can be initialized with a custom HTTPTrafficFilter, which allows HTTP CONNECT
+ * proxy initialization to be customized.
+ */
+ //public HTTPTrafficFilter httpTrafficFilter; //TOPORT NETWORK
/**
* Options passed to the TLS stack
@@ -72,14 +132,28 @@ public class ClientOptions {
RequireTLS
}
+ public enum ProxyType {
+ NoProxy,
+ SystemConfiguredProxy,
+ SOCKS5Proxy,
+ HTTPConnectProxy
+ };
+
public ClientOptions() {
useStreamCompression = true;
useTLS = UseTLS.UseTLSWhenAvailable;
- useStreamResumption = false;
allowPLAINWithoutTLS = false;
+ useStreamResumption = false;
+ forgetPassword = false;
useAcks = true;
+ singleSignOn = false;
manualHostname = "";
manualPort = -1;
+ proxyType = ProxyType.SystemConfiguredProxy;
+ manualProxyHostname = "";
+ manualProxyPort = -1;
+ boshHTTPConnectProxyAuthID = new SafeByteArray("");
+ boshHTTPConnectProxyAuthPassword = new SafeByteArray("");
}
@Override
diff --git a/src/com/isode/stroke/client/ClientSession.java b/src/com/isode/stroke/client/ClientSession.java
index b8ec4a9..42d82ca 100644
--- a/src/com/isode/stroke/client/ClientSession.java
+++ b/src/com/isode/stroke/client/ClientSession.java
@@ -38,6 +38,8 @@ import com.isode.stroke.jid.JID;
import com.isode.stroke.sasl.ClientAuthenticator;
import com.isode.stroke.sasl.PLAINClientAuthenticator;
import com.isode.stroke.sasl.SCRAMSHA1ClientAuthenticator;
+import com.isode.stroke.sasl.DIGESTMD5ClientAuthenticator;
+import com.isode.stroke.sasl.EXTERNALClientAuthenticator;
import com.isode.stroke.session.SessionStream;
import com.isode.stroke.signals.Signal;
import com.isode.stroke.signals.Signal1;
@@ -54,7 +56,7 @@ import com.isode.stroke.idn.IDNConverter;
import com.isode.stroke.idn.ICUConverter;
import com.isode.stroke.crypto.CryptoProvider;
import com.isode.stroke.crypto.JavaCryptoProvider;
-
+import java.util.logging.Logger;
import java.util.List;
import java.util.UUID;
@@ -65,6 +67,7 @@ public class ClientSession {
public final Signal1<Stanza> onStanzaReceived = new Signal1<Stanza>();
public final Signal1<Stanza> onStanzaAcked = new Signal1<Stanza>();
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
private SignalConnection streamElementReceivedConnection;
private SignalConnection streamStreamStartReceivedConnection;
private SignalConnection streamClosedConnection;
@@ -88,8 +91,10 @@ public class ClientSession {
private StanzaAckResponder stanzaAckResponder_;
private com.isode.stroke.base.Error error_;
private CertificateTrustChecker certificateTrustChecker;
- private IDNConverter idnConverter = new ICUConverter(); //TOPORT: Accomodated later in Constructor.
- private CryptoProvider crypto = new JavaCryptoProvider(); //TOPORT: Accomodated later in Constructor.
+ private IDNConverter idnConverter;
+ private CryptoProvider crypto;
+ private boolean singleSignOn;
+ private int authenticationPort;
public enum State {
@@ -126,9 +131,6 @@ public class ClientSession {
};
public Error(final Type type) {
- if (type == null) {
- throw new IllegalStateException();
- }
this.type = type;
}
};
@@ -139,10 +141,12 @@ public class ClientSession {
RequireTLS
}
- private ClientSession(final JID jid, final SessionStream stream) {
+ private ClientSession(final JID jid, final SessionStream stream, IDNConverter idnConverter, CryptoProvider crypto) {
localJID = jid;
state = State.Initial;
this.stream = stream;
+ this.idnConverter = idnConverter;
+ this.crypto = crypto;
allowPLAINOverNonTLS = false;
useStreamCompression = true;
useTLS = UseTLS.UseTLSWhenAvailable;
@@ -150,11 +154,15 @@ public class ClientSession {
needSessionStart = false;
needResourceBind = false;
needAcking = false;
+ rosterVersioningSupported = false;
authenticator = null;
+ certificateTrustChecker = null;
+ singleSignOn = false;
+ authenticationPort = -1;
}
- public static ClientSession create(final JID jid, final SessionStream stream) {
- return new ClientSession(jid, stream);
+ public static ClientSession create(final JID jid, final SessionStream stream, IDNConverter idnConverter, CryptoProvider crypto) {
+ return new ClientSession(jid, stream, idnConverter, crypto);
}
public State getState() {
@@ -201,6 +209,18 @@ public class ClientSession {
certificateTrustChecker = checker;
}
+ public void setSingleSignOn(boolean b) {
+ singleSignOn = b;
+ }
+
+ /**
+ * Sets the port number used in Kerberos authentication
+ * Does not affect network connectivity.
+ */
+ public void setAuthenticationPort(int i) {
+ authenticationPort = i;
+ }
+
public void start() {
streamStreamStartReceivedConnection = stream.onStreamStartReceived.connect(new Slot1<ProtocolHeader>(){
public void call(final ProtocolHeader p1) {
@@ -308,13 +328,13 @@ public class ClientSession {
if (ack.isValid()) {
stanzaAckRequester_.handleAckReceived(ack.getHandledStanzasCount());
}
- //else {
- //logger_.warning("Got invalid ack from server"); /*FIXME: Do we want logging here?
- //}
+ else {
+ logger_.warning("Got invalid ack from server");
+ }
+ }
+ else {
+ logger_.warning("Ignoring ack");
}
- //else {
- //logger_.warning("Ignoring ack"); /*FIXME: Do we want logging here?*/
- //}
}
else if (element instanceof StreamError) {
finishSession(Error.Type.StreamError);
@@ -341,7 +361,7 @@ public class ClientSession {
else if (UseTLS.RequireTLS.equals(useTLS) && !stream.isTLSEncrypted()) {
finishSession(Error.Type.NoSupportedAuthMechanismsError);
}
- else if (useStreamCompression && streamFeatures.hasCompressionMethod("zlib")) {
+ else if (useStreamCompression && stream.supportsZLibCompression() && streamFeatures.hasCompressionMethod("zlib")) {
state = State.Compressing;
stream.writeElement(new CompressRequest("zlib"));
}
@@ -349,20 +369,27 @@ public class ClientSession {
if (stream.hasTLSCertificate()) {
if (streamFeatures.hasAuthenticationMechanism("EXTERNAL")) {
state = State.Authenticating;
- stream.writeElement(new AuthRequest("EXTERNAL",new SafeByteArray()));
+ stream.writeElement(new AuthRequest("EXTERNAL",new SafeByteArray("")));
}
else {
finishSession(Error.Type.TLSClientCertificateError);
}
}
else if (streamFeatures.hasAuthenticationMechanism("EXTERNAL")) {
+ authenticator = new EXTERNALClientAuthenticator();
state = State.Authenticating;
- stream.writeElement(new AuthRequest("EXTERNAL",new SafeByteArray()));
+ stream.writeElement(new AuthRequest("EXTERNAL",new SafeByteArray("")));
}
else if (streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1") || streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1-PLUS")) {
- final SCRAMSHA1ClientAuthenticator scramAuthenticator = new SCRAMSHA1ClientAuthenticator(UUID.randomUUID().toString(), streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1-PLUS"), idnConverter, crypto);
+ ByteArray finishMessage = new ByteArray();
+ boolean plus = streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1-PLUS");
if (stream.isTLSEncrypted()) {
- scramAuthenticator.setTLSChannelBindingData(stream.getTLSFinishMessage());
+ finishMessage = stream.getTLSFinishMessage();
+ plus &= !(finishMessage.isEmpty());
+ }
+ final SCRAMSHA1ClientAuthenticator scramAuthenticator = new SCRAMSHA1ClientAuthenticator(UUID.randomUUID().toString(), plus, idnConverter, crypto);
+ if (!(finishMessage.isEmpty())) {
+ scramAuthenticator.setTLSChannelBindingData(finishMessage);
}
authenticator = scramAuthenticator;
state = State.WaitingForCredentials;
@@ -373,13 +400,12 @@ public class ClientSession {
state = State.WaitingForCredentials;
onNeedCredentials.emit();
}
-// //FIXME: Port
-// else if (streamFeatures.hasAuthenticationMechanism("DIGEST-MD5")) {
-// // FIXME: Host should probably be the actual host
-// authenticator = new DIGESTMD5ClientAuthenticator(localJID.getDomain(), UUID.randomUUID());
-// state = State.WaitingForCredentials;
-// onNeedCredentials.emit();
-// }
+ else if (streamFeatures.hasAuthenticationMechanism("DIGEST-MD5") && crypto.isMD5AllowedForCrypto()) {
+ // FIXME: Host should probably be the actual host
+ authenticator = new DIGESTMD5ClientAuthenticator(localJID.getDomain(), UUID.randomUUID().toString(), crypto);
+ state = State.WaitingForCredentials;
+ onNeedCredentials.emit();
+ }
else {
finishSession(Error.Type.NoSupportedAuthMechanismsError);
}
@@ -400,7 +426,9 @@ public class ClientSession {
}
}
else if (element instanceof Compressed) {
- checkState(State.Compressing);
+ if(!checkState(State.Compressing)) {
+ return;
+ }
state = State.WaitingForStreamStart;
stream.addZLibCompression();
stream.resetXMPPParser();
@@ -439,7 +467,9 @@ public class ClientSession {
}
else if (element instanceof AuthChallenge) {
final AuthChallenge challenge = (AuthChallenge) element;
- checkState(State.Authenticating);
+ if(!checkState(State.Authenticating)) {
+ return;
+ }
assert authenticator != null;
if (authenticator.setChallenge(challenge.getValue())) {
stream.writeElement(new AuthResponse(authenticator.getResponse()));
@@ -450,8 +480,11 @@ public class ClientSession {
}
else if (element instanceof AuthSuccess) {
final AuthSuccess authSuccess = (AuthSuccess)element;
- checkState(State.Authenticating);
- if (authenticator != null && !authenticator.setChallenge(authSuccess.getValue())) {
+ if(!checkState(State.Authenticating)) {
+ return;
+ }
+ assert(authenticator != null);
+ if (!authenticator.setChallenge(authSuccess.getValue())) {
finishSession(Error.Type.ServerVerificationFailedError);
}
else {
@@ -462,7 +495,6 @@ public class ClientSession {
}
}
else if (element instanceof AuthFailure) {
- authenticator = null;
finishSession(Error.Type.AuthenticationFailedError);
}
else if (element instanceof TLSProceed) {
@@ -518,6 +550,8 @@ public class ClientSession {
if (!checkState(State.WaitingForCredentials)) {
throw new IllegalStateException("Asking for credentials when we shouldn't be asked.");
}
+ //assert(WaitingForCredentials);
+ //assert(authenticator);
state = State.Authenticating;
authenticator.setCredentials(localJID.getNode(), password);
stream.writeElement(new AuthRequest(authenticator.getName(), authenticator.getResponse()));
@@ -536,7 +570,7 @@ public class ClientSession {
}
else {
final ServerIdentityVerifier identityVerifier = new ServerIdentityVerifier(localJID, idnConverter);
- if (identityVerifier.certificateVerifies(peerCertificate)) {
+ if (!certificateChain.isEmpty() && identityVerifier.certificateVerifies(peerCertificate)) {
continueAfterTLSEncrypted();
}
else {
@@ -592,20 +626,23 @@ public class ClientSession {
}
private void finishSession(final Error.Type error) {
- Error localError = null;
- if (error != null) {
- localError = new Error(error);
- }
- finishSession(localError);
+ finishSession(new ClientSession.Error(error));
}
private void finishSession(final com.isode.stroke.base.Error error) {
state = State.Finishing;
- error_ = error;
+ if(error_ == null) {
+ error_ = error;
+ } else {
+ logger_.warning("Session finished twice");
+ }
assert stream.isOpen();
if (stanzaAckResponder_ != null) {
stanzaAckResponder_.handleAckRequestReceived();
}
+ if (authenticator != null) {
+ authenticator = null;
+ }
stream.writeFooter();
stream.close();
}
diff --git a/src/com/isode/stroke/client/ClientSessionStanzaChannel.java b/src/com/isode/stroke/client/ClientSessionStanzaChannel.java
index 0aa024f..e8275a0 100644
--- a/src/com/isode/stroke/client/ClientSessionStanzaChannel.java
+++ b/src/com/isode/stroke/client/ClientSessionStanzaChannel.java
@@ -9,6 +9,7 @@
package com.isode.stroke.client;
import com.isode.stroke.base.Error;
+import com.isode.stroke.base.IDGenerator;
import com.isode.stroke.elements.IQ;
import com.isode.stroke.elements.Message;
import com.isode.stroke.elements.Presence;
@@ -26,7 +27,7 @@ import java.util.logging.Logger;
* StanzaChannel implementation around a ClientSession.
*/
public class ClientSessionStanzaChannel extends StanzaChannel {
- private final IDGenerator idGenerator = new IDGenerator();
+ private IDGenerator idGenerator = new IDGenerator();
private ClientSession session;
private static final Logger logger_ = Logger.getLogger(ClientSessionStanzaChannel.class.getName());
private SignalConnection sessionInitializedConnection;
@@ -34,6 +35,21 @@ public class ClientSessionStanzaChannel extends StanzaChannel {
private SignalConnection sessionStanzaReceivedConnection;
private SignalConnection sessionStanzaAckedConnection;
+ protected void finalize() throws Throwable {
+ try {
+ if(session != null) {
+ sessionFinishedConnection.disconnect();
+ sessionStanzaReceivedConnection.disconnect();
+ sessionStanzaAckedConnection.disconnect();
+ sessionInitializedConnection.disconnect();
+ session = null;
+ }
+ }
+ finally {
+ super.finalize();
+ }
+ }
+
public void setSession(final ClientSession session) {
assert this.session == null;
this.session = session;
diff --git a/src/com/isode/stroke/client/ClientXMLTracer.java b/src/com/isode/stroke/client/ClientXMLTracer.java
new file mode 100644
index 0000000..e3a65a6
--- /dev/null
+++ b/src/com/isode/stroke/client/ClientXMLTracer.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2010-2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.client;
+
+import com.isode.stroke.client.CoreClient;
+import com.isode.stroke.client.XMLBeautifier;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot1;
+
+public class ClientXMLTracer {
+
+ private XMLBeautifier beautifier;
+ private boolean bosh;
+ private SignalConnection onDataReadConnection;
+ private SignalConnection onDataWrittenConnection;
+
+ public ClientXMLTracer(CoreClient client) {
+ this(client, false);
+ }
+
+ public ClientXMLTracer(CoreClient client, boolean bosh) {
+ this.bosh = bosh;
+ beautifier = new XMLBeautifier(true, true);
+ onDataReadConnection = client.onDataRead.connect(new Slot1<SafeByteArray>() {
+ @Override
+ public void call(SafeByteArray s) {
+ printData('<', s);
+ }
+ });
+ onDataWrittenConnection = client.onDataWritten.connect(new Slot1<SafeByteArray>() {
+ @Override
+ public void call(SafeByteArray s) {
+ printData('>', s);
+ }
+ });
+ }
+
+ private void printData(char direction, final SafeByteArray data) {
+ printLine(direction);
+ if (bosh) {
+ String line = data.toString();
+ // Disabled because it swallows bits of XML (namespaces, if I recall)
+ // size_t endOfHTTP = line.find("\r\n\r\n");
+ // if (false && endOfHTTP != std::string::npos) {
+ // std::cerr << line.substr(0, endOfHTTP) << std::endl << beautifier->beautify(line.substr(endOfHTTP)) << std::endl;
+ // }
+ // else {
+ System.err.println(line);
+ // }
+ }
+ else {
+ System.err.println(beautifier.beautify(data.toString()));
+ }
+ }
+
+ private void printLine(char c) {
+ for (int i = 0; i < 80; ++i) {
+ System.err.println(c);
+ }
+ System.err.println();
+ }
+}
diff --git a/src/com/isode/stroke/client/CoreClient.java b/src/com/isode/stroke/client/CoreClient.java
index 74ba031..2fc10ae 100644
--- a/src/com/isode/stroke/client/CoreClient.java
+++ b/src/com/isode/stroke/client/CoreClient.java
@@ -11,7 +11,10 @@ import com.isode.stroke.elements.Presence;
import com.isode.stroke.elements.Stanza;
import com.isode.stroke.elements.StreamType;
import com.isode.stroke.jid.JID;
+import com.isode.stroke.crypto.JavaCryptoProvider;
+import com.isode.stroke.idn.ICUConverter;
import com.isode.stroke.network.Connection;
+import com.isode.stroke.network.ConnectionFactory;
import com.isode.stroke.network.Connector;
import com.isode.stroke.network.DomainNameResolveError;
import com.isode.stroke.network.NetworkFactories;
@@ -30,6 +33,9 @@ import com.isode.stroke.tls.CertificateTrustChecker;
import com.isode.stroke.tls.CertificateVerificationError;
import com.isode.stroke.tls.CertificateWithKey;
import com.isode.stroke.tls.TLSOptions;
+import com.isode.stroke.tls.TLSError;
+import java.util.logging.Logger;
+import java.util.Vector;
/**
* The central class for communicating with an XMPP server.
@@ -85,7 +91,6 @@ public class CoreClient {
private ClientSessionStanzaChannel stanzaChannel_;
private IQRouter iqRouter_;
private Connector connector_;
- //private ConnectionFactory connectionFactory_;
private FullPayloadParserFactoryCollection payloadParserFactories_ = new FullPayloadParserFactoryCollection();
private FullPayloadSerializerCollection payloadSerializers_ = new FullPayloadSerializerCollection();
private Connection connection_;
@@ -101,6 +106,12 @@ public class CoreClient {
private SignalConnection sessionFinishedConnection_;
private SignalConnection sessionNeedCredentialsConnection_;
private SignalConnection connectorConnectFinishedConnection_;
+ private SignalConnection onMessageReceivedConnection;
+ private SignalConnection onPresenceReceivedConnection;
+ private SignalConnection onStanzaAckedConnection;
+ private SignalConnection onAvailableChangedConnection;
+ private Vector<ConnectionFactory> proxyConnectionFactories = new Vector<ConnectionFactory>();
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
/**
* Constructor.
@@ -125,25 +136,25 @@ public class CoreClient {
this.networkFactories = networkFactories;
this.certificateTrustChecker = null;
stanzaChannel_ = new ClientSessionStanzaChannel();
- stanzaChannel_.onMessageReceived.connect(new Slot1<Message>() {
+ onMessageReceivedConnection = stanzaChannel_.onMessageReceived.connect(new Slot1<Message>() {
public void call(Message p1) {
- onMessageReceived.emit(p1);
+ handleMessageReceived(p1);
}
});
- stanzaChannel_.onPresenceReceived.connect(new Slot1<Presence>() {
+ onPresenceReceivedConnection = stanzaChannel_.onPresenceReceived.connect(new Slot1<Presence>() {
public void call(Presence p1) {
- onPresenceReceived.emit(p1);
+ handlePresenceReceived(p1);
}
});
- stanzaChannel_.onStanzaAcked.connect(new Slot1<Stanza>() {
+ onStanzaAckedConnection = stanzaChannel_.onStanzaAcked.connect(new Slot1<Stanza>() {
public void call(Stanza p1) {
- onStanzaAcked.emit(p1);
+ handleStanzaAcked(p1);
}
});
- stanzaChannel_.onAvailableChanged.connect(new Slot1<Boolean>() {
+ onAvailableChangedConnection = stanzaChannel_.onAvailableChanged.connect(new Slot1<Boolean>() {
public void call(Boolean p1) {
handleStanzaChannelAvailableChanged(p1);
@@ -154,22 +165,29 @@ public class CoreClient {
iqRouter_.setJID(jid);
}
- /*CoreClient::~CoreClient() {
- if (session_ || connection_) {
- std::cerr << "Warning: Client not disconnected properly" << std::endl;
+ protected void finalize() throws Throwable {
+ try {
+ forceReset();
+ onAvailableChangedConnection.disconnect();
+ onMessageReceivedConnection.disconnect();
+ onPresenceReceivedConnection.disconnect();
+ onStanzaAckedConnection.disconnect();
+ stanzaChannel_ = null;
+ }
+ finally {
+ super.finalize();
+ }
}
- delete tlsLayerFactory_;
- delete timerFactory_;
- delete connectionFactory_;
- delete iqRouter_;
-
- stanzaChannel_->onAvailableChanged.disconnect(boost::bind(&CoreClient::handleStanzaChannelAvailableChanged, this, _1));
- stanzaChannel_->onMessageReceived.disconnect(boost::ref(onMessageReceived));
- stanzaChannel_->onPresenceReceived.disconnect(boost::ref(onPresenceReceived));
- stanzaChannel_->onStanzaAcked.disconnect(boost::ref(onStanzaAcked));
- delete stanzaChannel_;
- }*/
+ /**
+ * Connects the client to the server.
+ *
+ * After the connection is established, the client will set
+ * initialize the stream and authenticate.
+ */
+ public void connect() {
+ connect(new ClientOptions());
+ }
/**
* Connect using the standard XMPP connection rules (i.e. SRV then A/AAAA).
@@ -177,33 +195,114 @@ public class CoreClient {
* @param o Client options to use in the connection, must not be null
*/
public void connect(final ClientOptions o) {
+ logger_.fine("Connecting ");
forceReset();
disconnectRequested_ = false;
assert (connector_ == null);
options = o;
- /* FIXME: Port Proxies */
+
+ //TO PORT
+ /*// Determine connection types to use
+ assert(proxyConnectionFactories.isEmpty());
+ boolean useDirectConnection = true;
+ HostAddressPort systemSOCKS5Proxy = networkFactories.getProxyProvider().getSOCKS5Proxy();
+ HostAddressPort systemHTTPConnectProxy = networkFactories.getProxyProvider().getHTTPConnectProxy();
+ switch (o.proxyType) {
+ case ClientOptions.ProxyType.NoProxy:
+ logger_.fine(" without a proxy\n");
+ break;
+ case ClientOptions.ProxyType.SystemConfiguredProxy:
+ logger_.fine(" with a system configured proxy\n");
+ if (systemSOCKS5Proxy.isValid()) {
+ logger_.fine("Found SOCK5 Proxy: " + systemSOCKS5Proxy.getAddress().toString() + ":" + systemSOCKS5Proxy.getPort() + "\n");
+ proxyConnectionFactories.add(new SOCKS5ProxiedConnectionFactory(networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory(), systemSOCKS5Proxy.getAddress().toString(), systemSOCKS5Proxy.getPort()));
+ }
+ if (systemHTTPConnectProxy.isValid()) {
+ logger_.fine("Found HTTPConnect Proxy: " + systemHTTPConnectProxy.getAddress().toString() + ":" + systemHTTPConnectProxy.getPort() + "\n");
+ proxyConnectionFactories.add(new HTTPConnectProxiedConnectionFactory(networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory(), systemHTTPConnectProxy.getAddress().toString(), systemHTTPConnectProxy.getPort()));
+ }
+ break;
+ case ClientOptions.ProxyType.SOCKS5Proxy: {
+ logger_.fine(" with manual configured SOCKS5 proxy\n");
+ String proxyHostname = o.manualProxyHostname.empty() ? systemSOCKS5Proxy.getAddress().toString() : o.manualProxyHostname;
+ int proxyPort = o.manualProxyPort == -1 ? systemSOCKS5Proxy.getPort() : o.manualProxyPort;
+ logger_.fine("Proxy: " + proxyHostname + ":" + proxyPort + "\n");
+ proxyConnectionFactories.add(new SOCKS5ProxiedConnectionFactory(networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory(), proxyHostname, proxyPort));
+ useDirectConnection = false;
+ break;
+ }
+ case ClientOptions.ProxyType.HTTPConnectProxy: {
+ logger_.fine(" with manual configured HTTPConnect proxy\n");
+ std::string proxyHostname = o.manualProxyHostname.empty() ? systemHTTPConnectProxy.getAddress().toString() : o.manualProxyHostname;
+ int proxyPort = o.manualProxyPort == -1 ? systemHTTPConnectProxy.getPort() : o.manualProxyPort;
+ logger_.fine("Proxy: " + proxyHostname + ":" << proxyPort + "\n");
+ proxyConnectionFactories.add(new HTTPConnectProxiedConnectionFactory(networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory(), proxyHostname, proxyPort, o.httpTrafficFilter));
+ useDirectConnection = false;
+ break;
+ }
+ }
+ Vector<ConnectionFactory> connectionFactories = new Vector<ConnectionFactory>(proxyConnectionFactories);
+ if (useDirectConnection) {
+ connectionFactories.add(networkFactories.getConnectionFactory());
+ }*/
+
String host = (o.manualHostname == null || o.manualHostname.isEmpty()) ? jid_.getDomain() : o.manualHostname;
int port = o.manualPort;
String serviceLookupPrefix = (o.manualHostname == null || o.manualHostname.isEmpty() ? "_xmpp-client._tcp." : null);
+ assert(connector_ == null);
+ //TO PORT
+ /*if (options.boshURL.isEmpty()) {
+ connector_ = new ChainedConnector(host, port, serviceLookupPrefix, networkFactories.getDomainNameResolver(), connectionFactories, networkFactories.getTimerFactory());
+ connector_.onConnectFinished.connect(boost::bind(&CoreClient::handleConnectorFinished, this, _1, _2));
+ connector_.setTimeoutMilliseconds(2*60*1000);
+ connector_.start();
+ }
+ else {
+ /* Autodiscovery of which proxy works is largely ok with a TCP session, because this is a one-off. With BOSH
+ * it would be quite painful given that potentially every stanza could be sent on a new connection.
+ */
+ //sessionStream_ = boost::make_shared<BOSHSessionStream>(boost::make_shared<BOSHConnectionFactory>(options.boshURL, networkFactories.getConnectionFactory(), networkFactories.getXMLParserFactory(), networkFactories.getTLSContextFactory()), getPayloadParserFactories(), getPayloadSerializers(), networkFactories.getTLSContextFactory(), networkFactories.getTimerFactory(), networkFactories.getXMLParserFactory(), networkFactories.getEventLoop(), host, options.boshHTTPConnectProxyURL, options.boshHTTPConnectProxyAuthID, options.boshHTTPConnectProxyAuthPassword);
+ /*sessionStream_ = boost::shared_ptr<BOSHSessionStream>(new BOSHSessionStream(
+ options.boshURL,
+ getPayloadParserFactories(),
+ getPayloadSerializers(),
+ networkFactories.getConnectionFactory(),
+ networkFactories.getTLSContextFactory(),
+ networkFactories.getTimerFactory(),
+ networkFactories.getXMLParserFactory(),
+ networkFactories.getEventLoop(),
+ networkFactories.getDomainNameResolver(),
+ host,
+ options.boshHTTPConnectProxyURL,
+ options.boshHTTPConnectProxyAuthID,
+ options.boshHTTPConnectProxyAuthPassword,
+ options.tlsOptions));
+ sessionStream_.onDataRead.connect(boost::bind(&CoreClient::handleDataRead, this, _1));
+ sessionStream_.onDataWritten.connect(boost::bind(&CoreClient::handleDataWritten, this, _1));
+ bindSessionToStream();
+ }*/
connector_ = Connector.create(host, port, serviceLookupPrefix, networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory());
connectorConnectFinishedConnection_ = connector_.onConnectFinished.connect(new Slot2<Connection, com.isode.stroke.base.Error>() {
public void call(Connection p1, com.isode.stroke.base.Error p2) {
handleConnectorFinished(p1, p2);
}
});
- connector_.setTimeoutMilliseconds(60 * 1000);
+ connector_.setTimeoutMilliseconds(2*60*1000);
connector_.start();
}
private void bindSessionToStream() {
- session_ = ClientSession.create(jid_, sessionStream_);
+ //TO PORT
+ //session_ = ClientSession::create(jid_, sessionStream_, networkFactories.getIDNConverter(), networkFactories.getCryptoProvider());
+ session_ = ClientSession.create(jid_, sessionStream_, new ICUConverter(), new JavaCryptoProvider());
session_.setCertificateTrustChecker(certificateTrustChecker);
session_.setUseStreamCompression(options.useStreamCompression);
session_.setAllowPLAINOverNonTLS(options.allowPLAINWithoutTLS);
+ session_.setSingleSignOn(options.singleSignOn);
+ session_.setAuthenticationPort(options.manualPort);
switch (options.useTLS) {
case UseTLSWhenAvailable:
session_.setUseTLS(ClientSession.UseTLS.UseTLSWhenAvailable);
- session_.setCertificateTrustChecker(certificateTrustChecker);
break;
case NeverUseTLS:
session_.setUseTLS(ClientSession.UseTLS.NeverUseTLS);
@@ -229,10 +328,13 @@ public class CoreClient {
session_.start();
}
- void handleConnectorFinished(final Connection connection, final com.isode.stroke.base.Error error) {
+ private void handleConnectorFinished(final Connection connection, final com.isode.stroke.base.Error error) {
resetConnector();
if (connection == null) {
+ if (options.forgetPassword) {
+ purgePassword();
+ }
ClientError clientError = null;
if (!disconnectRequested_) {
if (error instanceof DomainNameResolveError) {
@@ -242,7 +344,8 @@ public class CoreClient {
}
}
onDisconnected.emit(clientError);
- } else {
+ }
+ else {
assert (connection_ == null);
connection_ = connection;
@@ -284,7 +387,12 @@ public class CoreClient {
connector_.stop();
}
}
-
+
+ /**
+ * Checks whether the client is active.
+ *
+ * A client is active when it is connected or connecting to the server.
+ */
public boolean isActive() {
return (session_ != null && !session_.isFinished()) || connector_ != null;
}
@@ -308,19 +416,14 @@ public class CoreClient {
}
private void handleSessionFinished(final com.isode.stroke.base.Error error) {
- sessionFinishedConnection_.disconnect();
- sessionNeedCredentialsConnection_.disconnect();
- session_ = null;
-
- sessionStreamDataReadConnection_.disconnect();
- sessionStreamDataWrittenConnection_.disconnect();
- sessionStream_ = null;
-
- connection_.disconnect();
- connection_ = null;
+ if (options.forgetPassword) {
+ purgePassword();
+ }
+ resetSession();
- ClientError clientError = null;
+ ClientError actualerror = null;
if (error != null) {
+ ClientError clientError = null;
if (error instanceof ClientSession.Error) {
ClientSession.Error actualError = (ClientSession.Error) error;
switch (actualError.type) {
@@ -351,9 +454,23 @@ public class CoreClient {
case TLSClientCertificateError:
clientError = new ClientError(ClientError.Type.ClientCertificateError);
break;
- /* Note: no case clause for "StreamError" */
+ case StreamError:
+ clientError = new ClientError(ClientError.Type.StreamError);
+ break;
}
- } else if (error instanceof SessionStream.SessionStreamError) {
+ }
+ else if (error instanceof TLSError) {
+ TLSError actualError = (TLSError)error;
+ switch(actualError.getType()) {
+ case CertificateCardRemoved:
+ clientError = new ClientError(ClientError.Type.CertificateCardRemoved);
+ break;
+ case UnknownError:
+ clientError = new ClientError(ClientError.Type.TLSError);
+ break;
+ }
+ }
+ else if (error instanceof SessionStream.SessionStreamError) {
SessionStream.SessionStreamError actualError = (SessionStream.SessionStreamError) error;
switch (actualError.type) {
case ParseError:
@@ -408,19 +525,29 @@ public class CoreClient {
case InvalidServerIdentity:
clientError = new ClientError(ClientError.Type.InvalidServerIdentityError);
break;
+ case Revoked:
+ clientError = new ClientError(ClientError.Type.RevokedError);
+ break;
+ case RevocationCheckFailed:
+ clientError = new ClientError(ClientError.Type.RevocationCheckFailedError);
+ break;
}
}
/* If "error" was non-null, we expect to be able to derive
* a non-null "clientError".
*/
NotNull.exceptIfNull(clientError,"clientError");
+ actualerror = clientError;
}
- onDisconnected.emit(clientError);
+ onDisconnected.emit(actualerror);
}
private void handleNeedCredentials() {
assert session_ != null;
session_.sendCredentials(password_);
+ if (options.forgetPassword) {
+ purgePassword();
+ }
}
private void handleDataRead(final SafeByteArray data) {
@@ -431,8 +558,26 @@ public class CoreClient {
onDataWritten.emit(data);
}
+ private void handlePresenceReceived(Presence presence) {
+ onPresenceReceived.emit(presence);
+ }
+
+ private void handleMessageReceived(Message message) {
+ onMessageReceived.emit(message);
+ }
+
+ private void handleStanzaAcked(Stanza stanza) {
+ onStanzaAcked.emit(stanza);
+ }
+
+ private void purgePassword() {
+ password_ = new SafeByteArray();
+ }
+
private void handleStanzaChannelAvailableChanged(final boolean available) {
if (available) {
+ iqRouter_.setJID(session_.getLocalJID());
+ handleConnected();
onConnected.emit();
}
}
@@ -446,6 +591,13 @@ public class CoreClient {
}
/**
+ * Sends raw, unchecked data.
+ */
+ public void sendData(final String data) {
+ sessionStream_.writeData(data);
+ }
+
+ /**
* Get the IQRouter responsible for all IQs on this connection.
* Use this to send IQs.
*/
@@ -458,6 +610,8 @@ public class CoreClient {
}
/**
+ * Checks whether the client is connected to the server,
+ * and stanzas can be sent.
* @return session is available for sending/receiving stanzas.
*/
public boolean isAvailable() {
@@ -478,12 +632,26 @@ public class CoreClient {
connectorConnectFinishedConnection_.disconnect();
}
connector_ = null;
+ //TO PORT
+ /*for(ConnectionFactory* f, proxyConnectionFactories) {
+ delete f;
+ }
+ proxyConnectionFactories.clear();*/
}
protected ClientSession getSession() {
return session_;
}
+ protected NetworkFactories getNetworkFactories() {
+ return networkFactories;
+ }
+
+ /**
+ * Called before onConnected signal is emmitted.
+ */
+ protected void handleConnected() {}
+
private void resetSession() {
session_.onFinished.disconnectAll();
session_.onNeedCredentials.disconnectAll();
@@ -494,6 +662,10 @@ public class CoreClient {
if (connection_ != null) {
connection_.disconnect();
}
+ // TO PORT
+ /*else if (sessionStream_ instanceof BOSHSessionStream) {
+ sessionStream_.close();
+ }*/
sessionStream_ = null;
connection_ = null;
}
@@ -512,11 +684,25 @@ public class CoreClient {
}
}
+ /**
+ * Checks whether stream management is enabled.
+ *
+ * If stream management is enabled, onStanzaAcked will be
+ * emitted when a stanza is received by the server.
+ *
+ * \see onStanzaAcked
+ */
+ public boolean getStreamManagementEnabled() {
+ return stanzaChannel_.getStreamManagementEnabled();
+ }
+
private void forceReset() {
if (connector_ != null) {
+ logger_.warning("Client not disconnected properly: Connector still active\n");
resetConnector();
}
if (sessionStream_ != null || connection_ != null) {
+ logger_.warning("Client not disconnected properly: Session still active\n");
resetSession();
}
diff --git a/src/com/isode/stroke/client/DummyNickManager.java b/src/com/isode/stroke/client/DummyNickManager.java
new file mode 100644
index 0000000..ee7e67b
--- /dev/null
+++ b/src/com/isode/stroke/client/DummyNickManager.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.client;
+
+public class DummyNickManager extends NickManager {
+
+ public String getOwnNick() {
+ return "";
+ }
+
+ public void setOwnNick(final String nick) {
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/client/IDGenerator.java b/src/com/isode/stroke/client/IDGenerator.java
deleted file mode 100644
index c7bb494..0000000
--- a/src/com/isode/stroke/client/IDGenerator.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (c) 2010, Isode Limited, London, England.
- * All rights reserved.
- */
-/*
- * Copyright (c) 2010, Remko Tronçon.
- * All rights reserved.
- */
-
-package com.isode.stroke.client;
-
-/**
- *
- */
-public class IDGenerator {
- private int next_ = 42;
- public String generateID() {
- next_++;
- return String.valueOf(next_);
-
- }
-}
diff --git a/src/com/isode/stroke/client/MemoryStorages.java b/src/com/isode/stroke/client/MemoryStorages.java
index 16d235d..bafd480 100644
--- a/src/com/isode/stroke/client/MemoryStorages.java
+++ b/src/com/isode/stroke/client/MemoryStorages.java
@@ -14,8 +14,12 @@ import com.isode.stroke.roster.RosterStorage;
import com.isode.stroke.vcards.VCardMemoryStorage;
import com.isode.stroke.vcards.VCardStorage;
+/**
+ * An implementation of Storages for storing all
+ * controller data in memory.
+ */
public class MemoryStorages implements Storages {
- private VCardStorage vcardStorage;
+ private VCardMemoryStorage vcardStorage;
private AvatarStorage avatarStorage;
private CapsStorage capsStorage;
private RosterStorage rosterStorage;
@@ -53,4 +57,8 @@ public class MemoryStorages implements Storages {
return avatarStorage;
}
+ /*@Override
+ public HistoryStorage getHistoryStorage() {
+ return historyStorage;
+ }*/
}
diff --git a/src/com/isode/stroke/client/NickManagerImpl.java b/src/com/isode/stroke/client/NickManagerImpl.java
index 17f9d6b..8a2ebed 100644
--- a/src/com/isode/stroke/client/NickManagerImpl.java
+++ b/src/com/isode/stroke/client/NickManagerImpl.java
@@ -13,11 +13,12 @@ import com.isode.stroke.vcards.VCardManager;
public class NickManagerImpl extends NickManager {
private JID ownJID = new JID();
private String ownNick = "";
+ private VCardManager vcardManager;
private SignalConnection vCardChangedSignal;
- NickManagerImpl(final JID ownJID, VCardManager vcardManager) {
+ public NickManagerImpl(final JID ownJID, VCardManager vcardManager) {
this.ownJID = ownJID;
-
+ this.vcardManager = vcardManager;
vCardChangedSignal = vcardManager.onVCardChanged.connect(new Slot2<JID, VCard>() {
@Override
public void call(JID p1, VCard p2) {
@@ -53,7 +54,7 @@ public class NickManagerImpl extends NickManager {
if (vcard != null && !vcard.getNickname().isEmpty()) {
nick = vcard.getNickname();
}
- if (ownNick != nick && nick != null && !nick.equals(ownNick)) {
+ if (nick != ownNick && nick != null && !nick.equals(ownNick)) {
ownNick = nick;
onOwnNickChanged.emit(ownNick);
}
diff --git a/src/com/isode/stroke/client/NickResolver.java b/src/com/isode/stroke/client/NickResolver.java
index fe2b129..470e4b9 100644
--- a/src/com/isode/stroke/client/NickResolver.java
+++ b/src/com/isode/stroke/client/NickResolver.java
@@ -16,14 +16,19 @@ import com.isode.stroke.signals.Slot2;
import com.isode.stroke.signals.Slot3;
import com.isode.stroke.vcards.VCardManager;
+// FIXME: The NickResolver currently relies on the vcard being requested by the client on login.
+// The VCardManager should get an onConnected() signal (which is signalled when the stanzachannel is available(, and each time this is emitted,
+// the nickresolver should request the vcard.
+// FIXME: The ownJID functionality should probably be removed, and NickManager should be used directly.
+
public class NickResolver {
- private JID ownJID_;
- private String ownNick_;
+ private JID ownJID_ = new JID();
+ private String ownNick_ = "";
private XMPPRoster xmppRoster_;
private MUCRegistry mucRegistry_;
private VCardManager vcardManager_;
- public final Signal2<JID, String> onNickChanged = new Signal2<JID, String>();
+ public final Signal2<JID, String /*previousNick*/ > onNickChanged = new Signal2<JID, String>();
public NickResolver(final JID ownJID, XMPPRoster xmppRoster, VCardManager vcardManager, MUCRegistry mucRegistry) {
ownJID_ = ownJID;
@@ -57,7 +62,7 @@ public class NickResolver {
}
void handleJIDAdded(final JID jid) {
- String oldNick= jidToNick(jid);
+ String oldNick = jidToNick(jid);
onNickChanged.emit(jid, oldNick);
}
@@ -67,7 +72,7 @@ public class NickResolver {
return ownNick_;
}
}
-
+ String nick = "";
if (mucRegistry_ != null && mucRegistry_.isMUC(jid.toBare()) ) {
return jid.getResource().isEmpty() ? jid.toBare().toString() : jid.getResource();
}
@@ -83,6 +88,7 @@ public class NickResolver {
if (jid.compare(ownJID_, JID.CompareType.WithoutResource) != 0) {
return;
}
+ String initialNick = ownNick_;
ownNick_ = ownJID_.toString();
if (ownVCard != null) {
if (!ownVCard.getNickname().isEmpty()) {
diff --git a/src/com/isode/stroke/client/StanzaChannel.java b/src/com/isode/stroke/client/StanzaChannel.java
index 5a1d270..253f026 100644
--- a/src/com/isode/stroke/client/StanzaChannel.java
+++ b/src/com/isode/stroke/client/StanzaChannel.java
@@ -32,7 +32,7 @@ public abstract class StanzaChannel extends IQChannel {
public final Signal1<Message> onMessageReceived = new Signal1<Message>();
public final Signal1<Presence> onPresenceReceived = new Signal1<Presence>();
- public final Signal1<Boolean> onAvailableChanged = new Signal1<Boolean>();
+ public final Signal1<Boolean /* isAvailable */ > onAvailableChanged = new Signal1<Boolean>();
public final Signal1<Stanza> onStanzaAcked = new Signal1<Stanza>();
diff --git a/src/com/isode/stroke/client/Storages.java b/src/com/isode/stroke/client/Storages.java
index 25821e5..24ffff4 100644
--- a/src/com/isode/stroke/client/Storages.java
+++ b/src/com/isode/stroke/client/Storages.java
@@ -9,6 +9,10 @@ import com.isode.stroke.disco.CapsStorage;
import com.isode.stroke.roster.RosterStorage;
import com.isode.stroke.vcards.VCardStorage;
+/**
+ * An interface to hold storage classes for different
+ * controllers.
+ */
public interface Storages {
VCardStorage getVCardStorage();
AvatarStorage getAvatarStorage();
diff --git a/src/com/isode/stroke/client/XMLBeautifier.java b/src/com/isode/stroke/client/XMLBeautifier.java
new file mode 100644
index 0000000..7f6e346
--- /dev/null
+++ b/src/com/isode/stroke/client/XMLBeautifier.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+/*
+ * Copyright (c) 2014-2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.client;
+
+import java.util.Stack;
+import com.isode.stroke.parser.XMLParserClient;
+import com.isode.stroke.parser.XMLParser;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.PlatformXMLParserFactory;
+
+public class XMLBeautifier implements XMLParserClient {
+
+ private boolean doIndention;
+ private boolean doColoring;
+
+ private int intLevel;
+ private String inputBuffer = "";
+ private StringBuffer buffer = new StringBuffer();
+ private XMLParser parser;
+
+ private boolean lastWasStepDown;
+ private Stack<String> parentNSs = new Stack<String>();
+
+ // all bold but reset
+ public static final String colorReset = "\u001B[0m";
+ public static final String ANSI_BLACK = "\u001B[30m";
+ public static final String colorRed = "\u001B[31m";
+ public static final String colorGreen = "\u001B[32m";
+ public static final String colorYellow = "\u001B[33m";
+ public static final String colorBlue = "\u001B[34m";
+ public static final String ANSI_PURPLE = "\u001B[35m";
+ public static final String colorCyan = "\u001B[36m";
+ public static final String ANSI_WHITE = "\u001B[37m";
+
+ public XMLBeautifier(boolean indention, boolean coloring) {
+ this.doIndention = indention;
+ this.doColoring = coloring;
+ intLevel = 0;
+ parser = null;
+ lastWasStepDown = false;
+ }
+
+ public String beautify(final String text) {
+ parser = PlatformXMLParserFactory.createXMLParser(this);
+ intLevel = 0;
+ buffer.append("");
+ parser.parse(text);
+ parser = null;
+ return buffer.toString();
+ }
+
+ public void handleStartElement(final String element, final String ns, final AttributeMap attributes) {
+ if (doIndention) {
+ if (intLevel != 0) buffer.append("\n");
+ }
+ indent();
+ buffer.append("<").append(doColoring ? styleTag(element) : element);
+ if (!ns.isEmpty() && (!parentNSs.isEmpty() && !parentNSs.peek().equals(ns))) {
+ buffer.append(" ");
+ buffer.append((doColoring ? styleAttribute("xmlns") : "xmlns"));
+ buffer.append("=");
+ buffer.append("\"").append((doColoring ? styleNamespace(ns) : ns)).append("\"");
+ }
+ if (!attributes.getEntries().isEmpty()) {
+ for(AttributeMap.Entry entry : attributes.getEntries()) {
+ buffer.append(" ");
+ buffer.append((doColoring ? styleAttribute(entry.getAttribute().getName()) : entry.getAttribute().getName()));
+ buffer.append("=");
+ buffer.append("\"").append((doColoring ? styleValue(entry.getValue()) : entry.getValue())).append("\"");
+ }
+ }
+ buffer.append(">");
+ ++intLevel;
+ lastWasStepDown = false;
+ parentNSs.push(ns);
+ }
+
+ public void handleEndElement(final String element, final String ns) {
+ --intLevel;
+ parentNSs.pop();
+ if (/*hadCDATA.top() ||*/ lastWasStepDown) {
+ if (doIndention) {
+ buffer.append("\n");
+ }
+ indent();
+ }
+ buffer.append("</").append((doColoring ? styleTag(element) : element)).append(">");
+ lastWasStepDown = true;
+ }
+
+ public void handleCharacterData(final String data) {
+ buffer.append(data);
+ lastWasStepDown = false;
+ }
+
+ private void indent() {
+ for (int i = 0; i < intLevel; ++i) {
+ buffer.append(" ");
+ }
+ }
+
+ private String styleTag(final String text) {
+ String result = "";
+ result += colorYellow;
+ result += text;
+ result += colorReset;
+ return result;
+ }
+
+ private String styleNamespace(final String text) {
+ String result = "";
+ result += colorRed;
+ result += text;
+ result += colorReset;
+ return result;
+ }
+
+ private String styleAttribute(final String text) {
+ String result = "";
+ result += colorGreen;
+ result += text;
+ result += colorReset;
+ return result;
+ }
+
+ private String styleValue(final String text) {
+ String result = "";
+ result += colorCyan;
+ result += text;
+ result += colorReset;
+ return result;
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/crypto/JavaCryptoProvider.java b/src/com/isode/stroke/crypto/JavaCryptoProvider.java
index 1353225..98eee89 100644
--- a/src/com/isode/stroke/crypto/JavaCryptoProvider.java
+++ b/src/com/isode/stroke/crypto/JavaCryptoProvider.java
@@ -139,6 +139,6 @@ public class JavaCryptoProvider extends CryptoProvider {
@Override
public boolean isMD5AllowedForCrypto() {
- return false;
+ return true;
}
} \ No newline at end of file
diff --git a/src/com/isode/stroke/disco/ClientDiscoManager.java b/src/com/isode/stroke/disco/ClientDiscoManager.java
index ba620ed..52a31f2 100644
--- a/src/com/isode/stroke/disco/ClientDiscoManager.java
+++ b/src/com/isode/stroke/disco/ClientDiscoManager.java
@@ -69,7 +69,7 @@ public class ClientDiscoManager {
* Called when the client is connected. This resets the presence sender,
* such that it assumes initial presence hasn't been sent yet.
*/
- void handleConnected() {
+ public void handleConnected() {
presenceSender.reset();
}
diff --git a/test/com/isode/stroke/client/ClientBlockListManagerTest.java b/test/com/isode/stroke/client/ClientBlockListManagerTest.java
new file mode 100644
index 0000000..2ec75bc
--- /dev/null
+++ b/test/com/isode/stroke/client/ClientBlockListManagerTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.Before;
+import com.isode.stroke.elements.BlockPayload;
+import com.isode.stroke.elements.BlockListPayload;
+import com.isode.stroke.elements.UnblockPayload;
+import com.isode.stroke.client.StanzaChannel;
+import com.isode.stroke.client.DummyStanzaChannel;
+import com.isode.stroke.client.ClientBlockListManager;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+
+public class ClientBlockListManagerTest {
+
+ private JID ownJID_ = new JID();
+ private IQRouter iqRouter_;
+ private DummyStanzaChannel stanzaChannel_;
+ private ClientBlockListManager clientBlockListManager_;
+
+ private void helperInitialBlockListFetch(final Vector<JID> blockedJids) {
+ BlockList blockList = clientBlockListManager_.requestBlockList();
+ assertNotNull(blockList);
+
+ // check for IQ request
+ IQ request = stanzaChannel_.getStanzaAtIndex(new IQ(), 0);
+ assertNotNull(request);
+ BlockListPayload requestPayload = request.getPayload(new BlockListPayload());
+ assertNotNull(requestPayload);
+
+ assertEquals(BlockList.State.Requesting, blockList.getState());
+ assertEquals(BlockList.State.Requesting, clientBlockListManager_.getBlockList().getState());
+
+ // build IQ response
+ BlockListPayload responsePayload = new BlockListPayload();
+ for(final JID jid : blockedJids) {
+ responsePayload.addItem(jid);
+ }
+
+ IQ response = IQ.createResult(ownJID_, new JID(), request.getID(), responsePayload);
+ stanzaChannel_.sendIQ(response);
+ stanzaChannel_.onIQReceived.emit(response);
+
+ assertEquals(BlockList.State.Available, clientBlockListManager_.getBlockList().getState());
+ assertEquals(responsePayload.getItems(), clientBlockListManager_.getBlockList().getItems());
+ }
+
+ @Before
+ public void setUp() {
+ ownJID_ = new JID("kev@wonderland.lit");
+ stanzaChannel_ = new DummyStanzaChannel();
+ iqRouter_ = new IQRouter(stanzaChannel_);
+ iqRouter_.setJID(ownJID_);
+ clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
+ }
+
+ @Test
+ public void testFetchBlockList() {
+ Vector<JID> blockJids = new Vector<JID>();
+ blockJids.add(new JID("romeo@montague.net"));
+ blockJids.add(new JID("iago@shakespeare.lit"));
+ helperInitialBlockListFetch(blockJids);
+
+ assertEquals(2, clientBlockListManager_.getBlockList().getItems().size());
+ }
+
+ @Test
+ public void testBlockCommand() {
+ // start with an already fetched block list
+ Vector<JID> vec = new Vector<JID>();
+ vec.add(new JID("iago@shakespeare.lit"));
+ helperInitialBlockListFetch(vec);
+
+ assertEquals(1, clientBlockListManager_.getBlockList().getItems().size());
+ assertEquals(BlockList.State.Available, clientBlockListManager_.getBlockList().getState());
+
+ GenericRequest<BlockPayload> blockRequest = clientBlockListManager_.createBlockJIDRequest(new JID("romeo@montague.net"));
+ blockRequest.send();
+ IQ request = stanzaChannel_.getStanzaAtIndex(new IQ(), 2);
+ assertNotNull(request);
+ BlockPayload blockPayload = request.getPayload(new BlockPayload());
+ assertNotNull(blockPayload);
+ assertEquals(new JID("romeo@montague.net"), blockPayload.getItems().get(0));
+
+ IQ blockRequestResponse = IQ.createResult(request.getFrom(), new JID(), request.getID());
+ stanzaChannel_.sendIQ(blockRequestResponse);
+ stanzaChannel_.onIQReceived.emit(blockRequestResponse);
+
+ assertEquals((1), clientBlockListManager_.getBlockList().getItems().size());
+
+ // send block push
+ BlockPayload pushPayload = new BlockPayload();
+ pushPayload.addItem(new JID("romeo@montague.net"));
+ IQ blockPush = IQ.createRequest(IQ.Type.Set, ownJID_, "push1", pushPayload);
+ stanzaChannel_.sendIQ(blockPush);
+ stanzaChannel_.onIQReceived.emit(blockPush);
+
+ Vector<JID> blockedJIDs = clientBlockListManager_.getBlockList().getItems();
+ assertTrue(blockedJIDs.contains(new JID("romeo@montague.net")));
+ }
+
+ @Test
+ public void testUnblockCommand() {
+ // start with an already fetched block list
+ Vector<JID> initialBlockList = new Vector<JID>();
+ initialBlockList.add(new JID("iago@shakespeare.lit"));
+ initialBlockList.add(new JID("romeo@montague.net"));
+ helperInitialBlockListFetch(initialBlockList);
+
+ assertEquals((2), clientBlockListManager_.getBlockList().getItems().size());
+ assertEquals(BlockList.State.Available, clientBlockListManager_.getBlockList().getState());
+
+ GenericRequest<UnblockPayload> unblockRequest = clientBlockListManager_.createUnblockJIDRequest(new JID("romeo@montague.net"));
+ unblockRequest.send();
+ IQ request = stanzaChannel_.getStanzaAtIndex(new IQ(), 2);
+ assertNotNull(request);
+ UnblockPayload unblockPayload = request.getPayload(new UnblockPayload());
+ assertNotNull(unblockPayload);
+ assertEquals(new JID("romeo@montague.net"), unblockPayload.getItems().get(0));
+
+ IQ unblockRequestResponse = IQ.createResult(request.getFrom(), new JID(), request.getID());
+ stanzaChannel_.sendIQ(unblockRequestResponse);
+ stanzaChannel_.onIQReceived.emit(unblockRequestResponse);
+
+ assertEquals((2), clientBlockListManager_.getBlockList().getItems().size());
+
+ // send block push
+ UnblockPayload pushPayload = new UnblockPayload();
+ pushPayload.addItem(new JID("romeo@montague.net"));
+ IQ unblockPush = IQ.createRequest(IQ.Type.Set, ownJID_, "push1", pushPayload);
+ stanzaChannel_.sendIQ(unblockPush);
+ stanzaChannel_.onIQReceived.emit(unblockPush);
+
+ Vector<JID> blockedJIDs = clientBlockListManager_.getBlockList().getItems();
+ assertFalse(blockedJIDs.contains(new JID("romeo@montague.net")));
+ }
+
+ @Test
+ public void testUnblockAllCommand() {
+ // start with an already fetched block list
+ Vector<JID> initialBlockList = new Vector<JID>();
+ initialBlockList.add(new JID("iago@shakespeare.lit"));
+ initialBlockList.add(new JID("romeo@montague.net"));
+ initialBlockList.add(new JID("benvolio@montague.net"));
+ helperInitialBlockListFetch(initialBlockList);
+
+ assertEquals((3), clientBlockListManager_.getBlockList().getItems().size());
+ assertEquals(BlockList.State.Available, clientBlockListManager_.getBlockList().getState());
+
+ GenericRequest<UnblockPayload> unblockRequest = clientBlockListManager_.createUnblockAllRequest();
+ unblockRequest.send();
+ IQ request = stanzaChannel_.getStanzaAtIndex(new IQ(), 2);
+ assertNotNull(request);
+ UnblockPayload unblockPayload = request.getPayload(new UnblockPayload());
+ assertNotNull(unblockPayload);
+ assertEquals(true, unblockPayload.getItems().isEmpty());
+
+ IQ unblockRequestResponse = IQ.createResult(request.getFrom(), new JID(), request.getID());
+ stanzaChannel_.sendIQ(unblockRequestResponse);
+ stanzaChannel_.onIQReceived.emit(unblockRequestResponse);
+
+ assertEquals((3), clientBlockListManager_.getBlockList().getItems().size());
+
+ // send block push
+ UnblockPayload pushPayload = new UnblockPayload();
+ IQ unblockPush = IQ.createRequest(IQ.Type.Set, ownJID_, "push1", pushPayload);
+ stanzaChannel_.sendIQ(unblockPush);
+ stanzaChannel_.onIQReceived.emit(unblockPush);
+
+ assertEquals(true, clientBlockListManager_.getBlockList().getItems().isEmpty());
+ }
+} \ No newline at end of file
diff --git a/test/com/isode/stroke/client/ClientSessionTest.java b/test/com/isode/stroke/client/ClientSessionTest.java
new file mode 100644
index 0000000..75ad504
--- /dev/null
+++ b/test/com/isode/stroke/client/ClientSessionTest.java
@@ -0,0 +1,871 @@
+/*
+ * Copyright (c) 2010-2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.Before;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.session.SessionStream;
+import com.isode.stroke.elements.AuthChallenge;
+import com.isode.stroke.elements.AuthFailure;
+import com.isode.stroke.elements.AuthRequest;
+import com.isode.stroke.elements.Message;
+import com.isode.stroke.elements.AuthResponse;
+import com.isode.stroke.elements.AuthSuccess;
+import com.isode.stroke.elements.CompressFailure;
+import com.isode.stroke.elements.CompressRequest;
+import com.isode.stroke.elements.Compressed;
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.EnableStreamManagement;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.elements.ProtocolHeader;
+import com.isode.stroke.elements.ResourceBind;
+import com.isode.stroke.elements.Stanza;
+import com.isode.stroke.elements.StanzaAck;
+import com.isode.stroke.elements.StanzaAckRequest;
+import com.isode.stroke.elements.StartSession;
+import com.isode.stroke.elements.StartTLSFailure;
+import com.isode.stroke.elements.StreamFeatures;
+import com.isode.stroke.elements.StartTLSRequest;
+import com.isode.stroke.elements.StreamError;
+import com.isode.stroke.elements.StreamManagementEnabled;
+import com.isode.stroke.elements.StreamManagementFailed;
+import com.isode.stroke.elements.TLSProceed;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.sasl.ClientAuthenticator;
+import com.isode.stroke.sasl.PLAINClientAuthenticator;
+import com.isode.stroke.sasl.SCRAMSHA1ClientAuthenticator;
+import com.isode.stroke.sasl.DIGESTMD5ClientAuthenticator;
+import com.isode.stroke.sasl.EXTERNALClientAuthenticator;
+import com.isode.stroke.session.SessionStream;
+import com.isode.stroke.client.ClientSession;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.streammanagement.StanzaAckRequester;
+import com.isode.stroke.streammanagement.StanzaAckResponder;
+import com.isode.stroke.tls.Certificate;
+import com.isode.stroke.tls.CertificateTrustChecker;
+import com.isode.stroke.tls.CertificateVerificationError;
+import com.isode.stroke.tls.ServerIdentityVerifier;
+import com.isode.stroke.tls.SimpleCertificate;
+import com.isode.stroke.tls.BlindCertificateTrustChecker;
+import com.isode.stroke.idn.IDNConverter;
+import com.isode.stroke.idn.ICUConverter;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.crypto.JavaCryptoProvider;
+import java.util.logging.Logger;
+import java.util.List;
+import java.util.UUID;
+import java.util.Vector;
+import java.util.Collection;
+
+public class ClientSessionTest {
+
+ private IDNConverter idnConverter;
+ private MockSessionStream server;
+ private boolean sessionFinishedReceived;
+ private boolean needCredentials;
+ private com.isode.stroke.base.Error sessionFinishedError;
+ private BlindCertificateTrustChecker blindCertificateTrustChecker;
+ private CryptoProvider crypto;
+
+ private class MockSessionStream extends SessionStream {
+
+ class Event {
+ public Event(Element element) {
+ this.element = element;
+ this.footer = false;
+ }
+ public Event(final ProtocolHeader header) {
+ this.header = header;
+ this.footer = false;
+ }
+ public Event() {
+ this.footer = true;
+ }
+
+ public Element element;
+ public ProtocolHeader header;
+ public boolean footer;
+ };
+
+ public MockSessionStream() {
+ this.available = true;
+ this.canTLSEncrypt = true;
+ this.tlsEncrypted = false;
+ this.compressed = false;
+ this.whitespacePingEnabled = false;
+ this.resetCount = 0;
+ }
+
+ public void disconnect() {
+
+ }
+
+ public void close() {
+ onClosed.emit((com.isode.stroke.base.Error)null);
+ }
+
+ public boolean isOpen() {
+ return available;
+ }
+
+ public void writeHeader(final ProtocolHeader header) {
+ receivedEvents.add(new Event(header));
+ }
+
+ public void writeFooter() {
+ receivedEvents.add(new Event());
+ }
+
+ public void writeElement(Element element) {
+ receivedEvents.add(new Event(element));
+ }
+
+ public void writeData(final String data) {
+ }
+
+ public boolean supportsTLSEncryption() {
+ return canTLSEncrypt;
+ }
+
+ public void addTLSEncryption() {
+ tlsEncrypted = true;
+ }
+
+ public boolean isTLSEncrypted() {
+ return tlsEncrypted;
+ }
+
+ public ByteArray getTLSFinishMessage() {
+ return new ByteArray();
+ }
+
+ public Certificate getPeerCertificate() {
+ return new SimpleCertificate();
+ }
+
+ public Vector<Certificate> getPeerCertificateChain() {
+ return new Vector<Certificate>();
+ }
+
+ public CertificateVerificationError getPeerCertificateVerificationError() {
+ return null;
+ }
+
+ public boolean supportsZLibCompression() {
+ return true;
+ }
+
+ public void addZLibCompression() {
+ compressed = true;
+ }
+
+ public void setWhitespacePingEnabled(boolean enabled) {
+ whitespacePingEnabled = enabled;
+ }
+
+ public void resetXMPPParser() {
+ resetCount++;
+ }
+
+ public void breakConnection() {
+ onClosed.emit(new SessionStream.SessionStreamError(SessionStream.SessionStreamError.Type.ConnectionReadError));
+ }
+
+ public void breakTLS() {
+ onClosed.emit(new SessionStream.SessionStreamError(SessionStream.SessionStreamError.Type.TLSError));
+ }
+
+
+ public void sendStreamStart() {
+ ProtocolHeader header = new ProtocolHeader();
+ header.setTo("foo.com");
+ onStreamStartReceived.emit(header);
+ }
+
+ public void sendStreamFeaturesWithStartTLS() {
+ StreamFeatures streamFeatures = new StreamFeatures();
+ streamFeatures.setHasStartTLS();
+ onElementReceived.emit(streamFeatures);
+ }
+
+ public void sendChallenge() {
+ onElementReceived.emit(new AuthChallenge());
+ }
+
+ public void sendStreamError() {
+ onElementReceived.emit(new StreamError());
+ }
+
+ public void sendTLSProceed() {
+ onElementReceived.emit(new TLSProceed());
+ }
+
+ public void sendTLSFailure() {
+ onElementReceived.emit(new StartTLSFailure());
+ }
+
+ public void sendStreamFeaturesWithMultipleAuthentication() {
+ StreamFeatures streamFeatures = new StreamFeatures();
+ streamFeatures.addAuthenticationMechanism("PLAIN");
+ streamFeatures.addAuthenticationMechanism("DIGEST-MD5");
+ streamFeatures.addAuthenticationMechanism("SCRAM-SHA1");
+ onElementReceived.emit(streamFeatures);
+ }
+
+ public void sendStreamFeaturesWithPLAINAuthentication() {
+ StreamFeatures streamFeatures = new StreamFeatures();
+ streamFeatures.addAuthenticationMechanism("PLAIN");
+ onElementReceived.emit(streamFeatures);
+ }
+
+ public void sendStreamFeaturesWithEXTERNALAuthentication() {
+ StreamFeatures streamFeatures = new StreamFeatures();
+ streamFeatures.addAuthenticationMechanism("EXTERNAL");
+ onElementReceived.emit(streamFeatures);
+ }
+
+ public void sendStreamFeaturesWithUnknownAuthentication() {
+ StreamFeatures streamFeatures = new StreamFeatures();
+ streamFeatures.addAuthenticationMechanism("UNKNOWN");
+ onElementReceived.emit(streamFeatures);
+ }
+
+ public void sendStreamFeaturesWithBindAndStreamManagement() {
+ StreamFeatures streamFeatures = new StreamFeatures();
+ streamFeatures.setHasResourceBind();
+ streamFeatures.setHasStreamManagement();
+ onElementReceived.emit(streamFeatures);
+ }
+
+ public void sendEmptyStreamFeatures() {
+ onElementReceived.emit(new StreamFeatures());
+ }
+
+ public void sendAuthSuccess() {
+ onElementReceived.emit(new AuthSuccess());
+ }
+
+ public void sendAuthFailure() {
+ onElementReceived.emit(new AuthFailure());
+ }
+
+ public void sendStreamManagementEnabled() {
+ onElementReceived.emit(new StreamManagementEnabled());
+ }
+
+ public void sendStreamManagementFailed() {
+ onElementReceived.emit(new StreamManagementFailed());
+ }
+
+ public void sendBindResult() {
+ ResourceBind resourceBind = new ResourceBind();
+ resourceBind.setJID(new JID("foo@bar.com/bla"));
+ IQ iq = IQ.createResult(new JID("foo@bar.com"), bindID, resourceBind);
+ onElementReceived.emit(iq);
+ }
+
+ public void sendMessage() {
+ Message message = new Message();
+ message.setTo(new JID("foo@bar.com/bla"));
+ onElementReceived.emit(message);
+ }
+
+ public void receiveStreamStart() {
+ Event event = popEvent();
+ assertNotNull(event.header);
+ }
+
+ public void receiveStartTLS() {
+ Event event = popEvent();
+ assertNotNull(event.element);
+ assertTrue(event.element instanceof StartTLSRequest);
+ }
+
+ public void receiveAuthRequest(final String mech) {
+ Event event = popEvent();
+ assertNotNull(event.element);
+ AuthRequest request = (AuthRequest)(event.element);
+ assertNotNull(request);
+ assertEquals(mech, request.getMechanism());
+ }
+
+ public void receiveStreamManagementEnable() {
+ Event event = popEvent();
+ assertNotNull(event.element);
+ assertTrue(event.element instanceof EnableStreamManagement);
+ }
+
+ public void receiveBind() {
+ Event event = popEvent();
+ assertNotNull(event.element);
+ IQ iq = (IQ)(event.element);
+ assertNotNull(iq);
+ assertNotNull(iq.getPayload(new ResourceBind()));
+ bindID = iq.getID();
+ }
+
+ public void receiveAck(int n) {
+ Event event = popEvent();
+ assertNotNull(event.element);
+ StanzaAck ack = (StanzaAck)(event.element);
+ assertNotNull(ack);
+ assertEquals(n, ack.getHandledStanzasCount());
+ }
+
+ public Event popEvent() {
+ assertFalse(receivedEvents.isEmpty());
+ Event event = receivedEvents.firstElement();
+ receivedEvents.remove(receivedEvents.firstElement());
+ return event;
+ }
+
+ public boolean available;
+ public boolean canTLSEncrypt;
+ public boolean tlsEncrypted;
+ public boolean compressed;
+ public boolean whitespacePingEnabled;
+ public String bindID = "";
+ public int resetCount;
+ public Vector<Event> receivedEvents = new Vector<Event>();
+ };
+
+ public ClientSession createSession() {
+ ClientSession session = ClientSession.create(new JID("me@foo.com"), server, idnConverter, crypto);
+ session.onFinished.connect(new Slot1<com.isode.stroke.base.Error>() {
+ @Override
+ public void call(com.isode.stroke.base.Error e) {
+ handleSessionFinished(e);
+ }
+ });
+ session.onNeedCredentials.connect(new Slot() {
+ @Override
+ public void call() {
+ handleSessionNeedCredentials();
+ }
+ });
+ session.setAllowPLAINOverNonTLS(true);
+ return session;
+ }
+
+ public void initializeSession(ClientSession session) {
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithPLAINAuthentication();
+ session.sendCredentials(new SafeByteArray("mypass"));
+ server.receiveAuthRequest("PLAIN");
+ server.sendAuthSuccess();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithBindAndStreamManagement();
+ server.receiveBind();
+ server.sendBindResult();
+ server.receiveStreamManagementEnable();
+ server.sendStreamManagementEnabled();
+ }
+
+ public void handleSessionFinished(com.isode.stroke.base.Error error) {
+ sessionFinishedReceived = true;
+ sessionFinishedError = error;
+ }
+
+ public void handleSessionNeedCredentials() {
+ needCredentials = true;
+ }
+
+ @Before
+ public void setUp() {
+ crypto = new JavaCryptoProvider();
+ idnConverter = new ICUConverter();
+ server = new MockSessionStream();
+ sessionFinishedReceived = false;
+ needCredentials = false;
+ blindCertificateTrustChecker = new BlindCertificateTrustChecker();
+ }
+
+ @Test
+ public void testStart_Error() {
+ ClientSession session = createSession();
+ session.start();
+ server.breakConnection();
+
+ assertEquals(ClientSession.State.Finished, session.getState());
+ assertTrue(sessionFinishedReceived);
+ assertNotNull(sessionFinishedError);
+ }
+
+ @Test
+ public void testStart_StreamError() {
+ ClientSession session = createSession();
+ session.start();
+ server.sendStreamStart();
+ server.sendStreamError();
+
+ assertEquals(ClientSession.State.Finished, session.getState());
+ assertTrue(sessionFinishedReceived);
+ assertNotNull(sessionFinishedError);
+ }
+
+ @Test
+ public void testStartTLS() {
+ ClientSession session = createSession();
+ session.setCertificateTrustChecker(blindCertificateTrustChecker);
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithStartTLS();
+ server.receiveStartTLS();
+ assertFalse(server.tlsEncrypted);
+ server.sendTLSProceed();
+ assertTrue(server.tlsEncrypted);
+ server.onTLSEncrypted.emit();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+
+ session.finish();
+ }
+
+ @Test
+ public void testStartTLS_ServerError() {
+ ClientSession session = createSession();
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithStartTLS();
+ server.receiveStartTLS();
+ server.sendTLSFailure();
+
+ assertFalse(server.tlsEncrypted);
+ assertEquals(ClientSession.State.Finished, session.getState());
+ assertTrue(sessionFinishedReceived);
+ assertNotNull(sessionFinishedError);
+ }
+
+ @Test
+ public void testStartTLS_ConnectError() {
+ ClientSession session = createSession();
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithStartTLS();
+ server.receiveStartTLS();
+ server.sendTLSProceed();
+ server.breakTLS();
+
+ assertEquals(ClientSession.State.Finished, session.getState());
+ assertTrue(sessionFinishedReceived);
+ assertNotNull(sessionFinishedError);
+ }
+
+ @Test
+ public void testStartTLS_InvalidIdentity() {
+ ClientSession session = createSession();
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithStartTLS();
+ server.receiveStartTLS();
+ assertFalse(server.tlsEncrypted);
+ server.sendTLSProceed();
+ assertTrue(server.tlsEncrypted);
+ server.onTLSEncrypted.emit();
+
+ assertEquals(ClientSession.State.Finished, session.getState());
+ assertTrue(sessionFinishedReceived);
+ assertNotNull(sessionFinishedError);
+ assertEquals(CertificateVerificationError.Type.InvalidServerIdentity, ((CertificateVerificationError)(sessionFinishedError)).getType());
+ }
+
+ @Test
+ public void testStart_StreamFeaturesWithoutResourceBindingFails() {
+ ClientSession session = createSession();
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendEmptyStreamFeatures();
+
+ assertEquals(ClientSession.State.Finished, session.getState());
+ assertTrue(sessionFinishedReceived);
+ assertNotNull(sessionFinishedError);
+ }
+
+ @Test
+ public void testAuthenticate() {
+ ClientSession session = createSession();
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithPLAINAuthentication();
+ assertTrue(needCredentials);
+ assertEquals(ClientSession.State.WaitingForCredentials, session.getState());
+ session.sendCredentials(new SafeByteArray("mypass"));
+ server.receiveAuthRequest("PLAIN");
+ server.sendAuthSuccess();
+ server.receiveStreamStart();
+
+ session.finish();
+ }
+
+ @Test
+ public void testAuthenticate_Unauthorized() {
+ ClientSession session = createSession();
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithPLAINAuthentication();
+ assertTrue(needCredentials);
+ assertEquals(ClientSession.State.WaitingForCredentials, session.getState());
+ session.sendCredentials(new SafeByteArray("mypass"));
+ server.receiveAuthRequest("PLAIN");
+ server.sendAuthFailure();
+
+ assertEquals(ClientSession.State.Finished, session.getState());
+ assertTrue(sessionFinishedReceived);
+ assertNotNull(sessionFinishedError);
+ }
+
+ @Test
+ public void testAuthenticate_PLAINOverNonTLS() {
+ ClientSession session = createSession();
+ session.setAllowPLAINOverNonTLS(false);
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithPLAINAuthentication();
+
+ assertEquals(ClientSession.State.Finished, session.getState());
+ assertTrue(sessionFinishedReceived);
+ assertNotNull(sessionFinishedError);
+ }
+
+ @Test
+ public void testAuthenticate_RequireTLS() {
+ ClientSession session = createSession();
+ session.setUseTLS(ClientSession.UseTLS.RequireTLS);
+ session.setAllowPLAINOverNonTLS(true);
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithMultipleAuthentication();
+
+ assertEquals(ClientSession.State.Finished, session.getState());
+ assertTrue(sessionFinishedReceived);
+ assertNotNull(sessionFinishedError);
+ }
+
+ @Test
+ public void testAuthenticate_NoValidAuthMechanisms() {
+ ClientSession session = createSession();
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithUnknownAuthentication();
+
+ assertEquals(ClientSession.State.Finished, session.getState());
+ assertTrue(sessionFinishedReceived);
+ assertNotNull(sessionFinishedError);
+ }
+
+ @Test
+ public void testAuthenticate_EXTERNAL() {
+ ClientSession session = createSession();
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithEXTERNALAuthentication();
+ server.receiveAuthRequest("EXTERNAL");
+ server.sendAuthSuccess();
+ server.receiveStreamStart();
+
+ session.finish();
+ }
+
+ @Test
+ public void testUnexpectedChallenge() {
+ ClientSession session = createSession();
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithEXTERNALAuthentication();
+ server.receiveAuthRequest("EXTERNAL");
+ server.sendChallenge();
+ server.sendChallenge();
+
+ assertEquals(ClientSession.State.Finished, session.getState());
+ assertTrue(sessionFinishedReceived);
+ assertNotNull(sessionFinishedError);
+ }
+
+ @Test
+ public void testStreamManagement() {
+ ClientSession session = createSession();
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithPLAINAuthentication();
+ session.sendCredentials(new SafeByteArray("mypass"));
+ server.receiveAuthRequest("PLAIN");
+ server.sendAuthSuccess();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithBindAndStreamManagement();
+ server.receiveBind();
+ server.sendBindResult();
+ server.receiveStreamManagementEnable();
+ server.sendStreamManagementEnabled();
+
+ assertTrue(session.getStreamManagementEnabled());
+ // TODO: Test if the requesters & responders do their work
+ assertEquals(ClientSession.State.Initialized, session.getState());
+
+ session.finish();
+ }
+
+ @Test
+ public void testStreamManagement_Failed() {
+ ClientSession session = createSession();
+ session.start();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithPLAINAuthentication();
+ session.sendCredentials(new SafeByteArray("mypass"));
+ server.receiveAuthRequest("PLAIN");
+ server.sendAuthSuccess();
+ server.receiveStreamStart();
+ server.sendStreamStart();
+ server.sendStreamFeaturesWithBindAndStreamManagement();
+ server.receiveBind();
+ server.sendBindResult();
+ server.receiveStreamManagementEnable();
+ server.sendStreamManagementFailed();
+
+ assertFalse(session.getStreamManagementEnabled());
+ assertEquals(ClientSession.State.Initialized, session.getState());
+
+ session.finish();
+ }
+
+ @Test
+ public void testFinishAcksStanzas() {
+ ClientSession session = createSession();
+ initializeSession(session);
+ server.sendMessage();
+ server.sendMessage();
+ server.sendMessage();
+
+ session.finish();
+
+ server.receiveAck(3);
+ }
+
+ /*void testAuthenticate() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar"));
+ session->onNeedCredentials.connect(boost::bind(&ClientSessionTest::setNeedCredentials, this));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeaturesWithAuthentication();
+ session->startSession();
+ processEvents();
+ CPPUNIT_ASSERT_EQUAL(ClientSession::WaitingForCredentials, session->getState());
+ CPPUNIT_ASSERT(needCredentials_);
+
+ getMockServer()->expectAuth("me", "mypass");
+ getMockServer()->sendAuthSuccess();
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ session->sendCredentials("mypass");
+ CPPUNIT_ASSERT_EQUAL(ClientSession::Authenticating, session->getState());
+ processEvents();
+ CPPUNIT_ASSERT_EQUAL(ClientSession::Negotiating, session->getState());
+ }
+
+ void testAuthenticate_Unauthorized() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar"));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeaturesWithAuthentication();
+ session->startSession();
+ processEvents();
+
+ getMockServer()->expectAuth("me", "mypass");
+ getMockServer()->sendAuthFailure();
+ session->sendCredentials("mypass");
+ processEvents();
+
+ CPPUNIT_ASSERT_EQUAL(ClientSession::Error, session->getState());
+ CPPUNIT_ASSERT_EQUAL(ClientSession::AuthenticationFailedError, *session->getError());
+ }
+
+ void testAuthenticate_NoValidAuthMechanisms() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar"));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeaturesWithUnsupportedAuthentication();
+ session->startSession();
+ processEvents();
+
+ CPPUNIT_ASSERT_EQUAL(ClientSession::Error, session->getState());
+ CPPUNIT_ASSERT_EQUAL(ClientSession::NoSupportedAuthMechanismsError, *session->getError());
+ }
+
+ void testResourceBind() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar"));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeaturesWithResourceBind();
+ getMockServer()->expectResourceBind("Bar", "session-bind");
+ // FIXME: Check CPPUNIT_ASSERT_EQUAL(ClientSession::BindingResource, session->getState());
+ getMockServer()->sendResourceBindResponse("me@foo.com/Bar", "session-bind");
+ session->startSession();
+
+ processEvents();
+
+ CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStarted, session->getState());
+ CPPUNIT_ASSERT_EQUAL(JID("me@foo.com/Bar"), session->getLocalJID());
+ }
+
+ void testResourceBind_ChangeResource() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar"));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeaturesWithResourceBind();
+ getMockServer()->expectResourceBind("Bar", "session-bind");
+ getMockServer()->sendResourceBindResponse("me@foo.com/Bar123", "session-bind");
+ session->startSession();
+ processEvents();
+
+ CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStarted, session->getState());
+ CPPUNIT_ASSERT_EQUAL(JID("me@foo.com/Bar123"), session->getLocalJID());
+ }
+
+ void testResourceBind_EmptyResource() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com"));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeaturesWithResourceBind();
+ getMockServer()->expectResourceBind("", "session-bind");
+ getMockServer()->sendResourceBindResponse("me@foo.com/NewResource", "session-bind");
+ session->startSession();
+ processEvents();
+
+ CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStarted, session->getState());
+ CPPUNIT_ASSERT_EQUAL(JID("me@foo.com/NewResource"), session->getLocalJID());
+ }
+
+ void testResourceBind_Error() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com"));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeaturesWithResourceBind();
+ getMockServer()->expectResourceBind("", "session-bind");
+ getMockServer()->sendError("session-bind");
+ session->startSession();
+ processEvents();
+
+ CPPUNIT_ASSERT_EQUAL(ClientSession::Error, session->getState());
+ CPPUNIT_ASSERT_EQUAL(ClientSession::ResourceBindError, *session->getError());
+ }
+
+ void testSessionStart() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar"));
+ session->onSessionStarted.connect(boost::bind(&ClientSessionTest::setSessionStarted, this));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeaturesWithSession();
+ getMockServer()->expectSessionStart("session-start");
+ // FIXME: Check CPPUNIT_ASSERT_EQUAL(ClientSession::StartingSession, session->getState());
+ getMockServer()->sendSessionStartResponse("session-start");
+ session->startSession();
+ processEvents();
+
+ CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStarted, session->getState());
+ CPPUNIT_ASSERT(sessionStarted_);
+ }
+
+ void testSessionStart_Error() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar"));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeaturesWithSession();
+ getMockServer()->expectSessionStart("session-start");
+ getMockServer()->sendError("session-start");
+ session->startSession();
+ processEvents();
+
+ CPPUNIT_ASSERT_EQUAL(ClientSession::Error, session->getState());
+ CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStartError, *session->getError());
+ }
+
+ void testSessionStart_AfterResourceBind() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar"));
+ session->onSessionStarted.connect(boost::bind(&ClientSessionTest::setSessionStarted, this));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeaturesWithResourceBindAndSession();
+ getMockServer()->expectResourceBind("Bar", "session-bind");
+ getMockServer()->sendResourceBindResponse("me@foo.com/Bar", "session-bind");
+ getMockServer()->expectSessionStart("session-start");
+ getMockServer()->sendSessionStartResponse("session-start");
+ session->startSession();
+ processEvents();
+
+ CPPUNIT_ASSERT_EQUAL(ClientSession::SessionStarted, session->getState());
+ CPPUNIT_ASSERT(sessionStarted_);
+ }
+
+ void testWhitespacePing() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar"));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeatures();
+ session->startSession();
+ processEvents();
+ CPPUNIT_ASSERT(session->getWhitespacePingLayer());
+ }
+
+ void testReceiveElementAfterSessionStarted() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar"));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeatures();
+ session->startSession();
+ processEvents();
+
+ getMockServer()->expectMessage();
+ session->sendElement(boost::make_shared<Message>()));
+ }
+
+ void testSendElement() {
+ boost::shared_ptr<MockSession> session(createSession("me@foo.com/Bar"));
+ session->onElementReceived.connect(boost::bind(&ClientSessionTest::addReceivedElement, this, _1));
+ getMockServer()->expectStreamStart();
+ getMockServer()->sendStreamStart();
+ getMockServer()->sendStreamFeatures();
+ getMockServer()->sendMessage();
+ session->startSession();
+ processEvents();
+
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(receivedElements_.size()));
+ CPPUNIT_ASSERT(boost::dynamic_pointer_cast<Message>(receivedElements_[0]));
+ }*/
+} \ No newline at end of file
diff --git a/test/com/isode/stroke/client/NickResolverTest.java b/test/com/isode/stroke/client/NickResolverTest.java
new file mode 100644
index 0000000..afe3706
--- /dev/null
+++ b/test/com/isode/stroke/client/NickResolverTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2010-2013 Isode Limited.
+ * All right reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.Before;
+import com.isode.stroke.elements.VCard;
+import com.isode.stroke.elements.RosterItemPayload;
+import com.isode.stroke.client.StanzaChannel;
+import com.isode.stroke.client.DummyStanzaChannel;
+import com.isode.stroke.client.ClientBlockListManager;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.crypto.JavaCryptoProvider;
+import com.isode.stroke.vcards.VCardMemoryStorage;
+import com.isode.stroke.vcards.VCardManager;
+import com.isode.stroke.vcards.VCardStorage;
+import com.isode.stroke.roster.XMPPRosterImpl;
+import com.isode.stroke.muc.MUCRegistry;
+import com.isode.stroke.client.NickResolver;
+import java.util.Vector;
+import java.util.Collection;
+
+public class NickResolverTest {
+
+ private Collection<String> groups_ = new Vector<String>();
+ private XMPPRosterImpl xmppRoster_;
+ private VCardStorage vCardStorage_;
+ private IQRouter iqRouter_;
+ private DummyStanzaChannel stanzaChannel_;
+ private VCardManager vCardManager_;
+ private MUCRegistry registry_;
+ private NickResolver resolver_;
+ private JID ownJID_;
+ private CryptoProvider crypto;
+
+ public void populateOwnVCard(final String nick, final String given, final String full) {
+ VCard vcard = new VCard();
+ if (!nick.isEmpty()) {
+ vcard.setNickname(nick);
+ }
+ if (!given.isEmpty()) {
+ vcard.setGivenName(given);
+ }
+ if (!full.isEmpty()) {
+ vcard.setFullName(full);
+ }
+ vCardManager_.requestVCard(ownJID_);
+ IQ result = IQ.createResult(new JID(), stanzaChannel_.sentStanzas.get(0).getID(), vcard);
+ stanzaChannel_.onIQReceived.emit(result);
+ }
+
+ @Before
+ public void setUp() {
+ crypto = new JavaCryptoProvider();
+ ownJID_ = new JID("kev@wonderland.lit");
+ xmppRoster_ = new XMPPRosterImpl();
+ stanzaChannel_ = new DummyStanzaChannel();
+ iqRouter_ = new IQRouter(stanzaChannel_);
+ vCardStorage_ = new VCardMemoryStorage(crypto);
+ vCardManager_ = new VCardManager(ownJID_, iqRouter_, vCardStorage_);
+ registry_ = new MUCRegistry();
+ resolver_ = new NickResolver(ownJID_, xmppRoster_, vCardManager_, registry_);
+ }
+
+ @Test
+ public void testMUCNick() {
+ registry_.addMUC(new JID("foo@bar"));
+ JID testJID = new JID("foo@bar/baz");
+
+ assertEquals(("baz"), resolver_.jidToNick(testJID));
+ }
+
+ @Test
+ public void testMUCNoNick() {
+ registry_.addMUC(new JID("foo@bar"));
+ JID testJID = new JID("foo@bar");
+
+ assertEquals(("foo@bar"), resolver_.jidToNick(testJID));
+ }
+
+ @Test
+ public void testNoMatch() {
+ JID testJID = new JID("foo@bar/baz");
+
+ assertEquals(("foo@bar"), resolver_.jidToNick(testJID));
+ }
+
+ @Test
+ public void testZeroLengthMatch() {
+ JID testJID = new JID("foo@bar/baz");
+ xmppRoster_.addContact(testJID, "", groups_, RosterItemPayload.Subscription.Both);
+ assertEquals(("foo@bar"), resolver_.jidToNick(testJID));
+ }
+
+ @Test
+ public void testMatch() {
+ JID testJID = new JID("foo@bar/baz");
+ xmppRoster_.addContact(testJID, "Test", groups_, RosterItemPayload.Subscription.Both);
+
+ assertEquals(("Test"), resolver_.jidToNick(testJID));
+ }
+
+ @Test
+ public void testOverwrittenMatch() {
+ JID testJID = new JID("foo@bar/baz");
+ xmppRoster_.addContact(testJID, "FailTest", groups_, RosterItemPayload.Subscription.Both);
+ xmppRoster_.addContact(testJID, "Test", groups_, RosterItemPayload.Subscription.Both);
+
+ assertEquals(("Test"), resolver_.jidToNick(testJID));
+ }
+
+ @Test
+ public void testRemovedMatch() {
+ JID testJID = new JID("foo@bar/baz");
+ xmppRoster_.addContact(testJID, "FailTest", groups_, RosterItemPayload.Subscription.Both);
+ xmppRoster_.removeContact(testJID);
+ assertEquals(("foo@bar"), resolver_.jidToNick(testJID));
+ }
+
+ @Test
+ public void testOwnNickFullOnly() {
+ populateOwnVCard("", "", "Kevin Smith");
+ assertEquals(("Kevin Smith"), resolver_.jidToNick(ownJID_));
+ }
+
+ @Test
+ public void testOwnNickGivenAndFull() {
+ populateOwnVCard("", "Kevin", "Kevin Smith");
+ assertEquals(("Kevin"), resolver_.jidToNick(ownJID_));
+ }
+
+ @Test
+ public void testOwnNickNickEtAl() {
+ populateOwnVCard("Kev", "Kevin", "Kevin Smith");
+ assertEquals(("Kev"), resolver_.jidToNick(ownJID_));
+ }
+} \ No newline at end of file