diff options
author | Tarun Gupta <tarun1995gupta@gmail.com> | 2015-07-15 23:49:56 (GMT) |
---|---|---|
committer | Nick Hudson <nick.hudson@isode.com> | 2015-07-28 15:42:52 (GMT) |
commit | 251813ccca9404d8d4122b2848f9fec86a451bf5 (patch) | |
tree | a1ebb0f6a04547ef6b588ea85c1a50ea5d8e4997 /src/com | |
parent | 673655830b0325d964e67fa835ea83f485e9beeb (diff) | |
download | stroke-251813ccca9404d8d4122b2848f9fec86a451bf5.zip stroke-251813ccca9404d8d4122b2848f9fec86a451bf5.tar.bz2 |
Completes Components and AdHoc.
Adds Component, ComponentConnector, ComponentError, ComponentSession, ComponentXMLTracer, CoreComponent,
ComponentSessionStanzaChannel, ComponentXMTracer, CoreComponent.
Updates CoreClient, StrokeGUI, BasicSessionStream and SessionStream and Client, so that signal definition can be changed.
Updates ComponentHandshake element, Entity.
Updates OutgoingAdHocCommandSession to have feature parity with Swiften.
This patch does not port Client or Session fully, which will be done in separate future patches.
License:
This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.
Test-Information:
Test added for ComponentSession, which passes.
Test for ComponentConnector cannot be ported right now and will be done in future patches,
as it requires some bits of Network to be ported.
Change-Id: I7138a2041fe28a2be7ac57cb47b15365f9334b24
Diffstat (limited to 'src/com')
-rw-r--r-- | src/com/isode/stroke/adhoc/OutgoingAdHocCommandSession.java | 14 | ||||
-rw-r--r-- | src/com/isode/stroke/client/CoreClient.java | 17 | ||||
-rw-r--r-- | src/com/isode/stroke/component/Component.java | 47 | ||||
-rw-r--r-- | src/com/isode/stroke/component/ComponentConnector.java | 160 | ||||
-rw-r--r-- | src/com/isode/stroke/component/ComponentError.java | 39 | ||||
-rw-r--r-- | src/com/isode/stroke/component/ComponentSession.java | 200 | ||||
-rw-r--r-- | src/com/isode/stroke/component/ComponentSessionStanzaChannel.java | 127 | ||||
-rw-r--r-- | src/com/isode/stroke/component/ComponentXMLTracer.java | 48 | ||||
-rw-r--r-- | src/com/isode/stroke/component/CoreComponent.java | 271 | ||||
-rw-r--r-- | src/com/isode/stroke/elements/ComponentHandshake.java | 4 | ||||
-rw-r--r-- | src/com/isode/stroke/entity/Entity.java | 57 | ||||
-rw-r--r-- | src/com/isode/stroke/examples/gui/StrokeGUI.java | 9 | ||||
-rw-r--r-- | src/com/isode/stroke/parser/PayloadParserFactoryCollection.java | 6 | ||||
-rw-r--r-- | src/com/isode/stroke/session/BasicSessionStream.java | 12 | ||||
-rw-r--r-- | src/com/isode/stroke/session/SessionStream.java | 7 |
15 files changed, 996 insertions, 22 deletions
diff --git a/src/com/isode/stroke/adhoc/OutgoingAdHocCommandSession.java b/src/com/isode/stroke/adhoc/OutgoingAdHocCommandSession.java index 5bccb31..4369f8e 100644 --- a/src/com/isode/stroke/adhoc/OutgoingAdHocCommandSession.java +++ b/src/com/isode/stroke/adhoc/OutgoingAdHocCommandSession.java @@ -23,6 +23,7 @@ import com.isode.stroke.queries.GenericRequest; import com.isode.stroke.queries.IQRouter; import com.isode.stroke.signals.Signal1; import com.isode.stroke.signals.Slot2; +import com.isode.stroke.signals.SignalConnection; /** * This class maintains the session between the client and the server for an @@ -71,6 +72,7 @@ public class OutgoingAdHocCommandSession { private boolean isMultiStage_; private String sessionID_; private HashMap<Action, ActionState> actionStates_ = new HashMap<Action, ActionState>(); + private SignalConnection connection_; /** * Create an Ad-Hoc command session. The initial command will be sent to the @@ -101,6 +103,13 @@ public class OutgoingAdHocCommandSession { isMultiStage_ = false; } + /** + * This method needs to be called for the object to be eligible for garbage collection. + */ + public void delete() { + connection_.disconnect(); + } + private void handleResponse(Command payload, ErrorPayload error) { if (error != null) { onError.emit(error); @@ -156,7 +165,7 @@ public class OutgoingAdHocCommandSession { public void start() { GenericRequest<Command> commandRequest = new GenericRequest<Command>( IQ.Type.Set, to_, new Command(commandNode_), iqRouter_); - commandRequest.onResponse.connect(new Slot2<Command, ErrorPayload>() { + connection_ = commandRequest.onResponse.connect(new Slot2<Command, ErrorPayload>() { public void call(Command payload, ErrorPayload error) { handleResponse(payload, error); } @@ -206,7 +215,8 @@ public class OutgoingAdHocCommandSession { GenericRequest<Command> commandRequest = new GenericRequest<Command>( IQ.Type.Set, to_, command, iqRouter_); - commandRequest.onResponse.connect(new Slot2<Command, ErrorPayload>() { + connection_.disconnect(); + connection_ = commandRequest.onResponse.connect(new Slot2<Command, ErrorPayload>() { public void call(Command payload, ErrorPayload error) { handleResponse(payload, error); } diff --git a/src/com/isode/stroke/client/CoreClient.java b/src/com/isode/stroke/client/CoreClient.java index 84ea673..efefa1c 100644 --- a/src/com/isode/stroke/client/CoreClient.java +++ b/src/com/isode/stroke/client/CoreClient.java @@ -5,6 +5,7 @@ package com.isode.stroke.client; import com.isode.stroke.base.NotNull; +import com.isode.stroke.base.SafeByteArray; import com.isode.stroke.elements.Message; import com.isode.stroke.elements.Presence; import com.isode.stroke.elements.Stanza; @@ -58,13 +59,13 @@ public class CoreClient { * The user may add a listener to this signal, which will be called when * data are received from the server. Useful for observing protocol exchange. */ - public final Signal1<String> onDataRead = new Signal1<String>(); + public final Signal1<SafeByteArray> onDataRead = new Signal1<SafeByteArray>(); /** * The user may add a listener to this signal, which will be called when * data are sent to the server. Useful for observing protocol exchange. */ - public final Signal1<String> onDataWritten = new Signal1<String>(); + public final Signal1<SafeByteArray> onDataWritten = new Signal1<SafeByteArray>(); /** * Called when a message stanza is received. @@ -249,16 +250,16 @@ public class CoreClient { if (certificate_ != null && !certificate_.isNull()) { sessionStream_.setTLSCertificate(certificate_); } - sessionStreamDataReadConnection_ = sessionStream_.onDataRead.connect(new Slot1<String>() { + sessionStreamDataReadConnection_ = sessionStream_.onDataRead.connect(new Slot1<SafeByteArray>() { - public void call(String p1) { + public void call(SafeByteArray p1) { handleDataRead(p1); } }); - sessionStreamDataWrittenConnection_ = sessionStream_.onDataWritten.connect(new Slot1<String>() { + sessionStreamDataWrittenConnection_ = sessionStream_.onDataWritten.connect(new Slot1<SafeByteArray>() { - public void call(String p1) { + public void call(SafeByteArray p1) { handleDataWritten(p1); } }); @@ -421,11 +422,11 @@ public class CoreClient { session_.sendCredentials(password_); } - private void handleDataRead(final String data) { + private void handleDataRead(final SafeByteArray data) { onDataRead.emit(data); } - private void handleDataWritten(final String data) { + private void handleDataWritten(final SafeByteArray data) { onDataWritten.emit(data); } diff --git a/src/com/isode/stroke/component/Component.java b/src/com/isode/stroke/component/Component.java new file mode 100644 index 0000000..61c2bb2 --- /dev/null +++ b/src/com/isode/stroke/component/Component.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010-2013 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.component; + +import com.isode.stroke.component.CoreComponent; +import com.isode.stroke.queries.responders.SoftwareVersionResponder; +import com.isode.stroke.jid.JID; +import com.isode.stroke.network.NetworkFactories; + +/** + * Provides the core functionality for writing XMPP component software. + * + * Besides connecting to an XMPP server, this class also provides interfaces for + * performing most component tasks on the XMPP network. + */ +public class Component extends CoreComponent { + + private SoftwareVersionResponder softwareVersionResponder; + + public Component(JID jid, String secret, NetworkFactories networkFactories) { + super(jid, secret, networkFactories); + softwareVersionResponder = new SoftwareVersionResponder(getIQRouter()); + softwareVersionResponder.start(); + } + + public void delete() { + softwareVersionResponder.stop(); + } + + /** + * Sets the software version of the client. + * + * This will be used to respond to version queries from other entities. + */ + public void setSoftwareVersion(final String name, final String version) { + softwareVersionResponder.setVersion(name, version); + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/component/ComponentConnector.java b/src/com/isode/stroke/component/ComponentConnector.java new file mode 100644 index 0000000..795fd7c --- /dev/null +++ b/src/com/isode/stroke/component/ComponentConnector.java @@ -0,0 +1,160 @@ +/* + * 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.component; + +import com.isode.stroke.network.Connection; +import com.isode.stroke.network.JavaConnection; +import com.isode.stroke.network.Timer; +import com.isode.stroke.network.HostAddressPort; +import com.isode.stroke.network.DomainNameResolveError; +import com.isode.stroke.network.DomainNameAddressQuery; +import com.isode.stroke.network.DomainNameResolver; +import com.isode.stroke.network.ConnectionFactory; +import com.isode.stroke.network.TimerFactory; +import com.isode.stroke.network.HostAddress; +import com.isode.stroke.signals.Signal1; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot2; +import com.isode.stroke.signals.Slot1; +import com.isode.stroke.signals.Slot; +import java.util.Vector; +import java.util.Collection; + +public class ComponentConnector { + + private String hostname = ""; + private int port; + private DomainNameResolver resolver; + private ConnectionFactory connectionFactory; + private TimerFactory timerFactory; + private int timeoutMilliseconds; + private Timer timer; + private DomainNameAddressQuery addressQuery; + private Vector<HostAddress> addressQueryResults = new Vector<HostAddress>(); + private Connection currentConnection; + private SignalConnection onConnectFinishedConnection; + private SignalConnection onTickConnection; + private SignalConnection onResultConnection; + + public ComponentConnector(final String hostname, int port, DomainNameResolver resolver, ConnectionFactory connectionFactory, TimerFactory timerFactory) { + this.hostname = hostname; + this.port = port; + this.resolver = resolver; + this.connectionFactory = connectionFactory; + this.timerFactory = timerFactory; + this.timeoutMilliseconds = 0; + } + + public static ComponentConnector create(final String hostname, int port, DomainNameResolver resolver, ConnectionFactory connectionFactory, TimerFactory timerFactory) { + return new ComponentConnector(hostname, port, resolver, connectionFactory, timerFactory); + } + + public void setTimeoutMilliseconds(int milliseconds) { + timeoutMilliseconds = milliseconds; + } + + public void start() { + assert(currentConnection == null); + assert(timer == null); + assert(addressQuery == null); + addressQuery = resolver.createAddressQuery(hostname); + onResultConnection = addressQuery.onResult.connect(new Slot2<Collection<HostAddress>, DomainNameResolveError>() { + @Override + public void call(Collection<HostAddress> c1, DomainNameResolveError d1) { + handleAddressQueryResult(c1, d1); + } + }); + if (timeoutMilliseconds > 0) { + timer = timerFactory.createTimer(timeoutMilliseconds); + onTickConnection = timer.onTick.connect(new Slot() { + @Override + public void call() { + handleTimeout(); + } + }); + timer.start(); + } + addressQuery.run(); + } + + public void stop() { + finish((Connection)null); + } + + public final Signal1<Connection> onConnectFinished = new Signal1<Connection>(); + + private void handleAddressQueryResult(final Collection<HostAddress> addresses, DomainNameResolveError error) { + addressQuery = null; + if (error != null || addresses.isEmpty()) { + finish((Connection)null); + } + else { + addressQueryResults.addAll(addresses); + tryNextAddress(); + } + } + + private void tryNextAddress() { + assert(!addressQueryResults.isEmpty()); + HostAddress address = addressQueryResults.remove(0); + tryConnect(new HostAddressPort(address, port)); + } + + private void tryConnect(final HostAddressPort target) { + assert(currentConnection == null); + currentConnection = connectionFactory.createConnection(); + onConnectFinishedConnection = currentConnection.onConnectFinished.connect(new Slot1<Boolean>() { + @Override + public void call(Boolean b) { + handleConnectionConnectFinished(b); + } + }); + currentConnection.connect(target); + } + + private void handleConnectionConnectFinished(boolean error) { + onConnectFinishedConnection.disconnect(); + if (error) { + currentConnection = null; + if (!addressQueryResults.isEmpty()) { + tryNextAddress(); + } + else { + finish((Connection)null); + } + } + else { + finish(currentConnection); + } + } + + private void finish(Connection connection) { + if (timer != null) { + timer.stop(); + onTickConnection.disconnect(); + timer = null; + } + if (addressQuery != null) { + onResultConnection.disconnect(); + addressQuery = null; + } + if (currentConnection != null) { + onConnectFinishedConnection.disconnect(); + currentConnection = null; + } + onConnectFinished.emit(connection); + } + + private void handleTimeout() { + finish((Connection)null); + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/component/ComponentError.java b/src/com/isode/stroke/component/ComponentError.java new file mode 100644 index 0000000..576e3dc --- /dev/null +++ b/src/com/isode/stroke/component/ComponentError.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2010-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.component; + +public class ComponentError { + + public enum Type { + UnknownError, + ConnectionError, + ConnectionReadError, + ConnectionWriteError, + XMLError, + AuthenticationFailedError, + UnexpectedElementError + }; + + private Type type_; + + public ComponentError() { + this(Type.UnknownError); + } + + public ComponentError(Type type) { + this.type_ = type; + } + + public Type getType() { + return type_; + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/component/ComponentSession.java b/src/com/isode/stroke/component/ComponentSession.java new file mode 100644 index 0000000..40bb391 --- /dev/null +++ b/src/com/isode/stroke/component/ComponentSession.java @@ -0,0 +1,200 @@ +/* + * 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.component; + +import com.isode.stroke.jid.JID; +import com.isode.stroke.elements.Stanza; +import com.isode.stroke.elements.Element; +import com.isode.stroke.elements.ProtocolHeader; +import com.isode.stroke.elements.ComponentHandshake; +import com.isode.stroke.elements.StreamFeatures; +import com.isode.stroke.session.SessionStream; +import com.isode.stroke.crypto.CryptoProvider; +import com.isode.stroke.crypto.JavaCryptoProvider; +import com.isode.stroke.signals.Signal1; +import com.isode.stroke.signals.Signal; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot1; + +public class ComponentSession { + + public enum State { + Initial, + WaitingForStreamStart, + Authenticating, + Initialized, + Finishing, + Finished + }; + + public static class Error implements com.isode.stroke.base.Error { + public enum Type { + AuthenticationFailedError, + UnexpectedElementError + } + public Type type; + public Error(Type type) { + if (type == null) { + throw new IllegalStateException(); + } + this.type = type; + } + }; + + private JID jid = new JID(); + private String secret = ""; + private SessionStream stream; + private CryptoProvider crypto; + private com.isode.stroke.base.Error error; + private State state; + public final Signal onInitialized = new Signal(); + public final Signal1<com.isode.stroke.base.Error> onFinished = new Signal1<com.isode.stroke.base.Error>(); + public final Signal1<Stanza> onStanzaReceived = new Signal1<Stanza>(); + private SignalConnection onStreamStartReceivedConnection; + private SignalConnection onElementReceivedConnection; + private SignalConnection onClosedConnection; + + public static ComponentSession create(final JID jid, final String secret, SessionStream stream, CryptoProvider crypto) { + return new ComponentSession(jid, secret, stream, crypto); + } + + public State getState() { + return state; + } + + public void start() { + onStreamStartReceivedConnection = stream.onStreamStartReceived.connect(new Slot1<ProtocolHeader>() { + @Override + public void call(ProtocolHeader p1) { + handleStreamStart(p1); + } + }); + onElementReceivedConnection = stream.onElementReceived.connect(new Slot1<Element>() { + @Override + public void call(Element e1) { + handleElement(e1); + } + }); + onClosedConnection = stream.onClosed.connect(new Slot1<SessionStream.Error>() { + @Override + public void call(SessionStream.Error e1) { + handleStreamClosed(e1); + } + }); + + assert(State.Initial.equals(state)); + state = State.WaitingForStreamStart; + sendStreamHeader(); + } + + public void finish() { + finishSession((Error.Type)null); + } + + public void sendStanza(Stanza stanza) { + stream.writeElement(stanza); + } + + private ComponentSession(final JID jid, final String secret, SessionStream stream, CryptoProvider crypto) { + this.jid = jid; + this.secret = secret; + this.stream = stream; + this.crypto = crypto; + this.state = State.Initial; + } + + private void finishSession(Error.Type error) { + Error localError = null; + if (error != null) { + localError = new Error(error); + } + finishSession(localError); + } + + private void finishSession(com.isode.stroke.base.Error finishError) { + state = State.Finishing; + error = finishError; + assert(stream.isOpen() == true); + stream.writeFooter(); + stream.close(); + } + + private void sendStreamHeader() { + ProtocolHeader header = new ProtocolHeader(); + header.setTo(jid.toString()); + stream.writeHeader(header); + } + + private void handleElement(Element element) { + if(element instanceof Stanza) { + Stanza stanza = (Stanza)element; + if (State.Initialized.equals(getState())) { + onStanzaReceived.emit(stanza); + } + else { + finishSession(Error.Type.UnexpectedElementError); + } + } + else if (element instanceof ComponentHandshake) { + if (!checkState(State.Authenticating)) { + return; + } + stream.setWhitespacePingEnabled(true); + state = State.Initialized; + onInitialized.emit(); + } + else if (State.Authenticating.equals(getState())) { + if (element instanceof StreamFeatures) { + // M-Link sends stream features, so swallow that. + } + else { + // FIXME: We should actually check the element received + finishSession(Error.Type.AuthenticationFailedError); + } + } + else { + finishSession(Error.Type.UnexpectedElementError); + } + } + + private void handleStreamStart(final ProtocolHeader header) { + checkState(State.WaitingForStreamStart); + state = State.Authenticating; + stream.writeElement(new ComponentHandshake(ComponentHandshakeGenerator.getHandshake(header.getID(), secret, crypto))); + } + + private void handleStreamClosed(SessionStream.Error streamError) { + State oldState = state; + state = State.Finished; + stream.setWhitespacePingEnabled(false); + onStreamStartReceivedConnection.disconnect(); + onElementReceivedConnection.disconnect(); + onClosedConnection.disconnect(); + if (State.Finishing.equals(oldState)) { + onFinished.emit(error); + } + else { + onFinished.emit(streamError); + } + } + + private boolean checkState(State state) { + if (!(this.state.equals(state))) { + finishSession(Error.Type.UnexpectedElementError); + return false; + } + return true; + } +} + + + diff --git a/src/com/isode/stroke/component/ComponentSessionStanzaChannel.java b/src/com/isode/stroke/component/ComponentSessionStanzaChannel.java new file mode 100644 index 0000000..8d97bef --- /dev/null +++ b/src/com/isode/stroke/component/ComponentSessionStanzaChannel.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2010-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.component; + +import com.isode.stroke.base.IDGenerator; +import com.isode.stroke.component.ComponentSession; +import com.isode.stroke.client.StanzaChannel; +import com.isode.stroke.elements.Message; +import com.isode.stroke.elements.IQ; +import com.isode.stroke.elements.Presence; +import com.isode.stroke.elements.Stanza; +import com.isode.stroke.tls.Certificate; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot; +import com.isode.stroke.signals.Slot1; +import java.util.Vector; + +public class ComponentSessionStanzaChannel extends StanzaChannel { + + private IDGenerator idGenerator; + private ComponentSession session; + private SignalConnection onInitializedConnection; + private SignalConnection onFinishedConnection; + private SignalConnection onStanzaReceivedConnection; + + public void setSession(ComponentSession session) { + assert(this.session == null); + this.session = session; + onInitializedConnection = session.onInitialized.connect(new Slot() { + @Override + public void call() { + handleSessionInitialized(); + } + }); + onFinishedConnection = session.onFinished.connect(new Slot1<com.isode.stroke.base.Error>() { + @Override + public void call(com.isode.stroke.base.Error e1) { + handleSessionFinished(e1); + } + }); + onStanzaReceivedConnection = session.onStanzaReceived.connect(new Slot1<Stanza>() { + @Override + public void call(Stanza s1) { + handleStanza(s1); + } + }); + } + + public void sendIQ(IQ iq) { + send(iq); + } + + public void sendMessage(Message message) { + send(message); + } + + public void sendPresence(Presence presence) { + send(presence); + } + + public boolean getStreamManagementEnabled() { + return false; + } + + public Vector<Certificate> getPeerCertificateChain() { + // TODO: actually implement this method + return (Vector<Certificate>)null; + } + + public boolean isAvailable() { + return (session != null) && (ComponentSession.State.Initialized.equals(session.getState())); + } + + public String getNewIQID() { + return idGenerator.generateID(); + } + + private void send(Stanza stanza) { + if (!isAvailable()) { + System.err.println("Warning: Component: Trying to send a stanza while disconnected.\n"); + return; + } + session.sendStanza(stanza); + } + + private void handleSessionFinished(com.isode.stroke.base.Error error) { + onFinishedConnection.disconnect(); + onStanzaReceivedConnection.disconnect(); + onInitializedConnection.disconnect(); + session = null; + + onAvailableChanged.emit(false); + } + + private void handleStanza(Stanza stanza) { + if(stanza instanceof Message) { + Message message = (Message)(stanza); + onMessageReceived.emit(message); + return; + } + + if(stanza instanceof Presence) { + Presence presence = (Presence)(stanza); + onPresenceReceived.emit(presence); + return; + } + + if(stanza instanceof IQ) { + IQ iq = (IQ)(stanza); + onIQReceived.emit(iq); + return; + } + } + + private void handleSessionInitialized() { + onAvailableChanged.emit(true); + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/component/ComponentXMLTracer.java b/src/com/isode/stroke/component/ComponentXMLTracer.java new file mode 100644 index 0000000..005a937 --- /dev/null +++ b/src/com/isode/stroke/component/ComponentXMLTracer.java @@ -0,0 +1,48 @@ +/* + * 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.component; + +import com.isode.stroke.component.Component; +import com.isode.stroke.component.CoreComponent; +import com.isode.stroke.base.SafeByteArray; +import com.isode.stroke.signals.Slot1; + +public class ComponentXMLTracer { + + public ComponentXMLTracer(CoreComponent client) { + client.onDataRead.connect(new Slot1<SafeByteArray>() { + @Override + public void call(SafeByteArray b1) { + printData('<', b1); + } + }); + + client.onDataWritten.connect(new Slot1<SafeByteArray>() { + @Override + public void call(SafeByteArray b1) { + printData('>', b1); + } + }); + } + + private static void printData(char direction, final SafeByteArray data) { + printLine(direction); + System.err.println(data); + } + + private static void printLine(char c) { + for (int i = 0; i < 80; ++i) { + System.err.print(c); + } + System.err.println(); + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/component/CoreComponent.java b/src/com/isode/stroke/component/CoreComponent.java new file mode 100644 index 0000000..63b9cd5 --- /dev/null +++ b/src/com/isode/stroke/component/CoreComponent.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2010-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.component; + +import com.isode.stroke.base.Error; +import com.isode.stroke.component.ComponentConnector; +import com.isode.stroke.component.ComponentSession; +import com.isode.stroke.component.ComponentError; +import com.isode.stroke.component.ComponentSessionStanzaChannel; +import com.isode.stroke.elements.Presence; +import com.isode.stroke.elements.Message; +import com.isode.stroke.elements.StreamType; +import com.isode.stroke.jid.JID; +import com.isode.stroke.parser.payloadparsers.FullPayloadParserFactoryCollection; +import com.isode.stroke.serializer.payloadserializers.FullPayloadSerializerCollection; +import com.isode.stroke.entity.Entity; +import com.isode.stroke.base.SafeByteArray; +import com.isode.stroke.eventloop.EventLoop; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.network.NetworkFactories; +import com.isode.stroke.network.Connection; +import com.isode.stroke.session.BasicSessionStream; +import com.isode.stroke.session.SessionStream; +import com.isode.stroke.signals.Signal1; +import com.isode.stroke.signals.Signal; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot1; +import com.isode.stroke.client.StanzaChannel; + +/** + * The central class for communicating with an XMPP server as a component. + * + * This class is responsible for setting up the connection with the XMPP + * server and authenticating the component. + * + * This class can be used directly in your application, although the Component + * subclass provides more functionality and interfaces, and is better suited + * for most needs. + */ +public class CoreComponent extends Entity { + + private NetworkFactories networkFactories; + private JID jid_; + private String secret_; + private ComponentSessionStanzaChannel stanzaChannel_; + private IQRouter iqRouter_; + private ComponentConnector connector_; + private Connection connection_; + private BasicSessionStream sessionStream_; + private ComponentSession session_; + private boolean disconnectRequested_; + private SignalConnection onMessageReceivedConnection; + private SignalConnection onPresenceReceivedConnection; + private SignalConnection onAvailableChangedConnection; + private SignalConnection onConnectFinishedConnection; + private SignalConnection onDataReadConnection; + private SignalConnection onDataWrittenConnection; + private SignalConnection onFinishedConnection; + + public final Signal1<ComponentError> onError = new Signal1<ComponentError>(); + public final Signal onConnected = new Signal(); + public final Signal1<SafeByteArray> onDataRead = new Signal1<SafeByteArray>(); + public final Signal1<SafeByteArray> onDataWritten = new Signal1<SafeByteArray>(); + + public final Signal1<Message> onMessageReceived = new Signal1<Message>(); + public final Signal1<Presence> onPresenceReceived = new Signal1<Presence>(); + + public CoreComponent(final JID jid, final String secret, NetworkFactories networkFactories) { + this.networkFactories = networkFactories; + this.jid_ = jid; + this.secret_ = secret; + this.disconnectRequested_ = false; + stanzaChannel_ = new ComponentSessionStanzaChannel(); + onMessageReceivedConnection = stanzaChannel_.onMessageReceived.connect(onMessageReceived); + onPresenceReceivedConnection = stanzaChannel_.onPresenceReceived.connect(onPresenceReceived); + onAvailableChangedConnection = stanzaChannel_.onAvailableChanged.connect(new Slot1<Boolean>() { + @Override + public void call(Boolean b1) { + handleStanzaChannelAvailableChanged(b1); + } + }); + iqRouter_ = new IQRouter(stanzaChannel_); + iqRouter_.setFrom(jid); + } + + /** + * This method needs to be called for the object to be eligible for garbage collection. + */ + public void delete() { + if (session_ != null || connection_ != null) { + System.err.println("Warning: Component not disconnected properly\n"); + } + onAvailableChangedConnection.disconnect(); + onMessageReceivedConnection.disconnect(); + onAvailableChangedConnection.disconnect(); + } + + public void connect(final String host, int port) { + assert(connector_ == null); + connector_ = ComponentConnector.create(host, port, networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory()); + onConnectFinishedConnection = connector_.onConnectFinished.connect(new Slot1<Connection>() { + @Override + public void call(Connection c1) { + handleConnectorFinished(c1); + } + }); + connector_.setTimeoutMilliseconds(60*1000); + connector_.start(); + } + + public void disconnect() { + // FIXME: We should be able to do without this boolean. We just have to make sure we can tell the difference between + // connector finishing without a connection due to an error or because of a disconnect. + disconnectRequested_ = true; + if (session_ != null) { + session_.finish(); + } + else if (connector_ != null) { + connector_.stop(); + assert(session_ == null); + } + //assert(!session_); /* commenting out until we have time to refactor to be like CoreClient */ + //assert(!sessionStream_); + //assert(!connector_); + disconnectRequested_ = false; + } + + public void sendMessage(Message message) { + stanzaChannel_.sendMessage(message); + } + + public void sendPresence(Presence presence) { + stanzaChannel_.sendPresence(presence); + } + + public void sendData(final String data) { + sessionStream_.writeData(data); + } + + public IQRouter getIQRouter() { + return iqRouter_; + } + + public StanzaChannel getStanzaChannel() { + return stanzaChannel_; + } + + public boolean isAvailable() { + return stanzaChannel_.isAvailable(); + } + + /** + * Returns the JID of the component + */ + public JID getJID() { + return jid_; + } + + private void handleConnectorFinished(Connection connection) { + onConnectFinishedConnection.disconnect(); + connector_ = null; + if (connection == null) { + if (!disconnectRequested_) { + onError.emit(new ComponentError(ComponentError.Type.ConnectionError)); + } + } + else { + assert(connection_ == null); + connection_ = connection; + + assert(sessionStream_ == null); + //TODO: PORT TLSOPTIONS. + sessionStream_ = new BasicSessionStream(StreamType.ComponentStreamType, connection_, getPayloadParserFactories(), getPayloadSerializers(), null, networkFactories.getTimerFactory()); + onDataReadConnection = sessionStream_.onDataRead.connect(new Slot1<SafeByteArray>() { + @Override + public void call(SafeByteArray s1) { + handleDataRead(s1); + } + }); + onDataWrittenConnection = sessionStream_.onDataWritten.connect(new Slot1<SafeByteArray>() { + @Override + public void call(SafeByteArray s1) { + handleDataWritten(s1); + } + }); + + session_ = ComponentSession.create(jid_, secret_, sessionStream_, networkFactories.getCryptoProvider()); + stanzaChannel_.setSession(session_); + onFinishedConnection = session_.onFinished.connect(new Slot1<com.isode.stroke.base.Error>() { + @Override + public void call(com.isode.stroke.base.Error e1) { + handleSessionFinished(e1); + } + }); + session_.start(); + } + } + + private void handleStanzaChannelAvailableChanged(boolean available) { + if (available) { + onConnected.emit(); + } + } + + private void handleSessionFinished(Error error) { + onFinishedConnection.disconnect(); + session_ = null; + + onDataReadConnection.disconnect(); + onDataWrittenConnection.disconnect(); + sessionStream_ = null; + + connection_.disconnect(); + connection_ = null; + + if (error != null) { + ComponentError componentError = new ComponentError(); + if(error instanceof ComponentSession.Error) { + ComponentSession.Error actualError = (ComponentSession.Error)error; + switch(actualError.type) { + case AuthenticationFailedError: + componentError = new ComponentError(ComponentError.Type.AuthenticationFailedError); + break; + case UnexpectedElementError: + componentError = new ComponentError(ComponentError.Type.UnexpectedElementError); + break; + } + } + else if(error instanceof SessionStream.Error) { + SessionStream.Error actualError = (SessionStream.Error)error; + switch(actualError.type) { + case ParseError: + componentError = new ComponentError(ComponentError.Type.XMLError); + break; + case TLSError: + assert(false); + componentError = new ComponentError(ComponentError.Type.UnknownError); + break; + case InvalidTLSCertificateError: + assert(false); + componentError = new ComponentError(ComponentError.Type.UnknownError); + break; + case ConnectionReadError: + componentError = new ComponentError(ComponentError.Type.ConnectionReadError); + break; + case ConnectionWriteError: + componentError = new ComponentError(ComponentError.Type.ConnectionWriteError); + break; + } + } + onError.emit(componentError); + } + } + + private void handleDataRead(SafeByteArray data) { + onDataRead.emit(data); + } + + private void handleDataWritten(SafeByteArray data) { + onDataWritten.emit(data); + } +} diff --git a/src/com/isode/stroke/elements/ComponentHandshake.java b/src/com/isode/stroke/elements/ComponentHandshake.java index f5ae830..0e42988 100644 --- a/src/com/isode/stroke/elements/ComponentHandshake.java +++ b/src/com/isode/stroke/elements/ComponentHandshake.java @@ -12,9 +12,9 @@ package com.isode.stroke.elements; import com.isode.stroke.base.NotNull; -import com.isode.stroke.elements.TopLevelElement; +import com.isode.stroke.elements.Element; -public class ComponentHandshake implements TopLevelElement { +public class ComponentHandshake implements Element { private String data = ""; diff --git a/src/com/isode/stroke/entity/Entity.java b/src/com/isode/stroke/entity/Entity.java new file mode 100644 index 0000000..80395ca --- /dev/null +++ b/src/com/isode/stroke/entity/Entity.java @@ -0,0 +1,57 @@ +/* + * 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.entity; + +import com.isode.stroke.parser.PayloadParserFactory; +import com.isode.stroke.serializer.PayloadSerializer; +import com.isode.stroke.parser.payloadparsers.FullPayloadParserFactoryCollection; +import com.isode.stroke.serializer.payloadserializers.FullPayloadSerializerCollection; +import com.isode.stroke.parser.PayloadParserFactoryCollection; +import com.isode.stroke.serializer.PayloadSerializerCollection; + +/** + * The base class for XMPP entities (Clients, Components). + */ +public class Entity { + + private FullPayloadParserFactoryCollection payloadParserFactories; + private FullPayloadSerializerCollection payloadSerializers; + + public Entity() { + payloadParserFactories = new FullPayloadParserFactoryCollection(); + payloadSerializers = new FullPayloadSerializerCollection(); + } + + public void addPayloadParserFactory(PayloadParserFactory payloadParserFactory) { + payloadParserFactories.addFactory(payloadParserFactory); + } + + public void removePayloadParserFactory(PayloadParserFactory payloadParserFactory) { + payloadParserFactories.removeFactory(payloadParserFactory); + } + + public void addPayloadSerializer(PayloadSerializer payloadSerializer) { + payloadSerializers.addSerializer(payloadSerializer); + } + + public void removePayloadSerializer(PayloadSerializer payloadSerializer) { + payloadSerializers.removeSerializer(payloadSerializer); + } + + protected PayloadParserFactoryCollection getPayloadParserFactories() { + return payloadParserFactories; + } + + protected PayloadSerializerCollection getPayloadSerializers() { + return payloadSerializers; + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/examples/gui/StrokeGUI.java b/src/com/isode/stroke/examples/gui/StrokeGUI.java index 282fde6..6fc07de 100644 --- a/src/com/isode/stroke/examples/gui/StrokeGUI.java +++ b/src/com/isode/stroke/examples/gui/StrokeGUI.java @@ -11,6 +11,7 @@ package com.isode.stroke.examples.gui; +import com.isode.stroke.base.SafeByteArray; import com.isode.stroke.client.ClientError; import com.isode.stroke.client.ClientOptions; import com.isode.stroke.client.CoreClient; @@ -179,15 +180,15 @@ public class StrokeGUI extends javax.swing.JFrame { thisObject.handleClientError(p1); } }); - client_.onDataRead.connect(new Slot1<String>() { + client_.onDataRead.connect(new Slot1<SafeByteArray>() { - public void call(String p1) { + public void call(SafeByteArray p1) { xmlText_.append(">>> " + p1 + "\n"); } }); - client_.onDataWritten.connect(new Slot1<String>() { + client_.onDataWritten.connect(new Slot1<SafeByteArray>() { - public void call(String p1) { + public void call(SafeByteArray p1) { xmlText_.append("<<< " + p1 + "\n"); } }); diff --git a/src/com/isode/stroke/parser/PayloadParserFactoryCollection.java b/src/com/isode/stroke/parser/PayloadParserFactoryCollection.java index 6d38ebd..d034826 100644 --- a/src/com/isode/stroke/parser/PayloadParserFactoryCollection.java +++ b/src/com/isode/stroke/parser/PayloadParserFactoryCollection.java @@ -24,6 +24,12 @@ public class PayloadParserFactoryCollection { } } + public void removeFactory(PayloadParserFactory factory) { + while(factories_.contains(factory)) { + factories_.remove(factory); + } + } + public void setDefaultFactory(PayloadParserFactory factory) { defaultFactory_ = factory; } diff --git a/src/com/isode/stroke/session/BasicSessionStream.java b/src/com/isode/stroke/session/BasicSessionStream.java index 4f0e75b..9ba862d 100644 --- a/src/com/isode/stroke/session/BasicSessionStream.java +++ b/src/com/isode/stroke/session/BasicSessionStream.java @@ -175,6 +175,10 @@ public class BasicSessionStream extends SessionStream { return tlsLayer.getContext().getFinishMessage(); } + public boolean supportsZLibCompression() { + return true; + } + public void addZLibCompression() { compressionLayer = new CompressionLayer(); streamStack.addLayer(compressionLayer); @@ -232,12 +236,12 @@ public class BasicSessionStream extends SessionStream { } } - private void handleDataRead(ByteArray data) { - onDataRead.emit(data.toString()); + private void handleDataRead(SafeByteArray data) { + onDataRead.emit(data); } - private void handleDataWritten(ByteArray data) { - onDataWritten.emit(data.toString()); + private void handleDataWritten(SafeByteArray data) { + onDataWritten.emit(data); } @Override public String toString() { diff --git a/src/com/isode/stroke/session/SessionStream.java b/src/com/isode/stroke/session/SessionStream.java index 2b9932b..3171ec0 100644 --- a/src/com/isode/stroke/session/SessionStream.java +++ b/src/com/isode/stroke/session/SessionStream.java @@ -10,6 +10,7 @@ package com.isode.stroke.session; import java.util.List; +import com.isode.stroke.base.SafeByteArray; import com.isode.stroke.base.ByteArray; import com.isode.stroke.elements.Element; import com.isode.stroke.elements.ProtocolHeader; @@ -50,6 +51,8 @@ public abstract class SessionStream { public abstract void writeData(String data); + public abstract boolean supportsZLibCompression(); + public abstract void addZLibCompression(); public abstract boolean supportsTLSEncryption(); @@ -81,8 +84,8 @@ public abstract class SessionStream { public final Signal1<Element> onElementReceived = new Signal1<Element>(); public final Signal1<Error> onClosed = new Signal1<Error>(); public final Signal onTLSEncrypted = new Signal(); - public final Signal1<String> onDataRead = new Signal1<String>(); - public final Signal1<String> onDataWritten = new Signal1<String>(); + public final Signal1<SafeByteArray> onDataRead = new Signal1<SafeByteArray>(); + public final Signal1<SafeByteArray> onDataWritten = new Signal1<SafeByteArray>(); protected CertificateWithKey getTLSCertificate() { return certificate; } |