summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/isode/stroke/base/ByteArray.java162
-rw-r--r--src/com/isode/stroke/base/Error.java15
-rw-r--r--src/com/isode/stroke/client/ClientError.java57
-rw-r--r--src/com/isode/stroke/client/ClientOptions.java45
-rw-r--r--src/com/isode/stroke/client/ClientSession.java608
-rw-r--r--src/com/isode/stroke/client/ClientSessionStanzaChannel.java125
-rw-r--r--src/com/isode/stroke/client/CoreClient.java385
-rw-r--r--src/com/isode/stroke/client/IDGenerator.java22
-rw-r--r--src/com/isode/stroke/client/StanzaChannel.java35
-rw-r--r--src/com/isode/stroke/compress/ZLibCompressor.java33
-rw-r--r--src/com/isode/stroke/compress/ZLibDecompressor.java35
-rw-r--r--src/com/isode/stroke/compress/ZLibException.java19
-rw-r--r--src/com/isode/stroke/elements/AuthChallenge.java32
-rw-r--r--src/com/isode/stroke/elements/AuthFailure.java16
-rw-r--r--src/com/isode/stroke/elements/AuthRequest.java46
-rw-r--r--src/com/isode/stroke/elements/AuthResponse.java33
-rw-r--r--src/com/isode/stroke/elements/AuthSuccess.java26
-rw-r--r--src/com/isode/stroke/elements/Body.java28
-rw-r--r--src/com/isode/stroke/elements/CompressFailure.java13
-rw-r--r--src/com/isode/stroke/elements/CompressRequest.java29
-rw-r--r--src/com/isode/stroke/elements/Compressed.java13
-rw-r--r--src/com/isode/stroke/elements/Element.java14
-rw-r--r--src/com/isode/stroke/elements/EnableStreamManagement.java14
-rw-r--r--src/com/isode/stroke/elements/ErrorPayload.java88
-rw-r--r--src/com/isode/stroke/elements/IQ.java58
-rw-r--r--src/com/isode/stroke/elements/Last.java28
-rw-r--r--src/com/isode/stroke/elements/Message.java56
-rw-r--r--src/com/isode/stroke/elements/Payload.java14
-rw-r--r--src/com/isode/stroke/elements/Presence.java69
-rw-r--r--src/com/isode/stroke/elements/Priority.java33
-rw-r--r--src/com/isode/stroke/elements/ProtocolHeader.java52
-rw-r--r--src/com/isode/stroke/elements/RawXMLPayload.java25
-rw-r--r--src/com/isode/stroke/elements/ResourceBind.java32
-rw-r--r--src/com/isode/stroke/elements/RosterItemPayload.java86
-rw-r--r--src/com/isode/stroke/elements/RosterPayload.java41
-rw-r--r--src/com/isode/stroke/elements/SearchPayload.java124
-rw-r--r--src/com/isode/stroke/elements/Stanza.java86
-rw-r--r--src/com/isode/stroke/elements/StanzaAck.java35
-rw-r--r--src/com/isode/stroke/elements/StanzaAckRequest.java14
-rw-r--r--src/com/isode/stroke/elements/StartSession.java14
-rw-r--r--src/com/isode/stroke/elements/StartTLSFailure.java17
-rw-r--r--src/com/isode/stroke/elements/StartTLSRequest.java15
-rw-r--r--src/com/isode/stroke/elements/Status.java29
-rw-r--r--src/com/isode/stroke/elements/StatusShow.java53
-rw-r--r--src/com/isode/stroke/elements/StreamError.java75
-rw-r--r--src/com/isode/stroke/elements/StreamFeatures.java99
-rw-r--r--src/com/isode/stroke/elements/StreamManagementEnabled.java30
-rw-r--r--src/com/isode/stroke/elements/StreamManagementFailed.java14
-rw-r--r--src/com/isode/stroke/elements/StreamResume.java30
-rw-r--r--src/com/isode/stroke/elements/StreamResumed.java30
-rw-r--r--src/com/isode/stroke/elements/StreamType.java18
-rw-r--r--src/com/isode/stroke/elements/Subject.java28
-rw-r--r--src/com/isode/stroke/elements/TLSProceed.java16
-rw-r--r--src/com/isode/stroke/elements/UnknownElement.java14
-rw-r--r--src/com/isode/stroke/elements/Version.java51
-rw-r--r--src/com/isode/stroke/eventloop/Event.java42
-rw-r--r--src/com/isode/stroke/eventloop/EventLoop.java90
-rw-r--r--src/com/isode/stroke/eventloop/EventOwner.java14
-rw-r--r--src/com/isode/stroke/eventloop/SimpleEventLoop.java17
-rw-r--r--src/com/isode/stroke/examples/gui/StrokeGUI.form201
-rw-r--r--src/com/isode/stroke/examples/gui/StrokeGUI.java247
-rw-r--r--src/com/isode/stroke/idn/IDNA.java18
-rw-r--r--src/com/isode/stroke/jid/JID.java211
-rw-r--r--src/com/isode/stroke/network/Connection.java40
-rw-r--r--src/com/isode/stroke/network/ConnectionFactory.java14
-rw-r--r--src/com/isode/stroke/network/Connector.java211
-rw-r--r--src/com/isode/stroke/network/DomainNameAddressQuery.java19
-rw-r--r--src/com/isode/stroke/network/DomainNameResolveError.java13
-rw-r--r--src/com/isode/stroke/network/DomainNameResolver.java30
-rw-r--r--src/com/isode/stroke/network/DomainNameServiceQuery.java46
-rw-r--r--src/com/isode/stroke/network/HostAddress.java56
-rw-r--r--src/com/isode/stroke/network/HostAddressPort.java57
-rw-r--r--src/com/isode/stroke/network/JavaConnection.java176
-rw-r--r--src/com/isode/stroke/network/JavaConnectionFactory.java26
-rw-r--r--src/com/isode/stroke/network/JavaNetworkFactories.java33
-rw-r--r--src/com/isode/stroke/network/JavaTimer.java72
-rw-r--r--src/com/isode/stroke/network/JavaTimerFactory.java27
-rw-r--r--src/com/isode/stroke/network/NetworkFactories.java17
-rw-r--r--src/com/isode/stroke/network/PlatformDomainNameResolver.java103
-rw-r--r--src/com/isode/stroke/network/PlatformDomainNameServiceQuery.java186
-rw-r--r--src/com/isode/stroke/network/Timer.java18
-rw-r--r--src/com/isode/stroke/network/TimerFactory.java15
-rw-r--r--src/com/isode/stroke/parser/AttributeMap.java33
-rw-r--r--src/com/isode/stroke/parser/AuthChallengeParser.java40
-rw-r--r--src/com/isode/stroke/parser/AuthFailureParser.java21
-rw-r--r--src/com/isode/stroke/parser/AuthRequestParser.java42
-rw-r--r--src/com/isode/stroke/parser/AuthResponseParser.java40
-rw-r--r--src/com/isode/stroke/parser/AuthSuccessParser.java40
-rw-r--r--src/com/isode/stroke/parser/CompressFailureParser.java21
-rw-r--r--src/com/isode/stroke/parser/CompressParser.java46
-rw-r--r--src/com/isode/stroke/parser/CompressedParser.java21
-rw-r--r--src/com/isode/stroke/parser/ElementParser.java25
-rw-r--r--src/com/isode/stroke/parser/EnableStreamManagementParser.java21
-rw-r--r--src/com/isode/stroke/parser/GenericElementParser.java46
-rw-r--r--src/com/isode/stroke/parser/GenericPayloadParser.java28
-rw-r--r--src/com/isode/stroke/parser/GenericPayloadParserFactory.java42
-rw-r--r--src/com/isode/stroke/parser/GenericStanzaParser.java29
-rw-r--r--src/com/isode/stroke/parser/IQParser.java34
-rw-r--r--src/com/isode/stroke/parser/MessageParser.java34
-rw-r--r--src/com/isode/stroke/parser/PayloadParser.java25
-rw-r--r--src/com/isode/stroke/parser/PayloadParserFactory.java18
-rw-r--r--src/com/isode/stroke/parser/PayloadParserFactoryCollection.java41
-rw-r--r--src/com/isode/stroke/parser/PlatformXMLParserFactory.java21
-rw-r--r--src/com/isode/stroke/parser/PresenceParser.java44
-rw-r--r--src/com/isode/stroke/parser/PullXMLParser.java219
-rw-r--r--src/com/isode/stroke/parser/SerializingParser.java48
-rw-r--r--src/com/isode/stroke/parser/StanzaAckParser.java39
-rw-r--r--src/com/isode/stroke/parser/StanzaAckRequestParser.java21
-rw-r--r--src/com/isode/stroke/parser/StanzaParser.java95
-rw-r--r--src/com/isode/stroke/parser/StartTLSFailureParser.java21
-rw-r--r--src/com/isode/stroke/parser/StartTLSParser.java21
-rw-r--r--src/com/isode/stroke/parser/StreamFeaturesParser.java74
-rw-r--r--src/com/isode/stroke/parser/StreamManagementEnabledParser.java38
-rw-r--r--src/com/isode/stroke/parser/StreamManagementFailedParser.java21
-rw-r--r--src/com/isode/stroke/parser/StreamResumeParser.java42
-rw-r--r--src/com/isode/stroke/parser/StreamResumedParser.java42
-rw-r--r--src/com/isode/stroke/parser/TLSProceedParser.java20
-rw-r--r--src/com/isode/stroke/parser/UnknownElementParser.java18
-rw-r--r--src/com/isode/stroke/parser/UnknownPayloadParser.java32
-rw-r--r--src/com/isode/stroke/parser/XMLParser.java24
-rw-r--r--src/com/isode/stroke/parser/XMLParserClient.java19
-rw-r--r--src/com/isode/stroke/parser/XMPPParser.java168
-rw-r--r--src/com/isode/stroke/parser/XMPPParserClient.java19
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/BodyParser.java40
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/BodyParserFactory.java19
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java55
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/LastParser.java46
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/RawXMLPayloadParser.java45
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/RawXMLPayloadParserFactory.java26
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/ResourceBindParser.java56
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/ResourceBindParserFactory.java20
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/RosterParser.java82
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/RosterParserFactory.java19
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/SearchPayloadParser.java124
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/SearchPayloadParserFactory.java21
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/SoftwareVersionParser.java51
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/SoftwareVersionParserFactory.java20
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/StartSessionParser.java35
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/StartSessionParserFactory.java19
-rw-r--r--src/com/isode/stroke/queries/GenericRequest.java44
-rw-r--r--src/com/isode/stroke/queries/GetRosterRequest.java24
-rw-r--r--src/com/isode/stroke/queries/GetVersionRequest.java24
-rw-r--r--src/com/isode/stroke/queries/IQChannel.java24
-rw-r--r--src/com/isode/stroke/queries/IQHandler.java19
-rw-r--r--src/com/isode/stroke/queries/IQReceivedHandler.java16
-rw-r--r--src/com/isode/stroke/queries/IQRouter.java73
-rw-r--r--src/com/isode/stroke/queries/Request.java86
-rw-r--r--src/com/isode/stroke/sasl/ClientAuthenticator.java52
-rw-r--r--src/com/isode/stroke/sasl/PLAINClientAuthenticator.java26
-rw-r--r--src/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java199
-rw-r--r--src/com/isode/stroke/serializer/AuthChallengeSerializer.java38
-rw-r--r--src/com/isode/stroke/serializer/AuthFailureSerializer.java26
-rw-r--r--src/com/isode/stroke/serializer/AuthRequestSerializer.java38
-rw-r--r--src/com/isode/stroke/serializer/AuthResponseSerializer.java35
-rw-r--r--src/com/isode/stroke/serializer/AuthSuccessSerializer.java38
-rw-r--r--src/com/isode/stroke/serializer/CompressFailureSerializer.java26
-rw-r--r--src/com/isode/stroke/serializer/CompressRequestSerializer.java27
-rw-r--r--src/com/isode/stroke/serializer/ElementSerializer.java17
-rw-r--r--src/com/isode/stroke/serializer/EnableStreamManagementSerializer.java26
-rw-r--r--src/com/isode/stroke/serializer/GenericElementSerializer.java28
-rw-r--r--src/com/isode/stroke/serializer/GenericPayloadSerializer.java34
-rw-r--r--src/com/isode/stroke/serializer/GenericStanzaSerializer.java33
-rw-r--r--src/com/isode/stroke/serializer/IQSerializer.java37
-rw-r--r--src/com/isode/stroke/serializer/MessageSerializer.java38
-rw-r--r--src/com/isode/stroke/serializer/PayloadSerializer.java20
-rw-r--r--src/com/isode/stroke/serializer/PayloadSerializerCollection.java34
-rw-r--r--src/com/isode/stroke/serializer/PresenceSerializer.java36
-rw-r--r--src/com/isode/stroke/serializer/StanzaAckRequestSerializer.java26
-rw-r--r--src/com/isode/stroke/serializer/StanzaAckSerializer.java28
-rw-r--r--src/com/isode/stroke/serializer/StanzaSerializer.java63
-rw-r--r--src/com/isode/stroke/serializer/StartTLSFailureSerializer.java26
-rw-r--r--src/com/isode/stroke/serializer/StartTLSRequestSerializer.java26
-rw-r--r--src/com/isode/stroke/serializer/StreamManagementEnabledSerializer.java32
-rw-r--r--src/com/isode/stroke/serializer/StreamManagementFailedSerializer.java26
-rw-r--r--src/com/isode/stroke/serializer/StreamResumeSerializer.java32
-rw-r--r--src/com/isode/stroke/serializer/StreamResumedSerializer.java32
-rw-r--r--src/com/isode/stroke/serializer/TLSProceedSerializer.java26
-rw-r--r--src/com/isode/stroke/serializer/XMPPSerializer.java93
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/BodySerializer.java29
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/ErrorSerializer.java70
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java52
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/LastSerializer.java24
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/RawXMLPayloadSerializer.java25
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/ResourceBindSerializer.java38
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/RosterSerializer.java64
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/SearchPayloadSerializer.java66
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/SoftwareVersionSerializer.java39
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/StartSessionSerializer.java27
-rw-r--r--src/com/isode/stroke/serializer/xml/XMLElement.java70
-rw-r--r--src/com/isode/stroke/serializer/xml/XMLNode.java14
-rw-r--r--src/com/isode/stroke/serializer/xml/XMLRawTextNode.java22
-rw-r--r--src/com/isode/stroke/serializer/xml/XMLTextNode.java25
-rw-r--r--src/com/isode/stroke/session/BasicSessionStream.java252
-rw-r--r--src/com/isode/stroke/session/Session.java183
-rw-r--r--src/com/isode/stroke/session/SessionStream.java88
-rw-r--r--src/com/isode/stroke/session/SessionTracer.java43
-rw-r--r--src/com/isode/stroke/signals/Signal.java54
-rw-r--r--src/com/isode/stroke/signals/Signal1.java48
-rw-r--r--src/com/isode/stroke/signals/Signal2.java40
-rw-r--r--src/com/isode/stroke/signals/SignalConnection.java15
-rw-r--r--src/com/isode/stroke/signals/Slot.java13
-rw-r--r--src/com/isode/stroke/signals/Slot1.java13
-rw-r--r--src/com/isode/stroke/signals/Slot2.java13
-rw-r--r--src/com/isode/stroke/streammanagement/StanzaAckRequester.java55
-rw-r--r--src/com/isode/stroke/streammanagement/StanzaAckResponder.java29
-rw-r--r--src/com/isode/stroke/streamstack/CompressionLayer.java43
-rw-r--r--src/com/isode/stroke/streamstack/ConnectionLayer.java58
-rw-r--r--src/com/isode/stroke/streamstack/HighLayer.java31
-rw-r--r--src/com/isode/stroke/streamstack/LowLayer.java30
-rw-r--r--src/com/isode/stroke/streamstack/StreamLayer.java50
-rw-r--r--src/com/isode/stroke/streamstack/StreamStack.java52
-rw-r--r--src/com/isode/stroke/streamstack/TLSLayer.java73
-rw-r--r--src/com/isode/stroke/streamstack/WhitespacePingLayer.java61
-rw-r--r--src/com/isode/stroke/streamstack/XMPPLayer.java152
-rw-r--r--src/com/isode/stroke/stringcodecs/Base64.java23
-rw-r--r--src/com/isode/stroke/stringcodecs/Base64BSD.java575
-rw-r--r--src/com/isode/stroke/stringcodecs/HMACSHA1.java49
-rw-r--r--src/com/isode/stroke/stringcodecs/Hexify.java26
-rw-r--r--src/com/isode/stroke/stringcodecs/PBKDF2.java29
-rw-r--r--src/com/isode/stroke/stringcodecs/SHA1.java27
-rw-r--r--src/com/isode/stroke/tls/Certificate.java47
-rw-r--r--src/com/isode/stroke/tls/CertificateFactory.java16
-rw-r--r--src/com/isode/stroke/tls/CertificateTrustChecker.java23
-rw-r--r--src/com/isode/stroke/tls/CertificateVerificationError.java39
-rw-r--r--src/com/isode/stroke/tls/PKCS12Certificate.java40
-rw-r--r--src/com/isode/stroke/tls/PlatformTLSFactories.java21
-rw-r--r--src/com/isode/stroke/tls/ServerIdentityVerifier.java88
-rw-r--r--src/com/isode/stroke/tls/TLSContext.java34
-rw-r--r--src/com/isode/stroke/tls/TLSContextFactory.java15
229 files changed, 12090 insertions, 0 deletions
diff --git a/src/com/isode/stroke/base/ByteArray.java b/src/com/isode/stroke/base/ByteArray.java
new file mode 100644
index 0000000..a36e305
--- /dev/null
+++ b/src/com/isode/stroke/base/ByteArray.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.base;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ *
+ */
+public class ByteArray {
+
+ public ByteArray() {
+ }
+
+ public ByteArray(String s) {
+ try {
+ fromBytes(s.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException ex) {
+ throw new IllegalStateException("JVM has no 'UTF-8' encoding");
+ }
+ }
+
+ public ByteArray(byte[] c) {
+ fromBytes(c);
+ }
+
+ public ByteArray(ByteArray b) {
+ fromBytes(b.getData());
+ }
+
+ private void fromBytes(final byte[] b) {
+ data_ = new byte[b.length];
+ System.arraycopy(b, 0, data_, 0, b.length);
+ }
+
+ /*public ByteArray(char[] c, int n) {
+ for (int i = 0; i < n; i++) {
+ append(c[i]);
+ }
+ }*/
+
+ /**
+ * These are the raw, modifyable data!
+ * @return
+ */
+ public byte[] getData() {
+ return data_;
+ }
+
+ public int getSize() {
+ return data_.length;
+ }
+
+ public boolean isEmpty() {
+ return getSize() == 0;
+ }
+
+ /*public void resize(size_t size) {
+ return data_.resize(size);
+ }*/
+ /** Immutable add */
+ public static ByteArray plus(ByteArray a, ByteArray b) {
+ ByteArray x = new ByteArray(a.getData());
+ x.append(b);
+ return x;
+ }
+
+ /** Immutable add */
+ /*public ByteArray plus(ByteArray a, char b) {
+ ByteArray x = new ByteArray(a.getData());
+ x.append(b);
+ return x;
+ }*/
+
+ /** Mutable add */
+ public ByteArray append(ByteArray b) {
+ append(b.getData());
+ return this;
+ }
+
+ /** Mutable add */
+ private ByteArray append(byte[] b) {
+ int newLength = data_.length + b.length;
+ byte[] newData = new byte[newLength];
+ for (int i = 0; i < data_.length; i++) {
+ newData[i] = data_[i];
+ }
+ for (int i = 0; i < b.length; i++) {
+ newData[i + data_.length] = b[i];
+ }
+ data_ = newData;
+ return this;
+ }
+
+ /** Mutable add */
+ public ByteArray append(byte b) {
+ byte[] bytes = {b};
+ append(bytes);
+ return this;
+ }
+
+ /** mutable add */
+ public ByteArray append(String s) {
+ byte[] bytes;
+ try {
+ bytes = s.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException ex) {
+ throw new IllegalStateException("JVM has no 'UTF-8' encoding");
+ }
+ append(bytes);
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 97 * hash + (this.data_ != null ? this.data_.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof ByteArray && toString().equals(other.toString());
+ }
+
+ /*public char charAt(int i) {
+ return data_.charAt(i);
+ }*/
+
+ /*public const_iterator begin() const {
+ return data_.begin();
+ }
+
+ public const_iterator end() const {
+ return data_.end();
+ }*/
+ @Override
+ public String toString() {
+ try {
+ return new String(data_, "UTF-8");
+ } catch (UnsupportedEncodingException ex) {
+ throw new IllegalStateException("JVM has no 'UTF-8' encoding");
+ }
+ }
+
+ public void readFromFile(String file) {
+ //FIXME: port
+ }
+
+ public void clear() {
+ data_ = new byte[]{};
+ }
+ private byte[] data_ = {};
+
+}
diff --git a/src/com/isode/stroke/base/Error.java b/src/com/isode/stroke/base/Error.java
new file mode 100644
index 0000000..60ccbeb
--- /dev/null
+++ b/src/com/isode/stroke/base/Error.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.base;
+
+public interface Error {
+
+}
diff --git a/src/com/isode/stroke/client/ClientError.java b/src/com/isode/stroke/client/ClientError.java
new file mode 100644
index 0000000..9412e9a
--- /dev/null
+++ b/src/com/isode/stroke/client/ClientError.java
@@ -0,0 +1,57 @@
+/*
+ * 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 ClientError {
+
+ private final Type type_;
+
+ enum Type {
+
+ UnknownError,
+ DomainNameResolveError,
+ ConnectionError,
+ ConnectionReadError,
+ ConnectionWriteError,
+ XMLError,
+ AuthenticationFailedError,
+ CompressionFailedError,
+ ServerVerificationFailedError,
+ NoSupportedAuthMechanismsError,
+ UnexpectedElementError,
+ ResourceBindError,
+ SessionStartError,
+ TLSError,
+ ClientCertificateLoadError,
+ ClientCertificateError,
+ // Certificate verification errors
+ UnknownCertificateError,
+ CertificateExpiredError,
+ CertificateNotYetValidError,
+ CertificateSelfSignedError,
+ CertificateRejectedError,
+ CertificateUntrustedError,
+ InvalidCertificatePurposeError,
+ CertificatePathLengthExceededError,
+ InvalidCertificateSignatureError,
+ InvalidCAError,
+ InvalidServerIdentityError,
+ };
+
+ ClientError(Type type) {
+ type_ = type;
+ }
+
+ Type getType() {
+ return type_;
+ }
+}
diff --git a/src/com/isode/stroke/client/ClientOptions.java b/src/com/isode/stroke/client/ClientOptions.java
new file mode 100644
index 0000000..dad4204
--- /dev/null
+++ b/src/com/isode/stroke/client/ClientOptions.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2011 Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.client;
+
+/**
+ *
+ */
+public class ClientOptions {
+
+ enum UseTLS {
+
+ NeverUseTLS,
+ UseTLSWhenAvailable
+ };
+
+ public ClientOptions() {
+ useStreamCompression = true;
+ useTLS = UseTLS.UseTLSWhenAvailable;
+ useStreamResumption = false;
+ }
+ /**
+ * Whether ZLib stream compression should be used when available.
+ *
+ * Default: true
+ */
+ public boolean useStreamCompression;
+ /**
+ * Sets whether TLS encryption should be used.
+ *
+ * Default: UseTLSWhenAvailable
+ */
+ public UseTLS useTLS;
+ /**
+ * Use XEP-196 stream resumption when available.
+ *
+ * Default: false
+ */
+ public boolean useStreamResumption;
+}
diff --git a/src/com/isode/stroke/client/ClientSession.java b/src/com/isode/stroke/client/ClientSession.java
new file mode 100644
index 0000000..59427b0
--- /dev/null
+++ b/src/com/isode/stroke/client/ClientSession.java
@@ -0,0 +1,608 @@
+/*
+ * Copyright (c) 2010-2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010-2011 Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.client;
+
+import com.isode.stroke.elements.AuthChallenge;
+import com.isode.stroke.elements.AuthFailure;
+import com.isode.stroke.elements.AuthRequest;
+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.session.SessionStream;
+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 java.util.UUID;
+
+public class ClientSession {
+ private SignalConnection streamElementReceivedConnection;
+ private SignalConnection streamStreamStartReceivedConnection;
+ private SignalConnection streamClosedConnection;
+ private SignalConnection streamTLSEncryptedConnection;
+ private SignalConnection stanzaAckOnRequestConnection_;
+ private SignalConnection stanzaAckOnAckedConnection_;
+ private SignalConnection stanzaResponderAckConnection_;
+
+ public enum State {
+
+ Initial,
+ WaitingForStreamStart,
+ Negotiating,
+ Compressing,
+ WaitingForEncrypt,
+ Encrypting,
+ WaitingForCredentials,
+ Authenticating,
+ EnablingSessionManagement,
+ BindingResource,
+ StartingSession,
+ Initialized,
+ Finishing,
+ Finished
+ };
+
+ public static class Error implements com.isode.stroke.base.Error {
+
+ public enum Type {
+
+ AuthenticationFailedError,
+ CompressionFailedError,
+ ServerVerificationFailedError,
+ NoSupportedAuthMechanismsError,
+ UnexpectedElementError,
+ ResourceBindError,
+ SessionStartError,
+ TLSClientCertificateError,
+ TLSError,
+ StreamError
+ };
+
+ public Error(Type type) {
+ if (type == null) {
+ throw new IllegalStateException();
+ }
+ this.type = type;
+ }
+ public final Type type;
+ };
+
+ public enum UseTLS {
+ NeverUseTLS,
+ UseTLSWhenAvailable
+ }
+
+ private ClientSession(JID jid, SessionStream stream) {
+ localJID = jid;
+ state = State.Initial;
+ this.stream = stream;
+ allowPLAINOverNonTLS = true; /* FIXME: false */
+ needSessionStart = false;
+ needResourceBind = false;
+ needAcking = false;
+ authenticator = null;
+ }
+
+ public static ClientSession create(JID jid, SessionStream stream) {
+ return new ClientSession(jid, stream);
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public void setAllowPLAINOverNonTLS(boolean b) {
+ allowPLAINOverNonTLS = b;
+ }
+
+ public void setUseStreamCompression(boolean b) {
+ useStreamCompression = b;
+ }
+
+ public void setUseTLS(UseTLS use) {
+ useTLS = use;
+ }
+
+ public boolean getStreamManagementEnabled() {
+ return stanzaAckRequester_ != null;
+ }
+
+ public boolean getRosterVersioningSuported() {
+ return rosterVersioningSupported;
+ }
+
+ public JID getLocalJID() {
+ return localJID;
+ }
+
+ public boolean isFinished() {
+ return State.Finished.equals(getState());
+ }
+
+ public void setCertificateTrustChecker(CertificateTrustChecker checker) {
+ certificateTrustChecker = checker;
+ }
+
+ public void start() {
+ streamStreamStartReceivedConnection = stream.onStreamStartReceived.connect(new Slot1<ProtocolHeader>(){
+ public void call(ProtocolHeader p1) {
+ handleStreamStart(p1);
+ }
+ });
+ streamElementReceivedConnection = stream.onElementReceived.connect(new Slot1<Element>(){
+ public void call(Element p1) {
+ handleElement(p1);
+ }
+ });
+ streamClosedConnection = stream.onClosed.connect(new Slot1<SessionStream.Error>(){
+ public void call(SessionStream.Error p1) {
+ handleStreamClosed(p1);
+ }
+ });
+ streamTLSEncryptedConnection = stream.onTLSEncrypted.connect(new Slot(){
+ public void call() {
+ handleTLSEncrypted();
+ }
+ });
+
+ assert state.equals(State.Initial);
+ state = State.WaitingForStreamStart;
+ sendStreamHeader();
+ }
+
+ private void sendStreamHeader() {
+ ProtocolHeader header = new ProtocolHeader();
+ header.setTo(getRemoteJID().toString());
+ stream.writeHeader(header);
+ }
+
+ public void sendStanza(Stanza stanza) {
+ stream.writeElement(stanza);
+ if (stanzaAckRequester_ != null) {
+ stanzaAckRequester_.handleStanzaSent(stanza);
+ }
+ }
+
+ private void handleStreamStart(ProtocolHeader header) {
+ if (!checkState(State.WaitingForStreamStart)) {
+ return;
+ }
+ state = State.Negotiating;
+ }
+
+ private void handleElement(Element element) {
+ if (element instanceof Stanza) {
+ Stanza stanza = (Stanza) element;
+ if (stanzaAckResponder_ != null) {
+ stanzaAckResponder_.handleStanzaReceived();
+ }
+ if (getState().equals(State.Initialized)) {
+ onStanzaReceived.emit(stanza);
+ }
+ else if (stanza instanceof IQ) {
+ IQ iq = (IQ)stanza;
+ if (getState().equals(State.BindingResource)) {
+ ResourceBind resourceBind = iq.getPayload(new ResourceBind());
+ if (IQ.Type.Error.equals(iq.getType()) && iq.getID().equals("session-bind")) {
+ finishSession(Error.Type.ResourceBindError);
+ }
+ else if (resourceBind == null) {
+ finishSession(Error.Type.UnexpectedElementError);
+ }
+ else if (IQ.Type.Result.equals(iq.getType())) {
+ localJID = resourceBind.getJID();
+ if (!localJID.isValid()) {
+ finishSession(Error.Type.ResourceBindError);
+ }
+ needResourceBind = false;
+ continueSessionInitialization();
+ }
+ else {
+ finishSession(Error.Type.UnexpectedElementError);
+ }
+ }
+ else if (state.equals(State.StartingSession)) {
+ if (IQ.Type.Result.equals(iq.getType())) {
+ needSessionStart = false;
+ continueSessionInitialization();
+ }
+ else if (IQ.Type.Error.equals(iq.getType())) {
+ finishSession(Error.Type.SessionStartError);
+ }
+ else {
+ finishSession(Error.Type.UnexpectedElementError);
+ }
+ }
+ else {
+ finishSession(Error.Type.UnexpectedElementError);
+ }
+ }
+ }
+ else if (element instanceof StanzaAckRequest) {
+ if (stanzaAckResponder_ != null) {
+
+ stanzaAckResponder_.handleAckRequestReceived();
+ }
+ }
+ else if (element instanceof StanzaAck) {
+ StanzaAck ack = (StanzaAck) element;
+ if (stanzaAckRequester_ != null) {
+ if (ack.isValid()) {
+ stanzaAckRequester_.handleAckReceived(ack.getHandledStanzasCount());
+ }
+ else {
+ //logger_.warning("Got invalid ack from server"); /*FIXME: Do we want logging here?
+ }
+ }
+ else {
+ //logger_.warning("Ignoring ack"); /*FIXME: Do we want logging here?*/
+ }
+ }
+ else if (element instanceof StreamError) {
+ finishSession(Error.Type.StreamError);
+ }
+ else if (State.Initialized.equals(getState())) {
+ Stanza stanza = element instanceof Stanza ? (Stanza)element : null;
+ if (stanza != null) {
+ if (stanzaAckResponder_ != null) {
+ stanzaAckResponder_.handleStanzaReceived();
+ }
+ onStanzaReceived.emit(stanza);
+ }
+ }
+ else if (element instanceof StreamFeatures) {
+ StreamFeatures streamFeatures = (StreamFeatures) element;
+ if (!checkState(State.Negotiating)) {
+ return;
+ }
+
+ if (streamFeatures.hasStartTLS() && stream.supportsTLSEncryption()) {
+ state = State.WaitingForEncrypt;
+ stream.writeElement(new StartTLSRequest());
+ }
+ else if (false && streamFeatures.hasCompressionMethod("zlib")) { /*FIXME: test and enable!*/
+ state = State.Compressing;
+ stream.writeElement(new CompressRequest("zlib"));
+ }
+ else if (streamFeatures.hasAuthenticationMechanisms()) {
+ if (stream.hasTLSCertificate()) {
+ if (streamFeatures.hasAuthenticationMechanism("EXTERNAL")) {
+ state = State.Authenticating;
+ stream.writeElement(new AuthRequest("EXTERNAL"));
+ }
+ else {
+ finishSession(Error.Type.TLSClientCertificateError);
+ }
+ }
+ else if (streamFeatures.hasAuthenticationMechanism("EXTERNAL")) {
+ state = State.Authenticating;
+ stream.writeElement(new AuthRequest("EXTERNAL"));
+ }
+ else if (streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1") || streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1-PLUS")) {
+ SCRAMSHA1ClientAuthenticator scramAuthenticator = new SCRAMSHA1ClientAuthenticator(UUID.randomUUID().toString(), streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1-PLUS"));
+ if (stream.isTLSEncrypted()) {
+ scramAuthenticator.setTLSChannelBindingData(stream.getTLSFinishMessage());
+ }
+ authenticator = scramAuthenticator;
+ state = State.WaitingForCredentials;
+ onNeedCredentials.emit();
+ }
+ else if ((stream.isTLSEncrypted() || allowPLAINOverNonTLS) && streamFeatures.hasAuthenticationMechanism("PLAIN")) {
+ authenticator = new PLAINClientAuthenticator();
+ 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 {
+ finishSession(Error.Type.NoSupportedAuthMechanismsError);
+ }
+ }
+ else {
+ // Start the session
+ rosterVersioningSupported = streamFeatures.hasRosterVersioning();
+ stream.setWhitespacePingEnabled(true);
+ needSessionStart = streamFeatures.hasSession();
+ needResourceBind = streamFeatures.hasResourceBind();
+ needAcking = streamFeatures.hasStreamManagement();
+ if (!needResourceBind) {
+ // Resource binding is a MUST
+ finishSession(Error.Type.ResourceBindError);
+ } else {
+ continueSessionInitialization();
+ }
+ }
+ }
+ else if (element instanceof Compressed) {
+ checkState(State.Compressing);
+ state = State.WaitingForStreamStart;
+ stream.addZLibCompression();
+ stream.resetXMPPParser();
+ sendStreamHeader();
+ }
+ else if (element instanceof CompressFailure) {
+ finishSession(Error.Type.CompressionFailedError);
+ }
+ else if (element instanceof StreamManagementEnabled) {
+ stanzaAckRequester_ = new StanzaAckRequester();
+ stanzaAckOnRequestConnection_ = stanzaAckRequester_.onRequestAck.connect(new Slot() {
+
+ public void call() {
+ requestAck();
+ }
+ });
+ stanzaAckOnAckedConnection_ = stanzaAckRequester_.onStanzaAcked.connect(new Slot1<Stanza>() {
+
+ public void call(Stanza p1) {
+ handleStanzaAcked(p1);
+ }
+ });
+ stanzaAckResponder_ = new StanzaAckResponder();
+ stanzaResponderAckConnection_ = stanzaAckResponder_.onAck.connect(new Slot1<Long>() {
+
+ public void call(Long p1) {
+ ack(p1);
+ }
+ });
+ needAcking = false;
+ continueSessionInitialization();
+ }
+ else if (element instanceof StreamManagementFailed) {
+ needAcking = false;
+ continueSessionInitialization();
+ }
+ else if (element instanceof AuthChallenge) {
+ AuthChallenge challenge = (AuthChallenge) element;
+ checkState(State.Authenticating);
+ assert authenticator != null;
+ if (authenticator.setChallenge(challenge.getValue())) {
+ stream.writeElement(new AuthResponse(authenticator.getResponse()));
+ }
+ else {
+ finishSession(Error.Type.AuthenticationFailedError);
+ }
+ }
+ else if (element instanceof AuthSuccess) {
+ AuthSuccess authSuccess = (AuthSuccess)element;
+ checkState(State.Authenticating);
+ if (authenticator != null && !authenticator.setChallenge(authSuccess.getValue())) {
+ finishSession(Error.Type.ServerVerificationFailedError);
+ }
+ else {
+ state = State.WaitingForStreamStart;
+ authenticator = null;
+ stream.resetXMPPParser();
+ sendStreamHeader();
+ }
+ }
+ else if (element instanceof AuthFailure) {
+ authenticator = null;
+ finishSession(Error.Type.AuthenticationFailedError);
+ }
+ else if (element instanceof TLSProceed) {
+ if (!checkState(State.WaitingForEncrypt)) {
+ return;
+ }
+ state = State.Encrypting;
+ stream.addTLSEncryption();
+ }
+ else if (element instanceof StartTLSFailure) {
+ finishSession(Error.Type.TLSError);
+ }
+ else {
+ // FIXME Not correct?
+ state = State.Initialized;
+ onInitialized.emit();
+ }
+ }
+
+ private void continueSessionInitialization() {
+ if (needResourceBind) {
+ state = State.BindingResource;
+ ResourceBind resourceBind = new ResourceBind();
+ if (localJID.getResource().length() != 0) {
+ resourceBind.setResource(localJID.getResource());
+ }
+ sendStanza(IQ.createRequest(IQ.Type.Set, new JID(), "session-bind", resourceBind));
+ }
+ else if (needAcking) {
+ state = State.EnablingSessionManagement;
+ stream.writeElement(new EnableStreamManagement());
+ }
+ else if (needSessionStart) {
+ state = State.StartingSession;
+ sendStanza(IQ.createRequest(IQ.Type.Set, new JID(), "session-start", new StartSession()));
+ }
+ else {
+ state = State.Initialized;
+ onInitialized.emit();
+ }
+ }
+
+ private boolean checkState(State state) {
+ State currentState = this.state; /* For symbol debugging, as the following overwrites it */
+ if (!currentState.equals(state)) {
+ finishSession(Error.Type.UnexpectedElementError);
+ return false;
+ }
+ return true;
+ }
+
+ public void sendCredentials(String password) {
+ if (!checkState(State.WaitingForCredentials)) {
+ throw new IllegalStateException("Asking for credentials when we shouldn't be asked.");
+ }
+ state = State.Authenticating;
+ authenticator.setCredentials(localJID.getNode(), password);
+ stream.writeElement(new AuthRequest(authenticator.getName(), authenticator.getResponse()));
+ }
+
+ private void handleTLSEncrypted() {
+ if (!checkState(State.Encrypting)) {
+ return;
+ }
+ Certificate certificate = stream.getPeerCertificate();
+ CertificateVerificationError verificationError = stream.getPeerCertificateVerificationError();
+ if (verificationError != null) {
+ checkTrustOrFinish(certificate, verificationError);
+ }
+ else {
+ ServerIdentityVerifier identityVerifier = new ServerIdentityVerifier(localJID);
+ if (identityVerifier.certificateVerifies(certificate)) {
+ continueAfterTLSEncrypted();
+ }
+ else {
+ checkTrustOrFinish(certificate, new CertificateVerificationError(CertificateVerificationError.Type.InvalidServerIdentity));
+ }
+ }
+ }
+
+ private void checkTrustOrFinish(Certificate certificate, CertificateVerificationError error) {
+ if (certificateTrustChecker != null && certificateTrustChecker.isCertificateTrusted(certificate)) {
+ continueAfterTLSEncrypted();
+ }
+ else {
+ finishSession(error);
+ }
+ }
+
+ private void continueAfterTLSEncrypted() {
+ state = State.WaitingForStreamStart;
+ stream.resetXMPPParser();
+ sendStreamHeader();
+ }
+
+ private void handleStreamClosed(SessionStream.Error streamError) {
+ State previousState = state;
+ state = State.Finished;
+
+ if (stanzaAckRequester_ != null) {
+ stanzaAckOnRequestConnection_.disconnect();
+ stanzaAckOnAckedConnection_.disconnect();
+ stanzaAckRequester_ = null;
+ }
+ if (stanzaAckResponder_ != null) {
+ stanzaResponderAckConnection_.disconnect();
+ stanzaAckResponder_ = null;
+ }
+ stream.setWhitespacePingEnabled(false);
+ streamStreamStartReceivedConnection.disconnect();
+ streamElementReceivedConnection.disconnect();
+ streamClosedConnection.disconnect();
+ streamTLSEncryptedConnection.disconnect();
+
+ if (State.Finishing.equals(previousState)) {
+ onFinished.emit(error_);
+ }
+ else {
+ onFinished.emit(streamError);
+ }
+ }
+
+ public void finish() {
+ finishSession((Error.Type)null);
+ }
+
+ 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 error) {
+ state = State.Finishing;
+ error_ = error;
+ assert(stream.isOpen());
+ if (stanzaAckResponder_ != null) {
+ stanzaAckResponder_.handleAckRequestReceived();
+ }
+ stream.writeFooter();
+ stream.close();
+ }
+
+ private void requestAck() {
+ stream.writeElement(new StanzaAckRequest());
+ }
+
+ private void handleStanzaAcked(Stanza stanza) {
+ onStanzaAcked.emit(stanza);
+ }
+
+ private void ack(long handledStanzasCount) {
+ stream.writeElement(new StanzaAck(handledStanzasCount));
+ }
+
+ public final Signal onNeedCredentials = new Signal();
+ 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>();
+ public final Signal1<Stanza> onStanzaAcked = new Signal1<Stanza>();
+
+
+
+
+
+
+
+ private JID getRemoteJID() {
+ return new JID("", localJID.getDomain());
+ }
+
+ private JID localJID;
+ private State state;
+ private SessionStream stream;
+ private boolean allowPLAINOverNonTLS;
+ private boolean useStreamCompression;
+ private UseTLS useTLS;
+ private boolean needSessionStart;
+ private boolean needResourceBind;
+ private boolean needAcking;
+ private boolean rosterVersioningSupported;
+ private ClientAuthenticator authenticator;
+ private StanzaAckRequester stanzaAckRequester_;
+ private StanzaAckResponder stanzaAckResponder_;
+ private com.isode.stroke.base.Error error_;
+ private CertificateTrustChecker certificateTrustChecker;
+}
diff --git a/src/com/isode/stroke/client/ClientSessionStanzaChannel.java b/src/com/isode/stroke/client/ClientSessionStanzaChannel.java
new file mode 100644
index 0000000..531ff62
--- /dev/null
+++ b/src/com/isode/stroke/client/ClientSessionStanzaChannel.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron?on.
+ * All rights reserved.
+ */
+package com.isode.stroke.client;
+
+import com.isode.stroke.base.Error;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.elements.Message;
+import com.isode.stroke.elements.Presence;
+import com.isode.stroke.elements.Stanza;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot;
+import com.isode.stroke.signals.Slot1;
+import java.util.logging.Logger;
+
+/**
+ * StanzaChannel implementation around a ClientSession.
+ */
+public class ClientSessionStanzaChannel extends StanzaChannel {
+ private SignalConnection sessionInitializedConnection;
+ private SignalConnection sessionFinishedConnection;
+ private SignalConnection sessionStanzaReceivedConnection;
+ private SignalConnection sessionStanzaAckedConnection;
+
+ public void setSession(ClientSession session) {
+ assert this.session == null;
+ this.session = session;
+ sessionInitializedConnection = session.onInitialized.connect(new Slot() {
+
+ public void call() {
+ handleSessionInitialized();
+ }
+ });
+ sessionFinishedConnection = session.onFinished.connect(new Slot1<com.isode.stroke.base.Error>() {
+
+ public void call(com.isode.stroke.base.Error p1) {
+ handleSessionFinished(p1);
+ }
+ });
+ sessionStanzaReceivedConnection = session.onStanzaReceived.connect(new Slot1<Stanza>() {
+
+ public void call(Stanza p1) {
+ handleStanza(p1);
+ }
+ });
+ sessionStanzaAckedConnection = session.onStanzaAcked.connect(new Slot1<Stanza>() {
+
+ public void call(Stanza p1) {
+ handleStanzaAcked(p1);
+ }
+ });
+ }
+
+ public void sendIQ(IQ iq) {
+ send(iq);
+ }
+
+ public void sendMessage(Message message) {
+ send(message);
+ }
+
+ public void sendPresence(Presence presence) {
+ send(presence);
+ }
+
+ public boolean getStreamManagementEnabled() {
+ if (session != null) {
+ return session.getStreamManagementEnabled();
+ }
+ return false;
+ }
+
+ public boolean isAvailable() {
+ return session != null && ClientSession.State.Initialized.equals(session.getState());
+ }
+
+ public String getNewIQID() {
+ return idGenerator.generateID();
+ }
+
+ private void send(Stanza stanza) {
+ if (!isAvailable()) {
+ logger_.warning("Warning: Client: Trying to send a stanza while disconnected.");
+ return;
+ }
+ session.sendStanza(stanza);
+ }
+
+ private void handleSessionFinished(Error error) {
+ sessionFinishedConnection.disconnect();
+ sessionStanzaReceivedConnection.disconnect();
+ sessionStanzaAckedConnection.disconnect();
+ sessionInitializedConnection.disconnect();
+ session = null;
+ onAvailableChanged.emit(false);
+ }
+
+ private void handleStanza(Stanza stanza) {
+ if (stanza instanceof Message) {
+ onMessageReceived.emit((Message)stanza);
+ }
+ if (stanza instanceof Presence) {
+ onPresenceReceived.emit((Presence)stanza);
+ }
+ if (stanza instanceof IQ) {
+ onIQReceived.emit((IQ)stanza);
+ }
+ }
+
+ private void handleStanzaAcked(Stanza stanza) {
+ onStanzaAcked.emit(stanza);
+ }
+
+ private void handleSessionInitialized() {
+ onAvailableChanged.emit(true);
+ }
+ private IDGenerator idGenerator = new IDGenerator();
+ private ClientSession session;
+ private static final Logger logger_ = Logger.getLogger(ClientSessionStanzaChannel.class.getName());
+}
diff --git a/src/com/isode/stroke/client/CoreClient.java b/src/com/isode/stroke/client/CoreClient.java
new file mode 100644
index 0000000..0ce1503
--- /dev/null
+++ b/src/com/isode/stroke/client/CoreClient.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.client;
+
+import com.isode.stroke.elements.Message;
+import com.isode.stroke.elements.Presence;
+import com.isode.stroke.elements.Stanza;
+import com.isode.stroke.elements.StreamType;
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.network.Connection;
+import com.isode.stroke.network.ConnectionFactory;
+import com.isode.stroke.network.Connector;
+import com.isode.stroke.network.NetworkFactories;
+import com.isode.stroke.network.PlatformDomainNameResolver;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.parser.payloadparsers.FullPayloadParserFactoryCollection;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.serializer.payloadserializers.FullPayloadSerializerCollection;
+import com.isode.stroke.session.BasicSessionStream;
+import com.isode.stroke.session.SessionStream;
+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.tls.CertificateTrustChecker;
+import com.isode.stroke.tls.CertificateVerificationError;
+import com.isode.stroke.tls.PKCS12Certificate;
+import com.isode.stroke.tls.PlatformTLSFactories;
+import com.isode.stroke.tls.TLSContextFactory;
+
+/**
+ * The central class for communicating with an XMPP server.
+ *
+ * This class is responsible for setting up the connection with the XMPP server, authenticating, and
+ * initializing the session.
+ *
+ * This class can be used directly in your application, although the Client subclass provides more
+ * functionality and interfaces, and is better suited for most needs.
+ */
+public class CoreClient {
+ private SignalConnection sessionStreamDataReadConnection_;
+ private SignalConnection sessionStreamDataWrittenConnection_;
+ private SignalConnection sessionFinishedConnection_;
+ private SignalConnection sessionNeedCredentialsConnection_;
+ private SignalConnection connectorConnectFinishedConnection_;
+ private final EventLoop eventLoop_;
+
+ public CoreClient(EventLoop eventLoop, JID jid, String password, NetworkFactories networkFactories) {
+ jid_ = jid;
+ password_ = password;
+ disconnectRequested_ = false;
+ eventLoop_ = eventLoop;
+ this.networkFactories = networkFactories;
+ this.certificateTrustChecker = null;
+ resolver_ = new PlatformDomainNameResolver(eventLoop);
+ stanzaChannel_ = new ClientSessionStanzaChannel();
+ stanzaChannel_.onMessageReceived.connect(new Slot1<Message>() {
+
+ public void call(Message p1) {
+ onMessageReceived.emit(p1);
+ }
+ });
+ stanzaChannel_.onPresenceReceived.connect(new Slot1<Presence>() {
+
+ public void call(Presence p1) {
+ onPresenceReceived.emit(p1);
+ }
+ });
+ stanzaChannel_.onStanzaAcked.connect(new Slot1<Stanza>() {
+
+ public void call(Stanza p1) {
+ onStanzaAcked.emit(p1);
+ }
+ });
+ stanzaChannel_.onAvailableChanged.connect(new Slot1<Boolean>() {
+
+ public void call(Boolean p1) {
+ handleStanzaChannelAvailableChanged(p1);
+ }
+ });
+
+ iqRouter_ = new IQRouter(stanzaChannel_);
+ tlsFactories = new PlatformTLSFactories();
+ }
+
+ /*CoreClient::~CoreClient() {
+ if (session_ || connection_) {
+ std::cerr << "Warning: Client not disconnected properly" << std::endl;
+ }
+ 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_;
+ }*/
+ public void connect(ClientOptions o) {
+ options = o;
+ connect(jid_.getDomain());
+ }
+
+ public void connect(String host) {
+ disconnectRequested_ = false;
+ assert (connector_ == null);
+ /* FIXME: Port Proxies */
+ connector_ = Connector.create(host, networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory());
+ connectorConnectFinishedConnection_ = connector_.onConnectFinished.connect(new Slot1<Connection>() {
+ public void call(Connection p1) {
+ handleConnectorFinished(p1);
+ }
+ });
+ connector_.setTimeoutMilliseconds(60 * 1000);
+ connector_.start();
+ }
+
+ void handleConnectorFinished(Connection connection) {
+ if (connectorConnectFinishedConnection_ != null) {
+ connectorConnectFinishedConnection_.disconnect();
+ }
+ connector_ = null;
+ if (connection == null) {
+ if (!disconnectRequested_) {
+ onError.emit(new ClientError(ClientError.Type.ConnectionError));
+ }
+ } else {
+ assert (connection_ == null);
+ connection_ = connection;
+
+ assert (sessionStream_ == null);
+ sessionStream_ = new BasicSessionStream(StreamType.ClientStreamType, connection_, payloadParserFactories_, payloadSerializers_, tlsFactories.getTLSContextFactory(), networkFactories.getTimerFactory(), eventLoop_);
+ if (certificate_ != null && !certificate_.isEmpty()) {
+ sessionStream_.setTLSCertificate(new PKCS12Certificate(certificate_, password_));
+ }
+ sessionStreamDataReadConnection_ = sessionStream_.onDataRead.connect(new Slot1<String>() {
+
+ public void call(String p1) {
+ handleDataRead(p1);
+ }
+ });
+
+ sessionStreamDataWrittenConnection_ = sessionStream_.onDataWritten.connect(new Slot1<String>() {
+
+ public void call(String p1) {
+ handleDataWritten(p1);
+ }
+ });
+
+ session_ = ClientSession.create(jid_, sessionStream_);
+ session_.setCertificateTrustChecker(certificateTrustChecker);
+ session_.setUseStreamCompression(options.useStreamCompression);
+ switch (options.useTLS) {
+ case UseTLSWhenAvailable:
+ session_.setUseTLS(ClientSession.UseTLS.UseTLSWhenAvailable);
+ break;
+ case NeverUseTLS:
+ session_.setUseTLS(ClientSession.UseTLS.NeverUseTLS);
+ break;
+ }
+ stanzaChannel_.setSession(session_);
+ sessionFinishedConnection_ = session_.onFinished.connect(new Slot1<com.isode.stroke.base.Error>() {
+
+ public void call(com.isode.stroke.base.Error p1) {
+ handleSessionFinished(p1);
+ }
+ });
+ sessionNeedCredentialsConnection_ = session_.onNeedCredentials.connect(new Slot() {
+
+ public void call() {
+ handleNeedCredentials();
+ }
+ });
+ session_.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_.isFinished()) {
+ session_.finish();
+ } else if (connector_ != null) {
+ connector_.stop();
+ }
+ }
+
+ public void setCertificate(String certificate) {
+ certificate_ = certificate;
+ }
+
+ private void handleSessionFinished(com.isode.stroke.base.Error error) {
+ sessionFinishedConnection_.disconnect();
+ sessionNeedCredentialsConnection_.disconnect();
+ session_ = null;
+
+ sessionStreamDataReadConnection_.disconnect();
+ sessionStreamDataWrittenConnection_.disconnect();
+ sessionStream_ = null;
+
+ connection_.disconnect();
+ connection_ = null;
+
+ if (error != null) {
+ ClientError clientError = null;
+ if (error instanceof ClientSession.Error) {
+ ClientSession.Error actualError = (ClientSession.Error) error;
+ switch (actualError.type) {
+ case AuthenticationFailedError:
+ clientError = new ClientError(ClientError.Type.AuthenticationFailedError);
+ break;
+ case CompressionFailedError:
+ clientError = new ClientError(ClientError.Type.CompressionFailedError);
+ break;
+ case ServerVerificationFailedError:
+ clientError = new ClientError(ClientError.Type.ServerVerificationFailedError);
+ break;
+ case NoSupportedAuthMechanismsError:
+ clientError = new ClientError(ClientError.Type.NoSupportedAuthMechanismsError);
+ break;
+ case UnexpectedElementError:
+ clientError = new ClientError(ClientError.Type.UnexpectedElementError);
+ break;
+ case ResourceBindError:
+ clientError = new ClientError(ClientError.Type.ResourceBindError);
+ break;
+ case SessionStartError:
+ clientError = new ClientError(ClientError.Type.SessionStartError);
+ break;
+ case TLSError:
+ clientError = new ClientError(ClientError.Type.TLSError);
+ break;
+ case TLSClientCertificateError:
+ clientError = new ClientError(ClientError.Type.ClientCertificateError);
+ break;
+ }
+ } else if (error instanceof SessionStream.Error) {
+ SessionStream.Error actualError = (SessionStream.Error) error;
+ switch (actualError.type) {
+ case ParseError:
+ clientError = new ClientError(ClientError.Type.XMLError);
+ break;
+ case TLSError:
+ clientError = new ClientError(ClientError.Type.TLSError);
+ break;
+ case InvalidTLSCertificateError:
+ clientError = new ClientError(ClientError.Type.ClientCertificateLoadError);
+ break;
+ case ConnectionReadError:
+ clientError = new ClientError(ClientError.Type.ConnectionReadError);
+ break;
+ case ConnectionWriteError:
+ clientError = new ClientError(ClientError.Type.ConnectionWriteError);
+ break;
+ }
+ } else if (error instanceof CertificateVerificationError) {
+ CertificateVerificationError verificationError = (CertificateVerificationError)error;
+ switch (verificationError.type) {
+ case UnknownError:
+ clientError = new ClientError(ClientError.Type.UnknownCertificateError);
+ break;
+ case Expired:
+ clientError = new ClientError(ClientError.Type.CertificateExpiredError);
+ break;
+ case NotYetValid:
+ clientError = new ClientError(ClientError.Type.CertificateNotYetValidError);
+ break;
+ case SelfSigned:
+ clientError = new ClientError(ClientError.Type.CertificateSelfSignedError);
+ break;
+ case Rejected:
+ clientError = new ClientError(ClientError.Type.CertificateRejectedError);
+ break;
+ case Untrusted:
+ clientError = new ClientError(ClientError.Type.CertificateUntrustedError);
+ break;
+ case InvalidPurpose:
+ clientError = new ClientError(ClientError.Type.InvalidCertificatePurposeError);
+ break;
+ case PathLengthExceeded:
+ clientError = new ClientError(ClientError.Type.CertificatePathLengthExceededError);
+ break;
+ case InvalidSignature:
+ clientError = new ClientError(ClientError.Type.InvalidCertificateSignatureError);
+ break;
+ case InvalidCA:
+ clientError = new ClientError(ClientError.Type.InvalidCAError);
+ break;
+ case InvalidServerIdentity:
+ clientError = new ClientError(ClientError.Type.InvalidServerIdentityError);
+ break;
+ }
+ }
+ assert clientError != null;
+ onError.emit(clientError);
+ }
+ }
+
+ private void handleNeedCredentials() {
+ assert session_ != null;
+ session_.sendCredentials(password_);
+ }
+
+ private void handleDataRead(String data) {
+ onDataRead.emit(data);
+ }
+
+ private void handleDataWritten(String data) {
+ onDataWritten.emit(data);
+ }
+
+ private void handleStanzaChannelAvailableChanged(boolean available) {
+ if (available) {
+ onConnected.emit();
+ }
+ }
+
+ public void sendMessage(Message message) {
+ stanzaChannel_.sendMessage(message);
+ }
+
+ public void sendPresence(Presence presence) {
+ stanzaChannel_.sendPresence(presence);
+ }
+
+ public IQRouter getIQRouter() {
+ return iqRouter_;
+ }
+
+ public StanzaChannel getStanzaChannel() {
+ return stanzaChannel_;
+ }
+
+ public boolean isAvailable() {
+ return stanzaChannel_.isAvailable();
+ }
+
+ /**
+ * Returns the JID of the client.
+ * After the session was initialized, this returns the bound JID.
+ */
+ public JID getJID() {
+ if (session_ != null) {
+ return session_.getLocalJID();
+ } else {
+ return jid_;
+ }
+ }
+ public final Signal1<ClientError> onError = new Signal1<ClientError>();
+ public final Signal onConnected = new Signal();
+ public final Signal1<String> onDataRead = new Signal1<String>();
+ public final Signal1<String> onDataWritten = new Signal1<String>();
+ public final Signal1<Message> onMessageReceived = new Signal1<Message>();
+ public final Signal1<Presence> onPresenceReceived = new Signal1<Presence>();
+ public final Signal1<Stanza> onStanzaAcked = new Signal1<Stanza>();
+ private PlatformDomainNameResolver resolver_;
+ private JID jid_;
+ private String password_;
+ 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_;
+ private BasicSessionStream sessionStream_;
+ private ClientSession session_;
+ private String certificate_;
+ private boolean disconnectRequested_;
+ private ClientOptions options;
+ private CertificateTrustChecker certificateTrustChecker;
+ private NetworkFactories networkFactories;
+ private PlatformTLSFactories tlsFactories;
+}
diff --git a/src/com/isode/stroke/client/IDGenerator.java b/src/com/isode/stroke/client/IDGenerator.java
new file mode 100644
index 0000000..1810cdf
--- /dev/null
+++ b/src/com/isode/stroke/client/IDGenerator.java
@@ -0,0 +1,22 @@
+/*
+ * 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/StanzaChannel.java b/src/com/isode/stroke/client/StanzaChannel.java
new file mode 100644
index 0000000..62984b5
--- /dev/null
+++ b/src/com/isode/stroke/client/StanzaChannel.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron?on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.client;
+
+import com.isode.stroke.elements.Message;
+import com.isode.stroke.elements.Presence;
+import com.isode.stroke.elements.Stanza;
+import com.isode.stroke.queries.IQChannel;
+import com.isode.stroke.signals.Signal1;
+
+public abstract class StanzaChannel extends IQChannel {
+
+
+ public abstract void sendMessage(Message message);
+
+ public abstract void sendPresence(Presence presence);
+
+ public abstract boolean isAvailable();
+
+ public abstract boolean getStreamManagementEnabled();
+
+ 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<Stanza> onStanzaAcked = new Signal1<Stanza>();
+
+
+}
diff --git a/src/com/isode/stroke/compress/ZLibCompressor.java b/src/com/isode/stroke/compress/ZLibCompressor.java
new file mode 100644
index 0000000..f5276c8
--- /dev/null
+++ b/src/com/isode/stroke/compress/ZLibCompressor.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.compress;
+
+import com.isode.stroke.base.ByteArray;
+import java.util.zip.Deflater;
+
+/**
+ *
+ * @author Kev
+ */
+public class ZLibCompressor {
+
+ private static final int COMPRESSION_LEVEL = 9;
+
+ public ByteArray process(ByteArray data) throws ZLibException {
+ Deflater compressor = new Deflater(COMPRESSION_LEVEL);
+ compressor.setStrategy(Deflater.DEFAULT_STRATEGY);
+ compressor.setInput(data.getData());
+ compressor.finish();
+ byte[] output = new byte[100];
+ ByteArray result = new ByteArray();
+ while (!compressor.finished()) {
+ int size = compressor.deflate(output);
+ for (int i = 0; i < size; i++) {
+ result.append(output[i]); /* TODO: Terribly slow */
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/com/isode/stroke/compress/ZLibDecompressor.java b/src/com/isode/stroke/compress/ZLibDecompressor.java
new file mode 100644
index 0000000..2c78a57
--- /dev/null
+++ b/src/com/isode/stroke/compress/ZLibDecompressor.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.compress;
+
+import com.isode.stroke.base.ByteArray;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ *
+ * @author Kev
+ */
+public class ZLibDecompressor {
+ Inflater inflater_ = new Inflater();
+ public ByteArray process(ByteArray data) throws ZLibException {
+ try {
+ inflater_.setInput(data.getData());
+ byte[] output = new byte[100];
+ ByteArray result = new ByteArray();
+ int size = 0;
+ while ((size = inflater_.inflate(output)) != 0) {
+ for (int i = 0; i < size; i++) {
+ result.append(output[i]); /* TODO: Terribly slow */
+ }
+ }
+ return result;
+ }
+ catch (DataFormatException e) {
+ throw new ZLibException();
+ }
+ }
+
+}
diff --git a/src/com/isode/stroke/compress/ZLibException.java b/src/com/isode/stroke/compress/ZLibException.java
new file mode 100644
index 0000000..48a2d31
--- /dev/null
+++ b/src/com/isode/stroke/compress/ZLibException.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.compress;
+
+/**
+ *
+ * @author Kev
+ */
+public class ZLibException extends Exception {
+
+}
diff --git a/src/com/isode/stroke/elements/AuthChallenge.java b/src/com/isode/stroke/elements/AuthChallenge.java
new file mode 100644
index 0000000..ac87a3d
--- /dev/null
+++ b/src/com/isode/stroke/elements/AuthChallenge.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+import com.isode.stroke.base.ByteArray;
+
+public class AuthChallenge implements Element {
+ //FIXME: parser/serialiser
+ public AuthChallenge() {
+ value_ = new ByteArray();
+ }
+
+ public AuthChallenge(ByteArray value) {
+ value_ = new ByteArray(value);
+ }
+
+ public ByteArray getValue() {
+ return value_;
+ }
+
+ public void setValue(ByteArray value) {
+ value_ = new ByteArray(value);
+ }
+
+ private ByteArray value_;
+}
diff --git a/src/com/isode/stroke/elements/AuthFailure.java b/src/com/isode/stroke/elements/AuthFailure.java
new file mode 100644
index 0000000..ecd7639
--- /dev/null
+++ b/src/com/isode/stroke/elements/AuthFailure.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+
+public class AuthFailure implements Element {
+ //FIXME: parser/serialiser
+}
diff --git a/src/com/isode/stroke/elements/AuthRequest.java b/src/com/isode/stroke/elements/AuthRequest.java
new file mode 100644
index 0000000..21b1356
--- /dev/null
+++ b/src/com/isode/stroke/elements/AuthRequest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+import com.isode.stroke.base.ByteArray;
+
+public class AuthRequest implements Element {
+
+ public AuthRequest() {
+ this(null);
+ }
+
+ //FIXME: parser/serialiser
+ public AuthRequest(String mechanism) {
+ mechanism_ = mechanism;
+ }
+
+ public AuthRequest(String mechanism, ByteArray message) {
+ mechanism_ = mechanism;
+ message_ = message;
+ }
+
+ public ByteArray getMessage() {
+ return message_;
+ }
+
+ public void setMessage(ByteArray message) {
+ message_ = message;
+ }
+
+ public String getMechanism() {
+ return mechanism_;
+ }
+
+ public void setMechanism(String mechanism) {
+ mechanism_ = mechanism;
+ }
+ private String mechanism_;
+ private ByteArray message_ = new ByteArray();
+}
diff --git a/src/com/isode/stroke/elements/AuthResponse.java b/src/com/isode/stroke/elements/AuthResponse.java
new file mode 100644
index 0000000..357b72b
--- /dev/null
+++ b/src/com/isode/stroke/elements/AuthResponse.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+import com.isode.stroke.base.ByteArray;
+
+public class AuthResponse implements Element {
+ //FIXME: parser/serialiser
+
+ public AuthResponse() {
+ value = null;
+ }
+
+ public AuthResponse(ByteArray value) {
+ this.value = value;
+ }
+
+ public ByteArray getValue() {
+ return value;
+ }
+
+ public void setValue(ByteArray value) {
+ this.value = value;
+ }
+ private ByteArray value;
+}
diff --git a/src/com/isode/stroke/elements/AuthSuccess.java b/src/com/isode/stroke/elements/AuthSuccess.java
new file mode 100644
index 0000000..eba6b5a
--- /dev/null
+++ b/src/com/isode/stroke/elements/AuthSuccess.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+import com.isode.stroke.base.ByteArray;
+
+public class AuthSuccess implements Element {
+ //FIXME: parser/serialiser
+
+ public ByteArray getValue() {
+ return value;
+ }
+
+ public void setValue(ByteArray value) {
+ this.value = value;
+ }
+ private ByteArray value;
+}
diff --git a/src/com/isode/stroke/elements/Body.java b/src/com/isode/stroke/elements/Body.java
new file mode 100644
index 0000000..3cda307
--- /dev/null
+++ b/src/com/isode/stroke/elements/Body.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+public class Body extends Payload {
+ String text_ = "";
+
+ public Body() {}
+
+ public Body(String text) {
+ setText(text);
+ }
+
+ public void setText(String text) {
+ text_ = text;
+ }
+
+ public String getText() {
+ return text_;
+ }
+}
diff --git a/src/com/isode/stroke/elements/CompressFailure.java b/src/com/isode/stroke/elements/CompressFailure.java
new file mode 100644
index 0000000..c3a19bd
--- /dev/null
+++ b/src/com/isode/stroke/elements/CompressFailure.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class CompressFailure implements Element {
+
+}
diff --git a/src/com/isode/stroke/elements/CompressRequest.java b/src/com/isode/stroke/elements/CompressRequest.java
new file mode 100644
index 0000000..8150f1d
--- /dev/null
+++ b/src/com/isode/stroke/elements/CompressRequest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class CompressRequest implements Element {
+
+ public CompressRequest() {
+ this("");
+ }
+
+ public CompressRequest(String method) {
+ method_ = method;
+ }
+
+ public String getMethod() {
+ return method_;
+ }
+
+ public void setMethod(String method) {
+ method_ = method;
+ }
+ private String method_;
+}
diff --git a/src/com/isode/stroke/elements/Compressed.java b/src/com/isode/stroke/elements/Compressed.java
new file mode 100644
index 0000000..1309f5b
--- /dev/null
+++ b/src/com/isode/stroke/elements/Compressed.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class Compressed implements Element {
+
+}
diff --git a/src/com/isode/stroke/elements/Element.java b/src/com/isode/stroke/elements/Element.java
new file mode 100644
index 0000000..1b931e4
--- /dev/null
+++ b/src/com/isode/stroke/elements/Element.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+public interface Element {
+
+}
diff --git a/src/com/isode/stroke/elements/EnableStreamManagement.java b/src/com/isode/stroke/elements/EnableStreamManagement.java
new file mode 100644
index 0000000..e551551
--- /dev/null
+++ b/src/com/isode/stroke/elements/EnableStreamManagement.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+public class EnableStreamManagement implements Element {
+
+}
diff --git a/src/com/isode/stroke/elements/ErrorPayload.java b/src/com/isode/stroke/elements/ErrorPayload.java
new file mode 100644
index 0000000..913296d
--- /dev/null
+++ b/src/com/isode/stroke/elements/ErrorPayload.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+/**
+ * Error.
+ */
+public class ErrorPayload extends Payload {
+ private Condition condition_;
+ private Type type_;
+ private String text_;
+
+ public enum Type { Cancel, Continue, Modify, Auth, Wait };
+
+ public enum Condition {
+ BadRequest,
+ Conflict,
+ FeatureNotImplemented,
+ Forbidden,
+ Gone,
+ InternalServerError,
+ ItemNotFound,
+ JIDMalformed,
+ NotAcceptable,
+ NotAllowed,
+ NotAuthorized,
+ PaymentRequired,
+ RecipientUnavailable,
+ Redirect,
+ RegistrationRequired,
+ RemoteServerNotFound,
+ RemoteServerTimeout,
+ ResourceConstraint,
+ ServiceUnavailable,
+ SubscriptionRequired,
+ UndefinedCondition,
+ UnexpectedRequest
+ };
+
+ public ErrorPayload(Condition condition, Type type, String text) {
+ condition_ = condition;
+ type_ = type;
+ text_ = text;
+ }
+
+ public ErrorPayload(Condition condition, Type type) {
+ this(condition, type, "");
+ }
+
+ public ErrorPayload(Condition condition) {
+ this(condition, Type.Cancel);
+ }
+
+ public ErrorPayload() {
+ this(Condition.UndefinedCondition);
+ }
+
+ public Type getType() {
+ return type_;
+ }
+
+ public void setType(Type type) {
+ type_ = type;
+ }
+
+ public Condition getCondition() {
+ return condition_;
+ }
+
+ public void setCondition(Condition condition) {
+ condition_ = condition;
+ }
+
+ public void setText(String text) {
+ text_ = text;
+ }
+
+ public String getText() {
+ return text_;
+ }
+}
diff --git a/src/com/isode/stroke/elements/IQ.java b/src/com/isode/stroke/elements/IQ.java
new file mode 100644
index 0000000..a4fc9dc
--- /dev/null
+++ b/src/com/isode/stroke/elements/IQ.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+import com.isode.stroke.jid.JID;
+
+public class IQ extends Stanza {
+ public enum Type {Get, Set, Result, Error};
+
+ private Type type_;
+
+ public IQ() {
+ this(Type.Get);
+ }
+
+ public IQ(Type type) {
+ type_ = type;
+ }
+
+ public Type getType() {
+ return type_;
+ }
+
+ public void setType(Type type) {
+ type_ = type;
+ }
+
+ public static IQ createRequest(Type type, JID to, String id, Payload payload) {
+ IQ iq = new IQ(type);
+ iq.setTo(to);
+ iq.setID(id);
+ iq.addPayload(payload);
+ return iq;
+ }
+
+ public static IQ createResult(JID to, String id, Payload payload) {
+ IQ iq = new IQ(Type.Result);
+ iq.setTo(to);
+ iq.setID(id);
+ iq.addPayload(payload);
+ return iq;
+ }
+
+ public static IQ createError(JID to, String id, ErrorPayload.Condition condition, ErrorPayload.Type type) {
+ IQ iq = new IQ(Type.Error);
+ iq.setTo(to);
+ iq.setID(id);
+ iq.addPayload(new ErrorPayload(condition, type));
+ return iq;
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/Last.java b/src/com/isode/stroke/elements/Last.java
new file mode 100644
index 0000000..8f12616
--- /dev/null
+++ b/src/com/isode/stroke/elements/Last.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2011, Kevin Smith.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+public class Last extends Payload {
+ int seconds_;
+
+ public Last() {}
+
+ public Last(final int seconds) {
+ setSeconds(seconds);
+ }
+
+ public void setSeconds(final int seconds) {
+ seconds_ = seconds;
+ }
+
+ public int getSeconds() {
+ return seconds_;
+ }
+}
diff --git a/src/com/isode/stroke/elements/Message.java b/src/com/isode/stroke/elements/Message.java
new file mode 100644
index 0000000..6820e6f
--- /dev/null
+++ b/src/com/isode/stroke/elements/Message.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class Message extends Stanza {
+
+ Type type_ = Type.Chat;
+
+ public enum Type {
+
+ Normal, Chat, Error, Groupchat, Headline
+ };
+
+ public String getSubject() {
+ Subject subject = getPayload(new Subject());
+ if (subject != null) {
+ return subject.getText();
+ }
+ return "";
+ }
+
+ public void setSubject(String subject) {
+ updatePayload(new Subject(subject));
+ }
+
+ public String getBody() {
+ Body body = getPayload(new Body());
+ if (body != null) {
+ return body.getText();
+ }
+ return "";
+ }
+
+ public void setBody(String body) {
+ updatePayload(new Body(body));
+ }
+
+ public boolean isError() {
+ ErrorPayload error = getPayload(new ErrorPayload());
+ return getType().equals(Type.Error) || error != null;
+ }
+
+ public Type getType() {
+ return type_;
+ }
+
+ public void setType(Type type) {
+ type_ = type;
+ }
+}
diff --git a/src/com/isode/stroke/elements/Payload.java b/src/com/isode/stroke/elements/Payload.java
new file mode 100644
index 0000000..fbe47f2
--- /dev/null
+++ b/src/com/isode/stroke/elements/Payload.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+public class Payload {
+
+}
diff --git a/src/com/isode/stroke/elements/Presence.java b/src/com/isode/stroke/elements/Presence.java
new file mode 100644
index 0000000..f32fb9f
--- /dev/null
+++ b/src/com/isode/stroke/elements/Presence.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class Presence extends Stanza {
+
+ private Type type_;
+
+ public enum Type {
+
+ Available, Error, Probe, Subscribe, Subscribed, Unavailable, Unsubscribe, Unsubscribed
+ };
+
+ public Presence() {
+ type_ = Type.Available;
+ }
+
+ public Presence(String status) {
+ type_ = Type.Available;
+ setStatus(status);
+ }
+
+ public Type getType() {
+ return type_;
+ }
+
+ public void setType(Type type) {
+ type_ = type;
+ }
+
+ public StatusShow.Type getShow() {
+ StatusShow show = getPayload (new StatusShow());
+ if (show != null) {
+ return show.getType();
+ }
+ return type_ == Type.Available ? StatusShow.Type.Online : StatusShow.Type.None;
+ }
+
+ public void setShow(StatusShow.Type show) {
+ updatePayload(new StatusShow(show));
+ }
+
+ public String getStatus() {
+ Status status = getPayload(new Status());
+ if (status != null) {
+ return status.getText();
+ }
+ return "";
+ }
+
+ public void setStatus(String status) {
+ updatePayload(new Status(status));
+ }
+
+ public int getPriority() {
+ Priority priority = getPayload(new Priority());
+ return (priority != null ? priority.getPriority() : 0);
+ }
+
+ public void setPriority(int priority) {
+ updatePayload(new Priority(priority));
+ }
+}
diff --git a/src/com/isode/stroke/elements/Priority.java b/src/com/isode/stroke/elements/Priority.java
new file mode 100644
index 0000000..850c67a
--- /dev/null
+++ b/src/com/isode/stroke/elements/Priority.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+/**
+ * Resource priority from presence stanzas.
+ */
+public class Priority extends Payload {
+ int priority_ = 0;
+
+ public Priority(){
+
+ }
+
+ public Priority (int priority) {
+ priority_ = priority;
+ }
+
+ public void setPriority(int priority) {
+ priority_ = priority;
+ }
+
+ public int getPriority() {
+ return priority_;
+ }
+}
diff --git a/src/com/isode/stroke/elements/ProtocolHeader.java b/src/com/isode/stroke/elements/ProtocolHeader.java
new file mode 100644
index 0000000..16d2925
--- /dev/null
+++ b/src/com/isode/stroke/elements/ProtocolHeader.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class ProtocolHeader {
+
+ public ProtocolHeader() {
+ version = "1.0";
+ }
+
+ public String getTo() {
+ return to;
+ }
+
+ public void setTo(String a) {
+ to = a;
+ }
+
+ public String getFrom() {
+ return from;
+ }
+
+ public void setFrom(String a) {
+ from = a;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String a) {
+ version = a;
+ }
+
+ public String getID() {
+ return id;
+ }
+
+ public void setID(String a) {
+ id = a;
+ }
+ private String to = "";
+ private String from = "";
+ private String id = "";
+ private String version = "";
+}
diff --git a/src/com/isode/stroke/elements/RawXMLPayload.java b/src/com/isode/stroke/elements/RawXMLPayload.java
new file mode 100644
index 0000000..8c261e9
--- /dev/null
+++ b/src/com/isode/stroke/elements/RawXMLPayload.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+/**
+ * Unparsed content.
+ */
+public class RawXMLPayload extends Payload {
+ private String rawXML_;
+
+ public void setRawXML(String data) {
+ rawXML_ = data;
+ }
+
+ public String getRawXML() {
+ return rawXML_;
+ }
+}
diff --git a/src/com/isode/stroke/elements/ResourceBind.java b/src/com/isode/stroke/elements/ResourceBind.java
new file mode 100644
index 0000000..863ef98
--- /dev/null
+++ b/src/com/isode/stroke/elements/ResourceBind.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron?on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+import com.isode.stroke.jid.JID;
+
+public class ResourceBind extends Payload {
+//FIXME: serializer and parser
+ public void setJID(JID jid) {
+ jid_ = jid;
+ }
+
+ public JID getJID() {
+ return jid_;
+ }
+
+ public void setResource(String resource) {
+ resource_ = resource;
+ }
+
+ public String getResource() {
+ return resource_;
+ }
+ private JID jid_ = new JID();
+ private String resource_ = "";
+}
diff --git a/src/com/isode/stroke/elements/RosterItemPayload.java b/src/com/isode/stroke/elements/RosterItemPayload.java
new file mode 100644
index 0000000..a80ecc6
--- /dev/null
+++ b/src/com/isode/stroke/elements/RosterItemPayload.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron?on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+import com.isode.stroke.jid.JID;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Roster Items
+ */
+public class RosterItemPayload {
+
+ public enum Subscription {
+
+ None, To, From, Both, Remove
+ };
+
+ public RosterItemPayload() {
+ subscription_ = Subscription.None;
+ ask_ = false;
+ }
+
+ public RosterItemPayload(JID jid, String name, Subscription subscription) {
+ jid_ = jid;
+ name_ = name;
+ subscription_ = subscription;
+ ask_ = false;
+ }
+
+ public void setJID(JID jid) {
+ jid_ = jid;
+ }
+
+ public JID getJID() {
+ return jid_;
+ }
+
+ public void setName(String name) {
+ name_ = name;
+ }
+
+ public String getName() {
+ return name_;
+ }
+
+ public void setSubscription(Subscription subscription) {
+ subscription_ = subscription;
+ }
+
+ public Subscription getSubscription() {
+ return subscription_;
+ }
+
+ public void addGroup(String group) {
+ groups_.add(group);
+ }
+
+ public void setGroups(Collection<String> groups) {
+ groups_ = new ArrayList<String>();
+ groups_.addAll(groups);
+ }
+
+ public Collection<String> getGroups() {
+ return groups_;
+ }
+
+ public void setSubscriptionRequested() {
+ ask_ = true;
+ }
+
+ public boolean getSubscriptionRequested() {
+ return ask_;
+ }
+ private JID jid_;
+ private String name_;
+ private Subscription subscription_;
+ private ArrayList<String> groups_ = new ArrayList<String>();
+ private boolean ask_;
+}
diff --git a/src/com/isode/stroke/elements/RosterPayload.java b/src/com/isode/stroke/elements/RosterPayload.java
new file mode 100644
index 0000000..6208216
--- /dev/null
+++ b/src/com/isode/stroke/elements/RosterPayload.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron?on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+import com.isode.stroke.jid.JID;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Roster.
+ */
+public class RosterPayload extends Payload {
+
+ public RosterPayload() {
+ }
+
+ public RosterItemPayload getItem(JID jid) {
+ for (RosterItemPayload item : items_) {
+ if (item.getJID().equals(jid)) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ public void addItem(RosterItemPayload item) {
+ items_.add(item);
+ }
+
+ public Collection<RosterItemPayload> getItems() {
+ return items_;
+ }
+
+ private final ArrayList<RosterItemPayload> items_ = new ArrayList<RosterItemPayload>();
+}
diff --git a/src/com/isode/stroke/elements/SearchPayload.java b/src/com/isode/stroke/elements/SearchPayload.java
new file mode 100644
index 0000000..b8e8a0d
--- /dev/null
+++ b/src/com/isode/stroke/elements/SearchPayload.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+import com.isode.stroke.jid.JID;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SearchPayload extends Payload {
+
+ public static class Item {
+
+ public String first;
+ public String last;
+ public String nick;
+ public String email;
+ public JID jid;
+ };
+
+ public SearchPayload() {
+ }
+
+ //Form::ref getForm() const { return form; } /* Not ported yet */
+ //void setForm(Form::ref f) { form = f; } /* Not ported yet */
+ /**
+ * @return Can be null
+ */
+ public String getInstructions() {
+ return instructions;
+ }
+
+ /**
+ * @return Can be null
+ */
+ public String getNick() {
+ return nick;
+ }
+
+ /**
+ * @return Can be null
+ */
+ public String getFirst() {
+ return first;
+ }
+
+ /**
+ * @return Can be null
+ */
+ public String getLast() {
+ return last;
+ }
+
+ /**
+ * @return Can be null
+ */
+ public String getEMail() {
+ return email;
+ }
+
+ /**
+ * @param v Null means no value.
+ */
+ public void setInstructions(String v) {
+ this.instructions = v;
+ }
+
+ /**
+ * @param v Null means no value.
+ */
+ public void setNick(String v) {
+ this.nick = v;
+ }
+
+ /**
+ * @param v Null means no value.
+ */
+ public void setFirst(String v) {
+ this.first = v;
+ }
+
+ /**
+ * @param v Null means no value.
+ */
+ public void setLast(String v) {
+ this.last = v;
+ }
+
+ /**
+ * @param v Null means no value.
+ */
+ public void setEMail(String v) {
+ this.email = v;
+ }
+
+ /**
+ *
+ * @return non-null
+ */
+ public List<Item> getItems() {
+ return items;
+ }
+
+ /**
+ *
+ * @param item Non-null.
+ */
+ public void addItem(Item item) {
+ items.add(item);
+ }
+
+ //private Form::ref form; /*Not ported yet*/
+ private String instructions;
+ private String nick;
+ private String first;
+ private String last;
+ private String email;
+ private ArrayList<Item> items = new ArrayList<Item>();
+}
diff --git a/src/com/isode/stroke/elements/Stanza.java b/src/com/isode/stroke/elements/Stanza.java
new file mode 100644
index 0000000..ac4f06f
--- /dev/null
+++ b/src/com/isode/stroke/elements/Stanza.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+import com.isode.stroke.jid.JID;
+import java.util.Vector;
+
+/**
+ * Basic XMPP stanza.
+ */
+public class Stanza implements Element {
+ private String id_;
+ private JID from_;
+ private JID to_;
+ private Vector<Payload> payloads_ = new Vector<Payload>();
+
+
+ public <T extends Payload> T getPayload(T type) {
+ for (Payload payload : payloads_) {
+ if (payload.getClass().isAssignableFrom(type.getClass())) {
+ return (T)payload;
+ }
+ }
+ return null;
+ }
+
+ public <T extends Payload> Vector<T> getPayloads(T type) {
+ Vector<T> results = new Vector<T>();
+ for (Payload payload : payloads_) {
+ if (payload.getClass().isAssignableFrom(type.getClass())) {
+ results.add((T)payload);
+ }
+ }
+ return results;
+ }
+
+ public Vector<Payload> getPayloads() {
+ return payloads_;
+ }
+
+ public void addPayload(Payload payload) {
+ payloads_.add(payload);
+ }
+
+ public void updatePayload(Payload payload) {
+ for (int i = 0; i < payloads_.size(); i++) {
+ if (payloads_.get(i).getClass() == payload.getClass()) {
+ payloads_.set(i, payload);
+ return;
+ }
+ }
+ payloads_.add(payload);
+ }
+
+ public JID getFrom() {
+ return from_;
+ }
+
+ public void setFrom(JID from) {
+ from_ = from;
+ }
+
+ public JID getTo() {
+ return to_;
+ }
+
+ public void setTo(JID to) {
+ to_ = to;
+ }
+
+ public String getID() {
+ return id_;
+ }
+
+ public void setID(String id) {
+ id_ = id;
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/StanzaAck.java b/src/com/isode/stroke/elements/StanzaAck.java
new file mode 100644
index 0000000..0589d39
--- /dev/null
+++ b/src/com/isode/stroke/elements/StanzaAck.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class StanzaAck implements Element {
+ //FIXME: parser/serialiser
+ public StanzaAck() {
+ }
+
+ public StanzaAck(long handledStanzasCount) {
+ valid = true;
+ this.handledStanzasCount = handledStanzasCount;
+ }
+
+ public long getHandledStanzasCount() {
+ return handledStanzasCount;
+ }
+
+ public void setHandledStanzasCount(long i) {
+ handledStanzasCount = i;
+ valid = true;
+ }
+
+ public boolean isValid() {
+ return valid;
+ }
+ private boolean valid = false;
+ private long handledStanzasCount = 0;
+}
diff --git a/src/com/isode/stroke/elements/StanzaAckRequest.java b/src/com/isode/stroke/elements/StanzaAckRequest.java
new file mode 100644
index 0000000..b02ac4e
--- /dev/null
+++ b/src/com/isode/stroke/elements/StanzaAckRequest.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+public class StanzaAckRequest implements Element {
+
+}
diff --git a/src/com/isode/stroke/elements/StartSession.java b/src/com/isode/stroke/elements/StartSession.java
new file mode 100644
index 0000000..b886535
--- /dev/null
+++ b/src/com/isode/stroke/elements/StartSession.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron?on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+public class StartSession extends Payload{
+
+}
diff --git a/src/com/isode/stroke/elements/StartTLSFailure.java b/src/com/isode/stroke/elements/StartTLSFailure.java
new file mode 100644
index 0000000..7b18dfe
--- /dev/null
+++ b/src/com/isode/stroke/elements/StartTLSFailure.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+public class StartTLSFailure implements Element {
+
+ //FIXME: parser/serialiser
+
+}
diff --git a/src/com/isode/stroke/elements/StartTLSRequest.java b/src/com/isode/stroke/elements/StartTLSRequest.java
new file mode 100644
index 0000000..23c3297
--- /dev/null
+++ b/src/com/isode/stroke/elements/StartTLSRequest.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron?on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+
+public class StartTLSRequest implements Element {
+//FIXME: parser/serialiser
+
+}
diff --git a/src/com/isode/stroke/elements/Status.java b/src/com/isode/stroke/elements/Status.java
new file mode 100644
index 0000000..f40814d
--- /dev/null
+++ b/src/com/isode/stroke/elements/Status.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class Status extends Payload {
+
+ String text_ = "";
+
+ public Status() {
+ }
+
+ public Status(String text) {
+ text_ = text;
+ }
+
+ public void setText(String text) {
+ text_ = text;
+ }
+
+ public String getText() {
+ return text_;
+ }
+}
diff --git a/src/com/isode/stroke/elements/StatusShow.java b/src/com/isode/stroke/elements/StatusShow.java
new file mode 100644
index 0000000..d7f8575
--- /dev/null
+++ b/src/com/isode/stroke/elements/StatusShow.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class StatusShow extends Payload {
+
+ private Type type_;
+
+ public enum Type {
+
+ Online, Away, FFC, XA, DND, None
+ };
+
+ public StatusShow() {
+ type_ = Type.Online;
+ }
+
+ public StatusShow(Type type) {
+ type_ = type;
+ }
+
+ void setType(Type type) {
+ type_ = type;
+ }
+
+ Type getType() {
+ return type_;
+ }
+
+ static String typeToFriendlyName(Type type) {
+ switch (type) {
+ case Online:
+ return "Available";
+ case FFC:
+ return "Available";
+ case Away:
+ return "Away";
+ case XA:
+ return "Away";
+ case DND:
+ return "Busy";
+ case None:
+ return "Offline";
+ }
+ return "Unknown";
+ }
+}
diff --git a/src/com/isode/stroke/elements/StreamError.java b/src/com/isode/stroke/elements/StreamError.java
new file mode 100644
index 0000000..9335ddb
--- /dev/null
+++ b/src/com/isode/stroke/elements/StreamError.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class StreamError implements Element {
+
+ public enum Type {
+
+ BadFormat,
+ BadNamespacePrefix,
+ Conflict,
+ ConnectionTimeout,
+ HostGone,
+ HostUnknown,
+ ImproperAddressing,
+ InternalServerError,
+ InvalidFrom,
+ InvalidID,
+ InvalidNamespace,
+ InvalidXML,
+ NotAuthorized,
+ NotWellFormed,
+ PolicyViolation,
+ RemoteConnectionFailed,
+ Reset,
+ ResourceConstraint,
+ RestrictedXML,
+ SeeOtherHost,
+ SystemShutdown,
+ UndefinedCondition,
+ UnsupportedEncoding,
+ UnsupportedStanzaType,
+ UnsupportedVersion,
+ };
+
+ public StreamError() {
+ this(Type.UndefinedCondition);
+ }
+
+ public StreamError(Type type) {
+ this(type, "");
+ }
+
+ public StreamError(Type type, String text) {
+ if (type == null) {
+ throw new IllegalStateException();
+ }
+ type_ = type;
+ text_ = text;
+ }
+
+ public Type getType() {
+ return type_;
+ }
+
+ public void setType(Type type) {
+ type_ = type;
+ }
+
+ public void setText(String text) {
+ text_ = text;
+ }
+
+ public String getText() {
+ return text_;
+ }
+ private Type type_;
+ private String text_;
+};
diff --git a/src/com/isode/stroke/elements/StreamFeatures.java b/src/com/isode/stroke/elements/StreamFeatures.java
new file mode 100644
index 0000000..c784077
--- /dev/null
+++ b/src/com/isode/stroke/elements/StreamFeatures.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron?on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+//FIXME: parser/serialiser
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class StreamFeatures implements Element {
+
+ public StreamFeatures() {
+ hasStartTLS_ = false;
+ hasResourceBind_ = false;
+ hasSession_ = false;
+ hasStreamManagement_ = false;
+ }
+
+ public void setHasStartTLS() {
+ hasStartTLS_ = true;
+ }
+
+ public boolean hasStartTLS() {
+ return hasStartTLS_;
+ }
+
+ public void setHasSession() {
+ hasSession_ = true;
+ }
+
+ public boolean hasSession() {
+ return hasSession_;
+ }
+
+ public void setHasResourceBind() {
+ hasResourceBind_ = true;
+ }
+
+ public boolean hasResourceBind() {
+ return hasResourceBind_;
+ }
+
+ public Collection<String> getCompressionMethods() {
+ return compressionMethods_;
+ }
+
+ public void addCompressionMethod(String mechanism) {
+ compressionMethods_.add(mechanism);
+ }
+
+ public boolean hasCompressionMethod(String mechanism) {
+ return compressionMethods_.contains(mechanism);
+ }
+
+ public Collection<String> getAuthenticationMechanisms() {
+ return authenticationMechanisms_;
+ }
+
+ public void addAuthenticationMechanism(String mechanism) {
+ authenticationMechanisms_.add(mechanism);
+ }
+
+ public boolean hasAuthenticationMechanism(String mechanism) {
+ return authenticationMechanisms_.contains(mechanism);
+ }
+
+ public boolean hasAuthenticationMechanisms() {
+ return !authenticationMechanisms_.isEmpty();
+ }
+
+ public boolean hasStreamManagement() {
+ return hasStreamManagement_;
+ }
+
+ public void setHasStreamManagement() {
+ hasStreamManagement_ = true;
+ }
+
+ public boolean hasRosterVersioning() {
+ return hasRosterVersioning_;
+ }
+
+ public void setHasRosterVersioning() {
+ hasRosterVersioning_ = true;
+ }
+
+ private boolean hasStartTLS_;
+ private ArrayList<String> compressionMethods_ = new ArrayList<String>();
+ private ArrayList<String> authenticationMechanisms_ = new ArrayList<String>();
+ private boolean hasResourceBind_;
+ private boolean hasSession_;
+ private boolean hasStreamManagement_;
+ private boolean hasRosterVersioning_;
+}
diff --git a/src/com/isode/stroke/elements/StreamManagementEnabled.java b/src/com/isode/stroke/elements/StreamManagementEnabled.java
new file mode 100644
index 0000000..db2aa77
--- /dev/null
+++ b/src/com/isode/stroke/elements/StreamManagementEnabled.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class StreamManagementEnabled implements Element {
+
+ public void setResumeSupported() {
+ resumeSupported = true;
+ }
+
+ public boolean getResumeSupported() {
+ return resumeSupported;
+ }
+
+ public void setResumeID(String id) {
+ resumeID = id;
+ }
+
+ public String getResumeID() {
+ return resumeID;
+ }
+ private boolean resumeSupported;
+ private String resumeID;
+}
diff --git a/src/com/isode/stroke/elements/StreamManagementFailed.java b/src/com/isode/stroke/elements/StreamManagementFailed.java
new file mode 100644
index 0000000..7be357b
--- /dev/null
+++ b/src/com/isode/stroke/elements/StreamManagementFailed.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+public class StreamManagementFailed implements Element {
+
+}
diff --git a/src/com/isode/stroke/elements/StreamResume.java b/src/com/isode/stroke/elements/StreamResume.java
new file mode 100644
index 0000000..40c6326
--- /dev/null
+++ b/src/com/isode/stroke/elements/StreamResume.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2011, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class StreamResume implements Element {
+
+ public void setResumeID(String id) {
+ resumeID = id;
+ }
+
+ public String getResumeID() {
+ return resumeID;
+ }
+
+ public Long getHandledStanzasCount() {
+ return handledStanzasCount;
+ }
+
+ public void setHandledStanzasCount(long i) {
+ handledStanzasCount = i;
+ }
+ private String resumeID;
+ private Long handledStanzasCount;
+}
diff --git a/src/com/isode/stroke/elements/StreamResumed.java b/src/com/isode/stroke/elements/StreamResumed.java
new file mode 100644
index 0000000..23f0e54
--- /dev/null
+++ b/src/com/isode/stroke/elements/StreamResumed.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2011, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.elements;
+
+public class StreamResumed implements Element {
+
+ public void setResumeID(String id) {
+ resumeID = id;
+ }
+
+ public String getResumeID() {
+ return resumeID;
+ }
+
+ public Long getHandledStanzasCount() {
+ return handledStanzasCount;
+ }
+
+ public void setHandledStanzasCount(long i) {
+ handledStanzasCount = i;
+ }
+ private String resumeID;
+ private Long handledStanzasCount;
+}
diff --git a/src/com/isode/stroke/elements/StreamType.java b/src/com/isode/stroke/elements/StreamType.java
new file mode 100644
index 0000000..4ec7d9f
--- /dev/null
+++ b/src/com/isode/stroke/elements/StreamType.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+
+public enum StreamType {
+ ClientStreamType,
+ ServerStreamType,
+ ComponentStreamType
+}
diff --git a/src/com/isode/stroke/elements/Subject.java b/src/com/isode/stroke/elements/Subject.java
new file mode 100644
index 0000000..0881a26
--- /dev/null
+++ b/src/com/isode/stroke/elements/Subject.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+public class Subject extends Payload {
+ String text_ = "";
+
+ public Subject() {}
+
+ public Subject(String text) {
+ setText(text);
+ }
+
+ public void setText(String text) {
+ text_ = text;
+ }
+
+ public String getText() {
+ return text_;
+ }
+}
diff --git a/src/com/isode/stroke/elements/TLSProceed.java b/src/com/isode/stroke/elements/TLSProceed.java
new file mode 100644
index 0000000..d330364
--- /dev/null
+++ b/src/com/isode/stroke/elements/TLSProceed.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+
+public class TLSProceed implements Element {
+//FIXME: parser/serialiser
+}
diff --git a/src/com/isode/stroke/elements/UnknownElement.java b/src/com/isode/stroke/elements/UnknownElement.java
new file mode 100644
index 0000000..c9d1f4f
--- /dev/null
+++ b/src/com/isode/stroke/elements/UnknownElement.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+public class UnknownElement implements Element {
+
+}
diff --git a/src/com/isode/stroke/elements/Version.java b/src/com/isode/stroke/elements/Version.java
new file mode 100644
index 0000000..80c1af3
--- /dev/null
+++ b/src/com/isode/stroke/elements/Version.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+
+public class Version extends Payload {
+ private String name_;
+ private String version_;
+ private String os_;
+
+ public Version(final String name, final String version, final String os) {
+ name_ = name;
+ version_ = version;
+ os_ = os;
+ }
+
+ public Version() {
+
+ }
+
+ public String getName() {
+ return name_;
+ }
+
+ public String getVersion() {
+ return version_;
+ }
+
+ public String getOS() {
+ return os_;
+ }
+
+ public void setName(final String name) {
+ name_ = name;
+ }
+
+ public void setVersion(final String version) {
+ version_ = version;
+ }
+
+ public void setOS(final String os) {
+ os_ = os;
+ }
+}
diff --git a/src/com/isode/stroke/eventloop/Event.java b/src/com/isode/stroke/eventloop/Event.java
new file mode 100644
index 0000000..b3c8c00
--- /dev/null
+++ b/src/com/isode/stroke/eventloop/Event.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.eventloop;
+
+public class Event {
+
+ public interface Callback {
+
+ void run();
+ }
+
+ Event(EventOwner owner, Callback callback, int id) {
+ this.owner = owner;
+ this.callback = callback;
+ this.id = id;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Event) {
+ return id == ((Event) other).id;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 23 * hash + this.id;
+ return hash;
+ }
+ final int id;
+ final EventOwner owner;
+ public final Callback callback;
+}
diff --git a/src/com/isode/stroke/eventloop/EventLoop.java b/src/com/isode/stroke/eventloop/EventLoop.java
new file mode 100644
index 0000000..9399d9a
--- /dev/null
+++ b/src/com/isode/stroke/eventloop/EventLoop.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.eventloop;
+
+import java.util.ArrayList;
+import java.util.Vector;
+
+public abstract class EventLoop {
+
+ public EventLoop() {
+ }
+
+ public void postEvent(Event.Callback callback) {
+ postEvent(callback, null);
+ }
+
+ public void postEvent(Event.Callback callback, EventOwner owner) {
+ Event event;
+ synchronized (eventsMutex_) {
+ event = new Event(owner, callback, nextEventID_);
+ nextEventID_++;
+ events_.add(event);
+ }
+ post(event);
+ }
+
+ public void removeEventsFromOwner(EventOwner owner) {
+ synchronized (eventsMutex_) {
+ ArrayList<Event> matches = new ArrayList<Event>();
+ for (Event event : events_) {
+ if (event.owner == owner) {
+ matches.add(event);
+ }
+ }
+ events_.removeAll(matches);
+ }
+ }
+
+ /**
+ * Reimplement this to call handleEvent(event) from the thread in which
+ * the event loop is residing.
+ */
+ protected abstract void post(Event event);
+
+ protected void handleEvent(Event event) {
+ if (handlingEvents_) {
+ // We're being called recursively. Push in the list of events to
+ // handle in the parent handleEvent()
+ eventsToHandle_.add(event);
+ return;
+ }
+
+ boolean doCallback = false;
+ synchronized (eventsMutex_) {
+ doCallback = events_.contains(event);
+ if (doCallback) {
+ events_.remove(event);
+ }
+ }
+ if (doCallback) {
+ handlingEvents_ = true;
+ event.callback.run();
+ // Process events that were passed to handleEvent during the callback
+ // (i.e. through recursive calls of handleEvent)
+ while (!eventsToHandle_.isEmpty()) {
+ Event nextEvent = eventsToHandle_.firstElement();
+ eventsToHandle_.remove(0);
+ nextEvent.callback.run();
+ }
+ handlingEvents_ = false;
+ }
+ }
+ // struct HasOwner {
+ // HasOwner(boost::shared_ptr<EventOwner> owner) : owner(owner) {}
+ // bool operator()(const Event& event) { return event.owner == owner; }
+ // boost::shared_ptr<EventOwner> owner;
+ // };
+ private final Object eventsMutex_ = new Object();
+ private int nextEventID_ = 0;
+ private Vector<Event> events_ = new Vector<Event>();
+ boolean handlingEvents_ = false;
+ private Vector<Event> eventsToHandle_ = new Vector<Event>();
+}
diff --git a/src/com/isode/stroke/eventloop/EventOwner.java b/src/com/isode/stroke/eventloop/EventOwner.java
new file mode 100644
index 0000000..305ab1a
--- /dev/null
+++ b/src/com/isode/stroke/eventloop/EventOwner.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.eventloop;
+
+public interface EventOwner {
+
+}
diff --git a/src/com/isode/stroke/eventloop/SimpleEventLoop.java b/src/com/isode/stroke/eventloop/SimpleEventLoop.java
new file mode 100644
index 0000000..003fc98
--- /dev/null
+++ b/src/com/isode/stroke/eventloop/SimpleEventLoop.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.eventloop;
+
+/**
+ * Don't use this, it simply runs the callback in the same thread.
+ * It is useful for unit testing, but will break GUIs.
+ */
+public class SimpleEventLoop extends EventLoop {
+ @Override
+ protected void post(Event event) {
+ event.callback.run();
+ }
+}
diff --git a/src/com/isode/stroke/examples/gui/StrokeGUI.form b/src/com/isode/stroke/examples/gui/StrokeGUI.form
new file mode 100644
index 0000000..502e00b
--- /dev/null
+++ b/src/com/isode/stroke/examples/gui/StrokeGUI.form
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.3" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
+ <Properties>
+ <Property name="defaultCloseOperation" type="int" value="3"/>
+ </Properties>
+ <SyntheticProperties>
+ <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
+ </SyntheticProperties>
+ <AuxValues>
+ <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="2"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,4,60,0,0,3,46"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ <SubComponents>
+ <Container class="javax.swing.JPanel" name="jPanel1">
+ <Properties>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
+ <TitledBorder title="Login Stuff"/>
+ </Border>
+ </Property>
+ </Properties>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="jLabel1">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="JID"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JTextField" name="loginJID_">
+ <Properties>
+ <Property name="bounds" type="java.awt.Rectangle" editor="org.netbeans.beaninfo.editors.RectangleEditor">
+ <Rectangle value="[0, 0, 150, 0]"/>
+ </Property>
+ <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[150, 28]"/>
+ </Property>
+ <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[250, 28]"/>
+ </Property>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JLabel" name="jLabel2">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Password"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JTextField" name="loginPassword_">
+ <Properties>
+ <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[100, 28]"/>
+ </Property>
+ <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[150, 28]"/>
+ </Property>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JButton" name="loginButton_">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Login"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="loginButton_ActionPerformed"/>
+ </Events>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="jPanel2">
+ <Properties>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
+ <TitledBorder title="Send Stuff"/>
+ </Border>
+ </Property>
+ </Properties>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JTextField" name="sendTo_">
+ <Properties>
+ <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[150, 28]"/>
+ </Property>
+ <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[250, 28]"/>
+ </Property>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JLabel" name="jLabel3">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="To"/>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JButton" name="sendButton_">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="Send"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="sendButton_ActionPerformed"/>
+ </Events>
+ </Component>
+ <Container class="javax.swing.JScrollPane" name="jScrollPane1">
+ <AuxValues>
+ <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JTextArea" name="sendText_">
+ <Properties>
+ <Property name="columns" type="int" value="20"/>
+ <Property name="rows" type="int" value="5"/>
+ <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[150, 16]"/>
+ </Property>
+ <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[150, 150]"/>
+ </Property>
+ <Property name="size" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[450, 150]"/>
+ </Property>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="jPanel3">
+ <Properties>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
+ <TitledBorder title="Receive Stuff"/>
+ </Border>
+ </Property>
+ </Properties>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ <SubComponents>
+ <Container class="javax.swing.JScrollPane" name="jScrollPane2">
+ <AuxValues>
+ <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JTextArea" name="receiveText_">
+ <Properties>
+ <Property name="columns" type="int" value="20"/>
+ <Property name="editable" type="boolean" value="false"/>
+ <Property name="rows" type="int" value="5"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="jPanel4">
+ <Properties>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
+ <TitledBorder title="XML"/>
+ </Border>
+ </Property>
+ </Properties>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+ <SubComponents>
+ <Container class="javax.swing.JScrollPane" name="jScrollPane3">
+ <AuxValues>
+ <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JTextArea" name="xmlText_">
+ <Properties>
+ <Property name="columns" type="int" value="20"/>
+ <Property name="editable" type="boolean" value="false"/>
+ <Property name="rows" type="int" value="5"/>
+ <Property name="size" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[500, 300]"/>
+ </Property>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/src/com/isode/stroke/examples/gui/StrokeGUI.java b/src/com/isode/stroke/examples/gui/StrokeGUI.java
new file mode 100644
index 0000000..09f7936
--- /dev/null
+++ b/src/com/isode/stroke/examples/gui/StrokeGUI.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+/*
+ * MainFrame.java
+ *
+ * Created on Jul 7, 2010, 10:03:01 AM
+ */
+
+package com.isode.stroke.examples.gui;
+
+import com.isode.stroke.client.ClientError;
+import com.isode.stroke.client.ClientOptions;
+import com.isode.stroke.client.CoreClient;
+import com.isode.stroke.elements.Message;
+import com.isode.stroke.eventloop.Event;
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.network.JavaNetworkFactories;
+import com.isode.stroke.signals.Slot1;
+import java.awt.EventQueue;
+
+public class StrokeGUI extends javax.swing.JFrame {
+
+ private CoreClient client_;
+
+ /** Creates new form MainFrame */
+ public StrokeGUI() {
+ initComponents();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ jPanel1 = new javax.swing.JPanel();
+ jLabel1 = new javax.swing.JLabel();
+ loginJID_ = new javax.swing.JTextField();
+ jLabel2 = new javax.swing.JLabel();
+ loginPassword_ = new javax.swing.JTextField();
+ loginButton_ = new javax.swing.JButton();
+ jPanel2 = new javax.swing.JPanel();
+ sendTo_ = new javax.swing.JTextField();
+ jLabel3 = new javax.swing.JLabel();
+ sendButton_ = new javax.swing.JButton();
+ jScrollPane1 = new javax.swing.JScrollPane();
+ sendText_ = new javax.swing.JTextArea();
+ jPanel3 = new javax.swing.JPanel();
+ jScrollPane2 = new javax.swing.JScrollPane();
+ receiveText_ = new javax.swing.JTextArea();
+ jPanel4 = new javax.swing.JPanel();
+ jScrollPane3 = new javax.swing.JScrollPane();
+ xmlText_ = new javax.swing.JTextArea();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+ getContentPane().setLayout(new java.awt.FlowLayout());
+
+ jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Login Stuff"));
+
+ jLabel1.setText("JID");
+ jPanel1.add(jLabel1);
+
+ loginJID_.setBounds(new java.awt.Rectangle(0, 0, 150, 0));
+ loginJID_.setMinimumSize(new java.awt.Dimension(150, 28));
+ loginJID_.setPreferredSize(new java.awt.Dimension(250, 28));
+ jPanel1.add(loginJID_);
+
+ jLabel2.setText("Password");
+ jPanel1.add(jLabel2);
+
+ loginPassword_.setMinimumSize(new java.awt.Dimension(100, 28));
+ loginPassword_.setPreferredSize(new java.awt.Dimension(150, 28));
+ jPanel1.add(loginPassword_);
+
+ loginButton_.setText("Login");
+ loginButton_.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ loginButton_ActionPerformed(evt);
+ }
+ });
+ jPanel1.add(loginButton_);
+
+ getContentPane().add(jPanel1);
+
+ jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder("Send Stuff"));
+
+ sendTo_.setMinimumSize(new java.awt.Dimension(150, 28));
+ sendTo_.setPreferredSize(new java.awt.Dimension(250, 28));
+ jPanel2.add(sendTo_);
+
+ jLabel3.setText("To");
+ jPanel2.add(jLabel3);
+
+ sendButton_.setText("Send");
+ sendButton_.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ sendButton_ActionPerformed(evt);
+ }
+ });
+ jPanel2.add(sendButton_);
+
+ sendText_.setColumns(20);
+ sendText_.setRows(5);
+ sendText_.setMinimumSize(new java.awt.Dimension(150, 16));
+ sendText_.setPreferredSize(new java.awt.Dimension(150, 150));
+ sendText_.setSize(new java.awt.Dimension(450, 150));
+ jScrollPane1.setViewportView(sendText_);
+
+ jPanel2.add(jScrollPane1);
+
+ getContentPane().add(jPanel2);
+
+ jPanel3.setBorder(javax.swing.BorderFactory.createTitledBorder("Receive Stuff"));
+
+ receiveText_.setColumns(20);
+ receiveText_.setEditable(false);
+ receiveText_.setRows(5);
+ jScrollPane2.setViewportView(receiveText_);
+
+ jPanel3.add(jScrollPane2);
+
+ getContentPane().add(jPanel3);
+
+ jPanel4.setBorder(javax.swing.BorderFactory.createTitledBorder("XML"));
+
+ xmlText_.setColumns(20);
+ xmlText_.setEditable(false);
+ xmlText_.setRows(5);
+ xmlText_.setSize(new java.awt.Dimension(500, 300));
+ jScrollPane3.setViewportView(xmlText_);
+
+ jPanel4.add(jScrollPane3);
+
+ getContentPane().add(jPanel4);
+ jPanel4.getAccessibleContext().setAccessibleName("XML");
+
+ pack();
+ }// </editor-fold>//GEN-END:initComponents
+
+ private void loginButton_ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_loginButton_ActionPerformed
+ System.out.println("Client created from JID " + loginJID_.getText());
+ EventLoop eventLoop = new EventLoop() {
+ @Override
+ protected void post(final Event event) {
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ event.callback.run();
+ }
+ });
+ }
+
+ };
+ client_ = new CoreClient(eventLoop, JID.fromString(loginJID_.getText()), loginPassword_.getText(), new JavaNetworkFactories(eventLoop));
+ System.out.println("Connecting");
+ try {
+ client_.connect(new ClientOptions());
+ } catch (Exception e) {
+ //Something bad happened
+ System.out.println("Exception!");
+ }
+ System.out.println("Connected");
+ final StrokeGUI thisObject = this;
+ client_.onMessageReceived.connect(new Slot1<Message>() {
+
+ public void call(Message p1) {
+ thisObject.handleMessageReceived(p1);
+ }
+ });
+ client_.onError.connect(new Slot1<ClientError>() {
+
+ public void call(ClientError p1) {
+ thisObject.handleClientError(p1);
+ }
+ });
+ client_.onDataRead.connect(new Slot1<String>() {
+
+ public void call(String p1) {
+ xmlText_.append(">>> " + p1 + "\n");
+ }
+ });
+ client_.onDataWritten.connect(new Slot1<String>() {
+
+ public void call(String p1) {
+ xmlText_.append("<<< " + p1 + "\n");
+ }
+ });
+ }//GEN-LAST:event_loginButton_ActionPerformed
+
+ private void sendButton_ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sendButton_ActionPerformed
+ Message message = new Message();
+ message.setTo(JID.fromString(sendTo_.getText()));
+ message.setBody(sendText_.getText());
+ System.out.println("Message body is " + message.getBody());
+ client_.sendMessage(message);
+
+ }//GEN-LAST:event_sendButton_ActionPerformed
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String args[]) {
+ java.awt.EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ new StrokeGUI().setVisible(true);
+ }
+ });
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JLabel jLabel2;
+ private javax.swing.JLabel jLabel3;
+ private javax.swing.JPanel jPanel1;
+ private javax.swing.JPanel jPanel2;
+ private javax.swing.JPanel jPanel3;
+ private javax.swing.JPanel jPanel4;
+ private javax.swing.JScrollPane jScrollPane1;
+ private javax.swing.JScrollPane jScrollPane2;
+ private javax.swing.JScrollPane jScrollPane3;
+ private javax.swing.JButton loginButton_;
+ private javax.swing.JTextField loginJID_;
+ private javax.swing.JTextField loginPassword_;
+ private javax.swing.JTextArea receiveText_;
+ private javax.swing.JButton sendButton_;
+ private javax.swing.JTextArea sendText_;
+ private javax.swing.JTextField sendTo_;
+ private javax.swing.JTextArea xmlText_;
+ // End of variables declaration//GEN-END:variables
+
+ private void handleMessageReceived(Message message) {
+ String from = message.getFrom().toString();
+ String body = message.getBody();
+ receiveText_.append("<" + from + "> " + body + "\n");
+ }
+
+ private void handleClientError(ClientError error) {
+ receiveText_.append("Error connecting to server\n");
+ }
+
+}
diff --git a/src/com/isode/stroke/idn/IDNA.java b/src/com/isode/stroke/idn/IDNA.java
new file mode 100644
index 0000000..e180cc5
--- /dev/null
+++ b/src/com/isode/stroke/idn/IDNA.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.idn;
+
+import java.net.IDN;
+
+public class IDNA {
+ public static String getEncoded(String s) {
+ return IDN.toASCII(s);
+ }
+}
diff --git a/src/com/isode/stroke/jid/JID.java b/src/com/isode/stroke/jid/JID.java
new file mode 100644
index 0000000..70c2ccc
--- /dev/null
+++ b/src/com/isode/stroke/jid/JID.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.jid;
+
+/**
+ * JID helper.
+ *
+ * This represents the JID used in XMPP
+ * (RFC6120 - http://tools.ietf.org/html/rfc6120 particularly section 1.4),
+ * further defined in XMPP Address Format (http://tools.ietf.org/html/rfc6122 ).
+ * For a description of format, see the RFC or page 14 of
+ * XMPP: The Definitive Guide (Saint-Andre et al.)
+ *
+ * Particularly - a Bare JID is a JID without a resource part.
+ *
+ * Note that invalid JIDs shouldn't have any calls made to them beyond isValid().
+ *
+ */
+public class JID {
+ public enum CompareType {
+ WithResource, WithoutResource
+ };
+
+ private final String node_;
+ private final String domain_;
+ private final String resource_;
+
+ /**
+ * Create an invalid JID.
+ */
+ public JID() {
+ this("", "", null);
+ }
+
+ /**
+ * Create a JID using the JID(String) constructor.
+ * @param jid String formatted JID.
+ */
+ public static JID fromString(final String jid) {
+ return new JID(jid);
+ }
+
+ /**
+ * Create a JID from its String representation.
+ *
+ * e.g.
+ * wonderland.lit
+ * wonderland.lit/rabbithole
+ * alice@wonderland.lit
+ * alice@wonderland.lit/TeaParty
+ *
+ * @param jid String representation. Invalid JID if null or invalid.
+ */
+ public JID(final String jid) {
+ //FIXME: This doesn't nameprep!
+ if (jid == null || jid.startsWith("@")) {
+ node_ = "";
+ domain_ = "";
+ resource_ = "";
+ return;
+ }
+
+ String bare;
+ String resource;
+ String[] parts = jid.split("/", 2);
+ if (parts.length > 1) {
+ bare = parts[0];
+ resource = parts[1];
+ } else {
+ resource = null;
+ bare = jid;
+ }
+ String[] nodeAndDomain = bare.split("@", 2);
+ if (nodeAndDomain.length == 1) {
+ node_ = "";
+ domain_ = nodeAndDomain[0];
+ resource_ = resource;
+ } else {
+ node_ = nodeAndDomain[0];
+ domain_ = nodeAndDomain[1];
+ resource_ = resource;
+ }
+
+ }
+
+ /**
+ * Create a bare JID from the node and domain parts.
+ *
+ * JID("node@domain") == JID("node", "domain")
+ * Use a different constructor instead of passing nulls.
+ *
+ * @param node JID node part.
+ * @param domain JID domain part.
+ */
+ public JID(final String node, final String domain) {
+ this(node, domain, null);
+ }
+
+ /**
+ * Create a bare JID from the node, domain and resource parts.
+ *
+ * JID("node@domain/resource") == JID("node", "domain", "resource")
+ * Use a different constructor instead of passing nulls.
+ *
+ * @param node JID node part.
+ * @param domain JID domain part.
+ * @param resource JID resource part.
+ */
+ public JID(final String node, final String domain, final String resource) {
+ //FIXME: This doesn't nameprep!
+ node_ = node;
+ domain_ = domain;
+ resource_ = resource;
+
+ }
+
+ /**
+ * @return Is a correctly-formatted JID.
+ */
+ public boolean isValid() {
+ return (domain_.length()!=0);
+ }
+
+ /**
+ * e.g. JID("node@domain").getNode() == "node"
+ * @return null for nodeless JIDs.
+ */
+ public String getNode() {
+ return node_;
+ }
+
+ /**
+ * e.g. JID("node@domain").getDomain() == "domain"
+ * @return only null for invalid JIDs.
+ */
+ public String getDomain() {
+ return domain_;
+ }
+
+ /**
+ * e.g. JID("node@domain/resource").getResource() == "resource"
+ * @return null for bare JIDs.
+ */
+ public String getResource() {
+ return resource_ != null ? resource_ : "";
+ }
+
+ /**
+ * Is a bare JID, i.e. has no resource part.
+ */
+ public boolean isBare() {
+ return resource_ == null;
+ }
+
+ /**
+ * Get the JID without a resource.
+ * @return non-null. Invalid if the original is invalid.
+ */
+ public JID toBare() {
+ return new JID(getNode(), getDomain());
+ }
+
+ @Override
+ public String toString() {
+ String string = new String();
+ if (node_.length()!=0) {
+ string += node_ + "@";
+ }
+ string += domain_;
+ if (!isBare()) {
+ string += "/" + resource_;
+ }
+ return string;
+ }
+
+ @Override
+ public boolean equals(final Object otherObject) {
+ if (otherObject == null || getClass() != otherObject.getClass()) {
+ return false;
+ }
+ if (otherObject == this) {
+ return true;
+ }
+ JID other = (JID)otherObject;
+ String node1 = getNode();
+ String node2 = other.getNode();
+ String domain1 = getDomain();
+ String domain2 = other.getDomain();
+ String resource1 = getResource();
+ String resource2 = other.getResource();
+ boolean nodeMatch = (node1 == null && node2 == null) || (node1 != null && node1.equals(node2));
+ boolean domainMatch = (domain1 == null && domain2 == null) || (domain1 != null && domain1.equals(domain2));
+ boolean resourceMatch = (resource1 == null && resource2 == null) || (resource1 != null && resource1.equals(resource2));
+ return nodeMatch && domainMatch && resourceMatch;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 73 * hash + (this.node_ != null ? this.node_.hashCode() : 0);
+ hash = 73 * hash + (this.domain_ != null ? this.domain_.hashCode() : 0);
+ hash = 73 * hash + (this.resource_ != null ? this.resource_.hashCode() : 0);
+ return hash;
+ }
+}
diff --git a/src/com/isode/stroke/network/Connection.java b/src/com/isode/stroke/network/Connection.java
new file mode 100644
index 0000000..ff4a056
--- /dev/null
+++ b/src/com/isode/stroke/network/Connection.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.signals.Signal1;
+
+public abstract class Connection {
+
+ public enum Error {
+
+ ReadError,
+ WriteError
+ };
+
+ public Connection() {
+ }
+
+ public abstract void listen();
+
+ public abstract void connect(HostAddressPort address);
+
+ public abstract void disconnect();
+
+ public abstract void write(ByteArray data);
+
+ public abstract HostAddressPort getLocalAddress();
+ public final Signal1<Boolean /*error*/> onConnectFinished = new Signal1<Boolean>();
+ public final Signal1<Error> onDisconnected = new Signal1<Error>();
+ public final Signal1<ByteArray> onDataRead = new Signal1<ByteArray>();
+ public final Signal onDataWritten = new Signal();
+}
diff --git a/src/com/isode/stroke/network/ConnectionFactory.java b/src/com/isode/stroke/network/ConnectionFactory.java
new file mode 100644
index 0000000..6764d24
--- /dev/null
+++ b/src/com/isode/stroke/network/ConnectionFactory.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+public interface ConnectionFactory {
+ Connection createConnection();
+}
diff --git a/src/com/isode/stroke/network/Connector.java b/src/com/isode/stroke/network/Connector.java
new file mode 100644
index 0000000..560bf7b
--- /dev/null
+++ b/src/com/isode/stroke/network/Connector.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.network.DomainNameServiceQuery.Result;
+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.signals.Slot2;
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class Connector {
+
+ public static Connector create(String hostname, DomainNameResolver resolver, ConnectionFactory connectionFactory, TimerFactory timerFactory) {
+ return new Connector(hostname, resolver, connectionFactory, timerFactory);
+ }
+
+ public void setTimeoutMilliseconds(int milliseconds) {
+ timeoutMilliseconds = milliseconds;
+ }
+
+ public void start() {
+ assert currentConnection == null;
+ assert serviceQuery == null;
+ assert timer == null;
+ queriedAllServices = false;
+ serviceQuery = resolver.createServiceQuery("_xmpp-client._tcp." + hostname);
+ serviceQuery.onResult.connect(new Slot1<Collection<DomainNameServiceQuery.Result>>() {
+ public void call(Collection<Result> p1) {
+ handleServiceQueryResult(p1);
+ }
+ });
+ if (timeoutMilliseconds > 0) {
+ timer = timerFactory.createTimer(timeoutMilliseconds);
+ timer.onTick.connect(new Slot() {
+ public void call() {
+ handleTimeout();
+ }
+ });
+ timer.start();
+ }
+ serviceQuery.run();
+ }
+
+ public void stop() {
+ finish(null);
+ }
+
+ public final Signal1<Connection> onConnectFinished = new Signal1<Connection>();
+
+ private Connector(String hostname, DomainNameResolver resolver, ConnectionFactory connectionFactory, TimerFactory timerFactory) {
+ this.hostname = hostname;
+ this.resolver = resolver;
+ this.connectionFactory = connectionFactory;
+ this.timerFactory = timerFactory;
+ }
+
+ private void handleServiceQueryResult(Collection<Result> result) {
+ serviceQueryResults = new ArrayList<Result>();
+ serviceQueryResults.addAll(result);
+ serviceQuery = null;
+ tryNextServiceOrFallback();
+ }
+
+ private void handleAddressQueryResult(Collection<HostAddress> addresses, DomainNameResolveError error) {
+ //std::cout << "Connector::handleAddressQueryResult(): Start" << std::endl;
+ addressQuery = null;
+ if (error != null || addresses.isEmpty()) {
+ if (!serviceQueryResults.isEmpty()) {
+ serviceQueryResults.remove(0);
+ }
+ tryNextServiceOrFallback();
+ }
+ else {
+ addressQueryResults = new ArrayList<HostAddress>();
+ addressQueryResults.addAll(addresses);
+ tryNextAddress();
+ }
+ }
+
+ private void queryAddress(String hostname) {
+ assert addressQuery == null;
+ addressQuery = resolver.createAddressQuery(hostname);
+ addressQuery.onResult.connect(new Slot2<Collection<HostAddress>, DomainNameResolveError>() {
+ public void call(Collection<HostAddress> p1, DomainNameResolveError p2) {
+ handleAddressQueryResult(p1, p2);
+ }
+ });
+ addressQuery.run();
+ }
+
+ private void tryNextServiceOrFallback() {
+ if (queriedAllServices) {
+ //std::cout << "Connector::tryNextServiceOrCallback(): Queried all hosts. Error." << std::endl;
+ finish(null);
+ }
+ else if (serviceQueryResults.isEmpty()) {
+ //std::cout << "Connector::tryNextHostName(): Falling back on A resolution" << std::endl;
+ // Fall back on simple address resolving
+ queriedAllServices = true;
+ queryAddress(hostname);
+ }
+ else {
+ //std::cout << "Connector::tryNextHostName(): Querying next address" << std::endl;
+ queryAddress(serviceQueryResults.get(0).hostname);
+ }
+ }
+
+ private void tryNextAddress() {
+ if (addressQueryResults.isEmpty()) {
+ //std::cout << "Connector::tryNextAddress(): Done trying addresses. Moving on" << std::endl;
+ // Done trying all addresses. Move on to the next host.
+ if (!serviceQueryResults.isEmpty()) {
+ serviceQueryResults.remove(0);
+ }
+ tryNextServiceOrFallback();
+ }
+ else {
+ //std::cout << "Connector::tryNextAddress(): trying next address." << std::endl;
+ HostAddress address = addressQueryResults.get(0);
+ addressQueryResults.remove(0);
+
+ int port = 5222;
+ if (!serviceQueryResults.isEmpty()) {
+ port = serviceQueryResults.get(0).port;
+ }
+
+ tryConnect(new HostAddressPort(address, port));
+ }
+ }
+
+ private void tryConnect(HostAddressPort target) {
+ assert currentConnection == null;
+ //std::cout << "Connector::tryConnect() " << target.getAddress().toString() << " " << target.getPort() << std::endl;
+ currentConnection = connectionFactory.createConnection();
+ currentConnectionConnectFinishedConnection = currentConnection.onConnectFinished.connect(new Slot1<Boolean>() {
+ public void call(Boolean p1) {
+ handleConnectionConnectFinished(p1);
+ }
+ });
+
+ currentConnection.connect(target);
+ }
+
+ private void handleConnectionConnectFinished(boolean error) {
+ //std::cout << "Connector::handleConnectionConnectFinished() " << error << std::endl;
+ currentConnectionConnectFinishedConnection.disconnect();
+ if (error) {
+ currentConnection = null;
+ if (!addressQueryResults.isEmpty()) {
+ tryNextAddress();
+ }
+ else {
+ if (!serviceQueryResults.isEmpty()) {
+ serviceQueryResults.remove(0);
+ }
+ tryNextServiceOrFallback();
+ }
+ }
+ else {
+ finish(currentConnection);
+ }
+ }
+
+ private void finish(Connection connection) {
+ if (timer != null) {
+ timer.stop();
+ timer.onTick.disconnectAll();
+ timer = null;
+ }
+ if (serviceQuery != null) {
+ serviceQuery.onResult.disconnectAll();
+ serviceQuery = null;
+ }
+ if (addressQuery != null) {
+ addressQuery.onResult.disconnectAll();
+ addressQuery = null;
+ }
+ if (currentConnection != null) {
+ currentConnectionConnectFinishedConnection.disconnect();
+ currentConnection = null;
+ }
+ onConnectFinished.emit(connection);
+ }
+
+ private void handleTimeout() {
+ finish(null);
+ }
+ private String hostname;
+ private DomainNameResolver resolver;
+ private ConnectionFactory connectionFactory;
+ private TimerFactory timerFactory;
+ private int timeoutMilliseconds = 0;
+ private Timer timer;
+ private DomainNameServiceQuery serviceQuery;
+ private ArrayList<DomainNameServiceQuery.Result> serviceQueryResults;
+ private DomainNameAddressQuery addressQuery;
+ private ArrayList<HostAddress> addressQueryResults;
+ private boolean queriedAllServices = true;
+ private Connection currentConnection;
+ private SignalConnection currentConnectionConnectFinishedConnection;
+}
diff --git a/src/com/isode/stroke/network/DomainNameAddressQuery.java b/src/com/isode/stroke/network/DomainNameAddressQuery.java
new file mode 100644
index 0000000..1bc9edf
--- /dev/null
+++ b/src/com/isode/stroke/network/DomainNameAddressQuery.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.signals.Signal2;
+import java.util.Collection;
+
+public abstract class DomainNameAddressQuery {
+ public abstract void run();
+
+ public final Signal2<Collection<HostAddress>, DomainNameResolveError> onResult = new Signal2<Collection<HostAddress>, DomainNameResolveError>();
+}
diff --git a/src/com/isode/stroke/network/DomainNameResolveError.java b/src/com/isode/stroke/network/DomainNameResolveError.java
new file mode 100644
index 0000000..b470c34
--- /dev/null
+++ b/src/com/isode/stroke/network/DomainNameResolveError.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+public class DomainNameResolveError implements com.isode.stroke.base.Error {
+
+}
diff --git a/src/com/isode/stroke/network/DomainNameResolver.java b/src/com/isode/stroke/network/DomainNameResolver.java
new file mode 100644
index 0000000..bbd4e79
--- /dev/null
+++ b/src/com/isode/stroke/network/DomainNameResolver.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+
+public abstract class DomainNameResolver {
+ public abstract DomainNameServiceQuery createServiceQuery(String name);
+ public abstract DomainNameAddressQuery createAddressQuery(String name);
+
+ protected String getNormalized(String domain) {
+ return domain;
+ //FIXME: port idna
+// char* output;
+// if (idna_to_ascii_8z(domain.getUTF8Data(), &output, 0) == IDNA_SUCCESS) {
+// String result(output);
+// free(output);
+// return result;
+// }
+// else {
+// return domain;
+// }
+ }
+}
diff --git a/src/com/isode/stroke/network/DomainNameServiceQuery.java b/src/com/isode/stroke/network/DomainNameServiceQuery.java
new file mode 100644
index 0000000..ddb94a8
--- /dev/null
+++ b/src/com/isode/stroke/network/DomainNameServiceQuery.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.signals.Signal1;
+import java.util.Collection;
+
+public abstract class DomainNameServiceQuery {
+
+ public class Result {
+
+ public Result() {
+ hostname = "";
+ port = -1;
+ priority = -1;
+ weight = -1;
+ }
+
+ public Result(String hostname, int port, int priority, int weight) {
+ this.hostname = hostname;
+ this.port = port;
+ this.priority = priority;
+ this.weight = weight;
+ }
+ public final String hostname;
+ public final int port;
+ public final int priority;
+ public final int weight;
+ };
+
+ public class ResultPriorityComparator {
+
+ public boolean compare(DomainNameServiceQuery.Result a, DomainNameServiceQuery.Result b) {
+ return a.priority < b.priority;
+ }
+ };
+
+ public abstract void run();
+ public final Signal1<Collection<Result>> onResult = new Signal1<Collection<Result>>();
+}
diff --git a/src/com/isode/stroke/network/HostAddress.java b/src/com/isode/stroke/network/HostAddress.java
new file mode 100644
index 0000000..152dc2b
--- /dev/null
+++ b/src/com/isode/stroke/network/HostAddress.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import java.net.InetAddress;
+
+public class HostAddress {
+
+ public HostAddress() {
+ address_ = null;
+ }
+
+ public HostAddress(InetAddress address) {
+ address_ = address;
+ }
+ /* public HostAddress(const String&);
+ public HostAddress(const unsigned char* address, int length);
+ public HostAddress(const boost::asio::ip::address& address);*/
+
+ @Override
+ public String toString() {
+ return address_.getHostAddress();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 29 * hash + (this.address_ != null ? this.address_.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof HostAddress) {
+ return address_.equals(((HostAddress)other).getInetAddress());
+ }
+ return false;
+ }
+
+ public boolean isValid() {
+ return address_ != null;
+ }
+
+ InetAddress getInetAddress() {
+ return address_;
+ }
+
+ private final InetAddress address_;
+}
diff --git a/src/com/isode/stroke/network/HostAddressPort.java b/src/com/isode/stroke/network/HostAddressPort.java
new file mode 100644
index 0000000..01048a3
--- /dev/null
+++ b/src/com/isode/stroke/network/HostAddressPort.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+public class HostAddressPort {
+
+ public HostAddressPort(HostAddress address) {
+ address_ = address;
+ port_ = -1;
+ }
+
+ public HostAddressPort(HostAddress address, int port) {
+ address_ = address;
+ port_ = port;
+ }
+
+ /*
+ public HostAddressPort(const boost::asio::ip::tcp::endpoint& endpoint) {
+ address_ = HostAddress(endpoint.address());
+ port_ = endpoint.port();
+ }*/ //FIXME
+ public HostAddress getAddress() {
+ return address_;
+ }
+
+ public int getPort() {
+ return port_;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof HostAddressPort)) return false;
+ HostAddressPort o = (HostAddressPort)other;
+ return getAddress().equals(o.getAddress()) && port_ == o.getPort();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 17 * hash + (this.address_ != null ? this.address_.hashCode() : 0);
+ hash = 17 * hash + this.port_;
+ return hash;
+ }
+
+ public boolean isValid() {
+ return address_.isValid() && port_ > 0;
+ }
+ private HostAddress address_;
+ private int port_;
+}
diff --git a/src/com/isode/stroke/network/JavaConnection.java b/src/com/isode/stroke/network/JavaConnection.java
new file mode 100644
index 0000000..e43707c
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaConnection.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.eventloop.Event.Callback;
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.eventloop.EventOwner;
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class JavaConnection extends Connection implements EventOwner {
+
+ private class Worker implements Runnable {
+
+ private final HostAddressPort address_;
+ private OutputStream write_;
+ private BufferedReader read_;
+ private final List<ByteArray> writeBuffer_ = Collections.synchronizedList(new ArrayList<ByteArray>());
+
+ public Worker(HostAddressPort address) {
+ address_ = address;
+ }
+
+ public void run() {
+ try {
+ socket_ = new Socket(address_.getAddress().getInetAddress(), address_.getPort());
+ write_ = socket_.getOutputStream();
+ read_ = new BufferedReader(new InputStreamReader(socket_.getInputStream(), "utf-8"));
+ } catch (IOException ex) {
+ handleConnected(true);
+ return;
+ }
+ handleConnected(false);
+ while (!disconnecting_) {
+ boolean didWrite = false;
+ while (!writeBuffer_.isEmpty()) {
+ didWrite = true;
+ ByteArray data = writeBuffer_.get(0);
+ for (byte datum : data.getData()) {
+ try {
+ write_.write(datum);
+ } catch (IOException ex) {
+ disconnecting_ = true;
+ handleDisconnected(Error.WriteError);
+ }
+ }
+ writeBuffer_.remove(0);
+ }
+ if (didWrite && !disconnecting_) {
+ try {
+ write_.flush();
+ } catch (IOException ex) {
+ disconnecting_ = true;
+ handleDisconnected(Error.WriteError);
+ }
+ }
+ ByteArray data = new ByteArray();
+ try {
+ while (read_.ready()) {
+ char[] c = new char[1024];
+ int i = read_.read(c, 0, c.length);
+ if (i > 0) {
+ data.append(new String(c, 0, i));
+ }
+ }
+ } catch (IOException ex) {
+ handleDisconnected(Error.ReadError);
+ return;
+ }
+ if (!data.isEmpty()) {
+ handleDataRead(data);
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ /* We've been woken up, probably to force us to do something.*/
+ }
+ }
+ try {
+ read_.close();
+ write_.close();
+ socket_.close();
+ } catch (IOException ex) {
+ /* Do we need to return an error if we're already trying to close? */
+ }
+ }
+
+ private void handleConnected(final boolean error) {
+ eventLoop_.postEvent(new Callback() {
+ public void run() {
+ onConnectFinished.emit(error);
+ }
+ });
+ }
+
+ private void handleDisconnected(final Error error) {
+ eventLoop_.postEvent(new Callback() {
+ public void run() {
+ onDisconnected.emit(error);
+ }
+ });
+ }
+
+ private void handleDataRead(final ByteArray data) {
+ eventLoop_.postEvent(new Callback() {
+ public void run() {
+ onDataRead.emit(data);
+ }
+ });
+ }
+
+ public void write(ByteArray data) {
+ writeBuffer_.add(data);
+ }
+ }
+
+ private JavaConnection(EventLoop eventLoop) {
+ eventLoop_ = eventLoop;
+ }
+
+ public static JavaConnection create(EventLoop eventLoop) {
+ return new JavaConnection(eventLoop);
+ }
+
+ @Override
+ public void listen() {
+ //TODO: needed for server, not for client.
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void connect(HostAddressPort address) {
+ worker_ = new Worker(address);
+ Thread workerThread = new Thread(worker_);
+ workerThread.setDaemon(true);
+ workerThread.start();
+ }
+
+ @Override
+ public void disconnect() {
+ disconnecting_ = true;
+ }
+
+ @Override
+ public void write(ByteArray data) {
+ worker_.writeBuffer_.add(data);
+ }
+
+ @Override
+ public HostAddressPort getLocalAddress() {
+ return new HostAddressPort(new HostAddress(socket_.getLocalAddress()), socket_.getLocalPort());
+ }
+ private final EventLoop eventLoop_;
+ private boolean disconnecting_ = false;
+ private Socket socket_;
+ private Worker worker_;
+}
diff --git a/src/com/isode/stroke/network/JavaConnectionFactory.java b/src/com/isode/stroke/network/JavaConnectionFactory.java
new file mode 100644
index 0000000..956e80b
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaConnectionFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.EventLoop;
+
+public class JavaConnectionFactory implements ConnectionFactory {
+
+ public JavaConnectionFactory(EventLoop eventLoop) {
+ this.eventLoop = eventLoop;
+ }
+
+ public Connection createConnection() {
+ return JavaConnection.create(eventLoop);
+ }
+
+ private final EventLoop eventLoop;
+}
diff --git a/src/com/isode/stroke/network/JavaNetworkFactories.java b/src/com/isode/stroke/network/JavaNetworkFactories.java
new file mode 100644
index 0000000..acd289b
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaNetworkFactories.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.EventLoop;
+
+public class JavaNetworkFactories implements NetworkFactories {
+
+ public JavaNetworkFactories(EventLoop eventLoop) {
+ eventLoop_ = eventLoop;
+ timers_ = new JavaTimerFactory(eventLoop_);
+ connections_ = new JavaConnectionFactory(eventLoop_);
+ dns_ = new PlatformDomainNameResolver(eventLoop_);
+ }
+
+ public TimerFactory getTimerFactory() {
+ return timers_;
+ }
+
+ public ConnectionFactory getConnectionFactory() {
+ return connections_;
+ }
+
+ public DomainNameResolver getDomainNameResolver() {
+ return dns_;
+ }
+ private final EventLoop eventLoop_;
+ private final JavaTimerFactory timers_;
+ private final JavaConnectionFactory connections_;
+ private final PlatformDomainNameResolver dns_;
+}
diff --git a/src/com/isode/stroke/network/JavaTimer.java b/src/com/isode/stroke/network/JavaTimer.java
new file mode 100644
index 0000000..61357d4
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaTimer.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.Event;
+import com.isode.stroke.eventloop.EventLoop;
+
+class JavaTimer extends Timer {
+
+ private class TimerRunnable implements Runnable {
+
+ boolean running_ = true;
+ private final EventLoop eventLoop_;
+ private final int milliseconds_;
+
+ public TimerRunnable(EventLoop eventLoop, int milliseconds) {
+ eventLoop_ = eventLoop;
+ milliseconds_ = milliseconds;
+ }
+
+ public void run() {
+ while (shouldEmit()) {
+ try {
+ Thread.sleep(milliseconds_);
+ } catch (InterruptedException ex) {
+ /* If we were interrupted, either emit or don't, based on whether stop was called.*/
+ }
+ if (shouldEmit()) {
+ eventLoop_.postEvent(new Event.Callback() {
+ public void run() {
+ onTick.emit();
+ }
+ });
+ }
+ }
+ }
+
+
+ synchronized boolean shouldEmit() {
+ return running_;
+ }
+
+ public synchronized void stop() {
+ running_ = false;
+ }
+ }
+
+ public JavaTimer(EventLoop eventLoop, int milliseconds) {
+ timer_ = new TimerRunnable(eventLoop, milliseconds);
+ }
+
+ @Override
+ public void start() {
+ Thread thread = (new Thread(timer_));
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ @Override
+ public void stop() {
+ timer_.stop();
+ //FIXME: This needs to clear any remaining events out of the EventLoop queue.
+ }
+ private final TimerRunnable timer_;
+}
diff --git a/src/com/isode/stroke/network/JavaTimerFactory.java b/src/com/isode/stroke/network/JavaTimerFactory.java
new file mode 100644
index 0000000..10017a8
--- /dev/null
+++ b/src/com/isode/stroke/network/JavaTimerFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.EventLoop;
+
+public class JavaTimerFactory implements TimerFactory {
+
+ public JavaTimerFactory(EventLoop eventLoop) {
+ eventLoop_ = eventLoop;
+ }
+
+ public Timer createTimer(int milliseconds) {
+ return new JavaTimer(eventLoop_, milliseconds);
+ }
+
+ private final EventLoop eventLoop_;
+
+}
diff --git a/src/com/isode/stroke/network/NetworkFactories.java b/src/com/isode/stroke/network/NetworkFactories.java
new file mode 100644
index 0000000..b630b85
--- /dev/null
+++ b/src/com/isode/stroke/network/NetworkFactories.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.network;
+
+public interface NetworkFactories {
+
+ TimerFactory getTimerFactory();
+ ConnectionFactory getConnectionFactory();
+ DomainNameResolver getDomainNameResolver();
+
+}
diff --git a/src/com/isode/stroke/network/PlatformDomainNameResolver.java b/src/com/isode/stroke/network/PlatformDomainNameResolver.java
new file mode 100644
index 0000000..50d0e11
--- /dev/null
+++ b/src/com/isode/stroke/network/PlatformDomainNameResolver.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.eventloop.EventOwner;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+
+public class PlatformDomainNameResolver extends DomainNameResolver {
+
+ private class AddressQuery extends DomainNameAddressQuery implements EventOwner {
+ AddressQuery(String host, EventLoop eventLoop) {
+ hostname = host;
+ this.eventLoop = eventLoop;
+ //FIXME: port asyncDNS
+// thread = null;
+// safeToJoin = false;
+ }
+
+ public void run() {
+ //FIXME: port asyncDNS
+ Collection<HostAddress> results = new ArrayList<HostAddress>();
+ try {
+ results.add(new HostAddress(InetAddress.getByName(hostname)));
+ } catch (UnknownHostException ex) {
+
+ }
+ onResult.emit(results, results.isEmpty() ? new DomainNameResolveError() : null);
+
+// safeToJoin = false;
+// thread = new boost::thread(boost::bind(&AddressQuery::doRun, shared_from_this()));
+ }
+// FIXME: Port async DNS.
+// void doRun() {
+// //std::cout << "PlatformDomainNameResolver::doRun()" << std::endl;
+// boost::asio::ip::tcp::resolver resolver(ioService);
+// boost::asio::ip::tcp::resolver::query query(hostname.getUTF8String(), "5222");
+// try {
+// //std::cout << "PlatformDomainNameResolver::doRun(): Resolving" << std::endl;
+// boost::asio::ip::tcp::resolver::iterator endpointIterator = resolver.resolve(query);
+// //std::cout << "PlatformDomainNameResolver::doRun(): Resolved" << std::endl;
+// if (endpointIterator == boost::asio::ip::tcp::resolver::iterator()) {
+// //std::cout << "PlatformDomainNameResolver::doRun(): Error 1" << std::endl;
+// emitError();
+// }
+// else {
+// std::vector<HostAddress> results;
+// for ( ; endpointIterator != boost::asio::ip::tcp::resolver::iterator(); ++endpointIterator) {
+// boost::asio::ip::address address = (*endpointIterator).endpoint().address();
+// results.push_back(address.is_v4() ? HostAddress(&address.to_v4().to_bytes()[0], 4) : HostAddress(&address.to_v6().to_bytes()[0], 16));
+// }
+//
+// //std::cout << "PlatformDomainNameResolver::doRun(): Success" << std::endl;
+// eventLoop->postEvent(
+// boost::bind(boost::ref(onResult), results, boost::optional<DomainNameResolveError>()),
+// shared_from_this());
+// }
+// }
+// catch (...) {
+// //std::cout << "PlatformDomainNameResolver::doRun(): Error 2" << std::endl;
+// emitError();
+// }
+// safeToJoin = true;
+// }
+//
+// void emitError() {
+// eventLoop->postEvent(boost::bind(boost::ref(onResult), std::vector<HostAddress>(), boost::optional<DomainNameResolveError>(DomainNameResolveError())), shared_from_this());
+// }
+//
+// boost::asio::io_service ioService;
+ String hostname;
+ EventLoop eventLoop;
+// boost::thread* thread;
+// bool safeToJoin;
+ }
+
+ public PlatformDomainNameResolver(EventLoop eventLoop) {
+ this.eventLoop = eventLoop;
+ }
+
+ @Override
+ public DomainNameServiceQuery createServiceQuery(String name) {
+ return new PlatformDomainNameServiceQuery(getNormalized(name), eventLoop);
+ }
+
+ @Override
+ public DomainNameAddressQuery createAddressQuery(String name) {
+ return new AddressQuery(getNormalized(name), eventLoop);
+ }
+
+ private final EventLoop eventLoop;
+}
diff --git a/src/com/isode/stroke/network/PlatformDomainNameServiceQuery.java b/src/com/isode/stroke/network/PlatformDomainNameServiceQuery.java
new file mode 100644
index 0000000..5c94e4d
--- /dev/null
+++ b/src/com/isode/stroke/network/PlatformDomainNameServiceQuery.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.eventloop.EventOwner;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Hashtable;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+
+public class PlatformDomainNameServiceQuery extends DomainNameServiceQuery implements EventOwner {
+
+ public PlatformDomainNameServiceQuery(String service, EventLoop eventLoop) {
+ this.service = service;
+ this.eventLoop = eventLoop;
+ }
+
+ @Override
+ public void run() {
+ //TODO: Make async
+ Collection<Result> results = new ArrayList<Result>();
+ Hashtable env = new Hashtable();
+ env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
+ env.put("java.naming.provider.url", "dns:");
+ try {
+ DirContext ctx = new InitialDirContext(env);
+ Attributes attrs = ctx.getAttributes(this.service, new String[]{"SRV"});
+ Attribute attribute = attrs.get("SRV");
+ for (int i = 0; attribute != null && i < attribute.size(); i++) {
+ /* SRV results are going to be returned in the space-separated format
+ * Priority Weight Port Target
+ * (See RFC2782)
+ */
+ String[] srvParts = ((String) attribute.get(i)).split(" ");
+ String host = srvParts[3];
+ if (host.endsWith(".")) {
+ host = host.substring(0, host.length() - 1);
+ }
+ Result result = new Result(host, Integer.parseInt(srvParts[2]), Integer.parseInt(srvParts[0]), Integer.parseInt(srvParts[1]));
+ results.add(result);
+ }
+ } catch (NamingException ex) {
+ /* Turns out that you get the exception just for not finding a result, so we want to fall through to A lookups and ignore.*/
+ }
+
+ onResult.emit(results);
+ }
+
+
+// void PlatformDomainNameServiceQuery::doRun() {
+// std::vector<DomainNameServiceQuery::Result> records;
+//
+//#if defined(SWIFTEN_PLATFORM_WINDOWS)
+// DNS_RECORD* responses;
+// // FIXME: This conversion doesn't work if unicode is deffed above
+// if (DnsQuery(service.getUTF8Data(), DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &responses, NULL) != ERROR_SUCCESS) {
+// emitError();
+// return;
+// }
+//
+// DNS_RECORD* currentEntry = responses;
+// while (currentEntry) {
+// if (currentEntry->wType == DNS_TYPE_SRV) {
+// DomainNameServiceQuery::Result record;
+// record.priority = currentEntry->Data.SRV.wPriority;
+// record.weight = currentEntry->Data.SRV.wWeight;
+// record.port = currentEntry->Data.SRV.wPort;
+//
+// // The pNameTarget is actually a PCWSTR, so I would have expected this
+// // conversion to not work at all, but it does.
+// // Actually, it doesn't. Fix this and remove explicit cast
+// // Remove unicode undef above as well
+// record.hostname = String((const char*) currentEntry->Data.SRV.pNameTarget);
+// records.push_back(record);
+// }
+// currentEntry = currentEntry->pNext;
+// }
+// DnsRecordListFree(responses, DnsFreeRecordList);
+//
+//#else
+// // Make sure we reinitialize the domain list every time
+// res_init();
+//
+// //std::cout << "SRV: Querying " << service << std::endl;
+// ByteArray response;
+// response.resize(NS_PACKETSZ);
+// int responseLength = res_query(const_cast<char*>(service.getUTF8Data()), ns_c_in, ns_t_srv, reinterpret_cast<u_char*>(response.getData()), response.getSize());
+// if (responseLength == -1) {
+// emitError();
+// return;
+// }
+//
+// // Parse header
+// HEADER* header = reinterpret_cast<HEADER*>(response.getData());
+// unsigned char* messageStart = reinterpret_cast<unsigned char*>(response.getData());
+// unsigned char* messageEnd = messageStart + responseLength;
+// unsigned char* currentEntry = messageStart + NS_HFIXEDSZ;
+//
+// // Skip over the queries
+// int queriesCount = ntohs(header->qdcount);
+// while (queriesCount > 0) {
+// int entryLength = dn_skipname(currentEntry, messageEnd);
+// if (entryLength < 0) {
+// emitError();
+// return;
+// }
+// currentEntry += entryLength + NS_QFIXEDSZ;
+// queriesCount--;
+// }
+//
+// // Process the SRV answers
+// int answersCount = ntohs(header->ancount);
+// while (answersCount > 0) {
+// DomainNameServiceQuery::Result record;
+//
+// int entryLength = dn_skipname(currentEntry, messageEnd);
+// currentEntry += entryLength;
+// currentEntry += NS_RRFIXEDSZ;
+//
+// // Priority
+// if (currentEntry + 2 >= messageEnd) {
+// emitError();
+// return;
+// }
+// record.priority = ns_get16(currentEntry);
+// currentEntry += 2;
+//
+// // Weight
+// if (currentEntry + 2 >= messageEnd) {
+// emitError();
+// return;
+// }
+// record.weight = ns_get16(currentEntry);
+// currentEntry += 2;
+//
+// // Port
+// if (currentEntry + 2 >= messageEnd) {
+// emitError();
+// return;
+// }
+// record.port = ns_get16(currentEntry);
+// currentEntry += 2;
+//
+// // Hostname
+// if (currentEntry >= messageEnd) {
+// emitError();
+// return;
+// }
+// ByteArray entry;
+// entry.resize(NS_MAXDNAME);
+// entryLength = dn_expand(messageStart, messageEnd, currentEntry, entry.getData(), entry.getSize());
+// if (entryLength < 0) {
+// emitError();
+// return;
+// }
+// record.hostname = String(entry.getData());
+// records.push_back(record);
+// currentEntry += entryLength;
+// answersCount--;
+// }
+//#endif
+//
+// safeToJoin = true;
+// std::sort(records.begin(), records.end(), ResultPriorityComparator());
+// //std::cout << "Sending out " << records.size() << " SRV results " << std::endl;
+// eventLoop->postEvent(boost::bind(boost::ref(onResult), records));
+//}
+
+
+ private final String service;
+ private final EventLoop eventLoop;
+}
diff --git a/src/com/isode/stroke/network/Timer.java b/src/com/isode/stroke/network/Timer.java
new file mode 100644
index 0000000..5dc3854
--- /dev/null
+++ b/src/com/isode/stroke/network/Timer.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+import com.isode.stroke.signals.Signal;
+
+public abstract class Timer {
+ public abstract void start();
+ public abstract void stop();
+ public final Signal onTick = new Signal();
+}
diff --git a/src/com/isode/stroke/network/TimerFactory.java b/src/com/isode/stroke/network/TimerFactory.java
new file mode 100644
index 0000000..2732aaf
--- /dev/null
+++ b/src/com/isode/stroke/network/TimerFactory.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.network;
+
+
+public interface TimerFactory {
+ Timer createTimer(int milliseconds);
+}
diff --git a/src/com/isode/stroke/parser/AttributeMap.java b/src/com/isode/stroke/parser/AttributeMap.java
new file mode 100644
index 0000000..dc948ce
--- /dev/null
+++ b/src/com/isode/stroke/parser/AttributeMap.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import java.util.HashMap;
+
+/**
+ * XML element attributes.
+ */
+public class AttributeMap extends HashMap<String, String> {
+ public String getAttribute(String attribute) {
+ return this.get(attribute);
+ }
+
+ public String getAttributeValue(String attribute) {
+ return this.containsKey(attribute) ? this.get(attribute) : null;
+ }
+
+ public boolean getBoolAttribute(String attribute) {
+ return getBoolAttribute(attribute, false);
+ }
+
+ public boolean getBoolAttribute(String attribute, boolean defaultValue) {
+ String value = getAttribute(attribute);
+ return "true".equals(value) || "1".equals(value);
+ }
+}
diff --git a/src/com/isode/stroke/parser/AuthChallengeParser.java b/src/com/isode/stroke/parser/AuthChallengeParser.java
new file mode 100644
index 0000000..20238dd
--- /dev/null
+++ b/src/com/isode/stroke/parser/AuthChallengeParser.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.AuthChallenge;
+import com.isode.stroke.stringcodecs.Base64;
+
+class AuthChallengeParser extends GenericElementParser<AuthChallenge> {
+
+ public AuthChallengeParser() {
+ super(AuthChallenge.class);
+ }
+
+ @Override
+ public void handleStartElement(String unused1, String unused2, AttributeMap unused3) {
+ ++depth;
+ }
+
+ @Override
+ public void handleEndElement(String unused1, String unused2) {
+ --depth;
+ if (depth == 0) {
+ getElementGeneric().setValue(Base64.decode(text));
+ }
+ }
+
+ @Override
+ public void handleCharacterData(String text) {
+ this.text += text;
+ }
+ private int depth = 0;
+ private String text = "";
+}
diff --git a/src/com/isode/stroke/parser/AuthFailureParser.java b/src/com/isode/stroke/parser/AuthFailureParser.java
new file mode 100644
index 0000000..c42abed
--- /dev/null
+++ b/src/com/isode/stroke/parser/AuthFailureParser.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.AuthFailure;
+
+class AuthFailureParser extends GenericElementParser<AuthFailure> {
+
+ public AuthFailureParser() {
+ super(AuthFailure.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/AuthRequestParser.java b/src/com/isode/stroke/parser/AuthRequestParser.java
new file mode 100644
index 0000000..7f40c9c
--- /dev/null
+++ b/src/com/isode/stroke/parser/AuthRequestParser.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.AuthRequest;
+import com.isode.stroke.stringcodecs.Base64;
+
+class AuthRequestParser extends GenericElementParser<AuthRequest> {
+
+ public AuthRequestParser() {
+ super(AuthRequest.class);
+ }
+
+ @Override
+ public void handleStartElement(String a, String b, AttributeMap attribute) {
+ if (depth_ == 0) {
+ getElementGeneric().setMechanism(attribute.getAttribute("mechanism"));
+ }
+ ++depth_;
+ }
+
+ @Override
+ public void handleEndElement(String a, String b) {
+ --depth_;
+ if (depth_ == 0) {
+ getElementGeneric().setMessage(Base64.decode(text_));
+ }
+ }
+
+ @Override
+ public void handleCharacterData(String a) {
+ text_ += a;
+ }
+ String text_ = "";
+ int depth_ = 0;
+}
diff --git a/src/com/isode/stroke/parser/AuthResponseParser.java b/src/com/isode/stroke/parser/AuthResponseParser.java
new file mode 100644
index 0000000..a93249a
--- /dev/null
+++ b/src/com/isode/stroke/parser/AuthResponseParser.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.AuthResponse;
+import com.isode.stroke.stringcodecs.Base64;
+
+class AuthResponseParser extends GenericElementParser<AuthResponse> {
+
+ public AuthResponseParser() {
+ super(AuthResponse.class);
+ }
+
+ @Override
+ public void handleStartElement(String unused1, String unused2, AttributeMap unused3) {
+ ++depth;
+ }
+
+ @Override
+ public void handleEndElement(String unused1, String unused2) {
+ --depth;
+ if (depth == 0) {
+ getElementGeneric().setValue(Base64.decode(text));
+ }
+ }
+
+ @Override
+ public void handleCharacterData(String text) {
+ this.text += text;
+ }
+ private int depth = 0;
+ private String text = "";
+}
diff --git a/src/com/isode/stroke/parser/AuthSuccessParser.java b/src/com/isode/stroke/parser/AuthSuccessParser.java
new file mode 100644
index 0000000..111d3ce
--- /dev/null
+++ b/src/com/isode/stroke/parser/AuthSuccessParser.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.AuthSuccess;
+import com.isode.stroke.stringcodecs.Base64;
+
+class AuthSuccessParser extends GenericElementParser<AuthSuccess> {
+
+ public AuthSuccessParser() {
+ super(AuthSuccess.class);
+ }
+
+ @Override
+ public void handleStartElement(String a, String b, AttributeMap attribute) {
+ ++depth_;
+ }
+
+ @Override
+ public void handleEndElement(String a, String b) {
+ --depth_;
+ if (depth_ == 0) {
+ getElementGeneric().setValue(Base64.decode(text_));
+ }
+ }
+
+ @Override
+ public void handleCharacterData(String a) {
+ text_ += a;
+ }
+ String text_ = "";
+ int depth_ = 0;
+}
diff --git a/src/com/isode/stroke/parser/CompressFailureParser.java b/src/com/isode/stroke/parser/CompressFailureParser.java
new file mode 100644
index 0000000..cbfa413
--- /dev/null
+++ b/src/com/isode/stroke/parser/CompressFailureParser.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.CompressFailure;
+
+class CompressFailureParser extends GenericElementParser<CompressFailure> {
+
+ public CompressFailureParser() {
+ super(CompressFailure.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/CompressParser.java b/src/com/isode/stroke/parser/CompressParser.java
new file mode 100644
index 0000000..133b20b
--- /dev/null
+++ b/src/com/isode/stroke/parser/CompressParser.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.CompressRequest;
+
+class CompressParser extends GenericElementParser<CompressRequest> {
+
+ private int currentDepth_ = 0;
+ private String currentText_;
+ private boolean inMethod_;
+
+ public CompressParser() {
+ super(CompressRequest.class);
+ }
+
+ @Override
+ public void handleStartElement(String element, String ns, AttributeMap attributes) {
+ if (currentDepth_ == 1 && element.equals("method")) {
+ inMethod_ = true;
+ currentText_ = "";
+ }
+ ++currentDepth_;
+ }
+
+ @Override
+ public void handleEndElement(String el, String ns) {
+ --currentDepth_;
+ if (currentDepth_ == 1 && inMethod_) {
+ getElementGeneric().setMethod(currentText_);
+ inMethod_ = false;
+ }
+ }
+
+ @Override
+ public void handleCharacterData(String data) {
+ currentText_ += data;
+ }
+}
diff --git a/src/com/isode/stroke/parser/CompressedParser.java b/src/com/isode/stroke/parser/CompressedParser.java
new file mode 100644
index 0000000..77d17e4
--- /dev/null
+++ b/src/com/isode/stroke/parser/CompressedParser.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.Compressed;
+
+class CompressedParser extends GenericElementParser<Compressed> {
+
+ public CompressedParser() {
+ super(Compressed.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/ElementParser.java b/src/com/isode/stroke/parser/ElementParser.java
new file mode 100644
index 0000000..eebb51a
--- /dev/null
+++ b/src/com/isode/stroke/parser/ElementParser.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.Element;
+
+/**
+ * Parse XML Elements.
+ */
+public interface ElementParser {
+
+ void handleStartElement(String element, String ns, AttributeMap attributes);
+
+ void handleEndElement(String element, String ns);
+
+ void handleCharacterData(String data);
+
+ Element getElement();
+}
diff --git a/src/com/isode/stroke/parser/EnableStreamManagementParser.java b/src/com/isode/stroke/parser/EnableStreamManagementParser.java
new file mode 100644
index 0000000..fe657dd
--- /dev/null
+++ b/src/com/isode/stroke/parser/EnableStreamManagementParser.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.EnableStreamManagement;
+
+class EnableStreamManagementParser extends GenericElementParser<EnableStreamManagement> {
+
+ public EnableStreamManagementParser() {
+ super(EnableStreamManagement.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/GenericElementParser.java b/src/com/isode/stroke/parser/GenericElementParser.java
new file mode 100644
index 0000000..da961f6
--- /dev/null
+++ b/src/com/isode/stroke/parser/GenericElementParser.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.Element;
+
+public class GenericElementParser<T extends Element> implements ElementParser {
+
+ private final T element_;
+
+ public GenericElementParser(Class c) {
+ try {
+ element_ = (T) c.newInstance();
+ } catch (InstantiationException ex) {
+ /* Fatal */
+ throw new RuntimeException(ex);
+ } catch (IllegalAccessException ex) {
+ /* Fatal */
+ throw new RuntimeException(ex);
+ }
+ }
+
+
+ public Element getElement() {
+ return element_;
+ }
+
+ protected T getElementGeneric() {
+ return element_;
+ }
+
+ public void handleStartElement(String a, String b, AttributeMap c) {
+ }
+
+ public void handleEndElement(String a, String b) {
+ }
+
+ public void handleCharacterData(String a) {
+ }
+}
diff --git a/src/com/isode/stroke/parser/GenericPayloadParser.java b/src/com/isode/stroke/parser/GenericPayloadParser.java
new file mode 100644
index 0000000..7ac8337
--- /dev/null
+++ b/src/com/isode/stroke/parser/GenericPayloadParser.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.Payload;
+
+public abstract class GenericPayloadParser <T extends Payload> implements PayloadParser {
+ private T payload_;
+
+ public GenericPayloadParser(T payload) {
+ payload_ = payload;
+ }
+
+ public Payload getPayload() {
+ return payload_;
+ }
+
+ protected T getPayloadInternal() {
+ return payload_;
+ }
+}
diff --git a/src/com/isode/stroke/parser/GenericPayloadParserFactory.java b/src/com/isode/stroke/parser/GenericPayloadParserFactory.java
new file mode 100644
index 0000000..a6d7e25
--- /dev/null
+++ b/src/com/isode/stroke/parser/GenericPayloadParserFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2010, 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+public class GenericPayloadParserFactory<T extends PayloadParser> implements PayloadParserFactory {
+
+ private final String tag_;
+ private final String xmlns_;
+ private final Class payloadClass_;
+
+ public GenericPayloadParserFactory(final String tag, final Class<? extends PayloadParser> payloadClass) {
+ this(tag, "", payloadClass);
+ }
+
+ public GenericPayloadParserFactory(final String tag, final String xmlns, final Class<? extends PayloadParser> payloadClass) {
+ tag_ = tag;
+ xmlns_ = xmlns;
+ payloadClass_ = payloadClass;
+ }
+
+ public boolean canParse(final String element, final String ns, final AttributeMap attributes) {
+ return element.equals(tag_) && xmlns_.equals(ns);
+ }
+
+ public final PayloadParser createPayloadParser() {
+ try {
+ return (PayloadParser) payloadClass_.newInstance();
+ } catch (InstantiationException ex) {
+ /* Fatal */
+ throw new RuntimeException(ex);
+ } catch (IllegalAccessException ex) {
+ /* Fatal */
+ throw new RuntimeException(ex);
+ }
+ }
+}
diff --git a/src/com/isode/stroke/parser/GenericStanzaParser.java b/src/com/isode/stroke/parser/GenericStanzaParser.java
new file mode 100644
index 0000000..0f3c6c1
--- /dev/null
+++ b/src/com/isode/stroke/parser/GenericStanzaParser.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.Stanza;
+
+public class GenericStanzaParser<T extends Stanza> extends StanzaParser {
+ private final T stanza_;
+ public GenericStanzaParser(PayloadParserFactoryCollection collection, T blankStanza) {
+ super(collection);
+ stanza_ = blankStanza;
+ }
+
+ public Element getElement() {
+ return stanza_;
+ }
+
+ public T getStanzaGeneric() {
+ return stanza_;
+ }
+}
diff --git a/src/com/isode/stroke/parser/IQParser.java b/src/com/isode/stroke/parser/IQParser.java
new file mode 100644
index 0000000..3e8325a
--- /dev/null
+++ b/src/com/isode/stroke/parser/IQParser.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.IQ;
+
+public class IQParser extends GenericStanzaParser<IQ> {
+
+ public IQParser(PayloadParserFactoryCollection factories) {
+ super(factories, new IQ());
+ }
+
+ @Override
+ void handleStanzaAttributes(AttributeMap attributes) {
+ String type = attributes.getAttribute("type");
+ if ("set".equals(type)) {
+ getStanzaGeneric().setType(IQ.Type.Set);
+ } else if ("get".equals(type)) {
+ getStanzaGeneric().setType(IQ.Type.Get);
+ } else if ("result".equals(type)) {
+ getStanzaGeneric().setType(IQ.Type.Result);
+ } else if ("error".equals(type)) {
+ getStanzaGeneric().setType(IQ.Type.Error);
+ } else {
+ getStanzaGeneric().setType(IQ.Type.Get);
+ }
+ }
+}
diff --git a/src/com/isode/stroke/parser/MessageParser.java b/src/com/isode/stroke/parser/MessageParser.java
new file mode 100644
index 0000000..1294e62
--- /dev/null
+++ b/src/com/isode/stroke/parser/MessageParser.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.Message;
+
+public class MessageParser extends GenericStanzaParser<Message> {
+
+ public MessageParser(PayloadParserFactoryCollection factories) {
+ super(factories, new Message());
+ }
+
+ @Override
+ void handleStanzaAttributes(AttributeMap attributes) {
+ String type = attributes.getAttribute("type");
+ if ("chat".equals(type)) {
+ getStanzaGeneric().setType(Message.Type.Chat);
+ } else if ("error".equals(type)) {
+ getStanzaGeneric().setType(Message.Type.Error);
+ } else if ("groupchat".equals(type)) {
+ getStanzaGeneric().setType(Message.Type.Groupchat);
+ } else if ("headline".equals(type)) {
+ getStanzaGeneric().setType(Message.Type.Headline);
+ } else {
+ getStanzaGeneric().setType(Message.Type.Normal);
+ }
+ }
+}
diff --git a/src/com/isode/stroke/parser/PayloadParser.java b/src/com/isode/stroke/parser/PayloadParser.java
new file mode 100644
index 0000000..c0dfb43
--- /dev/null
+++ b/src/com/isode/stroke/parser/PayloadParser.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.Payload;
+
+/**
+ * Parse a payload.
+ */
+public interface PayloadParser {
+
+ void handleStartElement(String element, String ns, AttributeMap attributes);
+
+ void handleEndElement(String element, String ns);
+
+ void handleCharacterData(String data);
+
+ Payload getPayload();
+}
diff --git a/src/com/isode/stroke/parser/PayloadParserFactory.java b/src/com/isode/stroke/parser/PayloadParserFactory.java
new file mode 100644
index 0000000..3b07395
--- /dev/null
+++ b/src/com/isode/stroke/parser/PayloadParserFactory.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+/**
+ * Create a parser.
+ */
+public interface PayloadParserFactory {
+ boolean canParse(String element, String ns, AttributeMap attributes);
+ PayloadParser createPayloadParser();
+}
diff --git a/src/com/isode/stroke/parser/PayloadParserFactoryCollection.java b/src/com/isode/stroke/parser/PayloadParserFactoryCollection.java
new file mode 100644
index 0000000..2d3601b
--- /dev/null
+++ b/src/com/isode/stroke/parser/PayloadParserFactoryCollection.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import java.util.Vector;
+
+/**
+ * Collection of parser factories.
+ */
+public class PayloadParserFactoryCollection {
+
+ private final Vector<PayloadParserFactory> factories_ = new Vector<PayloadParserFactory>();
+ private PayloadParserFactory defaultFactory_ = null;
+
+ public void addFactory(PayloadParserFactory factory) {
+ synchronized (factories_) {
+ factories_.add(factory);
+ }
+ }
+
+ public void setDefaultFactory(PayloadParserFactory factory) {
+ defaultFactory_ = factory;
+ }
+
+ public PayloadParserFactory getPayloadParserFactory(String element, String ns, AttributeMap attributes) {
+ synchronized(factories_) {
+ for (PayloadParserFactory factory : factories_) {
+ if (factory.canParse(element, ns, attributes)) {
+ return factory;
+ }
+ }
+ }
+ return defaultFactory_;
+ }
+}
diff --git a/src/com/isode/stroke/parser/PlatformXMLParserFactory.java b/src/com/isode/stroke/parser/PlatformXMLParserFactory.java
new file mode 100644
index 0000000..85c9ae9
--- /dev/null
+++ b/src/com/isode/stroke/parser/PlatformXMLParserFactory.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.eventloop.EventLoop;
+
+class PlatformXMLParserFactory {
+ /**
+ * Unlike Swiften, this may be threaded, and therefore needs an eventloop.
+ */
+ public static XMLParser createXMLParser(XMLParserClient client, EventLoop eventLoop) {
+ return new PullXMLParser(client, eventLoop);
+ }
+}
diff --git a/src/com/isode/stroke/parser/PresenceParser.java b/src/com/isode/stroke/parser/PresenceParser.java
new file mode 100644
index 0000000..cca8729
--- /dev/null
+++ b/src/com/isode/stroke/parser/PresenceParser.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.Presence;
+
+public class PresenceParser extends GenericStanzaParser<Presence> {
+
+ public PresenceParser(PayloadParserFactoryCollection factories) {
+ super(factories, new Presence());
+ }
+
+ @Override
+ void handleStanzaAttributes(AttributeMap attributes) {
+ String type = attributes.getAttribute("type");
+ if (type != null) {
+ if (type.equals("unavailable")) {
+ getStanzaGeneric().setType(Presence.Type.Unavailable);
+ } else if (type.equals("probe")) {
+ getStanzaGeneric().setType(Presence.Type.Probe);
+ } else if (type.equals("subscribe")) {
+ getStanzaGeneric().setType(Presence.Type.Subscribe);
+ } else if (type.equals("subscribed")) {
+ getStanzaGeneric().setType(Presence.Type.Subscribed);
+ } else if (type.equals("unsubscribe")) {
+ getStanzaGeneric().setType(Presence.Type.Unsubscribe);
+ } else if (type.equals("unsubscribed")) {
+ getStanzaGeneric().setType(Presence.Type.Unsubscribed);
+ } else if (type.equals("error")) {
+ getStanzaGeneric().setType(Presence.Type.Error);
+ } else {
+ getStanzaGeneric().setType(Presence.Type.Available);
+ }
+ } else {
+ getStanzaGeneric().setType(Presence.Type.Available);
+ }
+ }
+}
diff --git a/src/com/isode/stroke/parser/PullXMLParser.java b/src/com/isode/stroke/parser/PullXMLParser.java
new file mode 100644
index 0000000..8b42afe
--- /dev/null
+++ b/src/com/isode/stroke/parser/PullXMLParser.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.eventloop.Event.Callback;
+import com.isode.stroke.eventloop.EventLoop;
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.xmlpull.mxp1.MXParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Parser based around the XmlPullParser
+ */
+class PullXMLParser extends XMLParser {
+
+ private final Logger logger_ = Logger.getLogger(this.getClass().getName());
+ private final XmlPullParser parser_ = new MXParser();
+ private final PipedInputStream reader_;
+ private final PipedOutputStream writer_;
+ private final ArrayBlockingQueue<Event> events_ = new ArrayBlockingQueue<Event>(20);
+ private final Thread parserThread_;
+ private boolean error_ = false;
+ private final EventLoop eventLoop_;
+
+
+ private enum EventType {Start, End, Text};
+
+ /**
+ * XML Event struct.
+ */
+ private class Event {
+ private final EventType type;
+ private final String name;
+ private final String namespace;
+ private final AttributeMap attributes;
+ public Event(EventType type, String name, String namespace, AttributeMap attributes) {
+ this.type = type;
+ this.name = name;
+ this.namespace = namespace;
+ this.attributes = attributes;
+ }
+
+ public Event(String name) {
+ this(EventType.Text, name, null, null);
+ }
+
+
+ public Event(String name, String namespace) {
+ this(EventType.End, name, namespace, null);
+ }
+
+ public Event(String name, String namespace, AttributeMap attributes) {
+ this(EventType.Start, name, namespace, attributes);
+ }
+
+ }
+
+ /**
+ * Put an XML event onto the queue ready for the main thread to pick up later.
+ */
+ private void addEvent(Event event) throws InterruptedException {
+ events_.put(event);
+ }
+
+ /**
+ * Deal with whatever was just read out of the parser_.
+ */
+ private void handleEvent(int eventType) throws XmlPullParserException, InterruptedException {
+ if (eventType == XmlPullParser.START_TAG) {
+ AttributeMap map = new AttributeMap();
+ for (int i = 0; i < parser_.getAttributeCount(); i++) {
+ map.put(parser_.getAttributeName(i), parser_.getAttributeValue(i));
+ }
+ addEvent(new Event(parser_.getName(), parser_.getNamespace(), map));
+ bump();
+ } else if (eventType == XmlPullParser.END_TAG) {
+ addEvent(new Event(parser_.getName(), parser_.getNamespace()));
+ bump();
+ } else if (eventType == XmlPullParser.TEXT) {
+ StringBuilder text = new StringBuilder();
+ int holderForStartAndLength[] = new int[2];
+ char ch[] = parser_.getTextCharacters(holderForStartAndLength);
+ int start = holderForStartAndLength[0];
+ int length = holderForStartAndLength[1];
+ for (int i = start; i < start + length; i++) {
+ text.append(ch[i]);
+ }
+ addEvent(new Event(text.toString()));
+ bump();
+ } else if (eventType == XmlPullParser.START_DOCUMENT) {
+ //System.out.println("Starting document");
+ } else if (eventType == XmlPullParser.END_DOCUMENT) {
+ //System.out.println("Ending document");
+
+ } else {
+ //System.out.println("Unhandled event");
+ }
+ }
+
+ /**
+ * Cause the main thread to process any outstanding events.
+ */
+ private void bump() {
+ eventLoop_.postEvent(new Callback() {
+ public void run() {
+ processEvents();
+ }
+ });
+ }
+
+ public PullXMLParser(XMLParserClient client, EventLoop eventLoop) {
+ super(client);
+ eventLoop_ = eventLoop;
+ writer_ = new PipedOutputStream();
+ try {
+ reader_ = new PipedInputStream(writer_, 128000);
+ } catch (IOException ex) {
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ throw new IllegalStateException(ex);
+ }
+ try {
+ parser_.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser_.setInput(reader_, "UTF-8");
+ } catch (XmlPullParserException ex) {
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ throw new IllegalStateException(ex);
+ }
+ Runnable parserRunnable = new Runnable() {
+ public void run() {
+ int eventType = XmlPullParser.END_DOCUMENT - 1; /* Anything to make the following not true*/
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ try {
+ parser_.next();
+ eventType = parser_.getEventType();
+ handleEvent(eventType);
+ } catch (XmlPullParserException ex) {
+ error_ = true;
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ break;
+ } catch (IOException ex) {
+ error_ = true;
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ break;
+ } catch (InterruptedException ex) {
+ /* The thread was interrupted while trying to process an event - presumably this is because we're shutting down.*/
+ error_ = true;
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ break;
+ }
+ }
+ }
+ };
+ parserThread_ = new Thread(parserRunnable);
+ parserThread_.setDaemon(true);
+ parserThread_.start();
+ }
+
+ /**
+ * Do not do this!
+ * This is only to allow the unit tests to join onto it.
+ * @return
+ */
+ Thread getParserThread() {
+ return parserThread_;
+ }
+
+ /**
+ * Process outstanding events.
+ * Call in the main thread only.
+ */
+ private void processEvents() {
+ while (events_.size() > 0) {
+ processEvent(events_.poll());
+ }
+ }
+
+ /**
+ * Main thread only.
+ */
+ private void processEvent(Event event) {
+ String name = event.name;
+ String namespace = event.namespace;
+ AttributeMap attributes = event.attributes;
+ switch (event.type) {
+ case Start: getClient().handleStartElement(name, namespace, attributes); break;
+ case End: getClient().handleEndElement(name, namespace); break;
+ case Text: getClient().handleCharacterData(name); break;
+ }
+ }
+
+ /**
+ * Cause the parser thread to parse these data later.
+ * Note that the return code is a best guess based on previous parsing,
+ * and will almost always give a false negative on a stanza before a
+ * true negative. True negatives will always mean an error in the stream.
+ */
+ @Override
+ public boolean parse(String data) {
+ try {
+ writer_.write(new ByteArray(data).getData());
+ writer_.flush();
+ } catch (IOException ex) {
+ error_ = true;
+ Logger.getLogger(PullXMLParser.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ return !error_;
+ }
+
+
+}
diff --git a/src/com/isode/stroke/parser/SerializingParser.java b/src/com/isode/stroke/parser/SerializingParser.java
new file mode 100644
index 0000000..1346dc8
--- /dev/null
+++ b/src/com/isode/stroke/parser/SerializingParser.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.serializer.xml.XMLElement;
+import com.isode.stroke.serializer.xml.XMLTextNode;
+import java.util.Vector;
+
+public class SerializingParser {
+
+ private final Vector<XMLElement> elementStack_ = new Vector<XMLElement>();
+ private XMLElement rootElement_;
+
+ public void handleStartElement(String tag, String ns, AttributeMap attributes) {
+ XMLElement element = new XMLElement(tag, ns);
+ for (String name : attributes.keySet()) {
+ element.setAttribute(name, attributes.get(name));
+ }
+
+ if (elementStack_.isEmpty()) {
+ rootElement_ = element;
+ } else {
+ elementStack_.lastElement().addNode(element);
+ }
+ elementStack_.add(element);
+ }
+
+ public void handleEndElement(String tag, String ns) {
+ assert (!elementStack_.isEmpty());
+ elementStack_.remove(elementStack_.size() - 1);
+ }
+
+ public void handleCharacterData(String data) {
+ if (!elementStack_.isEmpty()) {
+ elementStack_.lastElement().addNode(new XMLTextNode(data));
+ }
+ }
+
+ public String getResult() {
+ return (rootElement_ != null ? rootElement_.serialize() : "");
+ }
+}
diff --git a/src/com/isode/stroke/parser/StanzaAckParser.java b/src/com/isode/stroke/parser/StanzaAckParser.java
new file mode 100644
index 0000000..40fab80
--- /dev/null
+++ b/src/com/isode/stroke/parser/StanzaAckParser.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.StanzaAck;
+
+class StanzaAckParser extends GenericElementParser<StanzaAck> {
+
+ private int depth = 0;
+
+ public StanzaAckParser() {
+ super(StanzaAck.class);
+ }
+
+ @Override
+ public void handleStartElement(String el, String ns, AttributeMap attributes) {
+ if (depth == 0) {
+ String handledStanzasString = attributes.getAttribute("h");
+ try {
+ getElementGeneric().setHandledStanzasCount(Long.parseLong(handledStanzasString));
+ } catch (NumberFormatException e) {
+
+ }
+ }
+ ++depth;
+ }
+
+ @Override
+ public void handleEndElement(String el, String ns) {
+ --depth;
+ }
+}
diff --git a/src/com/isode/stroke/parser/StanzaAckRequestParser.java b/src/com/isode/stroke/parser/StanzaAckRequestParser.java
new file mode 100644
index 0000000..9a61d7f
--- /dev/null
+++ b/src/com/isode/stroke/parser/StanzaAckRequestParser.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.StanzaAckRequest;
+
+class StanzaAckRequestParser extends GenericElementParser<StanzaAckRequest> {
+
+ public StanzaAckRequestParser() {
+ super(StanzaAckRequest.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/StanzaParser.java b/src/com/isode/stroke/parser/StanzaParser.java
new file mode 100644
index 0000000..2aa865e
--- /dev/null
+++ b/src/com/isode/stroke/parser/StanzaParser.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.Payload;
+import com.isode.stroke.elements.Stanza;
+import com.isode.stroke.jid.JID;
+
+public abstract class StanzaParser implements ElementParser {
+
+ protected int currentDepth_ = 0;
+ protected final PayloadParserFactoryCollection factories_;
+ protected PayloadParser currentPayloadParser_;
+
+ public StanzaParser(PayloadParserFactoryCollection factories) {
+ factories_ = factories;
+ }
+
+ void handleStanzaAttributes(AttributeMap map) {
+ }
+
+ Stanza getStanza() {
+ return (Stanza) getElement();
+ }
+
+ private boolean inPayload() {
+ return currentDepth_ > 1;
+ }
+
+ private boolean inStanza() {
+ return currentDepth_ > 0;
+ }
+
+ public void handleStartElement(String element, String ns, AttributeMap attributes) {
+ if (inStanza()) {
+ if (!inPayload()) {
+ assert currentPayloadParser_ == null;
+ PayloadParserFactory payloadParserFactory = factories_.getPayloadParserFactory(element, ns, attributes);
+ if (payloadParserFactory != null) {
+ currentPayloadParser_ = payloadParserFactory.createPayloadParser();
+ } else {
+ currentPayloadParser_ = new UnknownPayloadParser();
+ }
+ }
+ assert currentPayloadParser_ != null;
+ currentPayloadParser_.handleStartElement(element, ns, attributes);
+ } else {
+ String from = attributes.getAttribute("from");
+ if (from != null) {
+ getStanza().setFrom(JID.fromString(from));
+ }
+ String to = attributes.getAttribute("to");
+ if (to != null) {
+ getStanza().setTo(JID.fromString(to));
+ }
+ String id = attributes.getAttribute("id");
+ if (id != null) {
+ getStanza().setID(id);
+ }
+ handleStanzaAttributes(attributes);
+ }
+ ++currentDepth_;
+ }
+
+ public void handleEndElement(String element, String ns) {
+ assert (inStanza());
+ if (inPayload()) {
+ assert currentPayloadParser_ != null;
+ currentPayloadParser_.handleEndElement(element, ns);
+ --currentDepth_;
+ if (!inPayload()) {
+ Payload payload = currentPayloadParser_.getPayload();
+ if (payload != null) {
+ getStanza().addPayload(payload);
+ }
+ currentPayloadParser_ = null;
+ }
+ } else {
+ --currentDepth_;
+ }
+ }
+
+ public void handleCharacterData(String data) {
+ if (currentPayloadParser_ != null) {
+ currentPayloadParser_.handleCharacterData(data);
+ }
+ }
+}
diff --git a/src/com/isode/stroke/parser/StartTLSFailureParser.java b/src/com/isode/stroke/parser/StartTLSFailureParser.java
new file mode 100644
index 0000000..e1a858c
--- /dev/null
+++ b/src/com/isode/stroke/parser/StartTLSFailureParser.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.StartTLSFailure;
+
+class StartTLSFailureParser extends GenericElementParser<StartTLSFailure>{
+
+ public StartTLSFailureParser() {
+ super(StartTLSFailure.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/StartTLSParser.java b/src/com/isode/stroke/parser/StartTLSParser.java
new file mode 100644
index 0000000..c2f3e5d
--- /dev/null
+++ b/src/com/isode/stroke/parser/StartTLSParser.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.StartTLSRequest;
+
+class StartTLSParser extends GenericElementParser<StartTLSRequest> {
+
+ public StartTLSParser() {
+ super(StartTLSRequest.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/StreamFeaturesParser.java b/src/com/isode/stroke/parser/StreamFeaturesParser.java
new file mode 100644
index 0000000..7181659
--- /dev/null
+++ b/src/com/isode/stroke/parser/StreamFeaturesParser.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.StreamFeatures;
+
+class StreamFeaturesParser extends GenericElementParser<StreamFeatures> {
+
+ public StreamFeaturesParser() {
+ super(StreamFeatures.class);
+ }
+
+ @Override
+ public void handleStartElement(String element, String ns, AttributeMap unused) {
+ if (currentDepth_ == 1) {
+ if (element.equals("starttls") && ns.equals("urn:ietf:params:xml:ns:xmpp-tls")) {
+ getElementGeneric().setHasStartTLS();
+ } else if (element.equals("session") && ns.equals("urn:ietf:params:xml:ns:xmpp-session")) {
+ getElementGeneric().setHasSession();
+ } else if (element.equals("bind") && ns.equals("urn:ietf:params:xml:ns:xmpp-bind")) {
+ getElementGeneric().setHasResourceBind();
+ } else if (element.equals("sm") && ns.equals("urn:xmpp:sm:2")) {
+ getElementGeneric().setHasStreamManagement();
+ } else if (element.equals("mechanisms") && ns.equals("urn:ietf:params:xml:ns:xmpp-sasl")) {
+ inMechanisms_ = true;
+ } else if (element.equals("compression") && ns.equals("http://jabber.org/features/compress")) {
+ inCompression_ = true;
+ }
+ } else if (currentDepth_ == 2) {
+ if (inCompression_ && element.equals("method")) {
+ inCompressionMethod_ = true;
+ currentText_ = "";
+ } else if (inMechanisms_ && element.equals("mechanism")) {
+ inMechanism_ = true;
+ currentText_ = "";
+ }
+ }
+ ++currentDepth_;
+ }
+
+ @Override
+ public void handleEndElement(String unused1, String unused2) {
+ --currentDepth_;
+ if (currentDepth_ == 1) {
+ inCompression_ = false;
+ inMechanisms_ = false;
+ } else if (currentDepth_ == 2) {
+ if (inCompressionMethod_) {
+ getElementGeneric().addCompressionMethod(currentText_);
+ inCompressionMethod_ = false;
+ } else if (inMechanism_) {
+ getElementGeneric().addAuthenticationMechanism(currentText_);
+ inMechanism_ = false;
+ }
+ }
+ }
+
+ @Override
+ public void handleCharacterData(String data) {
+ currentText_ = currentText_ + data;
+ }
+ private int currentDepth_ = 0;
+ private String currentText_ = "";
+ private boolean inMechanisms_ = false;
+ private boolean inMechanism_ = false;
+ private boolean inCompression_ = false;
+ private boolean inCompressionMethod_ = false;
+}
diff --git a/src/com/isode/stroke/parser/StreamManagementEnabledParser.java b/src/com/isode/stroke/parser/StreamManagementEnabledParser.java
new file mode 100644
index 0000000..a2d12b0
--- /dev/null
+++ b/src/com/isode/stroke/parser/StreamManagementEnabledParser.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2011 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.StreamManagementEnabled;
+
+class StreamManagementEnabledParser extends GenericElementParser<StreamManagementEnabled> {
+
+ private int level = 0;
+ private final static int TopLevel = 0;
+
+ public StreamManagementEnabledParser() {
+ super(StreamManagementEnabled.class);
+ }
+
+ @Override
+ public void handleStartElement(String el, String ns, AttributeMap attributes) {
+ if (level == TopLevel) {
+ if (attributes.getBoolAttribute("resume", false)) {
+ getElementGeneric().setResumeSupported();
+ }
+ getElementGeneric().setResumeID(attributes.getAttribute("id"));
+ }
+ ++level;
+ }
+
+ @Override
+ public void handleEndElement(String el, String ns) {
+ --level;
+ }
+}
diff --git a/src/com/isode/stroke/parser/StreamManagementFailedParser.java b/src/com/isode/stroke/parser/StreamManagementFailedParser.java
new file mode 100644
index 0000000..f8c63e7
--- /dev/null
+++ b/src/com/isode/stroke/parser/StreamManagementFailedParser.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.StreamManagementFailed;
+
+class StreamManagementFailedParser extends GenericElementParser<StreamManagementFailed> {
+
+ public StreamManagementFailedParser() {
+ super(StreamManagementFailed.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/StreamResumeParser.java b/src/com/isode/stroke/parser/StreamResumeParser.java
new file mode 100644
index 0000000..924e865
--- /dev/null
+++ b/src/com/isode/stroke/parser/StreamResumeParser.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2011 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.StreamResume;
+
+class StreamResumeParser extends GenericElementParser<StreamResume> {
+
+ private int level = 0;
+ private final static int TopLevel = 0;
+
+ public StreamResumeParser() {
+ super(StreamResume.class);
+ }
+
+ @Override
+ public void handleStartElement(String el, String ns, AttributeMap attributes) {
+ if (level == TopLevel) {
+ String handledStanzasCount = attributes.getAttributeValue("h");
+ if (handledStanzasCount != null) {
+ try {
+ getElementGeneric().setHandledStanzasCount(Long.parseLong(handledStanzasCount));
+ } catch (NumberFormatException e) {
+ }
+ }
+ getElementGeneric().setResumeID(attributes.getAttribute("previd"));
+ }
+ ++level;
+ }
+
+ @Override
+ public void handleEndElement(String el, String ns) {
+ --level;
+ }
+}
diff --git a/src/com/isode/stroke/parser/StreamResumedParser.java b/src/com/isode/stroke/parser/StreamResumedParser.java
new file mode 100644
index 0000000..2a874a2
--- /dev/null
+++ b/src/com/isode/stroke/parser/StreamResumedParser.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2011 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.StreamResumed;
+
+class StreamResumedParser extends GenericElementParser<StreamResumed> {
+
+ private int level = 0;
+ private final static int TopLevel = 0;
+
+ public StreamResumedParser() {
+ super(StreamResumed.class);
+ }
+
+ @Override
+ public void handleStartElement(String el, String ns, AttributeMap attributes) {
+ if (level == TopLevel) {
+ String handledStanzasCount = attributes.getAttributeValue("h");
+ if (handledStanzasCount != null) {
+ try {
+ getElementGeneric().setHandledStanzasCount(Long.parseLong(handledStanzasCount));
+ } catch (NumberFormatException e) {
+ }
+ }
+ getElementGeneric().setResumeID(attributes.getAttribute("previd"));
+ }
+ ++level;
+ }
+
+ @Override
+ public void handleEndElement(String el, String ns) {
+ --level;
+ }
+}
diff --git a/src/com/isode/stroke/parser/TLSProceedParser.java b/src/com/isode/stroke/parser/TLSProceedParser.java
new file mode 100644
index 0000000..b6f196d
--- /dev/null
+++ b/src/com/isode/stroke/parser/TLSProceedParser.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.TLSProceed;
+
+class TLSProceedParser extends GenericElementParser<TLSProceed>{
+
+ public TLSProceedParser() {
+ super(TLSProceed.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/UnknownElementParser.java b/src/com/isode/stroke/parser/UnknownElementParser.java
new file mode 100644
index 0000000..cf70dd6
--- /dev/null
+++ b/src/com/isode/stroke/parser/UnknownElementParser.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.UnknownElement;
+
+public class UnknownElementParser extends GenericElementParser<UnknownElement> {
+ public UnknownElementParser() {
+ super(UnknownElement.class);
+ }
+}
diff --git a/src/com/isode/stroke/parser/UnknownPayloadParser.java b/src/com/isode/stroke/parser/UnknownPayloadParser.java
new file mode 100644
index 0000000..2bdfbae
--- /dev/null
+++ b/src/com/isode/stroke/parser/UnknownPayloadParser.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.Payload;
+
+public class UnknownPayloadParser implements PayloadParser {
+
+ public void handleStartElement(String element, String ns, AttributeMap attributes) {
+
+ }
+
+ public void handleEndElement(String element, String ns) {
+
+ }
+
+ public void handleCharacterData(String data) {
+
+ }
+
+ public Payload getPayload() {
+ return null;
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/XMLParser.java b/src/com/isode/stroke/parser/XMLParser.java
new file mode 100644
index 0000000..a564b12
--- /dev/null
+++ b/src/com/isode/stroke/parser/XMLParser.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+public abstract class XMLParser {
+ private final XMLParserClient client_;
+
+ public XMLParser(XMLParserClient client) {
+ client_ = client;
+ }
+
+ public abstract boolean parse(String data);
+
+ protected XMLParserClient getClient() {
+ return client_;
+ }
+}
diff --git a/src/com/isode/stroke/parser/XMLParserClient.java b/src/com/isode/stroke/parser/XMLParserClient.java
new file mode 100644
index 0000000..5fb7071
--- /dev/null
+++ b/src/com/isode/stroke/parser/XMLParserClient.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+
+public interface XMLParserClient {
+ void handleStartElement(String element, String ns, AttributeMap attributes);
+
+ void handleEndElement(String element, String ns);
+
+ void handleCharacterData(String data);
+}
diff --git a/src/com/isode/stroke/parser/XMPPParser.java b/src/com/isode/stroke/parser/XMPPParser.java
new file mode 100644
index 0000000..c16a250
--- /dev/null
+++ b/src/com/isode/stroke/parser/XMPPParser.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.ProtocolHeader;
+import com.isode.stroke.eventloop.EventLoop;
+import java.util.logging.Logger;
+
+
+public class XMPPParser implements XMLParserClient {
+
+ private final XMLParser xmlParser_ ;
+ private final XMPPParserClient client_;
+ private final PayloadParserFactoryCollection payloadParserFactories_;
+ private int currentDepth_ = 0;
+ private ElementParser currentElementParser_ = null;
+ private boolean parseErrorOccurred_ = false;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public XMPPParser(XMPPParserClient parserClient, PayloadParserFactoryCollection payloadParserFactories, EventLoop eventLoop) {
+ client_ = parserClient;
+ payloadParserFactories_ = payloadParserFactories;
+ xmlParser_ = PlatformXMLParserFactory.createXMLParser(this, eventLoop);
+ }
+
+ public boolean parse(String data) {
+ parseErrorOccurred_ = false;
+ boolean xmlParseResult = xmlParser_.parse(data);
+ if (parseErrorOccurred_ || !xmlParseResult) {
+ logger_.warning(String.format("When parsing, %b and %b", parseErrorOccurred_, xmlParseResult));
+ }
+ return xmlParseResult && !parseErrorOccurred_;
+ }
+
+ public void handleStartElement(
+ String element,
+ String ns,
+ AttributeMap attributes) {
+ if (!inStream()) {
+ logger_.warning("Not in stream");
+ if (element.equals("stream") && ns.equals("http://etherx.jabber.org/streams")) {
+ ProtocolHeader header = new ProtocolHeader();
+ header.setFrom(attributes.getAttribute("from"));
+ header.setTo(attributes.getAttribute("to"));
+ header.setID(attributes.getAttribute("id"));
+ header.setVersion(attributes.getAttribute("version"));
+ client_.handleStreamStart(header);
+ } else {
+ parseErrorOccurred_ = true;
+ }
+ } else {
+ if (!inElement()) {
+ assert currentElementParser_ == null;
+ currentElementParser_ = createElementParser(element, ns);
+ }
+ currentElementParser_.handleStartElement(element, ns, attributes);
+ }
+ ++currentDepth_;
+ }
+
+ public void handleEndElement(String element, String ns) {
+ assert (inStream());
+ if (inElement()) {
+ assert currentElementParser_ != null;
+ currentElementParser_.handleEndElement(element, ns);
+ --currentDepth_;
+ if (!inElement()) {
+ client_.handleElement(currentElementParser_.getElement());
+ currentElementParser_ = null;
+ }
+ } else {
+ assert (element.equals("stream"));
+ --currentDepth_;
+ client_.handleStreamEnd();
+ }
+ }
+
+ public void handleCharacterData(String data) {
+ if (currentElementParser_ != null) {
+ currentElementParser_.handleCharacterData(data);
+ }
+ }
+
+ private boolean inStream() {
+ return currentDepth_ > 0;
+ }
+
+ private boolean inElement() {
+ return currentDepth_ > 1;
+ }
+
+ private ElementParser createElementParser(String element, String xmlns) {
+ if (element.equals("presence")) {
+ return new PresenceParser(payloadParserFactories_);
+ }
+ else if (element.equals("iq")) {
+ return new IQParser(payloadParserFactories_);
+ }
+ else if (element.equals("message")) {
+ return new MessageParser(payloadParserFactories_);
+ }
+ else if (element.equals("features") && xmlns.equals("http://etherx.jabber.org/streams")) {
+ return new StreamFeaturesParser();
+ }
+ else if (element.equals("auth")) {
+ return new AuthRequestParser();
+ }
+ else if (element.equals("success")) {
+ return new AuthSuccessParser();
+ }
+ else if (element.equals("failure") && xmlns.equals("urn:ietf:params:xml:ns:xmpp-sasl")) {
+ return new AuthFailureParser();
+ }
+ else if (element.equals("challenge") && xmlns.equals("urn:ietf:params:xml:ns:xmpp-sasl")) {
+ return new AuthChallengeParser();
+ }
+ else if (element.equals("response") && xmlns.equals("urn:ietf:params:xml:ns:xmpp-sasl")) {
+ return new AuthResponseParser();
+ }
+ else if (element.equals("starttls")) {
+ return new StartTLSParser();
+ }
+ else if (element.equals("failure") && xmlns.equals("urn:ietf:params:xml:ns:xmpp-tls")) {
+ return new StartTLSFailureParser();
+ }
+ else if (element.equals("compress")) {
+ return new CompressParser();
+ }
+ else if (element.equals("compressed")) {
+ return new CompressedParser();
+ }
+ else if (element.equals("failure") && xmlns.equals("http://jabber.org/protocol/compress")) {
+ return new CompressFailureParser();
+ }
+ else if (element.equals("proceed")) {
+ return new TLSProceedParser();
+ }
+ else if (element.equals("enable") && xmlns.equals("urn:xmpp:sm:2")) {
+ return new EnableStreamManagementParser();
+ }
+ else if (element.equals("enabled") && xmlns.equals("urn:xmpp:sm:2")) {
+ return new StreamManagementEnabledParser();
+ }
+ else if (element.equals("failed") && xmlns.equals("urn:xmpp:sm:2")) {
+ return new StreamManagementFailedParser();
+ }
+ else if (element.equals("resume") && xmlns.equals("urn:xmpp:sm:2")) {
+ return new StreamResumeParser();
+ }
+ else if (element.equals("resumed") && xmlns.equals("urn:xmpp:sm:2")) {
+ return new StreamResumedParser();
+ }
+ else if (element.equals("a") && xmlns.equals("urn:xmpp:sm:2")) {
+ return new StanzaAckParser();
+ }
+ else if (element.equals("r") && xmlns.equals("urn:xmpp:sm:2")) {
+ return new StanzaAckRequestParser();
+ }
+ return new UnknownElementParser();
+
+ }
+}
diff --git a/src/com/isode/stroke/parser/XMPPParserClient.java b/src/com/isode/stroke/parser/XMPPParserClient.java
new file mode 100644
index 0000000..c4499cd
--- /dev/null
+++ b/src/com/isode/stroke/parser/XMPPParserClient.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.ProtocolHeader;
+
+public interface XMPPParserClient {
+ void handleStreamStart(ProtocolHeader header);
+ void handleElement(Element element);
+ void handleStreamEnd();
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/BodyParser.java b/src/com/isode/stroke/parser/payloadparsers/BodyParser.java
new file mode 100644
index 0000000..3672c56
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/BodyParser.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.elements.Body;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.GenericPayloadParser;
+
+public class BodyParser extends GenericPayloadParser<Body> {
+
+ private int level_ = 0;
+ private String text_ = "";
+
+ public BodyParser() {
+ super(new Body());
+ }
+
+ public void handleStartElement(String element, String ns, AttributeMap attributes) {
+ ++level_;
+ }
+
+ public void handleEndElement(String element, String ns) {
+ --level_;
+ if (level_ == 0) {
+ getPayloadInternal().setText(text_);
+ }
+ }
+
+ public void handleCharacterData(String data) {
+ text_ += data;
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/BodyParserFactory.java b/src/com/isode/stroke/parser/payloadparsers/BodyParserFactory.java
new file mode 100644
index 0000000..154281b
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/BodyParserFactory.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010, 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.parser.GenericPayloadParserFactory;
+
+public class BodyParserFactory extends GenericPayloadParserFactory<BodyParser> {
+
+ public BodyParserFactory() {
+ super("body", BodyParser.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java b/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java
new file mode 100644
index 0000000..dfc1b61
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.parser.GenericPayloadParserFactory;
+import com.isode.stroke.parser.PayloadParserFactory;
+import com.isode.stroke.parser.PayloadParserFactoryCollection;
+
+public class FullPayloadParserFactoryCollection extends PayloadParserFactoryCollection {
+ public FullPayloadParserFactoryCollection() {
+ /* TODO: Port more */
+ //addFactory(new GenericPayloadParserFactory<IBBParser>("", "http://jabber.org/protocol/ibb"));
+ //addFactory(new GenericPayloadParserFactory<StatusShowParser>("show", StatusShowParser.class));
+ //addFactory(new GenericPayloadParserFactory<StatusParser>("status", StatusParser.class));
+ //addFactory(new GenericPayloadParserFactory<ReplaceParser>("replace", "http://swift.im/protocol/replace"));
+ addFactory(new GenericPayloadParserFactory<LastParser>("query", "jabber:iq:last", LastParser.class));
+ addFactory(new GenericPayloadParserFactory<BodyParser>("body", BodyParser.class));
+ //addFactory(new GenericPayloadParserFactory<SubjectParser>("subject", SubjectParser.class));
+ //addFactory(new GenericPayloadParserFactory<PriorityParser>("priority", PriorityParser.class));
+ //addFactory(new ErrorParserFactory(this)));
+ addFactory(new SoftwareVersionParserFactory());
+ //addFactory(new StorageParserFactory());
+ addFactory(new RosterParserFactory());
+ //addFactory(new DiscoInfoParserFactory());
+ //addFactory(new DiscoItemsParserFactory());
+ //addFactory(new CapsInfoParserFactory());
+ addFactory(new ResourceBindParserFactory());
+ addFactory(new StartSessionParserFactory());
+ //addFactory(new SecurityLabelParserFactory());
+ //addFactory(new SecurityLabelsCatalogParserFactory());
+ //addFactory(new FormParserFactory());
+ //addFactory(new CommandParserFactory());
+ //addFactery(new InBandRegistrationPayloadParserFactory());
+ addFactory(new SearchPayloadParserFactory());
+ //addFactory(new StreamInitiationParserFactory());
+ //addFactory(new BytestreamsParserFactory());
+ //addFactory(new VCardUpdateParserFactory());
+ //addFactory(new VCardParserFactory());
+ //addFactory(new PrivateStorageParserFactory(this));
+ //addFactory(new ChatStateParserFactory());
+ //addFactory(new DelayParserFactory());
+ //addFactory(new MUCUserPayloadParserFactory());
+ //addFactory(new NicknameParserFactory());
+
+
+ PayloadParserFactory defaultFactory = new RawXMLPayloadParserFactory();
+ setDefaultFactory(defaultFactory);
+ }
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/LastParser.java b/src/com/isode/stroke/parser/payloadparsers/LastParser.java
new file mode 100644
index 0000000..d2c2a82
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/LastParser.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2011, Kevin Smith.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.elements.Last;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.GenericPayloadParser;
+
+public class LastParser extends GenericPayloadParser<Last> {
+
+ private int level_ = 0;
+
+ public LastParser() {
+ super(new Last());
+ }
+
+ public void handleStartElement(String element, String ns, AttributeMap attributes) {
+ if (level_ == 0) {
+ int seconds = 0;
+
+ try {
+ seconds = Integer.parseInt(attributes.getAttribute("seconds"));
+ }
+ catch (NumberFormatException ex) {
+ }
+ getPayloadInternal().setSeconds(seconds);
+ }
+ ++level_;
+ }
+
+ public void handleEndElement(String element, String ns) {
+
+ }
+
+ public void handleCharacterData(String data) {
+
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/RawXMLPayloadParser.java b/src/com/isode/stroke/parser/payloadparsers/RawXMLPayloadParser.java
new file mode 100644
index 0000000..2a97fed
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/RawXMLPayloadParser.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.elements.RawXMLPayload;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.GenericPayloadParser;
+import com.isode.stroke.parser.SerializingParser;
+
+public class RawXMLPayloadParser extends GenericPayloadParser<RawXMLPayload> {
+
+ private int level_;
+ private final SerializingParser serializingParser_ = new SerializingParser();
+
+ public RawXMLPayloadParser() {
+ super(new RawXMLPayload());
+ }
+
+
+
+ public void handleStartElement(String element, String ns, AttributeMap attributes) {
+ ++level_;
+ serializingParser_.handleStartElement(element, ns, attributes);
+ }
+
+ public void handleEndElement(String element, String ns) {
+ serializingParser_.handleEndElement(element, ns);
+ --level_;
+ if (level_ == 0) {
+ getPayloadInternal().setRawXML(serializingParser_.getResult());
+ }
+ }
+
+ public void handleCharacterData(String data) {
+ serializingParser_.handleCharacterData(data);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/RawXMLPayloadParserFactory.java b/src/com/isode/stroke/parser/payloadparsers/RawXMLPayloadParserFactory.java
new file mode 100644
index 0000000..e0914e1
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/RawXMLPayloadParserFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.PayloadParser;
+import com.isode.stroke.parser.PayloadParserFactory;
+
+public class RawXMLPayloadParserFactory implements PayloadParserFactory {
+
+ public boolean canParse(String element, String ns, AttributeMap attributes) {
+ return true;
+ }
+
+ public PayloadParser createPayloadParser() {
+ return new RawXMLPayloadParser();
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/ResourceBindParser.java b/src/com/isode/stroke/parser/payloadparsers/ResourceBindParser.java
new file mode 100644
index 0000000..be409f4
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/ResourceBindParser.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2010, 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.elements.ResourceBind;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.GenericPayloadParser;
+
+public class ResourceBindParser extends GenericPayloadParser<ResourceBind> {
+
+ public ResourceBindParser() {
+ super(new ResourceBind());
+ level_ = 0;
+ inJID_ = false;
+ inResource_ = false;
+ }
+
+ public void handleStartElement(String element, String ns, AttributeMap attributes) {
+ if (level_ == 1) {
+ text_ = "";
+ if (element.equals("resource")) {
+ inResource_ = true;
+ }
+ if (element.equals("jid")) {
+ inJID_ = true;
+ }
+ }
+ ++level_;
+ }
+
+ public void handleEndElement(String element, String ns) {
+ --level_;
+ if (level_ == 1) {
+ if (inJID_) {
+ getPayloadInternal().setJID(JID.fromString(text_));
+ } else if (inResource_) {
+ getPayloadInternal().setResource(text_);
+ }
+ }
+ }
+
+ public void handleCharacterData(String data) {
+ text_ += data;
+ }
+ private int level_;
+ private boolean inJID_;
+ private boolean inResource_;
+ private String text_ = "";
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/ResourceBindParserFactory.java b/src/com/isode/stroke/parser/payloadparsers/ResourceBindParserFactory.java
new file mode 100644
index 0000000..a6b644d
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/ResourceBindParserFactory.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2010, 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.parser.GenericPayloadParserFactory;
+
+class ResourceBindParserFactory extends GenericPayloadParserFactory<ResourceBindParser> {
+
+ public ResourceBindParserFactory() {
+ super("bind", "urn:ietf:params:xml:ns:xmpp-bind", ResourceBindParser.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/RosterParser.java b/src/com/isode/stroke/parser/payloadparsers/RosterParser.java
new file mode 100644
index 0000000..fa115c2
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/RosterParser.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.elements.Payload;
+import com.isode.stroke.elements.RosterItemPayload;
+import com.isode.stroke.elements.RosterPayload;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.GenericPayloadParser;
+
+public class RosterParser extends GenericPayloadParser<RosterPayload> {
+
+ public RosterParser() {
+ super(new RosterPayload());
+ }
+
+ public void handleStartElement(String element, String ns, AttributeMap attributes) {
+ if (level_ == PayloadLevel) {
+ if (element.equals("item")) {
+ inItem_ = true;
+ currentItem_ = new RosterItemPayload();
+
+ currentItem_.setJID(JID.fromString(attributes.getAttribute("jid")));
+ currentItem_.setName(attributes.getAttribute("name"));
+
+ String subscription = attributes.getAttribute("subscription");
+ if ("both".equals(subscription)) {
+ currentItem_.setSubscription(RosterItemPayload.Subscription.Both);
+ } else if ("to".equals(subscription)) {
+ currentItem_.setSubscription(RosterItemPayload.Subscription.To);
+ } else if ("frome".equals(subscription)) {
+ currentItem_.setSubscription(RosterItemPayload.Subscription.From);
+ } else if ("remove".equals(subscription)) {
+ currentItem_.setSubscription(RosterItemPayload.Subscription.Remove);
+ } else {
+ currentItem_.setSubscription(RosterItemPayload.Subscription.None);
+ }
+
+ if ("subscribe".equals(attributes.getAttribute("ask"))) {
+ currentItem_.setSubscriptionRequested();
+ }
+ }
+ } else if (level_ == ItemLevel) {
+ if (element.equals("group")) {
+ currentText_ = "";
+ }
+ }
+ ++level_;
+ }
+
+ public void handleEndElement(String element, String ns) {
+ --level_;
+ if (level_ == PayloadLevel) {
+ if (inItem_) {
+ getPayloadInternal().addItem(currentItem_);
+ inItem_ = false;
+ }
+ } else if (level_ == ItemLevel) {
+ if (element.equals("group")) {
+ currentItem_.addGroup(currentText_);
+ }
+ }
+ }
+
+ public void handleCharacterData(String data) {
+ currentText_ += data;
+ }
+ private final int TopLevel = 0;
+ private final int PayloadLevel = 1;
+ private final int ItemLevel = 2;
+ private int level_ = TopLevel;
+ private boolean inItem_ = false;
+ private RosterItemPayload currentItem_;
+ private String currentText_;
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/RosterParserFactory.java b/src/com/isode/stroke/parser/payloadparsers/RosterParserFactory.java
new file mode 100644
index 0000000..ad690e1
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/RosterParserFactory.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010, 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.parser.GenericPayloadParserFactory;
+
+public class RosterParserFactory extends GenericPayloadParserFactory<RosterParser> {
+
+ public RosterParserFactory() {
+ super("query", "jabber:iq:roster", RosterParser.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/SearchPayloadParser.java b/src/com/isode/stroke/parser/payloadparsers/SearchPayloadParser.java
new file mode 100644
index 0000000..0ee1499
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/SearchPayloadParser.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.elements.SearchPayload;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.GenericPayloadParser;
+
+public class SearchPayloadParser extends GenericPayloadParser<SearchPayload> {
+
+ private static final int TopLevel = 0;
+ private static final int PayloadLevel = 1;
+ private static final int ItemLevel = 2;
+
+ private int level = 0;
+ private String currentText = "";
+ //private FormParserFactory formParserFactory = new FormParserFactory(); /* Not ported yet*/
+ //private FormParser formParser;
+ SearchPayload.Item currentItem;
+
+ public SearchPayloadParser() {
+ super(new SearchPayload());
+ }
+
+ public void handleStartElement(String element, String ns, AttributeMap attributes) {
+ if (level == TopLevel) {
+ }
+ else if (level == PayloadLevel) {
+ //if (element.equals("x") && ns.equals("jabber:x:data")) {
+ // assert formParser == null;
+ // formParser = dynamic_cast<FormParser*>(formParserFactory->createPayloadParser());
+ //} /* Not ported yet */
+ //else
+ if (element.equals("item")) {
+ assert currentItem == null;
+ currentItem = new SearchPayload.Item();
+ currentItem.jid = JID.fromString(attributes.getAttribute("jid"));
+ }
+ else {
+ currentText = "";
+ }
+ }
+ else if (level == ItemLevel && currentItem != null) {
+ currentText = "";
+ }
+
+ //if (formParser) {
+ // formParser->handleStartElement(element, ns, attributes);
+ //} /* Not ported yet */
+
+ ++level;
+ }
+
+ public void handleEndElement(String element, String ns) {
+ --level;
+
+ //if (formParser) {
+ // formParser->handleEndElement(element, ns);
+ //} /*Not Ported yet*/
+
+ if (level == TopLevel) {
+ }
+ else if (level == PayloadLevel) {
+ //if (formParser) {
+ // getPayloadInternal()->setForm(formParser->getPayloadInternal());
+ // delete formParser;
+ // formParser = NULL;
+ //}
+ //else /*Not ported yet*/
+ if (element.equals("item")) {
+ assert currentItem != null;
+ getPayloadInternal().addItem(currentItem);
+ currentItem = null;
+ }
+ else if (element.equals("instructions")) {
+ getPayloadInternal().setInstructions(currentText);
+ }
+ else if (element.equals("nick")) {
+ getPayloadInternal().setNick(currentText);
+ }
+ else if (element.equals("first")) {
+ getPayloadInternal().setFirst(currentText);
+ }
+ else if (element.equals("last")) {
+ getPayloadInternal().setLast(currentText);
+ }
+ else if (element.equals("email")) {
+ getPayloadInternal().setEMail(currentText);
+ }
+ }
+ else if (level == ItemLevel && currentItem != null) {
+ if (element.equals("nick")) {
+ currentItem.nick = currentText;
+ }
+ else if (element.equals("first")) {
+ currentItem.first = currentText;
+ }
+ else if (element.equals("last")) {
+ currentItem.last = currentText;
+ }
+ else if (element.equals("email")) {
+ currentItem.email = currentText;
+ }
+ }
+ }
+
+ public void handleCharacterData(String data) {
+ //if (formParser) {
+ // formParser->handleCharacterData(data);
+ //}
+ //else { /*Not ported yet*/
+ currentText += data;
+ //}
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/SearchPayloadParserFactory.java b/src/com/isode/stroke/parser/payloadparsers/SearchPayloadParserFactory.java
new file mode 100644
index 0000000..4add0a1
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/SearchPayloadParserFactory.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.parser.GenericPayloadParserFactory;
+import com.isode.stroke.parser.PayloadParser;
+
+public class SearchPayloadParserFactory extends GenericPayloadParserFactory<SearchPayloadParser> {
+
+ public SearchPayloadParserFactory() {
+ super("query", "jabber:iq:search", SearchPayloadParser.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/SoftwareVersionParser.java b/src/com/isode/stroke/parser/payloadparsers/SoftwareVersionParser.java
new file mode 100644
index 0000000..93563a3
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/SoftwareVersionParser.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.elements.Version;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.GenericPayloadParser;
+
+public class SoftwareVersionParser extends GenericPayloadParser<Version> {
+
+ public SoftwareVersionParser() {
+ super(new Version());
+ }
+
+ public void handleStartElement(String element, String ns, AttributeMap attributes) {
+ level_++;
+ }
+
+ public void handleEndElement(String element, String ns) {
+ --level_;
+ if (level_ == PayloadLevel) {
+ if (element.equals("name")) {
+ getPayloadInternal().setName(currentText_);
+ } else if (element.equals("version")) {
+ getPayloadInternal().setVersion(currentText_);
+ } else if (element.equals("os")) {
+ getPayloadInternal().setOS(currentText_);
+ }
+ currentText_ = "";
+ }
+
+ }
+
+ public void handleCharacterData(String data) {
+ currentText_ += data;
+ }
+
+ private static final int TopLevel = 0;
+ private static final int PayloadLevel = 1;
+ private int level_ = TopLevel;
+ private String currentText_ = "";
+
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/SoftwareVersionParserFactory.java b/src/com/isode/stroke/parser/payloadparsers/SoftwareVersionParserFactory.java
new file mode 100644
index 0000000..e2180f5
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/SoftwareVersionParserFactory.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.parser.GenericPayloadParserFactory;
+
+public class SoftwareVersionParserFactory extends GenericPayloadParserFactory<SoftwareVersionParser> {
+
+ public SoftwareVersionParserFactory() {
+ super("query", "jabber:iq:version", SoftwareVersionParser.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/StartSessionParser.java b/src/com/isode/stroke/parser/payloadparsers/StartSessionParser.java
new file mode 100644
index 0000000..ba7804c
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/StartSessionParser.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010, 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.elements.StartSession;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.GenericPayloadParser;
+
+public class StartSessionParser extends GenericPayloadParser<StartSession> {
+
+ public StartSessionParser() {
+ super(new StartSession());
+ }
+
+ public void handleStartElement(String element, String ns, AttributeMap attributes) {
+
+ }
+
+ public void handleEndElement(String element, String ns) {
+
+ }
+
+ public void handleCharacterData(String data) {
+
+ }
+
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/StartSessionParserFactory.java b/src/com/isode/stroke/parser/payloadparsers/StartSessionParserFactory.java
new file mode 100644
index 0000000..c2c9777
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/StartSessionParserFactory.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010, 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.parser.GenericPayloadParserFactory;
+
+class StartSessionParserFactory extends GenericPayloadParserFactory<StartSessionParser> {
+
+ public StartSessionParserFactory() {
+ super("session", "urn:ietf:params:xml:ns:xmpp-session", StartSessionParser.class);
+ }
+
+}
diff --git a/src/com/isode/stroke/queries/GenericRequest.java b/src/com/isode/stroke/queries/GenericRequest.java
new file mode 100644
index 0000000..cfbfb52
--- /dev/null
+++ b/src/com/isode/stroke/queries/GenericRequest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2010, 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.queries;
+
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.elements.Payload;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.signals.Signal2;
+
+/**
+ * IQ Request for standard payload.
+ */
+public class GenericRequest<T extends Payload> extends Request {
+
+ public Signal2<T, ErrorPayload> onResponse = new Signal2<T, ErrorPayload>();
+
+ public GenericRequest(IQ.Type type, JID receiver, Payload payload, IQRouter router) {
+ super(type, receiver, payload, router);
+ }
+
+ @Override
+ protected void handleResponse(Payload payload, ErrorPayload error) {
+ T genericPayload = null;
+ try {
+ genericPayload = (T)payload;
+ } catch (Exception ex) {
+ /* This isn't legal XMPP, so treat as NULL.*/
+ }
+ onResponse.emit(genericPayload, error);
+ }
+
+ protected T getPayloadGeneric() {
+ return (T)getPayload();
+ }
+
+}
diff --git a/src/com/isode/stroke/queries/GetRosterRequest.java b/src/com/isode/stroke/queries/GetRosterRequest.java
new file mode 100644
index 0000000..1c7ea6e
--- /dev/null
+++ b/src/com/isode/stroke/queries/GetRosterRequest.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.queries;
+
+import com.isode.stroke.elements.IQ.Type;
+import com.isode.stroke.elements.RosterPayload;
+import com.isode.stroke.jid.JID;
+
+public class GetRosterRequest extends GenericRequest<RosterPayload> {
+ public GetRosterRequest(JID target, IQRouter iqRouter) {
+ super(Type.Get, target, new RosterPayload(), iqRouter);
+ }
+
+ public GetRosterRequest(IQRouter iqRouter) {
+ super(Type.Get, new JID(), new RosterPayload(), iqRouter);
+ }
+}
diff --git a/src/com/isode/stroke/queries/GetVersionRequest.java b/src/com/isode/stroke/queries/GetVersionRequest.java
new file mode 100644
index 0000000..75db384
--- /dev/null
+++ b/src/com/isode/stroke/queries/GetVersionRequest.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.queries;
+
+import com.isode.stroke.elements.IQ.Type;
+import com.isode.stroke.elements.Version;
+import com.isode.stroke.jid.JID;
+
+public class GetVersionRequest extends GenericRequest<Version> {
+ public GetVersionRequest(JID target, IQRouter iqRouter) {
+ super(Type.Get, target, new Version(), iqRouter);
+ }
+
+ public GetVersionRequest(IQRouter iqRouter) {
+ super(Type.Get, new JID(), new Version(), iqRouter);
+ }
+}
diff --git a/src/com/isode/stroke/queries/IQChannel.java b/src/com/isode/stroke/queries/IQChannel.java
new file mode 100644
index 0000000..fd5fe8e
--- /dev/null
+++ b/src/com/isode/stroke/queries/IQChannel.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.queries;
+
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.signals.Signal1;
+
+public abstract class IQChannel {
+
+ public abstract void sendIQ(IQ iq);
+
+ public abstract String getNewIQID();
+
+ public abstract boolean isAvailable();
+
+ public final Signal1<IQ> onIQReceived = new Signal1<IQ>();
+}
diff --git a/src/com/isode/stroke/queries/IQHandler.java b/src/com/isode/stroke/queries/IQHandler.java
new file mode 100644
index 0000000..2d6f35e
--- /dev/null
+++ b/src/com/isode/stroke/queries/IQHandler.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.queries;
+
+import com.isode.stroke.elements.IQ;
+
+/**
+ * Thing reacting to IQs.
+ */
+public interface IQHandler {
+ boolean handleIQ(IQ iq);
+}
diff --git a/src/com/isode/stroke/queries/IQReceivedHandler.java b/src/com/isode/stroke/queries/IQReceivedHandler.java
new file mode 100644
index 0000000..37db43a
--- /dev/null
+++ b/src/com/isode/stroke/queries/IQReceivedHandler.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.queries;
+
+import com.isode.stroke.elements.IQ;
+
+interface IQReceivedHandler {
+ void handleIQ(IQ iq);
+}
diff --git a/src/com/isode/stroke/queries/IQRouter.java b/src/com/isode/stroke/queries/IQRouter.java
new file mode 100644
index 0000000..04ddb20
--- /dev/null
+++ b/src/com/isode/stroke/queries/IQRouter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.queries;
+
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.signals.Slot1;
+import java.util.Vector;
+
+/**
+ * Route IQs to handlers
+ */
+public class IQRouter {
+
+ private final Vector<IQHandler> handlers_ = new Vector<IQHandler>();
+ private final IQChannel channel_;
+
+ public IQRouter(IQChannel channel) {
+ channel_ = channel;
+ final IQRouter thisObject = this;
+ channel_.onIQReceived.connect(new Slot1<IQ>() {
+
+ public void call(IQ p1) {
+ handleIQ(p1);
+ }
+ });
+ }
+
+ public void addHandler(IQHandler handler) {
+ synchronized (handlers_) {
+ handlers_.add(handler);
+ }
+ }
+
+ public void removeHandler(IQHandler handler) {
+ synchronized (handlers_) {
+ handlers_.remove(handler);
+ }
+ }
+
+ public void sendIQ(IQ iq) {
+ channel_.sendIQ(iq);
+ }
+
+ public String getNewIQID() {
+ return channel_.getNewIQID();
+ }
+
+ public boolean isAvailable() {
+ return channel_.isAvailable();
+ }
+
+ private void handleIQ(IQ iq) {
+ boolean handled = false;
+ synchronized (handlers_) {
+ for (IQHandler handler : handlers_) {
+ handled |= handler.handleIQ(iq);
+ if (handled) {
+ break;
+ }
+ }
+ }
+ if (!handled && (iq.getType().equals(IQ.Type.Get) || iq.getType().equals(IQ.Type.Set))) {
+ sendIQ(IQ.createError(iq.getFrom(), iq.getID(), ErrorPayload.Condition.FeatureNotImplemented, ErrorPayload.Type.Cancel));
+ }
+ }
+}
diff --git a/src/com/isode/stroke/queries/Request.java b/src/com/isode/stroke/queries/Request.java
new file mode 100644
index 0000000..df38b10
--- /dev/null
+++ b/src/com/isode/stroke/queries/Request.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.queries;
+
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.elements.IQ.Type;
+import com.isode.stroke.elements.Payload;
+import com.isode.stroke.jid.JID;
+
+/**
+ * Base class for IQ requests.
+ */
+public abstract class Request implements IQHandler {
+ protected final Type type_;
+ protected final IQRouter router_;
+ protected final JID receiver_;
+ private boolean sent_;
+ private Payload payload_;
+ private String id_;
+
+ public Request(IQ.Type type, JID receiver, IQRouter router) {
+ this(type, receiver, null, router);
+ }
+
+ public Request(IQ.Type type, JID receiver, Payload payload, IQRouter router) {
+ type_ = type;
+ router_ = router;
+ receiver_ = receiver;
+ payload_ = payload;
+ sent_ = false;
+ }
+
+ public void send() {
+ assert payload_ != null;
+ assert !sent_;
+ sent_ = true;
+
+ IQ iq = new IQ(type_);
+ iq.setTo(receiver_);
+ iq.addPayload(payload_);
+ id_ = router_.getNewIQID();
+ iq.setID(id_);
+
+ router_.addHandler(this);
+
+ router_.sendIQ(iq);
+ }
+
+ protected void setPayload(Payload payload) {
+ payload_ = payload;
+ }
+
+ protected Payload getPayload() {
+ return payload_;
+ }
+
+ protected abstract void handleResponse(Payload payload, ErrorPayload error);
+
+ public boolean handleIQ(IQ iq) {
+ boolean handled = false;
+ if (sent_ && iq.getID().equals(id_)) {
+ if (iq.getType().equals(IQ.Type.Result)) {
+ handleResponse(iq.getPayload(payload_), null);
+ } else {
+ ErrorPayload errorPayload = iq.getPayload(new ErrorPayload());
+ if (errorPayload != null) {
+ handleResponse(null, errorPayload);
+ } else {
+ handleResponse(null, new ErrorPayload(ErrorPayload.Condition.UndefinedCondition));
+ }
+ }
+ router_.removeHandler(this);
+ handled = true;
+ }
+ return handled;
+ }
+
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/sasl/ClientAuthenticator.java b/src/com/isode/stroke/sasl/ClientAuthenticator.java
new file mode 100644
index 0000000..e8fcff9
--- /dev/null
+++ b/src/com/isode/stroke/sasl/ClientAuthenticator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.sasl;
+
+import com.isode.stroke.base.ByteArray;
+
+public abstract class ClientAuthenticator {
+
+ public ClientAuthenticator(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setCredentials(String authcid, String password) {
+ setCredentials(authcid, password, "");
+ }
+
+ public void setCredentials(String authcid, String password, String authzid) {
+ this.authcid = authcid;
+ this.password = password;
+ this.authzid = authzid;
+ }
+
+ public abstract ByteArray getResponse();
+
+ public abstract boolean setChallenge(ByteArray challenge);
+
+ public String getAuthenticationID() {
+ return authcid;
+ }
+
+ public String getAuthorizationID() {
+ return authzid;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+ private String name;
+ private String authcid;
+ private String password;
+ private String authzid;
+}
diff --git a/src/com/isode/stroke/sasl/PLAINClientAuthenticator.java b/src/com/isode/stroke/sasl/PLAINClientAuthenticator.java
new file mode 100644
index 0000000..889ac54
--- /dev/null
+++ b/src/com/isode/stroke/sasl/PLAINClientAuthenticator.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.sasl;
+
+import com.isode.stroke.base.ByteArray;
+
+public class PLAINClientAuthenticator extends ClientAuthenticator {
+ public PLAINClientAuthenticator() {
+ super("PLAIN");
+ }
+
+ public ByteArray getResponse() {
+ return new ByteArray().append(getAuthorizationID()).append((byte)0).append(getAuthenticationID()).append((byte)0).append(getPassword());
+ }
+
+ public boolean setChallenge(ByteArray challenge) {
+ return true;
+ }
+}
+
diff --git a/src/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java b/src/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java
new file mode 100644
index 0000000..da44d85
--- /dev/null
+++ b/src/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.sasl;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.stringcodecs.Base64;
+import com.isode.stroke.stringcodecs.HMACSHA1;
+import com.isode.stroke.stringcodecs.PBKDF2;
+import com.isode.stroke.stringcodecs.SHA1;
+import java.text.Normalizer;
+import java.text.Normalizer.Form;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SCRAMSHA1ClientAuthenticator extends ClientAuthenticator {
+
+ static String escape(String s) {
+ String result = "";
+ for (int i = 0; i < s.length(); ++i) {
+ if (s.charAt(i) == ',') {
+ result += "=2C";
+ } else if (s.charAt(i) == '=') {
+ result += "=3D";
+ } else {
+ result += s.charAt(i);
+ }
+ }
+ return result;
+ }
+
+ public SCRAMSHA1ClientAuthenticator(String nonce) {
+ this(nonce, false);
+ }
+ public SCRAMSHA1ClientAuthenticator(String nonce, boolean useChannelBinding) {
+ super(useChannelBinding ? "SCRAM-SHA-1-PLUS" : "SCRAM-SHA-1");
+ step = Step.Initial;
+ clientnonce = nonce;
+ this.useChannelBinding = useChannelBinding;
+ }
+
+ public void setTLSChannelBindingData(ByteArray channelBindingData) {
+ tlsChannelBindingData = channelBindingData;
+ }
+
+ public ByteArray getResponse() {
+ if (step.equals(Step.Initial)) {
+ return ByteArray.plus(getGS2Header(), getInitialBareClientMessage());
+ } else if (step.equals(Step.Proof)) {
+ ByteArray clientKey = HMACSHA1.getResult(saltedPassword, new ByteArray("Client Key"));
+ ByteArray storedKey = SHA1.getHash(clientKey);
+ ByteArray clientSignature = HMACSHA1.getResult(storedKey, authMessage);
+ ByteArray clientProof = clientKey;
+ byte[] clientProofData = clientProof.getData();
+ for (int i = 0; i < clientProofData.length; ++i) {
+ clientProofData[i] ^= clientSignature.getData()[i];
+ }
+ ByteArray result = getFinalMessageWithoutProof().append(",p=").append(Base64.encode(clientProof));
+ return result;
+ } else {
+ return null;
+ }
+ }
+
+ public boolean setChallenge(ByteArray challenge) {
+ if (step.equals(Step.Initial)) {
+ if (challenge == null) {
+ return false;
+ }
+ initialServerMessage = challenge;
+
+ Map<Character, String> keys = parseMap(initialServerMessage.toString());
+
+ // Extract the salt
+ ByteArray salt = Base64.decode(keys.get('s'));
+
+ // Extract the server nonce
+ String clientServerNonce = keys.get('r');
+ if (clientServerNonce.length() <= clientnonce.length()) {
+ return false;
+ }
+ String receivedClientNonce = clientServerNonce.substring(0, clientnonce.length());
+ if (!receivedClientNonce.equals(clientnonce)) {
+ return false;
+ }
+ serverNonce = new ByteArray(clientServerNonce.substring(clientnonce.length()));
+
+
+ // Extract the number of iterations
+ int iterations = 0;
+ try {
+ iterations = Integer.parseInt(keys.get('i'));
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ if (iterations <= 0) {
+ return false;
+ }
+
+ ByteArray channelBindData = new ByteArray();
+ if (useChannelBinding && tlsChannelBindingData != null) {
+ channelBindData = tlsChannelBindingData;
+ }
+
+ // Compute all the values needed for the server signature
+ saltedPassword = PBKDF2.encode(new ByteArray(SASLPrep(getPassword())), salt, iterations);
+ authMessage = getInitialBareClientMessage().append(",").append(initialServerMessage).append(",").append(getFinalMessageWithoutProof());
+ ByteArray serverKey = HMACSHA1.getResult(saltedPassword, new ByteArray("Server Key"));
+ serverSignature = HMACSHA1.getResult(serverKey, authMessage);
+
+ step = Step.Proof;
+ return true;
+ } else if (step.equals(step.Proof)) {
+ ByteArray result = new ByteArray("v=").append(new ByteArray(Base64.encode(serverSignature)));
+ step = Step.Final;
+ return challenge != null && challenge.equals(result);
+ } else {
+ return true;
+ }
+ }
+
+ private String SASLPrep(String source) {
+ return Normalizer.normalize(source, Form.NFKC); /* FIXME: Implement real SASLPrep */
+ }
+
+ private Map<Character, String> parseMap(String s) {
+ HashMap<Character, String> result = new HashMap<Character, String>();
+ if (s.length() > 0) {
+ char key = '~'; /* initialise so it'll compile */
+ String value = "";
+ int i = 0;
+ boolean expectKey = true;
+ while (i < s.length()) {
+ if (expectKey) {
+ key = s.charAt(i);
+ expectKey = false;
+ i++;
+ } else if (s.charAt(i) == ',') {
+ result.put(key, value);
+ value = "";
+ expectKey = true;
+ } else {
+ value += s.charAt(i);
+ }
+ i++;
+ }
+ result.put(key, value);
+ }
+ return result;
+ }
+
+ private ByteArray getInitialBareClientMessage() {
+ String authenticationID = SASLPrep(getAuthenticationID());
+ return new ByteArray("n=" + escape(authenticationID) + ",r=" + clientnonce);
+ }
+
+ private ByteArray getGS2Header() {
+
+ ByteArray channelBindingHeader = new ByteArray("n");
+ if (tlsChannelBindingData != null) {
+ if (useChannelBinding) {
+ channelBindingHeader = new ByteArray("p=tls-unique");
+ }
+ else {
+ channelBindingHeader = new ByteArray("y");
+ }
+ }
+ return new ByteArray().append(channelBindingHeader).append(",").append(getAuthorizationID().isEmpty() ? new ByteArray() : new ByteArray("a=" + escape(getAuthorizationID()))).append(",");
+ }
+
+ private ByteArray getFinalMessageWithoutProof() {
+ ByteArray channelBindData = new ByteArray();
+ if (useChannelBinding && tlsChannelBindingData != null) {
+ channelBindData = tlsChannelBindingData;
+ }
+ return new ByteArray("c=" + Base64.encode(new ByteArray(getGS2Header()).append(channelBindData)) + ",r=" + clientnonce).append(serverNonce);
+ }
+
+ private enum Step {
+
+ Initial,
+ Proof,
+ Final
+ };
+ private Step step;
+ private String clientnonce = "";
+ private ByteArray initialServerMessage = new ByteArray();
+ private ByteArray serverNonce = new ByteArray();
+ private ByteArray authMessage = new ByteArray();
+ private ByteArray saltedPassword = new ByteArray();
+ private ByteArray serverSignature = new ByteArray();
+ private boolean useChannelBinding;
+ private ByteArray tlsChannelBindingData;
+}
diff --git a/src/com/isode/stroke/serializer/AuthChallengeSerializer.java b/src/com/isode/stroke/serializer/AuthChallengeSerializer.java
new file mode 100644
index 0000000..eee392e
--- /dev/null
+++ b/src/com/isode/stroke/serializer/AuthChallengeSerializer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.elements.AuthChallenge;
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.stringcodecs.Base64;
+
+class AuthChallengeSerializer extends GenericElementSerializer<AuthChallenge> {
+
+ public AuthChallengeSerializer() {
+ super(AuthChallenge.class);
+ }
+
+ public String serialize(Element element) {
+ AuthChallenge authChallenge = (AuthChallenge)element;
+ String value = "";
+ ByteArray message = authChallenge.getValue();
+ if (message != null) {
+ if (message.isEmpty()) {
+ value = "=";
+ }
+ else {
+ value = Base64.encode(message);
+ }
+ }
+ return "<challenge xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">" + value + "</challenge>";
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/AuthFailureSerializer.java b/src/com/isode/stroke/serializer/AuthFailureSerializer.java
new file mode 100644
index 0000000..e75a0f4
--- /dev/null
+++ b/src/com/isode/stroke/serializer/AuthFailureSerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.AuthFailure;
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class AuthFailureSerializer extends GenericElementSerializer<AuthFailure>{
+
+ public AuthFailureSerializer() {
+ super(AuthFailure.class);
+ }
+
+ public String serialize(Element element) {
+ return new XMLElement("failure", "urn:ietf:params:xml:ns:xmpp-sasl").serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/AuthRequestSerializer.java b/src/com/isode/stroke/serializer/AuthRequestSerializer.java
new file mode 100644
index 0000000..eb3fbd5
--- /dev/null
+++ b/src/com/isode/stroke/serializer/AuthRequestSerializer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.elements.AuthRequest;
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.stringcodecs.Base64;
+
+class AuthRequestSerializer extends GenericElementSerializer<AuthRequest> {
+
+ public AuthRequestSerializer() {
+ super(AuthRequest.class);
+ }
+
+ public String serialize(Element element) {
+ AuthRequest authRequest = (AuthRequest)element;
+ String value = "";
+ ByteArray message = authRequest.getMessage();
+ if (message != null) {
+ if (message.isEmpty()) {
+ value = "=";
+ }
+ else {
+ value = Base64.encode(message);
+ }
+ }
+ return "<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" mechanism=\"" + authRequest.getMechanism() + "\">" + value + "</auth>";
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/AuthResponseSerializer.java b/src/com/isode/stroke/serializer/AuthResponseSerializer.java
new file mode 100644
index 0000000..b65f4b1
--- /dev/null
+++ b/src/com/isode/stroke/serializer/AuthResponseSerializer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.elements.AuthResponse;
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.stringcodecs.Base64;
+
+class AuthResponseSerializer extends GenericElementSerializer<AuthResponse> {
+
+ public AuthResponseSerializer() {
+ super(AuthResponse.class);
+ }
+
+ public String serialize(Element element) {
+ AuthResponse authResponse = (AuthResponse) element;
+ String value = "";
+ ByteArray message = authResponse.getValue();
+ if (message != null) {
+ if (message.isEmpty()) {
+ value = "=";
+ } else {
+ value = Base64.encode(message);
+ }
+ }
+ return "<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">" + value + "</response>";
+ }
+}
diff --git a/src/com/isode/stroke/serializer/AuthSuccessSerializer.java b/src/com/isode/stroke/serializer/AuthSuccessSerializer.java
new file mode 100644
index 0000000..1b8be25
--- /dev/null
+++ b/src/com/isode/stroke/serializer/AuthSuccessSerializer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.elements.AuthSuccess;
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.stringcodecs.Base64;
+
+
+class AuthSuccessSerializer extends GenericElementSerializer<AuthSuccess> {
+
+ public AuthSuccessSerializer() {
+ super(AuthSuccess.class);
+ }
+
+ public String serialize(Element element) {
+ AuthSuccess authSuccess = (AuthSuccess)element;
+ String value = "";
+ ByteArray message = authSuccess.getValue();
+ if (message != null) {
+ if (message.isEmpty()) {
+ value = "=";
+ }
+ else {
+ value = Base64.encode(message);
+ }
+ }
+ return "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">" + value + "</success>";
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/CompressFailureSerializer.java b/src/com/isode/stroke/serializer/CompressFailureSerializer.java
new file mode 100644
index 0000000..fe31f27
--- /dev/null
+++ b/src/com/isode/stroke/serializer/CompressFailureSerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.CompressFailure;
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class CompressFailureSerializer extends GenericElementSerializer<CompressFailure> {
+
+ public CompressFailureSerializer() {
+ super(CompressFailure.class);
+ }
+
+ public String serialize(Element element) {
+ return new XMLElement("failure", "http://jabber.org/protocol/compress").serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/CompressRequestSerializer.java b/src/com/isode/stroke/serializer/CompressRequestSerializer.java
new file mode 100644
index 0000000..512e178
--- /dev/null
+++ b/src/com/isode/stroke/serializer/CompressRequestSerializer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.CompressRequest;
+import com.isode.stroke.elements.Element;
+
+class CompressRequestSerializer implements ElementSerializer {
+
+ public CompressRequestSerializer() {
+ }
+
+ public String serialize(Element element) {
+ CompressRequest compressRequest = (CompressRequest) element;
+ return "<compress xmlns='http://jabber.org/protocol/compress'><method>" + compressRequest.getMethod() + "</method></compress>";
+ }
+
+ public boolean canSerialize(Element element) {
+ return element instanceof CompressRequest;
+ }
+}
diff --git a/src/com/isode/stroke/serializer/ElementSerializer.java b/src/com/isode/stroke/serializer/ElementSerializer.java
new file mode 100644
index 0000000..8f4ade7
--- /dev/null
+++ b/src/com/isode/stroke/serializer/ElementSerializer.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+
+public interface ElementSerializer {
+ String serialize(Element element);
+ boolean canSerialize(Element element);
+}
diff --git a/src/com/isode/stroke/serializer/EnableStreamManagementSerializer.java b/src/com/isode/stroke/serializer/EnableStreamManagementSerializer.java
new file mode 100644
index 0000000..8862bb3
--- /dev/null
+++ b/src/com/isode/stroke/serializer/EnableStreamManagementSerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.EnableStreamManagement;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class EnableStreamManagementSerializer extends GenericElementSerializer<EnableStreamManagement> {
+
+ public EnableStreamManagementSerializer() {
+ super(EnableStreamManagement.class);
+ }
+
+ public String serialize(Element element) {
+ return new XMLElement("enable", "urn:xmpp:sm:2").serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/GenericElementSerializer.java b/src/com/isode/stroke/serializer/GenericElementSerializer.java
new file mode 100644
index 0000000..ecd5bf4
--- /dev/null
+++ b/src/com/isode/stroke/serializer/GenericElementSerializer.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+
+public abstract class GenericElementSerializer<T> implements ElementSerializer {
+
+ private final Class elementClass_;
+
+ GenericElementSerializer(Class elementClass) {
+ elementClass_ = elementClass;
+ }
+
+ public boolean canSerialize(Element element) {
+ return elementClass_.isAssignableFrom(element.getClass());
+ }
+
+
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/serializer/GenericPayloadSerializer.java b/src/com/isode/stroke/serializer/GenericPayloadSerializer.java
new file mode 100644
index 0000000..3f5c92e
--- /dev/null
+++ b/src/com/isode/stroke/serializer/GenericPayloadSerializer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Payload;
+
+public abstract class GenericPayloadSerializer<T extends Payload> extends PayloadSerializer {
+
+ private final Class class_;
+
+ public GenericPayloadSerializer(Class c) {
+ class_ = c;
+ }
+
+ @Override
+ public boolean canSerialize(Payload payload) {
+ return class_.isAssignableFrom(payload.getClass());
+ }
+
+ @Override
+ public String serialize(Payload payload) {
+ return serializePayload((T)payload);
+ }
+
+ protected abstract String serializePayload(T payload);
+
+}
diff --git a/src/com/isode/stroke/serializer/GenericStanzaSerializer.java b/src/com/isode/stroke/serializer/GenericStanzaSerializer.java
new file mode 100644
index 0000000..89ac1d1
--- /dev/null
+++ b/src/com/isode/stroke/serializer/GenericStanzaSerializer.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.Stanza;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+public abstract class GenericStanzaSerializer<T extends Stanza> extends StanzaSerializer {
+
+ private final Class stanzaClass_;
+
+ GenericStanzaSerializer(Class stanzaClass, String tag, PayloadSerializerCollection payloadSerializers) {
+ super(tag, payloadSerializers);
+ stanzaClass_ = stanzaClass;
+ }
+
+ public boolean canSerialize(Element element) {
+ return stanzaClass_.isAssignableFrom(element.getClass());
+ }
+
+ public void setStanzaSpecificAttributes(Element stanza, XMLElement element) {
+ setStanzaSpecificAttributesGeneric((T)stanza, element);
+ }
+
+ abstract void setStanzaSpecificAttributesGeneric(T stanza, XMLElement element);
+}
diff --git a/src/com/isode/stroke/serializer/IQSerializer.java b/src/com/isode/stroke/serializer/IQSerializer.java
new file mode 100644
index 0000000..0025aad
--- /dev/null
+++ b/src/com/isode/stroke/serializer/IQSerializer.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+public class IQSerializer extends GenericStanzaSerializer<IQ> {
+
+ public IQSerializer(PayloadSerializerCollection payloadSerializers) {
+ super(IQ.class, "iq", payloadSerializers);
+ }
+
+ @Override
+ void setStanzaSpecificAttributesGeneric(IQ iq, XMLElement element) {
+ switch (iq.getType()) {
+ case Get:
+ element.setAttribute("type", "get");
+ break;
+ case Set:
+ element.setAttribute("type", "set");
+ break;
+ case Result:
+ element.setAttribute("type", "result");
+ break;
+ case Error:
+ element.setAttribute("type", "error");
+ break;
+ }
+ }
+}
diff --git a/src/com/isode/stroke/serializer/MessageSerializer.java b/src/com/isode/stroke/serializer/MessageSerializer.java
new file mode 100644
index 0000000..27d5c73
--- /dev/null
+++ b/src/com/isode/stroke/serializer/MessageSerializer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Message;
+import com.isode.stroke.elements.Stanza;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+public class MessageSerializer extends GenericStanzaSerializer<Message>{
+
+ public MessageSerializer(PayloadSerializerCollection payloadSerializers) {
+ super(Message.class, "message", payloadSerializers);
+ }
+
+ @Override
+ void setStanzaSpecificAttributesGeneric(Message message, XMLElement element) {
+ if (message.getType().equals(Message.Type.Chat)) {
+ element.setAttribute("type", "chat");
+ }
+ else if (message.getType().equals(Message.Type.Groupchat)) {
+ element.setAttribute("type", "groupchat");
+ }
+ else if (message.getType().equals(Message.Type.Headline)) {
+ element.setAttribute("type", "headline");
+ }
+ else if (message.getType().equals(Message.Type.Error)) {
+ element.setAttribute("type", "error");
+ }
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/PayloadSerializer.java b/src/com/isode/stroke/serializer/PayloadSerializer.java
new file mode 100644
index 0000000..2d15408
--- /dev/null
+++ b/src/com/isode/stroke/serializer/PayloadSerializer.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Payload;
+
+/**
+ * Serialise a particular payload.
+ */
+public abstract class PayloadSerializer {
+ public abstract boolean canSerialize(Payload payload);
+ public abstract String serialize(Payload payload);
+}
diff --git a/src/com/isode/stroke/serializer/PayloadSerializerCollection.java b/src/com/isode/stroke/serializer/PayloadSerializerCollection.java
new file mode 100644
index 0000000..49ffe41
--- /dev/null
+++ b/src/com/isode/stroke/serializer/PayloadSerializerCollection.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Payload;
+import java.util.Vector;
+
+public class PayloadSerializerCollection {
+
+ private final Vector<PayloadSerializer> serializers_ = new Vector<PayloadSerializer>();
+
+ public void addSerializer(PayloadSerializer serializer) {
+ synchronized (serializers_) {
+ serializers_.add(serializer);
+ }
+ }
+
+ public PayloadSerializer getPayloadSerializer(Payload payload) {
+ synchronized (serializers_) {
+ for (PayloadSerializer serializer : serializers_) {
+ if (serializer.canSerialize(payload)) {
+ return serializer;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/isode/stroke/serializer/PresenceSerializer.java b/src/com/isode/stroke/serializer/PresenceSerializer.java
new file mode 100644
index 0000000..a7439f7
--- /dev/null
+++ b/src/com/isode/stroke/serializer/PresenceSerializer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Presence;
+import com.isode.stroke.elements.Stanza;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+public class PresenceSerializer extends GenericStanzaSerializer<Presence> {
+public PresenceSerializer(PayloadSerializerCollection payloadSerializers) {
+ super(Presence.class, "presence", payloadSerializers);
+}
+
+ @Override
+ void setStanzaSpecificAttributesGeneric(Presence presence, XMLElement element) {
+ switch (presence.getType()) {
+ case Unavailable: element.setAttribute("type","unavailable"); break;
+ case Probe: element.setAttribute("type","probe"); break;
+ case Subscribe: element.setAttribute("type","subscribe"); break;
+ case Subscribed: element.setAttribute("type","subscribed"); break;
+ case Unsubscribe: element.setAttribute("type","unsubscribe"); break;
+ case Unsubscribed: element.setAttribute("type","unsubscribed"); break;
+ case Error: element.setAttribute("type","error"); break;
+ case Available: break;
+ }
+ }
+
+
+}
diff --git a/src/com/isode/stroke/serializer/StanzaAckRequestSerializer.java b/src/com/isode/stroke/serializer/StanzaAckRequestSerializer.java
new file mode 100644
index 0000000..2721518
--- /dev/null
+++ b/src/com/isode/stroke/serializer/StanzaAckRequestSerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.StanzaAckRequest;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class StanzaAckRequestSerializer extends GenericElementSerializer<StanzaAckRequest> {
+
+ public StanzaAckRequestSerializer() {
+ super(StanzaAckRequest.class);
+ }
+
+ public String serialize(Element element) {
+ return new XMLElement("r", "urn:xmpp:sm:2").serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/StanzaAckSerializer.java b/src/com/isode/stroke/serializer/StanzaAckSerializer.java
new file mode 100644
index 0000000..e8051a6
--- /dev/null
+++ b/src/com/isode/stroke/serializer/StanzaAckSerializer.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.StanzaAck;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class StanzaAckSerializer extends GenericElementSerializer<StanzaAck> {
+
+ public StanzaAckSerializer() {
+ super(StanzaAck.class);
+ }
+
+ public String serialize(Element element) {
+ StanzaAck stanzaAck = (StanzaAck) element;
+ assert stanzaAck.isValid();
+ XMLElement result = new XMLElement("a", "urn:xmpp:sm:2");
+ result.setAttribute("h", Double.toString(stanzaAck.getHandledStanzasCount()));
+ return result.serialize();
+ }
+}
diff --git a/src/com/isode/stroke/serializer/StanzaSerializer.java b/src/com/isode/stroke/serializer/StanzaSerializer.java
new file mode 100644
index 0000000..950ccf7
--- /dev/null
+++ b/src/com/isode/stroke/serializer/StanzaSerializer.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.Payload;
+import com.isode.stroke.elements.Stanza;
+import com.isode.stroke.serializer.xml.XMLElement;
+import com.isode.stroke.serializer.xml.XMLRawTextNode;
+import java.util.logging.Logger;
+
+public abstract class StanzaSerializer implements ElementSerializer {
+
+ private final String tag_;
+ private final PayloadSerializerCollection payloadSerializers_;
+ private final Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public StanzaSerializer(String tag, PayloadSerializerCollection payloadSerializers) {
+ payloadSerializers_ = payloadSerializers;
+ tag_ = tag;
+ }
+
+ public String serialize(Element element) {
+ assert element != null;
+ assert payloadSerializers_ != null;
+ Stanza stanza = (Stanza) element;
+ XMLElement stanzaElement = new XMLElement(tag_);
+ if (stanza.getFrom() != null && stanza.getFrom().isValid()) {
+ stanzaElement.setAttribute("from", stanza.getFrom().toString());
+ }
+ if (stanza.getTo() != null && stanza.getTo().isValid()) {
+ stanzaElement.setAttribute("to", stanza.getTo().toString());
+ }
+ if (stanza.getID() != null && (stanza.getID().length()!=0)) {
+ stanzaElement.setAttribute("id", stanza.getID());
+ }
+ setStanzaSpecificAttributes(stanza, stanzaElement);
+
+ StringBuilder serializedPayloads = new StringBuilder();
+ for (Payload payload : stanza.getPayloads()) {
+ PayloadSerializer serializer = payloadSerializers_.getPayloadSerializer(payload);
+ if (serializer != null) {
+ serializedPayloads.append(serializer.serialize(payload));
+ } else {
+ /*TODO: port*/
+ assert false;
+ //std::cerr << "Could not find serializer for " << typeid(*(payload.get())).name() << std::endl;
+ }
+ }
+ if (serializedPayloads.toString().length()!=0) {
+ stanzaElement.addNode(new XMLRawTextNode(serializedPayloads.toString()));
+ }
+ return stanzaElement.serialize();
+ }
+
+ public abstract void setStanzaSpecificAttributes(Element element, XMLElement xmlElement);
+}
diff --git a/src/com/isode/stroke/serializer/StartTLSFailureSerializer.java b/src/com/isode/stroke/serializer/StartTLSFailureSerializer.java
new file mode 100644
index 0000000..2de5193
--- /dev/null
+++ b/src/com/isode/stroke/serializer/StartTLSFailureSerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.StartTLSFailure;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class StartTLSFailureSerializer extends GenericElementSerializer<StartTLSFailure> {
+
+ public StartTLSFailureSerializer() {
+ super(StartTLSFailure.class);
+ }
+
+ public String serialize(Element element) {
+ return new XMLElement("failure", "urn:ietf:params:xml:ns:xmpp-tls").serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/StartTLSRequestSerializer.java b/src/com/isode/stroke/serializer/StartTLSRequestSerializer.java
new file mode 100644
index 0000000..afa6b7b
--- /dev/null
+++ b/src/com/isode/stroke/serializer/StartTLSRequestSerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.StartTLSRequest;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class StartTLSRequestSerializer extends GenericElementSerializer<StartTLSRequest> {
+
+ public StartTLSRequestSerializer() {
+ super(StartTLSRequest.class);
+ }
+
+ public String serialize(Element element) {
+ return new XMLElement("starttls", "urn:ietf:params:xml:ns:xmpp-tls").serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/StreamManagementEnabledSerializer.java b/src/com/isode/stroke/serializer/StreamManagementEnabledSerializer.java
new file mode 100644
index 0000000..84b127b
--- /dev/null
+++ b/src/com/isode/stroke/serializer/StreamManagementEnabledSerializer.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.StreamManagementEnabled;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class StreamManagementEnabledSerializer extends GenericElementSerializer<StreamManagementEnabled> {
+
+ public StreamManagementEnabledSerializer() {
+ super(StreamManagementEnabled.class);
+ }
+
+ public String serialize(Element el) {
+ StreamManagementEnabled e = (StreamManagementEnabled) el;
+ XMLElement element = new XMLElement("enabled", "urn:xmpp:sm:2");
+ if (!e.getResumeID().isEmpty()) {
+ element.setAttribute("id", e.getResumeID());
+ }
+ if (e.getResumeSupported()) {
+ element.setAttribute("resume", "true");
+ }
+ return element.serialize();
+ }
+}
diff --git a/src/com/isode/stroke/serializer/StreamManagementFailedSerializer.java b/src/com/isode/stroke/serializer/StreamManagementFailedSerializer.java
new file mode 100644
index 0000000..7f4fb6b
--- /dev/null
+++ b/src/com/isode/stroke/serializer/StreamManagementFailedSerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.StreamManagementFailed;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class StreamManagementFailedSerializer extends GenericElementSerializer<StreamManagementFailed> {
+
+ public StreamManagementFailedSerializer() {
+ super(StreamManagementFailed.class);
+ }
+
+ public String serialize(Element element) {
+ return new XMLElement("failed", "urn:xmpp:sm:2").serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/StreamResumeSerializer.java b/src/com/isode/stroke/serializer/StreamResumeSerializer.java
new file mode 100644
index 0000000..9e40f44
--- /dev/null
+++ b/src/com/isode/stroke/serializer/StreamResumeSerializer.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2011, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.StreamResume;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class StreamResumeSerializer extends GenericElementSerializer<StreamResume> {
+
+ public StreamResumeSerializer() {
+ super(StreamResume.class);
+ }
+
+ public String serialize(Element el) {
+ StreamResume e = (StreamResume)el;
+ XMLElement element = new XMLElement("resume", "urn:xmpp:sm:2");
+ element.setAttribute("previd", e.getResumeID());
+ if (e.getHandledStanzasCount() != null) {
+ element.setAttribute("h", Long.toString(e.getHandledStanzasCount()));
+ }
+ return element.serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/StreamResumedSerializer.java b/src/com/isode/stroke/serializer/StreamResumedSerializer.java
new file mode 100644
index 0000000..8fe6f53
--- /dev/null
+++ b/src/com/isode/stroke/serializer/StreamResumedSerializer.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2011, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.StreamResumed;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class StreamResumedSerializer extends GenericElementSerializer<StreamResumed> {
+
+ public StreamResumedSerializer() {
+ super(StreamResumed.class);
+ }
+
+ public String serialize(Element el) {
+ StreamResumed e = (StreamResumed)el;
+ XMLElement element = new XMLElement("resumed", "urn:xmpp:sm:2");
+ element.setAttribute("previd", e.getResumeID());
+ if (e.getHandledStanzasCount() != null) {
+ element.setAttribute("h", Long.toString(e.getHandledStanzasCount()));
+ }
+ return element.serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/TLSProceedSerializer.java b/src/com/isode/stroke/serializer/TLSProceedSerializer.java
new file mode 100644
index 0000000..ca73456
--- /dev/null
+++ b/src/com/isode/stroke/serializer/TLSProceedSerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.TLSProceed;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class TLSProceedSerializer extends GenericElementSerializer<TLSProceed>{
+
+ public TLSProceedSerializer() {
+ super(TLSProceed.class);
+ }
+
+ public String serialize(Element element) {
+ return new XMLElement("proceed", "urn:ietf:params:xml:ns:xmpp-tls").serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/XMPPSerializer.java b/src/com/isode/stroke/serializer/XMPPSerializer.java
new file mode 100644
index 0000000..b51a4dc
--- /dev/null
+++ b/src/com/isode/stroke/serializer/XMPPSerializer.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer;
+
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.ProtocolHeader;
+import com.isode.stroke.elements.StreamType;
+import java.util.Vector;
+
+public class XMPPSerializer {
+
+ private final Vector<ElementSerializer> serializers_ = new Vector<ElementSerializer>();
+ private final StreamType type_;
+
+ public XMPPSerializer(PayloadSerializerCollection payloadSerializers, StreamType type) {
+ type_ = type;
+ serializers_.add(new PresenceSerializer(payloadSerializers));
+ serializers_.add(new IQSerializer(payloadSerializers));
+ serializers_.add(new MessageSerializer(payloadSerializers));
+ serializers_.add(new CompressRequestSerializer());
+ serializers_.add(new CompressFailureSerializer());
+ serializers_.add(new AuthRequestSerializer());
+ serializers_.add(new AuthFailureSerializer());
+ serializers_.add(new AuthSuccessSerializer());
+ serializers_.add(new AuthChallengeSerializer());
+ serializers_.add(new AuthResponseSerializer());
+ serializers_.add(new StartTLSRequestSerializer());
+ serializers_.add(new StartTLSFailureSerializer());
+ serializers_.add(new TLSProceedSerializer());
+ //serializers_.add(new StreamFeaturesSerializer()); //TODO: Port
+ //serializers_.add(new StreamErrorSerializer()); //FIXME!!!: Port
+ serializers_.add(new EnableStreamManagementSerializer());
+ serializers_.add(new StreamManagementEnabledSerializer());
+ serializers_.add(new StreamManagementFailedSerializer());
+ serializers_.add(new StreamResumeSerializer());
+ serializers_.add(new StreamResumedSerializer());
+ serializers_.add(new StanzaAckSerializer());
+ serializers_.add(new StanzaAckRequestSerializer());
+ //serializers_.add(new ComponentHandshakeSerializer());
+ }
+
+ public String serializeHeader(ProtocolHeader header) {
+ String result = "<?xml version=\"1.0\"?><stream:stream xmlns=\"" + getDefaultNamespace() + "\" xmlns:stream=\"http://etherx.jabber.org/streams\"";
+ if (header.getFrom().length() != 0) {
+ result += " from=\"" + header.getFrom() + "\"";
+ }
+ if (header.getTo().length() != 0) {
+ result += " to=\"" + header.getTo() + "\"";
+ }
+ if (header.getID().length() != 0) {
+ result += " id=\"" + header.getID() + "\"";
+ }
+ if (header.getVersion().length() != 0) {
+ result += " version=\"" + header.getVersion() + "\"";
+ }
+ result += ">";
+ return result;
+ }
+
+ public String serializeFooter() {
+ return "</stream:stream>";
+ }
+
+ public String getDefaultNamespace() {
+ switch (type_) {
+ case ClientStreamType:
+ return "jabber:client";
+ case ServerStreamType:
+ return "jabber:server";
+ case ComponentStreamType:
+ return "jabber:component:accept";
+ }
+ assert false;
+ return "";
+ }
+
+ public String serializeElement(Element element) {
+ for (ElementSerializer serializer : serializers_) {
+ if (serializer.canSerialize(element)) {
+ return serializer.serialize(element);
+ }
+ }
+ throw new IllegalStateException("Trying to send an unknown element");
+ //assert false; /* UNKNOWN ELEMENT */
+ //return "";
+ }
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/BodySerializer.java b/src/com/isode/stroke/serializer/payloadserializers/BodySerializer.java
new file mode 100644
index 0000000..31037db
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/BodySerializer.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer.payloadserializers;
+
+import com.isode.stroke.elements.Body;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+import com.isode.stroke.serializer.xml.XMLTextNode;
+
+/**
+ * Body to String.
+ */
+public class BodySerializer extends GenericPayloadSerializer<Body> {
+
+ public BodySerializer() {
+ super(Body.class);
+ }
+
+ @Override
+ protected String serializePayload(Body body) {
+ XMLTextNode textNode = new XMLTextNode(body.getText());
+ return "<body>" + textNode.serialize() + "</body>";
+ }
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/ErrorSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/ErrorSerializer.java
new file mode 100644
index 0000000..9c34d53
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/ErrorSerializer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer.payloadserializers;
+
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+import com.isode.stroke.serializer.xml.XMLTextNode;
+
+class ErrorSerializer extends GenericPayloadSerializer<ErrorPayload> {
+
+ public ErrorSerializer() {
+ super(ErrorPayload.class);
+ }
+
+ @Override
+ protected String serializePayload(ErrorPayload error) {
+ String result = "<error type=\"";
+ switch (error.getType()) {
+ case Continue: result += "continue"; break;
+ case Modify: result += "modify"; break;
+ case Auth: result += "auth"; break;
+ case Wait: result += "wait"; break;
+ default: result += "cancel"; break;
+ }
+ result += "\">";
+
+ String conditionElement;
+ switch (error.getCondition()) {
+ case BadRequest: conditionElement = "bad-request"; break;
+ case Conflict: conditionElement = "conflict"; break;
+ case FeatureNotImplemented: conditionElement = "feature-not-implemented"; break;
+ case Forbidden: conditionElement = "forbidden"; break;
+ case Gone: conditionElement = "gone"; break;
+ case InternalServerError: conditionElement = "internal-server-error"; break;
+ case ItemNotFound: conditionElement = "item-not-found"; break;
+ case JIDMalformed: conditionElement = "jid-malformed"; break;
+ case NotAcceptable: conditionElement = "not-acceptable"; break;
+ case NotAllowed: conditionElement = "not-allowed"; break;
+ case NotAuthorized: conditionElement = "not-authorized"; break;
+ case PaymentRequired: conditionElement = "payment-required"; break;
+ case RecipientUnavailable: conditionElement = "recipient-unavailable"; break;
+ case Redirect: conditionElement = "redirect"; break;
+ case RegistrationRequired: conditionElement = "registration-required"; break;
+ case RemoteServerNotFound: conditionElement = "remote-server-not-found"; break;
+ case RemoteServerTimeout: conditionElement = "remote-server-timeout"; break;
+ case ResourceConstraint: conditionElement = "resource-constraint"; break;
+ case ServiceUnavailable: conditionElement = "service-unavailable"; break;
+ case SubscriptionRequired: conditionElement = "subscription-required"; break;
+ case UnexpectedRequest: conditionElement = "unexpected-request"; break;
+ default: conditionElement = "undefined-condition"; break;
+ }
+ result += "<" + conditionElement + " xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>";
+
+ if (error.getText().length() != 0) {
+ XMLTextNode textNode = new XMLTextNode(error.getText());
+ result += "<text xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">" + textNode.serialize() + "</text>";
+ }
+
+ result += "</error>";
+ return result;
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java b/src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java
new file mode 100644
index 0000000..52de35b
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer.payloadserializers;
+
+import com.isode.stroke.serializer.PayloadSerializerCollection;
+
+public class FullPayloadSerializerCollection extends PayloadSerializerCollection {
+
+ public FullPayloadSerializerCollection() {
+ /*FIXME: Implement what's needed. */
+ //addSerializer(new IBBSerializer());
+ addSerializer(new BodySerializer());
+ //addSerializer(new SubjectSerializer());
+ //addSerializer(new ChatStateSerializer());
+ //addSerializer(new PrioritySerializer());
+ addSerializer(new ErrorSerializer());
+ addSerializer(new RosterSerializer());
+ //addSerializer(new MUCPayloadSerializer());
+ //addSerializer(new MUCUserPayloadSerializer());
+ //addSerializer(new MUCOwnerPayloadSerializer(this));
+ addSerializer(new SoftwareVersionSerializer());
+ //addSerializer(new StatusSerializer());
+ //addSerializer(new StatusShowSerializer());
+ //addSerializer(new DiscoInfoSerializer());
+ //addSerializer(new DiscoItemsSerializer());
+ //addSerializer(new CapsInfoSerializer());
+ addSerializer(new ResourceBindSerializer());
+ addSerializer(new StartSessionSerializer());
+ //addSerializer(new SecurityLabelSerializer());
+ //addSerializer(new SecurityLabelsCatalogSerializer());
+ //addSerializer(new StreamInitiationSerializer());
+ //addSerializer(new BytestreamsSerializer());
+ //addSerializer(new VCardSerializer());
+ //addSerializer(new VCardUpdateSerializer());
+ addSerializer(new RawXMLPayloadSerializer());
+ //addSerializer(new StorageSerializer());
+ //addSerializer(new DelaySerializer());
+ //addSerializer(new FormSerializer());
+ //addSerializer(new PrivateStorageSerializer(this));
+ //addSerializer(new CommandSerializer());
+ //addSerializer(new NicknameSerializer());
+ addSerializer(new SearchPayloadSerializer());
+ addSerializer(new LastSerializer());
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/LastSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/LastSerializer.java
new file mode 100644
index 0000000..e78a724
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/LastSerializer.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer.payloadserializers;
+
+import com.isode.stroke.elements.Last;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+
+public class LastSerializer extends GenericPayloadSerializer<Last> {
+
+ public LastSerializer() {
+ super(Last.class);
+ }
+
+ @Override
+ protected String serializePayload(Last last) {
+ return "<query xmlns='jabber:iq:last' seconds='" + Integer.toString(last.getSeconds()) + "'/>";
+ }
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/RawXMLPayloadSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/RawXMLPayloadSerializer.java
new file mode 100644
index 0000000..4128537
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/RawXMLPayloadSerializer.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer.payloadserializers;
+
+import com.isode.stroke.elements.RawXMLPayload;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+
+class RawXMLPayloadSerializer extends GenericPayloadSerializer<RawXMLPayload> {
+
+ public RawXMLPayloadSerializer() {
+ super(RawXMLPayloadSerializer.class);
+ }
+
+ @Override
+ protected String serializePayload(RawXMLPayload payload) {
+ return payload.getRawXML();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/ResourceBindSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/ResourceBindSerializer.java
new file mode 100644
index 0000000..a33627b
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/ResourceBindSerializer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer.payloadserializers;
+
+import com.isode.stroke.elements.ResourceBind;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+import com.isode.stroke.serializer.xml.XMLElement;
+import com.isode.stroke.serializer.xml.XMLTextNode;
+
+class ResourceBindSerializer extends GenericPayloadSerializer<ResourceBind> {
+
+ public ResourceBindSerializer() {
+ super(ResourceBind.class);
+ }
+
+ @Override
+ protected String serializePayload(ResourceBind resourceBind) {
+ XMLElement bindElement = new XMLElement("bind", "urn:ietf:params:xml:ns:xmpp-bind");
+ if (resourceBind.getJID().isValid()) {
+ XMLElement jidNode = new XMLElement("jid");
+ jidNode.addNode(new XMLTextNode(resourceBind.getJID().toString()));
+ bindElement.addNode(jidNode);
+ }
+ else if (resourceBind.getResource().length() != 0) {
+ XMLElement resourceNode = new XMLElement("resource");
+ resourceNode.addNode(new XMLTextNode(resourceBind.getResource()));
+ bindElement.addNode(resourceNode);
+ }
+ return bindElement.serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/RosterSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/RosterSerializer.java
new file mode 100644
index 0000000..08a4b8e
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/RosterSerializer.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer.payloadserializers;
+
+import com.isode.stroke.elements.RosterItemPayload;
+import com.isode.stroke.elements.RosterPayload;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+import com.isode.stroke.serializer.xml.XMLElement;
+import com.isode.stroke.serializer.xml.XMLTextNode;
+
+/**
+ * Roster to string.
+ */
+public class RosterSerializer extends GenericPayloadSerializer<RosterPayload> {
+
+ public RosterSerializer() {
+ super(RosterPayload.class);
+ }
+
+ @Override
+ protected String serializePayload(RosterPayload roster) {
+ XMLElement queryElement = new XMLElement("query", "jabber:iq:roster");
+ for (RosterItemPayload item : roster.getItems()) {
+ XMLElement itemElement = new XMLElement("item");
+ itemElement.setAttribute("jid", item.getJID().toString());
+ if (item.getName() != null) {
+ itemElement.setAttribute("name", item.getName());
+ }
+
+ if (item.getSubscription() != null) {
+ switch (item.getSubscription()) {
+ case To: itemElement.setAttribute("subscription", "to"); break;
+ case From: itemElement.setAttribute("subscription", "from"); break;
+ case Both: itemElement.setAttribute("subscription", "both"); break;
+ case Remove: itemElement.setAttribute("subscription", "remove"); break;
+ case None: itemElement.setAttribute("subscription", "none"); break;
+ }
+ }
+
+ if (item.getSubscriptionRequested()) {
+ itemElement.setAttribute("ask", "subscribe");
+ }
+
+ for (String group : item.getGroups()) {
+ XMLElement groupElement = new XMLElement("group");
+ groupElement.addNode(new XMLTextNode(group));
+ itemElement.addNode(groupElement);
+ }
+
+ queryElement.addNode(itemElement);
+ }
+
+ return queryElement.serialize();
+
+
+
+ }
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/SearchPayloadSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/SearchPayloadSerializer.java
new file mode 100644
index 0000000..6928c97
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/SearchPayloadSerializer.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer.payloadserializers;
+
+import com.isode.stroke.elements.SearchPayload;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+import com.isode.stroke.serializer.xml.XMLElement;
+import com.isode.stroke.serializer.xml.XMLTextNode;
+
+/**
+ * SearchPayload to String.
+ */
+public class SearchPayloadSerializer extends GenericPayloadSerializer<SearchPayload> {
+
+ public SearchPayloadSerializer() {
+ super(SearchPayload.class);
+ }
+
+ @Override
+ protected String serializePayload(SearchPayload searchPayload) {
+ XMLElement searchElement = new XMLElement("query", "jabber:iq:search");
+
+ if (searchPayload.getInstructions() != null) {
+ searchElement.addNode(new XMLElement("instructions", "", searchPayload.getInstructions()));
+ }
+
+ if (searchPayload.getNick() != null) {
+ searchElement.addNode(new XMLElement("nick", "", searchPayload.getNick()));
+ }
+
+ if (searchPayload.getFirst() != null) {
+ searchElement.addNode(new XMLElement("first", "", searchPayload.getFirst()));
+ }
+
+ if (searchPayload.getLast() != null) {
+ searchElement.addNode(new XMLElement("last", "", searchPayload.getLast()));
+ }
+
+ if (searchPayload.getEMail() != null) {
+ searchElement.addNode(new XMLElement("email", "", searchPayload.getEMail()));
+ }
+
+ for (SearchPayload.Item item : searchPayload.getItems()) {
+ XMLElement itemElement = new XMLElement("item");
+ itemElement.setAttribute("jid", item.jid.toString());
+ itemElement.addNode(new XMLElement("first", "", item.first));
+ itemElement.addNode(new XMLElement("last", "", item.last));
+ itemElement.addNode(new XMLElement("nick", "", item.nick));
+ itemElement.addNode(new XMLElement("email", "", item.email));
+
+ searchElement.addNode(itemElement);
+ }
+
+ //if (Form::ref form = searchPayload->getForm()) {
+ // searchElement.addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(FormSerializer().serialize(form))));
+ //} /* Not ported yet. When the time comes, look at Swiften to check if it's changed. It will have. */
+
+ return searchElement.serialize();
+ }
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/SoftwareVersionSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/SoftwareVersionSerializer.java
new file mode 100644
index 0000000..cb2b653
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/SoftwareVersionSerializer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer.payloadserializers;
+
+import com.isode.stroke.elements.Version;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+
+public class SoftwareVersionSerializer extends GenericPayloadSerializer<Version>{
+
+ public SoftwareVersionSerializer() {
+ super(Version.class);
+ }
+
+ @Override
+ protected String serializePayload(Version version) {
+ StringBuilder result = new StringBuilder();
+ result.append("<query xmlns=\"jabber:iq:version\">");
+ if (version.getName() != null && version.getName().length() > 0) {
+ result.append("<name>").append(version.getName()).append("</name>");
+ }
+ if (version.getVersion() != null && version.getVersion().length() > 0) {
+ result.append("<version>").append(version.getVersion()).append("</version>");
+ }
+ if (version.getOS() != null && version.getOS().length() > 0) {
+ result.append("<os>").append(version.getOS()).append("</os>");
+ }
+ result.append("</query>");
+ return result.toString();
+
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/StartSessionSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/StartSessionSerializer.java
new file mode 100644
index 0000000..8d36b8c
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/StartSessionSerializer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer.payloadserializers;
+
+import com.isode.stroke.elements.StartSession;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+import com.isode.stroke.serializer.xml.XMLElement;
+
+class StartSessionSerializer extends GenericPayloadSerializer<StartSession> {
+
+ public StartSessionSerializer() {
+ super(StartSession.class);
+ }
+
+ @Override
+ protected String serializePayload(StartSession payload) {
+ return new XMLElement("session", "urn:ietf:params:xml:ns:xmpp-session").serialize();
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/xml/XMLElement.java b/src/com/isode/stroke/serializer/xml/XMLElement.java
new file mode 100644
index 0000000..909f277
--- /dev/null
+++ b/src/com/isode/stroke/serializer/xml/XMLElement.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer.xml;
+
+import java.util.HashMap;
+import java.util.Vector;
+
+public class XMLElement implements XMLNode {
+
+ private final String tag_;
+ private final HashMap<String, String> attributes_ = new HashMap<String, String>();
+ private final Vector<XMLNode> childNodes_ = new Vector<XMLNode>();
+
+ public XMLElement(String tag) {
+ this(tag, "");
+ }
+
+ public XMLElement(String tag, String xmlns) {
+ tag_ = tag;
+ if (xmlns.length()!=0) {
+ setAttribute("xmlns", xmlns);
+ }
+ }
+
+ public XMLElement(String tag, String xmlns, String text) {
+ this(tag, xmlns);
+ if (text.length() > 0) {
+ addNode(new XMLTextNode(text));
+ }
+ }
+
+ public String serialize() {
+ String result = "";
+ result += "<" + tag_;
+ for (String key : attributes_.keySet()) {
+ result += " " + key + "=\"" + attributes_.get(key) + "\"";
+ }
+
+ if (childNodes_.size() > 0) {
+ result += ">";
+ for (XMLNode node : childNodes_) {
+ result += node.serialize();
+ }
+ result += "</" + tag_ + ">";
+ } else {
+ result += "/>";
+ }
+ return result;
+ }
+
+ public void setAttribute(String attribute, String value) {
+ String escapedValue = value;
+ escapedValue.replaceAll("&", "&amp;");
+ escapedValue.replaceAll("<", "&lt;");
+ escapedValue.replaceAll(">", "&gt;");
+ escapedValue.replaceAll("'", "&apos;");
+ escapedValue.replaceAll("\"", "&quot;");
+ attributes_.put(attribute, escapedValue);
+ }
+
+ public void addNode(XMLNode node) {
+ childNodes_.add(node);
+ }
+}
diff --git a/src/com/isode/stroke/serializer/xml/XMLNode.java b/src/com/isode/stroke/serializer/xml/XMLNode.java
new file mode 100644
index 0000000..0b64f85
--- /dev/null
+++ b/src/com/isode/stroke/serializer/xml/XMLNode.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer.xml;
+
+public interface XMLNode {
+ public String serialize();
+}
diff --git a/src/com/isode/stroke/serializer/xml/XMLRawTextNode.java b/src/com/isode/stroke/serializer/xml/XMLRawTextNode.java
new file mode 100644
index 0000000..ad726bc
--- /dev/null
+++ b/src/com/isode/stroke/serializer/xml/XMLRawTextNode.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer.xml;
+
+public class XMLRawTextNode implements XMLNode {
+ private final String text_;
+
+ public XMLRawTextNode(String text) {
+ text_ = text;
+ }
+
+ public String serialize() {
+ return text_;
+ }
+}
diff --git a/src/com/isode/stroke/serializer/xml/XMLTextNode.java b/src/com/isode/stroke/serializer/xml/XMLTextNode.java
new file mode 100644
index 0000000..af11c27
--- /dev/null
+++ b/src/com/isode/stroke/serializer/xml/XMLTextNode.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.serializer.xml;
+
+public class XMLTextNode implements XMLNode {
+
+ private final String text_;
+
+ public XMLTextNode(String text) {
+ text_ = text;
+ text_.replaceAll("&", "&amp;"); // Should come first
+ text_.replaceAll("<", "&lt;");
+ text_.replaceAll(">", "&gt;");
+ }
+
+ public String serialize() {
+ return text_;
+ }
+}
diff --git a/src/com/isode/stroke/session/BasicSessionStream.java b/src/com/isode/stroke/session/BasicSessionStream.java
new file mode 100644
index 0000000..c7c98cf
--- /dev/null
+++ b/src/com/isode/stroke/session/BasicSessionStream.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.session;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.ProtocolHeader;
+import com.isode.stroke.elements.StreamType;
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.network.Connection;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.parser.PayloadParserFactoryCollection;
+import com.isode.stroke.serializer.PayloadSerializerCollection;
+import com.isode.stroke.signals.Slot;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.streamstack.CompressionLayer;
+import com.isode.stroke.streamstack.ConnectionLayer;
+import com.isode.stroke.streamstack.StreamStack;
+import com.isode.stroke.streamstack.TLSLayer;
+import com.isode.stroke.streamstack.WhitespacePingLayer;
+import com.isode.stroke.tls.TLSContextFactory;
+import com.isode.stroke.streamstack.XMPPLayer;
+import com.isode.stroke.tls.Certificate;
+import com.isode.stroke.tls.CertificateVerificationError;
+
+public class BasicSessionStream extends SessionStream {
+
+ public BasicSessionStream(
+ StreamType streamType,
+ Connection connection,
+ PayloadParserFactoryCollection payloadParserFactories,
+ PayloadSerializerCollection payloadSerializers,
+ TLSContextFactory tlsContextFactory,
+ TimerFactory timerFactory,
+ EventLoop eventLoop) {
+ available = false;
+ this.connection = connection;
+ this.payloadParserFactories = payloadParserFactories;
+ this.payloadSerializers = payloadSerializers;
+ this.tlsContextFactory = tlsContextFactory;
+ this.timerFactory = timerFactory;
+ if (timerFactory == null) {
+ throw new IllegalStateException(); //FIXME: remove conditional, debugging only.
+ }
+ this.streamType = streamType;
+ this.compressionLayer = null;
+ this.tlsLayer = null;
+ this.whitespacePingLayer = null;
+
+ xmppLayer = new XMPPLayer(payloadParserFactories, payloadSerializers, streamType, eventLoop);
+ xmppLayer.onStreamStart.connect(new Slot1<ProtocolHeader>() {
+
+ public void call(ProtocolHeader p1) {
+ handleStreamStartReceived(p1);
+ }
+ });
+ xmppLayer.onElement.connect(new Slot1<Element>() {
+
+ public void call(Element p1) {
+ handleElementReceived(p1);
+ }
+ });
+ xmppLayer.onError.connect(new Slot() {
+
+ public void call() {
+ handleXMPPError();
+ }
+ });
+ xmppLayer.onDataRead.connect(new Slot1<ByteArray>() {
+
+ public void call(ByteArray p1) {
+ handleDataRead(p1);
+ }
+ });
+ xmppLayer.onWriteData.connect(new Slot1<ByteArray>() {
+
+ public void call(ByteArray p1) {
+ handleDataWritten(p1);
+ }
+ });
+
+ connection.onDisconnected.connect(new Slot1<Connection.Error>() {
+
+ public void call(Connection.Error p1) {
+ handleConnectionFinished(p1);
+ }
+ });
+ connectionLayer = new ConnectionLayer(connection);
+
+ streamStack = new StreamStack(xmppLayer, connectionLayer);
+
+ available = true;
+
+ }
+
+ public void writeHeader(ProtocolHeader header) {
+ assert available;
+ xmppLayer.writeHeader(header);
+ }
+
+ public void writeElement(Element element) {
+ assert available;
+ xmppLayer.writeElement(element);
+ }
+
+ public void writeFooter() {
+ assert available;
+ xmppLayer.writeFooter();
+ }
+
+ public void writeData(String data) {
+ assert available;
+ xmppLayer.writeData(data);
+ }
+
+ public void close() {
+ connection.disconnect();
+ }
+
+ public boolean isOpen() {
+ return available;
+ }
+
+ public boolean supportsTLSEncryption() {
+ return tlsContextFactory != null && tlsContextFactory.canCreate();
+ }
+
+ public void addTLSEncryption() {
+ assert available;
+ tlsLayer = new TLSLayer(tlsContextFactory);
+ if (hasTLSCertificate() && !tlsLayer.setClientCertificate(getTLSCertificate())) {
+ onClosed.emit(new Error(Error.Type.InvalidTLSCertificateError));
+ } else {
+ streamStack.addLayer(tlsLayer);
+ tlsLayer.onError.connect(new Slot() {
+
+ public void call() {
+ handleTLSError();
+ }
+ });
+ tlsLayer.onConnected.connect(new Slot() {
+
+ public void call() {
+ handleTLSConnected();
+ }
+ });
+ tlsLayer.connect();
+ }
+ }
+
+ public boolean isTLSEncrypted() {
+ return tlsLayer != null;
+ }
+
+ public Certificate getPeerCertificate() {
+ return tlsLayer.getPeerCertificate();
+ }
+
+ public CertificateVerificationError getPeerCertificateVerificationError() {
+ return tlsLayer.getPeerCertificateVerificationError();
+ }
+
+ public ByteArray getTLSFinishMessage() {
+ return tlsLayer.getContext().getFinishMessage();
+ }
+
+ public void addZLibCompression() {
+ compressionLayer = new CompressionLayer();
+ streamStack.addLayer(compressionLayer);
+ }
+
+ public void setWhitespacePingEnabled(boolean enabled) {
+ if (enabled) {
+ if (whitespacePingLayer == null) {
+ whitespacePingLayer = new WhitespacePingLayer(timerFactory);
+ streamStack.addLayer(whitespacePingLayer);
+ }
+ whitespacePingLayer.setActive();
+ }
+ else if (whitespacePingLayer != null) {
+ whitespacePingLayer.setInactive();
+ }
+ }
+
+ public void resetXMPPParser() {
+ xmppLayer.resetParser();
+ }
+
+ private void handleStreamStartReceived(ProtocolHeader header) {
+ onStreamStartReceived.emit(header);
+ }
+
+ private void handleElementReceived(Element element) {
+ onElementReceived.emit(element);
+ }
+
+ private void handleXMPPError() {
+ available = false;
+ onClosed.emit(new Error(Error.Type.ParseError));
+ }
+
+ private void handleTLSConnected() {
+ onTLSEncrypted.emit();
+ }
+
+ private void handleTLSError() {
+ available = false;
+ onClosed.emit(new Error(Error.Type.TLSError));
+ }
+
+ private void handleConnectionFinished(Connection.Error error) {
+ available = false;
+ if (Connection.Error.ReadError.equals(error)) {
+ onClosed.emit(new Error(Error.Type.ConnectionReadError));
+ }
+ else if (error != null) {
+ onClosed.emit(new Error(Error.Type.ConnectionWriteError));
+ }
+ else {
+ onClosed.emit(null);
+ }
+ }
+
+ private void handleDataRead(ByteArray data) {
+ onDataRead.emit(data.toString());
+ }
+
+ private void handleDataWritten(ByteArray data) {
+ onDataWritten.emit(data.toString());
+ }
+ private boolean available;
+ private Connection connection;
+ private PayloadParserFactoryCollection payloadParserFactories;
+ private PayloadSerializerCollection payloadSerializers;
+ private TLSContextFactory tlsContextFactory;
+ private TimerFactory timerFactory;
+ private StreamType streamType;
+ private XMPPLayer xmppLayer;
+ private ConnectionLayer connectionLayer;
+ private CompressionLayer compressionLayer;
+ private TLSLayer tlsLayer;
+ private WhitespacePingLayer whitespacePingLayer;
+ private StreamStack streamStack;
+
+}
diff --git a/src/com/isode/stroke/session/Session.java b/src/com/isode/stroke/session/Session.java
new file mode 100644
index 0000000..79132e4
--- /dev/null
+++ b/src/com/isode/stroke/session/Session.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.session;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.ProtocolHeader;
+import com.isode.stroke.elements.StreamType;
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.network.Connection;
+import com.isode.stroke.parser.PayloadParserFactoryCollection;
+import com.isode.stroke.serializer.PayloadSerializerCollection;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.Slot;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.streamstack.ConnectionLayer;
+import com.isode.stroke.streamstack.StreamStack;
+import com.isode.stroke.streamstack.XMPPLayer;
+
+public abstract class Session {
+
+ public enum SessionError {
+
+ ConnectionReadError,
+ ConnectionWriteError,
+ XMLError,
+ AuthenticationFailedError,
+ NoSupportedAuthMechanismsError,
+ UnexpectedElementError,
+ ResourceBindError,
+ SessionStartError,
+ TLSError,
+ ClientCertificateLoadError,
+ ClientCertificateError
+ };
+
+ public Session(
+ final Connection connection,
+ final PayloadParserFactoryCollection payloadParserFactories,
+ final PayloadSerializerCollection payloadSerializers,
+ final EventLoop eventLoop) {
+ this.connection = connection;
+ this.eventLoop = eventLoop;
+ this.payloadParserFactories = payloadParserFactories;
+ this.payloadSerializers = payloadSerializers;
+ finishing = false;
+ }
+
+
+ public void startSession() {
+ initializeStreamStack();
+ handleSessionStarted();
+ }
+
+ public void finishSession() {
+ finishing = true;
+ connection.disconnect();
+ handleSessionFinished(null);
+ finishing = false;
+ onSessionFinished.emit(null);
+ }
+
+ public void sendElement(Element stanza) {
+ xmppLayer.writeElement(stanza);
+ }
+
+ public JID getLocalJID() {
+ return localJID;
+ }
+
+ public JID getRemoteJID() {
+ return remoteJID;
+ }
+ public final Signal1<Element> onElementReceived = new Signal1<Element>();
+ public final Signal1<SessionError> onSessionFinished = new Signal1<SessionError>();
+ public final Signal1<ByteArray> onDataWritten = new Signal1<ByteArray>();
+ public final Signal1<ByteArray> onDataRead = new Signal1<ByteArray>();
+
+ protected void setRemoteJID(JID j) {
+ remoteJID = j;
+ }
+
+ protected void setLocalJID(JID j) {
+ localJID = j;
+ }
+
+ protected void finishSession(SessionError error) {
+ finishing = true;
+ connection.disconnect();
+ handleSessionFinished(error);
+ finishing = false;
+ onSessionFinished.emit(error);
+ }
+
+ protected void handleSessionStarted() {
+ }
+
+ protected void handleSessionFinished(SessionError error) {
+ }
+
+ protected abstract void handleElement(Element element);
+
+ protected abstract void handleStreamStart(ProtocolHeader header);
+
+ protected void initializeStreamStack() {
+ xmppLayer = new XMPPLayer(payloadParserFactories, payloadSerializers, StreamType.ClientStreamType, eventLoop);
+ xmppLayer.onStreamStart.connect(new Slot1<ProtocolHeader>() {
+
+ public void call(ProtocolHeader header) {
+ handleStreamStart(header);
+ }
+ });
+ xmppLayer.onElement.connect(new Slot1<Element>() {
+
+ public void call(Element p1) {
+ handleElement(p1);
+ }
+ });
+ xmppLayer.onError.connect(new Slot() {
+
+ public void call() {
+ finishSession(SessionError.XMLError);
+ }
+ });
+ xmppLayer.onDataRead.connect(onDataRead);
+ xmppLayer.onWriteData.connect(onDataWritten);
+ connection.onDisconnected.connect(new Slot1<Connection.Error>() {
+
+ public void call(Connection.Error p1) {
+ handleDisconnected(p1);
+ }
+ });
+ connectionLayer = new ConnectionLayer(connection);
+ streamStack = new StreamStack(xmppLayer, connectionLayer);
+ }
+
+ public XMPPLayer getXMPPLayer() {
+ return xmppLayer;
+
+
+ }
+
+ public StreamStack getStreamStack() {
+ return streamStack;
+
+
+ }
+
+ /*void setFinished();*/ /* This seems to be unused in Swiften*/
+
+ private void handleDisconnected(Connection.Error connectionError) {
+ if (connectionError != null) {
+ switch (connectionError) {
+ case ReadError:
+ finishSession(SessionError.ConnectionReadError);
+ break;
+ case WriteError:
+ finishSession(SessionError.ConnectionWriteError);
+ break;
+ }
+ } else {
+ finishSession();
+ }
+ }
+ private JID localJID;
+ private JID remoteJID;
+ private Connection connection;
+ private PayloadParserFactoryCollection payloadParserFactories;
+ private PayloadSerializerCollection payloadSerializers;
+ private XMPPLayer xmppLayer;
+ private ConnectionLayer connectionLayer;
+ private StreamStack streamStack;
+ private boolean finishing;
+ private final EventLoop eventLoop;
+}
diff --git a/src/com/isode/stroke/session/SessionStream.java b/src/com/isode/stroke/session/SessionStream.java
new file mode 100644
index 0000000..c70622b
--- /dev/null
+++ b/src/com/isode/stroke/session/SessionStream.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.session;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.ProtocolHeader;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.tls.Certificate;
+import com.isode.stroke.tls.CertificateVerificationError;
+import com.isode.stroke.tls.PKCS12Certificate;
+
+public abstract class SessionStream {
+
+ public static class Error implements com.isode.stroke.base.Error {
+
+ public enum Type {
+
+ ParseError,
+ TLSError,
+ InvalidTLSCertificateError,
+ ConnectionReadError,
+ ConnectionWriteError
+ };
+
+ public Error(Type type) {
+ this.type = type;
+ }
+ public final Type type;
+ };
+
+ public abstract void close();
+
+ public abstract boolean isOpen();
+
+ public abstract void writeHeader(ProtocolHeader header);
+
+ public abstract void writeFooter();
+
+ public abstract void writeElement(Element element);
+
+ public abstract void writeData(String data);
+
+ public abstract void addZLibCompression();
+
+ public abstract boolean supportsTLSEncryption();
+
+ public abstract void addTLSEncryption();
+
+ public abstract boolean isTLSEncrypted();
+
+ public abstract void setWhitespacePingEnabled(boolean enabled);
+
+ public abstract void resetXMPPParser();
+
+ public void setTLSCertificate(PKCS12Certificate cert) {
+ certificate = cert;
+ }
+
+ public boolean hasTLSCertificate() {
+ return certificate != null && !certificate.isNull();
+ }
+
+ public abstract Certificate getPeerCertificate();
+
+ public abstract CertificateVerificationError getPeerCertificateVerificationError();
+
+ public abstract ByteArray getTLSFinishMessage();
+
+ public final Signal1<ProtocolHeader> onStreamStartReceived = new Signal1<ProtocolHeader>();
+ 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>();
+ protected PKCS12Certificate getTLSCertificate() {
+ return certificate;
+ }
+ private PKCS12Certificate certificate;
+}
diff --git a/src/com/isode/stroke/session/SessionTracer.java b/src/com/isode/stroke/session/SessionTracer.java
new file mode 100644
index 0000000..93c51a8
--- /dev/null
+++ b/src/com/isode/stroke/session/SessionTracer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.session;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.signals.Slot1;
+
+public class SessionTracer {
+
+ public SessionTracer(Session session) {
+ this.session = session;
+ session.onDataRead.connect(new Slot1<ByteArray>() {
+
+ public void call(ByteArray p1) {
+ printData('<', p1);
+ }
+ });
+
+ session.onDataWritten.connect(new Slot1<ByteArray>() {
+
+ public void call(ByteArray p1) {
+ printData('>', p1);
+ }
+ });
+ }
+
+ private void printData(char direction, ByteArray data) {
+ System.err.print("" + direction + direction + " " + session.getLocalJID().toString() + " ");
+ for (int i = 0; i < 72 - session.getLocalJID().toString().length() - session.getRemoteJID().toString().length(); ++i) {
+ System.err.print(direction);
+ }
+ System.err.println(" " + session.getRemoteJID().toString() + " " + direction + direction);
+ System.err.println(data);
+ }
+ private Session session;
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/signals/Signal.java b/src/com/isode/stroke/signals/Signal.java
new file mode 100644
index 0000000..cfa8665
--- /dev/null
+++ b/src/com/isode/stroke/signals/Signal.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.signals;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An approximation of the boost::signals system, although a little more warty.
+ */
+public class Signal {
+
+ private final Map<SignalConnection, Slot> binds_ = Collections.synchronizedMap(new HashMap<SignalConnection, Slot>());
+
+ public SignalConnection connect(Slot bind) {
+ final SignalConnection connection = new SignalConnection();
+ binds_.put(connection, bind);
+ connection.onDestroyed.connectWithoutReturn(new Slot() {
+
+ public void call() {
+ binds_.remove(connection);
+ }
+ });
+ return connection;
+ }
+
+ public SignalConnection connect(final Signal target) {
+ return connect(new Slot() {
+ public void call() {
+ target.emit();
+ }
+ });
+ }
+
+ void connectWithoutReturn(Slot bind) {
+ binds_.put(null, bind);
+ }
+
+ public void emit() {
+ ArrayList<Slot> binds = new ArrayList<Slot>();
+ binds.addAll(binds_.values());
+ for (Slot bind : binds) {
+ bind.call();
+ }
+ }
+
+ public void disconnectAll() {
+ binds_.clear();
+ }
+}
diff --git a/src/com/isode/stroke/signals/Signal1.java b/src/com/isode/stroke/signals/Signal1.java
new file mode 100644
index 0000000..c799d4c
--- /dev/null
+++ b/src/com/isode/stroke/signals/Signal1.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.signals;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An approximation of the boost::signals system, although a little more warty.
+ */
+public class Signal1<T1> {
+ private final Map<SignalConnection, Slot1<T1> > binds_ = Collections.synchronizedMap(new HashMap<SignalConnection, Slot1<T1> >());
+ public SignalConnection connect(Slot1<T1> bind) {
+ final SignalConnection connection = new SignalConnection();
+ binds_.put(connection, bind);
+ connection.onDestroyed.connect(new Slot() {
+ public void call() {
+ binds_.remove(connection);
+ }
+ });
+ return connection;
+ }
+
+ public void emit(T1 p1) {
+ ArrayList<Slot1<T1>> binds = new ArrayList<Slot1<T1>>();
+ binds.addAll(binds_.values());
+ for (Slot1<T1> bind : binds) {
+ bind.call(p1);
+ }
+ }
+
+ public SignalConnection connect(final Signal1<T1> target) {
+ return connect(new Slot1<T1>() {
+ public void call(T1 p1) {
+ target.emit(p1);
+ }
+ });
+ }
+
+ public void disconnectAll() {
+ binds_.clear();
+ }
+}
diff --git a/src/com/isode/stroke/signals/Signal2.java b/src/com/isode/stroke/signals/Signal2.java
new file mode 100644
index 0000000..fa3c4b5
--- /dev/null
+++ b/src/com/isode/stroke/signals/Signal2.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.signals;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An approximation of the boost::signals system, although a little more warty.
+ */
+public class Signal2<T1, T2> {
+ private final Map<SignalConnection, Slot2<T1, T2> > binds_ = Collections.synchronizedMap(new HashMap<SignalConnection, Slot2<T1, T2> >());
+ public SignalConnection connect(Slot2<T1, T2> bind) {
+ final SignalConnection connection = new SignalConnection();
+ binds_.put(connection, bind);
+ connection.onDestroyed.connect(new Slot() {
+ public void call() {
+ binds_.remove(connection);
+ }
+ });
+ return connection;
+ }
+
+ public void emit(T1 p1, T2 p2) {
+ ArrayList<Slot2<T1,T2>> binds = new ArrayList<Slot2<T1, T2>>();
+ binds.addAll(binds_.values());
+ for (Slot2<T1, T2> bind : binds) {
+ bind.call(p1, p2);
+ }
+ }
+
+ public void disconnectAll() {
+ binds_.clear();
+ }
+}
diff --git a/src/com/isode/stroke/signals/SignalConnection.java b/src/com/isode/stroke/signals/SignalConnection.java
new file mode 100644
index 0000000..452c80b
--- /dev/null
+++ b/src/com/isode/stroke/signals/SignalConnection.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.signals;
+
+
+public class SignalConnection {
+ public final Signal onDestroyed = new Signal();
+
+ public void disconnect() {
+ onDestroyed.emit();
+ }
+}
diff --git a/src/com/isode/stroke/signals/Slot.java b/src/com/isode/stroke/signals/Slot.java
new file mode 100644
index 0000000..35b8c70
--- /dev/null
+++ b/src/com/isode/stroke/signals/Slot.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.signals;
+
+/**
+ * Bind class for connecting to a signal.
+ */
+public interface Slot {
+ void call();
+}
diff --git a/src/com/isode/stroke/signals/Slot1.java b/src/com/isode/stroke/signals/Slot1.java
new file mode 100644
index 0000000..a8f0179
--- /dev/null
+++ b/src/com/isode/stroke/signals/Slot1.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.signals;
+
+/**
+ * Bind class for connecting to a signal.
+ */
+public interface Slot1<T1> {
+ void call(T1 p1);
+}
diff --git a/src/com/isode/stroke/signals/Slot2.java b/src/com/isode/stroke/signals/Slot2.java
new file mode 100644
index 0000000..b3b9330
--- /dev/null
+++ b/src/com/isode/stroke/signals/Slot2.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.signals;
+
+/**
+ * Bind class for connecting to a signal.
+ */
+public interface Slot2<T1, T2> {
+ void call(T1 p1, T2 p2);
+}
diff --git a/src/com/isode/stroke/streammanagement/StanzaAckRequester.java b/src/com/isode/stroke/streammanagement/StanzaAckRequester.java
new file mode 100644
index 0000000..d698e89
--- /dev/null
+++ b/src/com/isode/stroke/streammanagement/StanzaAckRequester.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.streammanagement;
+
+import com.isode.stroke.elements.Message;
+import com.isode.stroke.elements.Stanza;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.signals.Signal1;
+import java.util.ArrayList;
+import java.util.List;
+
+public class StanzaAckRequester {
+
+ static final long MAX_HANDLED_STANZA_COUNT = Long.parseLong("4294967295"); //boost::numeric_cast<unsigned int>((1ULL<<32) - 1);
+
+ public StanzaAckRequester() {
+
+ }
+
+ public void handleStanzaSent(Stanza stanza) {
+ unackedStanzas.add(stanza);
+ if (stanza instanceof Message) {
+ onRequestAck.emit();
+ }
+ }
+
+ public void handleAckReceived(long handledStanzasCount) {
+ long i = lastHandledStanzasCount;
+ while (i != handledStanzasCount) {
+ if (unackedStanzas.isEmpty()) {
+ //std::cerr << "Warning: Server acked more stanzas than we sent" << std::endl;
+ break;
+ }
+ Stanza ackedStanza = unackedStanzas.get(0);
+ unackedStanzas.remove(0);
+ onStanzaAcked.emit(ackedStanza);
+ i = (i == MAX_HANDLED_STANZA_COUNT ? 0 : i + 1);
+ }
+ lastHandledStanzasCount = handledStanzasCount;
+ }
+
+ public Signal onRequestAck = new Signal();
+
+ public Signal1<Stanza> onStanzaAcked = new Signal1<Stanza>();
+
+ private long lastHandledStanzasCount;
+
+ private List<Stanza> unackedStanzas = new ArrayList<Stanza>();
+}
diff --git a/src/com/isode/stroke/streammanagement/StanzaAckResponder.java b/src/com/isode/stroke/streammanagement/StanzaAckResponder.java
new file mode 100644
index 0000000..e0de87a
--- /dev/null
+++ b/src/com/isode/stroke/streammanagement/StanzaAckResponder.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.streammanagement;
+
+import com.isode.stroke.signals.Signal1;
+
+public class StanzaAckResponder {
+
+ static final long MAX_HANDLED_STANZA_COUNT = Long.parseLong("4294967295"); //boost::numeric_cast<unsigned int>((1ULL<<32) - 1);
+
+ public StanzaAckResponder() {
+ }
+
+ public void handleStanzaReceived() {
+ handledStanzasCount = (handledStanzasCount == MAX_HANDLED_STANZA_COUNT ? 0 : handledStanzasCount + 1);
+ }
+
+ public void handleAckRequestReceived() {
+ onAck.emit(handledStanzasCount);
+ }
+ public Signal1<Long> onAck = new Signal1<Long>();
+ private long handledStanzasCount = 0;
+}
diff --git a/src/com/isode/stroke/streamstack/CompressionLayer.java b/src/com/isode/stroke/streamstack/CompressionLayer.java
new file mode 100644
index 0000000..fae8263
--- /dev/null
+++ b/src/com/isode/stroke/streamstack/CompressionLayer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.streamstack;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.compress.ZLibCompressor;
+import com.isode.stroke.compress.ZLibDecompressor;
+import com.isode.stroke.compress.ZLibException;
+import com.isode.stroke.signals.Signal;
+
+public class CompressionLayer extends StreamLayer {
+
+ public void writeData(ByteArray data) {
+ try {
+ writeDataToChildLayer(compressor_.process(data));
+ }
+ catch (ZLibException e) {
+ onError.emit();
+ }
+ }
+
+ public void handleDataRead(ByteArray data) {
+ try {
+ writeDataToParentLayer(decompressor_.process(data));
+ }
+ catch (ZLibException e) {
+ onError.emit();
+ }
+ }
+
+ public Signal onError = new Signal();
+
+ private ZLibCompressor compressor_ = new ZLibCompressor();
+ private ZLibDecompressor decompressor_ = new ZLibDecompressor();
+
+}
diff --git a/src/com/isode/stroke/streamstack/ConnectionLayer.java b/src/com/isode/stroke/streamstack/ConnectionLayer.java
new file mode 100644
index 0000000..525da50
--- /dev/null
+++ b/src/com/isode/stroke/streamstack/ConnectionLayer.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.streamstack;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.network.Connection;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.Slot1;
+
+public class ConnectionLayer implements LowLayer {
+
+ public ConnectionLayer(Connection connection) {
+ this.connection = connection;
+ connection.onDataRead.connect(new Slot1<ByteArray>() {
+
+ public void call(ByteArray p1) {
+ writeDataToParentLayer(p1);
+ }
+ });
+ }
+
+ public void writeData(ByteArray data) {
+ connection.write(data);
+ }
+
+ private Connection connection;
+
+ /* Work around multiple inheritance workaround again */
+ StreamLayer fakeStreamLayer_ = new StreamLayer() {
+
+ public void writeData(ByteArray data) {
+ connection.write(data);
+ }
+
+ public void handleDataRead(ByteArray data) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ };
+
+ public HighLayer getParentLayer() {
+ return fakeStreamLayer_.getParentLayer();
+ }
+
+ public void setParentLayer(HighLayer parentLayer) {
+ fakeStreamLayer_.setParentLayer(parentLayer);
+ }
+
+ public void writeDataToParentLayer(ByteArray data) {
+ fakeStreamLayer_.writeDataToParentLayer(data);
+ }
+}
diff --git a/src/com/isode/stroke/streamstack/HighLayer.java b/src/com/isode/stroke/streamstack/HighLayer.java
new file mode 100644
index 0000000..9ef37d1
--- /dev/null
+++ b/src/com/isode/stroke/streamstack/HighLayer.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.streamstack;
+
+import com.isode.stroke.base.ByteArray;
+
+/**
+ * Because of the lack of multiple inheritance in Java, this has to be done
+ * slightly differently from Swiften. What happens is that the methods in Swiften
+ * are provided abstract here, and implemented in the StreamLayer instead.
+ */
+public interface HighLayer {
+
+ void handleDataRead(ByteArray data);
+
+
+ /* Should be protected */
+ LowLayer getChildLayer();
+
+ void setChildLayer(LowLayer childLayer);
+
+ void writeDataToChildLayer(ByteArray data);
+
+}
diff --git a/src/com/isode/stroke/streamstack/LowLayer.java b/src/com/isode/stroke/streamstack/LowLayer.java
new file mode 100644
index 0000000..fa31075
--- /dev/null
+++ b/src/com/isode/stroke/streamstack/LowLayer.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.streamstack;
+
+import com.isode.stroke.base.ByteArray;
+
+/**
+ * Because of the lack of multiple inheritance in Java, this has to be done
+ * slightly differently from Swiften. What happens is that the methods in Swiften
+ * are provided abstract here, and implemented in the StreamLayer instead.
+ */
+public interface LowLayer {
+
+ void writeData(ByteArray data);
+
+ /* Should be protected */
+
+ HighLayer getParentLayer();
+
+ void setParentLayer(HighLayer parentLayer);
+
+ void writeDataToParentLayer(ByteArray data);
+}
diff --git a/src/com/isode/stroke/streamstack/StreamLayer.java b/src/com/isode/stroke/streamstack/StreamLayer.java
new file mode 100644
index 0000000..c6d8f50
--- /dev/null
+++ b/src/com/isode/stroke/streamstack/StreamLayer.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.streamstack;
+
+import com.isode.stroke.base.ByteArray;
+
+/**
+ * Because of the lack of multiple inheritance in Java, this implements
+ * the abstract methods that should have been implemented in
+ * LowLayer and HighLayer.
+ */
+public abstract class StreamLayer implements LowLayer, HighLayer {
+
+ public HighLayer getParentLayer() {
+ return parentLayer;
+ }
+
+ public void setParentLayer(final HighLayer parentLayer) {
+ this.parentLayer = parentLayer;
+ }
+
+ public void writeDataToParentLayer(final ByteArray data) {
+ assert parentLayer != null;
+ parentLayer.handleDataRead(data);
+ }
+
+ public LowLayer getChildLayer() {
+ return childLayer;
+ }
+
+ public void setChildLayer(final LowLayer childLayer) {
+ this.childLayer = childLayer;
+ }
+
+ public void writeDataToChildLayer(final ByteArray data) {
+ assert childLayer != null;
+ childLayer.writeData(data);
+ }
+
+ private HighLayer parentLayer;
+ private LowLayer childLayer;
+}
diff --git a/src/com/isode/stroke/streamstack/StreamStack.java b/src/com/isode/stroke/streamstack/StreamStack.java
new file mode 100644
index 0000000..f539624
--- /dev/null
+++ b/src/com/isode/stroke/streamstack/StreamStack.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.streamstack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StreamStack {
+
+ public StreamStack(XMPPLayer xmppLayer, LowLayer physicalLayer) {
+ xmppLayer_ = xmppLayer;
+ physicalLayer_ = physicalLayer;
+ physicalLayer_.setParentLayer(xmppLayer_);
+ xmppLayer.setChildLayer(physicalLayer_);
+ }
+
+ public void addLayer(final StreamLayer newLayer) {
+ final LowLayer lowLayer = (layers_.isEmpty() ? physicalLayer_ : layers_.get(layers_.size() - 1));
+
+ xmppLayer_.setChildLayer(newLayer);
+ newLayer.setParentLayer(xmppLayer_);
+
+ lowLayer.setParentLayer(newLayer);
+ newLayer.setChildLayer(lowLayer);
+
+ layers_.add(newLayer);
+ }
+
+ public XMPPLayer getXMPPLayer() {
+ return xmppLayer_;
+ }
+
+ public Object getLayer(Class layerClass) {
+ for (StreamLayer layer : layers_) {
+ if (layerClass.isAssignableFrom(layer.getClass())) {
+ return layer;
+ }
+ }
+ return null;
+ }
+
+ private XMPPLayer xmppLayer_;
+ private LowLayer physicalLayer_;
+ private List<StreamLayer> layers_ = new ArrayList<StreamLayer>();
+}
diff --git a/src/com/isode/stroke/streamstack/TLSLayer.java b/src/com/isode/stroke/streamstack/TLSLayer.java
new file mode 100644
index 0000000..ecf908d
--- /dev/null
+++ b/src/com/isode/stroke/streamstack/TLSLayer.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.streamstack;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.tls.Certificate;
+import com.isode.stroke.tls.CertificateVerificationError;
+import com.isode.stroke.tls.PKCS12Certificate;
+import com.isode.stroke.tls.TLSContext;
+import com.isode.stroke.tls.TLSContextFactory;
+
+public class TLSLayer extends StreamLayer {
+
+ public TLSLayer(TLSContextFactory factory) {
+ context = factory.createTLSContext();
+ context.onDataForNetwork.connect(new Slot1<ByteArray>() {
+
+ public void call(ByteArray p1) {
+ writeDataToChildLayer(p1);
+ }
+ });
+ context.onDataForApplication.connect(new Slot1<ByteArray>() {
+
+ public void call(ByteArray p1) {
+ writeDataToParentLayer(p1);
+ }
+ });
+ context.onConnected.connect(onConnected);
+ context.onError.connect(onError);
+ }
+
+ public void connect() {
+ context.connect();
+ }
+
+ public void writeData(ByteArray data) {
+ context.handleDataFromApplication(data);
+ }
+
+ public void handleDataRead(ByteArray data) {
+ context.handleDataFromNetwork(data);
+ }
+
+ public boolean setClientCertificate(PKCS12Certificate certificate) {
+ return context.setClientCertificate(certificate);
+ }
+
+ public Certificate getPeerCertificate() {
+ return context.getPeerCertificate();
+ }
+
+ public CertificateVerificationError getPeerCertificateVerificationError() {
+ return context.getPeerCertificateVerificationError();
+ }
+
+ public TLSContext getContext() {
+ return context;
+ }
+
+ public final Signal onError = new Signal();
+ public final Signal onConnected = new Signal();
+
+ private final TLSContext context;
+}
diff --git a/src/com/isode/stroke/streamstack/WhitespacePingLayer.java b/src/com/isode/stroke/streamstack/WhitespacePingLayer.java
new file mode 100644
index 0000000..09d3af8
--- /dev/null
+++ b/src/com/isode/stroke/streamstack/WhitespacePingLayer.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.streamstack;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.network.Timer;
+import com.isode.stroke.network.TimerFactory;
+import com.isode.stroke.signals.Slot;
+
+public class WhitespacePingLayer extends StreamLayer {
+
+ private static final int TIMEOUT_MILLISECONDS = 60000;
+
+ public WhitespacePingLayer(TimerFactory timerFactory) {
+ isActive = false;
+ timer = timerFactory.createTimer(TIMEOUT_MILLISECONDS);
+ timer.onTick.connect(new Slot() {
+ public void call() {
+ handleTimerTick();
+ }
+ });
+ }
+
+ public void writeData(ByteArray data) {
+ writeDataToChildLayer(data);
+ }
+
+ public void handleDataRead(ByteArray data) {
+ writeDataToParentLayer(data);
+ }
+
+ private void handleTimerTick() {
+ timer.stop();
+ writeDataToChildLayer(new ByteArray(" "));
+ timer.start();
+ }
+
+ public void setActive() {
+ isActive = true;
+ timer.start();
+ }
+
+ public void setInactive() {
+ timer.stop();
+ isActive = false;
+ }
+
+ public boolean getIsActive() {
+ return isActive;
+ }
+
+ private boolean isActive;
+ private Timer timer;
+}
diff --git a/src/com/isode/stroke/streamstack/XMPPLayer.java b/src/com/isode/stroke/streamstack/XMPPLayer.java
new file mode 100644
index 0000000..86c7b0e
--- /dev/null
+++ b/src/com/isode/stroke/streamstack/XMPPLayer.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010-2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.streamstack;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.elements.Element;
+import com.isode.stroke.elements.ProtocolHeader;
+import com.isode.stroke.elements.StreamType;
+import com.isode.stroke.eventloop.EventLoop;
+import com.isode.stroke.parser.PayloadParserFactoryCollection;
+import com.isode.stroke.parser.XMPPParser;
+import com.isode.stroke.parser.XMPPParserClient;
+import com.isode.stroke.serializer.PayloadSerializerCollection;
+import com.isode.stroke.serializer.XMPPSerializer;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.signals.Signal1;
+
+/**
+ * This uses the inner StreamLayer to work around the HighLayer not having
+ * implementations because of the lack of multiple inheritance.
+ * Swiften doesn't require an eventLoop, Stroke does because of
+ * XML parsing being multi-threaded here.
+ */
+public class XMPPLayer implements HighLayer, XMPPParserClient {
+
+ public XMPPLayer(
+ PayloadParserFactoryCollection payloadParserFactories,
+ PayloadSerializerCollection payloadSerializers,
+ StreamType streamType,
+ EventLoop eventLoop) {
+ payloadParserFactories_ = payloadParserFactories;
+ payloadSerializers_ = payloadSerializers;
+ resetParserAfterParse_ = false;
+ eventLoop_ = eventLoop;
+ inParser_ = false;
+ xmppParser_ = new XMPPParser(this, payloadParserFactories_, eventLoop_);
+ xmppSerializer_ = new XMPPSerializer(payloadSerializers_, streamType);
+ }
+
+ public void writeHeader(ProtocolHeader header) {
+ writeDataInternal(new ByteArray(xmppSerializer_.serializeHeader(header)));
+ }
+
+ public void writeFooter() {
+ writeDataInternal(new ByteArray(xmppSerializer_.serializeFooter()));
+ }
+
+ public void writeElement(Element element) {
+ writeDataInternal(new ByteArray(xmppSerializer_.serializeElement(element)));
+ }
+
+ public void writeData(String data) {
+ writeDataInternal(new ByteArray(data));
+ }
+
+ public void resetParser() {
+ if (inParser_) {
+ resetParserAfterParse_ = true;
+ }
+ else {
+ doResetParser();
+ }
+ }
+
+ /**
+ * Should be protected, but can't because of interface implementation.
+ * @param data
+ */
+ public void handleDataRead(ByteArray data) {
+ handleDataReadInternal(data);
+ }
+
+ protected void writeDataInternal(ByteArray data) {
+ onWriteData.emit(data);
+ writeDataToChildLayer(data);
+ }
+
+ public final Signal1<ProtocolHeader> onStreamStart = new Signal1<ProtocolHeader>();
+ public final Signal1<Element> onElement = new Signal1<Element>();
+ public final Signal1<ByteArray> onWriteData = new Signal1<ByteArray>();
+ public final Signal1<ByteArray> onDataRead = new Signal1<ByteArray>();
+ public final Signal onError = new Signal();
+
+ public void handleStreamStart(ProtocolHeader header) {
+ onStreamStart.emit(header);
+ }
+
+ public void handleElement(Element element) {
+ onElement.emit(element);
+ }
+
+ public void handleStreamEnd() {
+ }
+
+ private void doResetParser() {
+ xmppParser_ = new XMPPParser(this, payloadParserFactories_, eventLoop_);
+ resetParserAfterParse_ = false;
+ }
+
+ private PayloadParserFactoryCollection payloadParserFactories_;
+ private XMPPParser xmppParser_;
+ private PayloadSerializerCollection payloadSerializers_;
+ private XMPPSerializer xmppSerializer_;
+ private boolean resetParserAfterParse_;
+ private boolean inParser_;
+ private EventLoop eventLoop_;
+
+ /* Multiple-inheritance workarounds */
+
+ private StreamLayer fakeStreamLayer_ = new StreamLayer() {
+ public void writeData(ByteArray data) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void handleDataRead(ByteArray data) {
+ handleDataReadInternal(data);
+ }
+ };
+
+ private void handleDataReadInternal(ByteArray data) {
+ onDataRead.emit(data);
+ inParser_ = true;
+ if(!xmppParser_.parse(data.toString())) {
+ inParser_ = false;
+ onError.emit();
+ return;
+ }
+ inParser_ = false;
+ if (resetParserAfterParse_) {
+ doResetParser();
+ }
+ }
+
+ public LowLayer getChildLayer() {
+ return fakeStreamLayer_.getChildLayer();
+ }
+
+ public void setChildLayer(LowLayer childLayer) {
+ fakeStreamLayer_.setChildLayer(childLayer);
+ }
+
+ public void writeDataToChildLayer(ByteArray data) {
+ fakeStreamLayer_.writeDataToChildLayer(data);
+ }
+}
diff --git a/src/com/isode/stroke/stringcodecs/Base64.java b/src/com/isode/stroke/stringcodecs/Base64.java
new file mode 100644
index 0000000..3b0faa5
--- /dev/null
+++ b/src/com/isode/stroke/stringcodecs/Base64.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.stringcodecs;
+
+import com.isode.stroke.base.ByteArray;
+
+public class Base64 {
+ /* FIXME: Check license is ok (it is, it's BSD) */
+ public static ByteArray decode(String input) {
+ return new ByteArray(Base64BSD.decode(input));
+ }
+
+ public static String encode(ByteArray input) {
+ return Base64BSD.encodeToString(input.getData(), false);
+ }
+}
diff --git a/src/com/isode/stroke/stringcodecs/Base64BSD.java b/src/com/isode/stroke/stringcodecs/Base64BSD.java
new file mode 100644
index 0000000..9a3e0e4
--- /dev/null
+++ b/src/com/isode/stroke/stringcodecs/Base64BSD.java
@@ -0,0 +1,575 @@
+package com.isode.stroke.stringcodecs;
+
+import java.util.Arrays;
+
+/** A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance
+ * with RFC 2045.<br><br>
+ * On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 times faster
+ * on small arrays (10 - 1000 bytes) and 2-3 times as fast on larger arrays (10000 - 1000000 bytes)
+ * compared to <code>sun.misc.Encoder()/Decoder()</code>.<br><br>
+ *
+ * On byte arrays the encoder is about 20% faster than Jakarta Commons Base64 Codec for encode and
+ * about 50% faster for decoding large arrays. This implementation is about twice as fast on very small
+ * arrays (&lt 30 bytes). If source/destination is a <code>String</code> this
+ * version is about three times as fast due to the fact that the Commons Codec result has to be recoded
+ * to a <code>String</code> from <code>byte[]</code>, which is very expensive.<br><br>
+ *
+ * This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only
+ * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice
+ * as large as algorithms that create a temporary array. (E.g. Jakarta Commons Codec). It is unknown
+ * whether Sun's <code>sun.misc.Encoder()/Decoder()</code> produce temporary arrays but since performance
+ * is quite low it probably does.<br><br>
+ *
+ * The encoder produces the same output as the Sun one except that the Sun's encoder appends
+ * a trailing line separator if the last character isn't a pad. Unclear why but it only adds to the
+ * length and is probably a side effect. Both are in conformance with RFC 2045 though.<br>
+ * Commons codec seem to always att a trailing line separator.<br><br>
+ *
+ * <b>Note!</b>
+ * The encode/decode method pairs (types) come in three versions with the <b>exact</b> same algorithm and
+ * thus a lot of code redundancy. This is to not create any temporary arrays for transcoding to/from different
+ * format types. The methods not used can simply be commented out.<br><br>
+ *
+ * There is also a "fast" version of all decode methods that works the same way as the normal ones, but
+ * har a few demands on the decoded input. Normally though, these fast verions should be used if the source if
+ * the input is known and it hasn't bee tampered with.<br><br>
+ *
+ * If you find the code useful or you find a bug, please send me a note at base64 @ miginfocom . com.
+ *
+ * Licence (BSD):
+ * ==============
+ *
+ * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this list
+ * of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this
+ * list of conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ * Neither the name of the MiG InfoCom AB nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * @version 2.2
+ * @author Mikael Grev
+ * Date: 2004-aug-02
+ * Time: 11:31:11
+ */
+
+public class Base64BSD
+{
+ private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
+ private static final int[] IA = new int[256];
+ static {
+ Arrays.fill(IA, -1);
+ for (int i = 0, iS = CA.length; i < iS; i++)
+ IA[CA[i]] = i;
+ IA['='] = 0;
+ }
+
+ // ****************************************************************************************
+ // * char[] version
+ // ****************************************************************************************
+
+ /** Encodes a raw byte array into a BASE64 <code>char[]</code> representation i accordance with RFC 2045.
+ * @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
+ * @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
+ * little faster.
+ * @return A BASE64 encoded array. Never <code>null</code>.
+ */
+ public final static char[] encodeToChar(byte[] sArr, boolean lineSep)
+ {
+ // Check special case
+ int sLen = sArr != null ? sArr.length : 0;
+ if (sLen == 0)
+ return new char[0];
+
+ int eLen = (sLen / 3) * 3; // Length of even 24-bits.
+ int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count
+ int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array
+ char[] dArr = new char[dLen];
+
+ // Encode even 24-bits
+ for (int s = 0, d = 0, cc = 0; s < eLen;) {
+ // Copy next three bytes into lower 24 bits of int, paying attension to sign.
+ int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
+
+ // Encode the int into four chars
+ dArr[d++] = CA[(i >>> 18) & 0x3f];
+ dArr[d++] = CA[(i >>> 12) & 0x3f];
+ dArr[d++] = CA[(i >>> 6) & 0x3f];
+ dArr[d++] = CA[i & 0x3f];
+
+ // Add optional line separator
+ if (lineSep && ++cc == 19 && d < dLen - 2) {
+ dArr[d++] = '\r';
+ dArr[d++] = '\n';
+ cc = 0;
+ }
+ }
+
+ // Pad and encode last bits if source isn't even 24 bits.
+ int left = sLen - eLen; // 0 - 2.
+ if (left > 0) {
+ // Prepare the int
+ int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
+
+ // Set last four chars
+ dArr[dLen - 4] = CA[i >> 12];
+ dArr[dLen - 3] = CA[(i >>> 6) & 0x3f];
+ dArr[dLen - 2] = left == 2 ? CA[i & 0x3f] : '=';
+ dArr[dLen - 1] = '=';
+ }
+ return dArr;
+ }
+
+ /** Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with
+ * and without line separators.
+ * @param sArr The source array. <code>null</code> or length 0 will return an empty array.
+ * @return The decoded array of bytes. May be of length 0. Will be <code>null</code> if the legal characters
+ * (including '=') isn't divideable by 4. (I.e. definitely corrupted).
+ */
+ public final static byte[] decode(char[] sArr)
+ {
+ // Check special case
+ int sLen = sArr != null ? sArr.length : 0;
+ if (sLen == 0)
+ return new byte[0];
+
+ // Count illegal characters (including '\r', '\n') to know what size the returned array will be,
+ // so we don't have to reallocate & copy it later.
+ int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
+ for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
+ if (IA[sArr[i]] < 0)
+ sepCnt++;
+
+ // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
+ if ((sLen - sepCnt) % 4 != 0)
+ return null;
+
+ int pad = 0;
+ for (int i = sLen; i > 1 && IA[sArr[--i]] <= 0;)
+ if (sArr[i] == '=')
+ pad++;
+
+ int len = ((sLen - sepCnt) * 6 >> 3) - pad;
+
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ for (int s = 0, d = 0; d < len;) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = 0;
+ for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
+ int c = IA[sArr[s++]];
+ if (c >= 0)
+ i |= c << (18 - j * 6);
+ else
+ j--;
+ }
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ if (d < len) {
+ dArr[d++]= (byte) (i >> 8);
+ if (d < len)
+ dArr[d++] = (byte) i;
+ }
+ }
+ return dArr;
+ }
+
+ /** Decodes a BASE64 encoded char array that is known to be resonably well formatted. The method is about twice as
+ * fast as {@link #decode(char[])}. The preconditions are:<br>
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
+ * + Line separator must be "\r\n", as specified in RFC 2045
+ * + The array must not contain illegal characters within the encoded string<br>
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
+ * @param sArr The source array. Length 0 will return an empty array. <code>null</code> will throw an exception.
+ * @return The decoded array of bytes. May be of length 0.
+ */
+ public final static byte[] decodeFast(char[] sArr)
+ {
+ // Check special case
+ int sLen = sArr.length;
+ if (sLen == 0)
+ return new byte[0];
+
+ int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
+
+ // Trim illegal chars from start
+ while (sIx < eIx && IA[sArr[sIx]] < 0)
+ sIx++;
+
+ // Trim illegal chars from end
+ while (eIx > 0 && IA[sArr[eIx]] < 0)
+ eIx--;
+
+ // get the padding count (=) (0, 1 or 2)
+ int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end.
+ int cCnt = eIx - sIx + 1; // Content count including possible separators
+ int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
+
+ int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ // Decode all but the last 0 - 2 bytes.
+ int d = 0;
+ for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ dArr[d++] = (byte) (i >> 8);
+ dArr[d++] = (byte) i;
+
+ // If line separator, jump over it.
+ if (sepCnt > 0 && ++cc == 19) {
+ sIx += 2;
+ cc = 0;
+ }
+ }
+
+ if (d < len) {
+ // Decode last 1-3 bytes (incl '=') into 1-3 bytes
+ int i = 0;
+ for (int j = 0; sIx <= eIx - pad; j++)
+ i |= IA[sArr[sIx++]] << (18 - j * 6);
+
+ for (int r = 16; d < len; r -= 8)
+ dArr[d++] = (byte) (i >> r);
+ }
+
+ return dArr;
+ }
+
+ // ****************************************************************************************
+ // * byte[] version
+ // ****************************************************************************************
+
+ /** Encodes a raw byte array into a BASE64 <code>byte[]</code> representation i accordance with RFC 2045.
+ * @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
+ * @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
+ * little faster.
+ * @return A BASE64 encoded array. Never <code>null</code>.
+ */
+ public final static byte[] encodeToByte(byte[] sArr, boolean lineSep)
+ {
+ // Check special case
+ int sLen = sArr != null ? sArr.length : 0;
+ if (sLen == 0)
+ return new byte[0];
+
+ int eLen = (sLen / 3) * 3; // Length of even 24-bits.
+ int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count
+ int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array
+ byte[] dArr = new byte[dLen];
+
+ // Encode even 24-bits
+ for (int s = 0, d = 0, cc = 0; s < eLen;) {
+ // Copy next three bytes into lower 24 bits of int, paying attension to sign.
+ int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
+
+ // Encode the int into four chars
+ dArr[d++] = (byte) CA[(i >>> 18) & 0x3f];
+ dArr[d++] = (byte) CA[(i >>> 12) & 0x3f];
+ dArr[d++] = (byte) CA[(i >>> 6) & 0x3f];
+ dArr[d++] = (byte) CA[i & 0x3f];
+
+ // Add optional line separator
+ if (lineSep && ++cc == 19 && d < dLen - 2) {
+ dArr[d++] = '\r';
+ dArr[d++] = '\n';
+ cc = 0;
+ }
+ }
+
+ // Pad and encode last bits if source isn't an even 24 bits.
+ int left = sLen - eLen; // 0 - 2.
+ if (left > 0) {
+ // Prepare the int
+ int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
+
+ // Set last four chars
+ dArr[dLen - 4] = (byte) CA[i >> 12];
+ dArr[dLen - 3] = (byte) CA[(i >>> 6) & 0x3f];
+ dArr[dLen - 2] = left == 2 ? (byte) CA[i & 0x3f] : (byte) '=';
+ dArr[dLen - 1] = '=';
+ }
+ return dArr;
+ }
+
+ /** Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with
+ * and without line separators.
+ * @param sArr The source array. Length 0 will return an empty array. <code>null</code> will throw an exception.
+ * @return The decoded array of bytes. May be of length 0. Will be <code>null</code> if the legal characters
+ * (including '=') isn't divideable by 4. (I.e. definitely corrupted).
+ */
+ public final static byte[] decode(byte[] sArr)
+ {
+ // Check special case
+ int sLen = sArr.length;
+
+ // Count illegal characters (including '\r', '\n') to know what size the returned array will be,
+ // so we don't have to reallocate & copy it later.
+ int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
+ for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
+ if (IA[sArr[i] & 0xff] < 0)
+ sepCnt++;
+
+ // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
+ if ((sLen - sepCnt) % 4 != 0)
+ return null;
+
+ int pad = 0;
+ for (int i = sLen; i > 1 && IA[sArr[--i] & 0xff] <= 0;)
+ if (sArr[i] == '=')
+ pad++;
+
+ int len = ((sLen - sepCnt) * 6 >> 3) - pad;
+
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ for (int s = 0, d = 0; d < len;) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = 0;
+ for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
+ int c = IA[sArr[s++] & 0xff];
+ if (c >= 0)
+ i |= c << (18 - j * 6);
+ else
+ j--;
+ }
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ if (d < len) {
+ dArr[d++]= (byte) (i >> 8);
+ if (d < len)
+ dArr[d++] = (byte) i;
+ }
+ }
+
+ return dArr;
+ }
+
+
+ /** Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as
+ * fast as {@link #decode(byte[])}. The preconditions are:<br>
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
+ * + Line separator must be "\r\n", as specified in RFC 2045
+ * + The array must not contain illegal characters within the encoded string<br>
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
+ * @param sArr The source array. Length 0 will return an empty array. <code>null</code> will throw an exception.
+ * @return The decoded array of bytes. May be of length 0.
+ */
+ public final static byte[] decodeFast(byte[] sArr)
+ {
+ // Check special case
+ int sLen = sArr.length;
+ if (sLen == 0)
+ return new byte[0];
+
+ int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
+
+ // Trim illegal chars from start
+ while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0)
+ sIx++;
+
+ // Trim illegal chars from end
+ while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0)
+ eIx--;
+
+ // get the padding count (=) (0, 1 or 2)
+ int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end.
+ int cCnt = eIx - sIx + 1; // Content count including possible separators
+ int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
+
+ int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ // Decode all but the last 0 - 2 bytes.
+ int d = 0;
+ for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ dArr[d++] = (byte) (i >> 8);
+ dArr[d++] = (byte) i;
+
+ // If line separator, jump over it.
+ if (sepCnt > 0 && ++cc == 19) {
+ sIx += 2;
+ cc = 0;
+ }
+ }
+
+ if (d < len) {
+ // Decode last 1-3 bytes (incl '=') into 1-3 bytes
+ int i = 0;
+ for (int j = 0; sIx <= eIx - pad; j++)
+ i |= IA[sArr[sIx++]] << (18 - j * 6);
+
+ for (int r = 16; d < len; r -= 8)
+ dArr[d++] = (byte) (i >> r);
+ }
+
+ return dArr;
+ }
+
+ // ****************************************************************************************
+ // * String version
+ // ****************************************************************************************
+
+ /** Encodes a raw byte array into a BASE64 <code>String</code> representation i accordance with RFC 2045.
+ * @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
+ * @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
+ * little faster.
+ * @return A BASE64 encoded array. Never <code>null</code>.
+ */
+ public final static String encodeToString(byte[] sArr, boolean lineSep)
+ {
+ // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower.
+ return new String(encodeToChar(sArr, lineSep));
+ }
+
+ /** Decodes a BASE64 encoded <code>String</code>. All illegal characters will be ignored and can handle both strings with
+ * and without line separators.<br>
+ * <b>Note!</b> It can be up to about 2x the speed to call <code>decode(str.toCharArray())</code> instead. That
+ * will create a temporary array though. This version will use <code>str.charAt(i)</code> to iterate the string.
+ * @param str The source string. <code>null</code> or length 0 will return an empty array.
+ * @return The decoded array of bytes. May be of length 0. Will be <code>null</code> if the legal characters
+ * (including '=') isn't divideable by 4. (I.e. definitely corrupted).
+ */
+ public final static byte[] decode(String str)
+ {
+ // Check special case
+ int sLen = str != null ? str.length() : 0;
+ if (sLen == 0)
+ return new byte[0];
+
+ // Count illegal characters (including '\r', '\n') to know what size the returned array will be,
+ // so we don't have to reallocate & copy it later.
+ int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
+ for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
+ if (IA[str.charAt(i)] < 0)
+ sepCnt++;
+
+ // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
+ if ((sLen - sepCnt) % 4 != 0)
+ return null;
+
+ // Count '=' at end
+ int pad = 0;
+ for (int i = sLen; i > 1 && IA[str.charAt(--i)] <= 0;)
+ if (str.charAt(i) == '=')
+ pad++;
+
+ int len = ((sLen - sepCnt) * 6 >> 3) - pad;
+
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ for (int s = 0, d = 0; d < len;) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = 0;
+ for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
+ int c = IA[str.charAt(s++)];
+ if (c >= 0)
+ i |= c << (18 - j * 6);
+ else
+ j--;
+ }
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ if (d < len) {
+ dArr[d++]= (byte) (i >> 8);
+ if (d < len)
+ dArr[d++] = (byte) i;
+ }
+ }
+ return dArr;
+ }
+
+ /** Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as
+ * fast as {@link #decode(String)}. The preconditions are:<br>
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
+ * + Line separator must be "\r\n", as specified in RFC 2045
+ * + The array must not contain illegal characters within the encoded string<br>
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
+ * @param s The source string. Length 0 will return an empty array. <code>null</code> will throw an exception.
+ * @return The decoded array of bytes. May be of length 0.
+ */
+ public final static byte[] decodeFast(String s)
+ {
+ // Check special case
+ int sLen = s.length();
+ if (sLen == 0)
+ return new byte[0];
+
+ int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
+
+ // Trim illegal chars from start
+ while (sIx < eIx && IA[s.charAt(sIx) & 0xff] < 0)
+ sIx++;
+
+ // Trim illegal chars from end
+ while (eIx > 0 && IA[s.charAt(eIx) & 0xff] < 0)
+ eIx--;
+
+ // get the padding count (=) (0, 1 or 2)
+ int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end.
+ int cCnt = eIx - sIx + 1; // Content count including possible separators
+ int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0;
+
+ int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ // Decode all but the last 0 - 2 bytes.
+ int d = 0;
+ for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6 | IA[s.charAt(sIx++)];
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ dArr[d++] = (byte) (i >> 8);
+ dArr[d++] = (byte) i;
+
+ // If line separator, jump over it.
+ if (sepCnt > 0 && ++cc == 19) {
+ sIx += 2;
+ cc = 0;
+ }
+ }
+
+ if (d < len) {
+ // Decode last 1-3 bytes (incl '=') into 1-3 bytes
+ int i = 0;
+ for (int j = 0; sIx <= eIx - pad; j++)
+ i |= IA[s.charAt(sIx++)] << (18 - j * 6);
+
+ for (int r = 16; d < len; r -= 8)
+ dArr[d++] = (byte) (i >> r);
+ }
+
+ return dArr;
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/stringcodecs/HMACSHA1.java b/src/com/isode/stroke/stringcodecs/HMACSHA1.java
new file mode 100644
index 0000000..1d8c9b7
--- /dev/null
+++ b/src/com/isode/stroke/stringcodecs/HMACSHA1.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.stringcodecs;
+
+import com.isode.stroke.base.ByteArray;
+
+public class HMACSHA1 {
+
+ private static final int B = 64;
+
+ public static ByteArray getResult(ByteArray key, ByteArray data) {
+ assert key.getSize() <= B;
+
+ /* And an assert that does something */
+ if (key.getSize() > B) {
+ throw new IllegalStateException("Invalid key size.");
+ }
+
+ // Create the padded key
+ ByteArray paddedKey = new ByteArray(key);
+ for (int i = key.getSize(); i < B; ++i) {
+ paddedKey.append((byte) 0x0);
+ }
+
+ // Create the first value
+ ByteArray x = new ByteArray(paddedKey);
+ byte[] xInner = x.getData();
+ for (int i = 0; i < xInner.length; ++i) {
+ xInner[i] ^= 0x36;
+ }
+ x.append(data);
+
+ // Create the second value
+ ByteArray y = new ByteArray(paddedKey);
+ byte[] yInner = y.getData();
+ for (int i = 0; i < yInner.length; ++i) {
+ yInner[i] ^= 0x5c;
+ }
+ y.append(SHA1.getHash(x));
+
+ return SHA1.getHash(y);
+ }
+}
diff --git a/src/com/isode/stroke/stringcodecs/Hexify.java b/src/com/isode/stroke/stringcodecs/Hexify.java
new file mode 100644
index 0000000..7f51fb9
--- /dev/null
+++ b/src/com/isode/stroke/stringcodecs/Hexify.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.stringcodecs;
+
+import com.isode.stroke.base.ByteArray;
+
+public class Hexify {
+
+ public static String hexify(byte datum) {
+ return String.format("%x", new Byte(datum));
+ }
+
+ public static String hexify(ByteArray data) {
+ StringBuilder result = new StringBuilder();
+ for (byte b : data.getData()) {
+ result.append(hexify(b));
+ }
+ return result.toString();
+ }
+}
diff --git a/src/com/isode/stroke/stringcodecs/PBKDF2.java b/src/com/isode/stroke/stringcodecs/PBKDF2.java
new file mode 100644
index 0000000..547cbec
--- /dev/null
+++ b/src/com/isode/stroke/stringcodecs/PBKDF2.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.stringcodecs;
+
+import com.isode.stroke.base.ByteArray;
+
+public class PBKDF2 {
+
+ public static ByteArray encode(ByteArray password, ByteArray salt, int iterations) {
+ ByteArray u = HMACSHA1.getResult(password, ByteArray.plus(salt, new ByteArray("\0\0\0\1")));
+ ByteArray result = new ByteArray(u);
+ byte[] resultData = result.getData();
+ int i = 1;
+ while (i < iterations) {
+ u = HMACSHA1.getResult(password, u);
+ for (int j = 0; j < u.getSize(); ++j) {
+ resultData[j] ^= u.getData()[j];
+ }
+ ++i;
+ }
+ return result;
+ }
+}
diff --git a/src/com/isode/stroke/stringcodecs/SHA1.java b/src/com/isode/stroke/stringcodecs/SHA1.java
new file mode 100644
index 0000000..e3d0e79
--- /dev/null
+++ b/src/com/isode/stroke/stringcodecs/SHA1.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2010, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.stringcodecs;
+
+import com.isode.stroke.base.ByteArray;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class SHA1 {
+
+ public static ByteArray getHash(ByteArray data) {
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException ex) {
+ throw new IllegalStateException("JRE doesn't have an SHA hash function", ex);
+ }
+ md.update(data.getData());
+ return new ByteArray(md.digest());
+ }
+}
diff --git a/src/com/isode/stroke/tls/Certificate.java b/src/com/isode/stroke/tls/Certificate.java
new file mode 100644
index 0000000..ac7daed
--- /dev/null
+++ b/src/com/isode/stroke/tls/Certificate.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.tls;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.stringcodecs.Hexify;
+import com.isode.stroke.stringcodecs.SHA1;
+import java.util.List;
+
+public abstract class Certificate {
+
+ /**
+ * Returns the textual representation of the full Subject
+ * name.
+ */
+ public abstract String getSubjectName();
+
+ public abstract List<String> getCommonNames();
+
+ public abstract List<String> getSRVNames();
+
+ public abstract List<String> getDNSNames();
+
+ public abstract List<String> getXMPPAddresses();
+
+ public abstract ByteArray toDER();
+
+ public String getSHA1Fingerprint() {
+ ByteArray hash = SHA1.getHash(toDER());
+ StringBuilder s = new StringBuilder();
+ for (int i = 0; i < hash.getSize(); ++i) {
+ if (i > 0) {
+ s.append(":");
+ }
+ s.append(Hexify.hexify(hash.getData()[i]));
+ }
+ return s.toString();
+ }
+ protected String ID_ON_XMPPADDR_OID = "1.3.6.1.5.5.7.8.5";
+ protected String ID_ON_DNSSRV_OID = "1.3.6.1.5.5.7.8.7";
+}
diff --git a/src/com/isode/stroke/tls/CertificateFactory.java b/src/com/isode/stroke/tls/CertificateFactory.java
new file mode 100644
index 0000000..a4c34db
--- /dev/null
+++ b/src/com/isode/stroke/tls/CertificateFactory.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.tls;
+
+import com.isode.stroke.base.ByteArray;
+
+public interface CertificateFactory {
+ Certificate createCertificateFromDER(ByteArray der);
+}
diff --git a/src/com/isode/stroke/tls/CertificateTrustChecker.java b/src/com/isode/stroke/tls/CertificateTrustChecker.java
new file mode 100644
index 0000000..08d4506
--- /dev/null
+++ b/src/com/isode/stroke/tls/CertificateTrustChecker.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.tls;
+
+/**
+ * A class to implement a check for certificate trust.
+ */
+public interface CertificateTrustChecker {
+
+ /**
+ * This method is called to find out whether a certificate is
+ * trusted. This usually happens when a certificate's validation
+ * fails, to check whether to proceed with the connection or not.
+ */
+ boolean isCertificateTrusted(Certificate certificate);
+}
diff --git a/src/com/isode/stroke/tls/CertificateVerificationError.java b/src/com/isode/stroke/tls/CertificateVerificationError.java
new file mode 100644
index 0000000..a8309ca
--- /dev/null
+++ b/src/com/isode/stroke/tls/CertificateVerificationError.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.tls;
+
+import com.isode.stroke.base.Error;
+
+public class CertificateVerificationError implements Error {
+
+ public enum Type {
+
+ UnknownError,
+ Expired,
+ NotYetValid,
+ SelfSigned,
+ Rejected,
+ Untrusted,
+ InvalidPurpose,
+ PathLengthExceeded,
+ InvalidSignature,
+ InvalidCA,
+ InvalidServerIdentity,
+ };
+
+ public CertificateVerificationError(Type type) {
+ if (type == null) {
+ throw new IllegalStateException();
+ }
+ this.type = type;
+ }
+ public final Type type;
+};
+
diff --git a/src/com/isode/stroke/tls/PKCS12Certificate.java b/src/com/isode/stroke/tls/PKCS12Certificate.java
new file mode 100644
index 0000000..af66545
--- /dev/null
+++ b/src/com/isode/stroke/tls/PKCS12Certificate.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.tls;
+
+import com.isode.stroke.base.ByteArray;
+
+public class PKCS12Certificate {
+
+ public PKCS12Certificate() {
+ }
+
+ public PKCS12Certificate(String filename, String password) {
+ password_ = password;
+ data_.readFromFile(filename);
+ }
+
+ public boolean isNull() {
+ return data_.isEmpty();
+ }
+
+ public ByteArray getData() {
+ return data_;
+ }
+
+ public void setData(ByteArray data) {
+ data_ = data;
+ }
+
+ public String getPassword() {
+ return password_;
+ }
+ private ByteArray data_;
+ private String password_;
+}
diff --git a/src/com/isode/stroke/tls/PlatformTLSFactories.java b/src/com/isode/stroke/tls/PlatformTLSFactories.java
new file mode 100644
index 0000000..0959d07
--- /dev/null
+++ b/src/com/isode/stroke/tls/PlatformTLSFactories.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2011 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tron¨on.
+ * All rights reserved.
+ */
+package com.isode.stroke.tls;
+
+public class PlatformTLSFactories {
+ public TLSContextFactory getTLSContextFactory() {
+ /*FIXME: Implement*/
+ return null;
+ }
+
+ public CertificateFactory getCertificateFactory() {
+ /*FIXME: Implement*/
+ return null;
+ }
+}
diff --git a/src/com/isode/stroke/tls/ServerIdentityVerifier.java b/src/com/isode/stroke/tls/ServerIdentityVerifier.java
new file mode 100644
index 0000000..903b296
--- /dev/null
+++ b/src/com/isode/stroke/tls/ServerIdentityVerifier.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2010 Remko Tron¨on
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.tls;
+
+import com.isode.stroke.idn.IDNA;
+import com.isode.stroke.jid.JID;
+import java.util.List;
+
+public class ServerIdentityVerifier {
+
+ public ServerIdentityVerifier(JID jid) {
+ domain = jid.getDomain();
+ encodedDomain = IDNA.getEncoded(domain);
+ }
+
+ public boolean certificateVerifies(Certificate certificate) {
+ boolean hasSAN = false;
+
+ // DNS names
+ List<String> dnsNames = certificate.getDNSNames();
+ for (String dnsName : dnsNames) {
+ if (matchesDomain(dnsName)) {
+ return true;
+ }
+ }
+ hasSAN |= !dnsNames.isEmpty();
+
+ // SRV names
+ List<String> srvNames = certificate.getSRVNames();
+ for (String srvName : srvNames) {
+ // Only match SRV names that begin with the service; this isn't required per
+ // spec, but we're being purist about this.
+ if (srvName.startsWith("_xmpp-client.") && matchesDomain(srvName.substring("_xmpp-client.".length()))) {
+ return true;
+ }
+ }
+ hasSAN |= !srvNames.isEmpty();
+
+ // XmppAddr
+ List<String> xmppAddresses = certificate.getXMPPAddresses();
+ for (String xmppAddress : xmppAddresses) {
+ if (matchesAddress(xmppAddress)) {
+ return true;
+ }
+ }
+ hasSAN |= !xmppAddresses.isEmpty();
+
+ // CommonNames. Only check this if there was no SAN (according to spec).
+ if (!hasSAN) {
+ List<String> commonNames = certificate.getCommonNames();
+ for (String commonName : commonNames) {
+ if (matchesDomain(commonName)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ boolean matchesDomain(String s) {
+ if (s.startsWith("*.")) {
+ String matchString = s.substring(2);
+ String matchDomain = encodedDomain;
+ int dotIndex = matchDomain.indexOf('.');
+ if (dotIndex >= 0) {
+ matchDomain = matchDomain.substring(dotIndex + 1);
+ }
+ return matchString.equals(matchDomain);
+ }
+ else {
+ return s.equals(encodedDomain);
+ }
+ }
+
+ boolean matchesAddress(String s) {
+ return s.equals(domain);
+ }
+ private String domain;
+ private String encodedDomain;
+}
diff --git a/src/com/isode/stroke/tls/TLSContext.java b/src/com/isode/stroke/tls/TLSContext.java
new file mode 100644
index 0000000..16f2ae3
--- /dev/null
+++ b/src/com/isode/stroke/tls/TLSContext.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.tls;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.signals.Signal;
+import com.isode.stroke.signals.Signal1;
+
+public abstract class TLSContext {
+ //See SSLEngine for real implementation when the time comes
+ public abstract void connect();
+
+ public abstract boolean setClientCertificate(PKCS12Certificate cert);
+
+ public abstract void handleDataFromNetwork(ByteArray data);
+ public abstract void handleDataFromApplication(ByteArray data);
+
+ public abstract Certificate getPeerCertificate();
+ public abstract CertificateVerificationError getPeerCertificateVerificationError();
+
+ public abstract ByteArray getFinishMessage();
+
+ public Signal1<ByteArray> onDataForNetwork = new Signal1<ByteArray>();
+ public Signal1<ByteArray> onDataForApplication = new Signal1<ByteArray>();
+ public Signal onError = new Signal();
+ public Signal onConnected = new Signal();
+}
diff --git a/src/com/isode/stroke/tls/TLSContextFactory.java b/src/com/isode/stroke/tls/TLSContextFactory.java
new file mode 100644
index 0000000..1b84a42
--- /dev/null
+++ b/src/com/isode/stroke/tls/TLSContextFactory.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2011, Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010, Remko Tron¨on.
+ * All rights reserved.
+ */
+
+package com.isode.stroke.tls;
+
+public interface TLSContextFactory {
+ boolean canCreate();
+ TLSContext createTLSContext();
+}