From 1167523c4f675c831e122b6afd9196752ace00a6 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 5 Mar 2013 10:32:23 +0000 Subject: [PATCH 01/24] SMACK-423 Parse unhandled IQ stanzas of type 'request' to dummy IQ class, so that the contents can be examined later on. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13538 b35dd754-fafc-0310-a699-88a17e54d16e --- .../smack/util/PacketParserUtils.java | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/source/org/jivesoftware/smack/util/PacketParserUtils.java b/source/org/jivesoftware/smack/util/PacketParserUtils.java index 14d5d47ab..a574be3db 100644 --- a/source/org/jivesoftware/smack/util/PacketParserUtils.java +++ b/source/org/jivesoftware/smack/util/PacketParserUtils.java @@ -171,13 +171,13 @@ public class PacketParserUtils { */ private static String parseContent(XmlPullParser parser) throws XmlPullParserException, IOException { - String content = ""; + StringBuffer content = new StringBuffer(); int parserDepth = parser.getDepth(); while (!(parser.next() == XmlPullParser.END_TAG && parser .getDepth() == parserDepth)) { - content += parser.getText(); + content.append(parser.getText()); } - return content; + return content.toString(); } /** @@ -325,6 +325,13 @@ public class PacketParserUtils { (Class)provider, parser); } } + // Only handle unknown IQs of type result. Types of 'get' and 'set' which are not understood + // have to be answered with an IQ error response. See the code a few lines below + else if (IQ.Type.RESULT == type){ + // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance + // so that the content of the IQ can be examined later on + iqPacket = new UnparsedResultIQ(parseContent(parser)); + } } } else if (eventType == XmlPullParser.END_TAG) { @@ -340,6 +347,7 @@ public class PacketParserUtils { // qualified by a namespace it does not understand, then answer an IQ of // type "error" with code 501 ("feature-not-implemented") iqPacket = new IQ() { + @Override public String getChildElementXML() { return null; } @@ -355,6 +363,7 @@ public class PacketParserUtils { else { // If an IQ packet wasn't created above, create an empty IQ packet. iqPacket = new IQ() { + @Override public String getChildElementXML() { return null; } @@ -854,7 +863,7 @@ public class PacketParserUtils { } } return object; - } + } /** * Decodes a String into an object of the specified type. If the object @@ -889,4 +898,24 @@ public class PacketParserUtils { } return null; } + + /** + * This class represents and unparsed IQ of the type 'result'. Usually it's created when no IQProvider + * was found for the IQ element. + * + * The child elements can be examined with the getChildElementXML() method. + * + */ + public static class UnparsedResultIQ extends IQ { + public UnparsedResultIQ(String content) { + this.str = content; + } + + private final String str; + + @Override + public String getChildElementXML() { + return this.str; + } + } } From 9123578ea5f6d65f705c3bb8e20697fa6bcc1f79 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 18 Mar 2013 08:40:35 +0000 Subject: [PATCH 02/24] SMACK-361 Added support for Entity Capabilities. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13558 b35dd754-fafc-0310-a699-88a17e54d16e --- build/resources/META-INF/smack-config.xml | 5 +- build/resources/META-INF/smack.providers | 7 + source/org/jivesoftware/smack/Connection.java | 27 + .../org/jivesoftware/smack/PacketReader.java | 13 + .../smack/SmackConfiguration.java | 25 + source/org/jivesoftware/smack/packet/IQ.java | 8 + .../org/jivesoftware/smack/packet/Packet.java | 33 +- .../smack/util/Base32Encoder.java | 187 +++++ .../smack/util/Base64Encoder.java | 44 ++ .../smack/util/StringEncoder.java | 38 + source/org/jivesoftware/smackx/Form.java | 11 +- source/org/jivesoftware/smackx/FormField.java | 42 ++ .../smackx/NodeInformationProvider.java | 9 +- .../smackx/ServiceDiscoveryManager.java | 238 ++++-- .../smackx/commands/AdHocCommandManager.java | 14 +- .../smackx/entitycaps/EntityCapsManager.java | 713 ++++++++++++++++++ .../cache/EntityCapsPersistentCache.java | 38 + .../cache/SimpleDirectoryPersistentCache.java | 193 +++++ .../entitycaps/packet/CapsExtension.java | 83 ++ .../provider/CapsExtensionProvider.java | 60 ++ .../smackx/muc/MultiUserChat.java | 6 + .../jivesoftware/smackx/packet/DataForm.java | 20 +- .../smackx/packet/DiscoverInfo.java | 205 ++++- .../smackx/packet/DiscoverItems.java | 13 + .../smackx/provider/DiscoverInfoProvider.java | 7 +- .../socks5/Socks5ByteStreamManagerTest.java | 23 +- .../entitycaps/EntityCapsManagerTest.java | 227 ++++++ .../smackx/pubsub/ConfigureFormTest.java | 6 +- .../jivesoftware/smack/ReconnectionTest.java | 2 +- .../smack/test/SmackTestCase.java | 10 + .../smack/util/ConnectionUtils.java | 27 + .../smackx/ServiceDiscoveryManagerTest.java | 2 +- .../smackx/entitycaps/EntityCapsTest.java | 147 ++++ 33 files changed, 2395 insertions(+), 88 deletions(-) create mode 100644 source/org/jivesoftware/smack/util/Base32Encoder.java create mode 100644 source/org/jivesoftware/smack/util/Base64Encoder.java create mode 100644 source/org/jivesoftware/smack/util/StringEncoder.java create mode 100644 source/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java create mode 100644 source/org/jivesoftware/smackx/entitycaps/cache/EntityCapsPersistentCache.java create mode 100644 source/org/jivesoftware/smackx/entitycaps/cache/SimpleDirectoryPersistentCache.java create mode 100644 source/org/jivesoftware/smackx/entitycaps/packet/CapsExtension.java create mode 100644 source/org/jivesoftware/smackx/entitycaps/provider/CapsExtensionProvider.java create mode 100644 test-unit/org/jivesoftware/smackx/entitycaps/EntityCapsManagerTest.java create mode 100644 test/org/jivesoftware/smack/util/ConnectionUtils.java create mode 100644 test/org/jivesoftware/smackx/entitycaps/EntityCapsTest.java diff --git a/build/resources/META-INF/smack-config.xml b/build/resources/META-INF/smack-config.xml index c4e99936a..6c4b891e2 100644 --- a/build/resources/META-INF/smack-config.xml +++ b/build/resources/META-INF/smack-config.xml @@ -30,8 +30,11 @@ 10000 - + 1800 + + false + diff --git a/build/resources/META-INF/smack.providers b/build/resources/META-INF/smack.providers index 0de71aa04..f1a7c1f91 100644 --- a/build/resources/META-INF/smack.providers +++ b/build/resources/META-INF/smack.providers @@ -678,4 +678,11 @@ urn:xmpp:receipts org.jivesoftware.smackx.receipts.DeliveryReceiptRequest$Provider + + + + c + http://jabber.org/protocol/caps + org.jivesoftware.smackx.entitycaps.provider.CapsExtensionProvider + diff --git a/source/org/jivesoftware/smack/Connection.java b/source/org/jivesoftware/smack/Connection.java index d041067a4..9a213dbc8 100644 --- a/source/org/jivesoftware/smack/Connection.java +++ b/source/org/jivesoftware/smack/Connection.java @@ -204,6 +204,11 @@ public abstract class Connection { */ protected final ConnectionConfiguration config; + /** + * Holds the Caps Node information for the used XMPP service (i.e. the XMPP server) + */ + private String serviceCapsNode; + protected XMPPInputOutputStream compressionHandler; /** @@ -795,7 +800,29 @@ public abstract class Connection { } } + /** + * Set the servers Entity Caps node + * + * Connection holds this information in order to avoid a dependency to + * smackx where EntityCapsManager lives from smack. + * + * @param node + */ + protected void setServiceCapsNode(String node) { + serviceCapsNode = node; + } + /** + * Retrieve the servers Entity Caps node + * + * Connection holds this information in order to avoid a dependency to + * smackx where EntityCapsManager lives from smack. + * + * @return + */ + public String getServiceCapsNode() { + return serviceCapsNode; + } /** * A wrapper class to associate a packet filter with a listener. diff --git a/source/org/jivesoftware/smack/PacketReader.java b/source/org/jivesoftware/smack/PacketReader.java index 616a18dbe..590dfd951 100644 --- a/source/org/jivesoftware/smack/PacketReader.java +++ b/source/org/jivesoftware/smack/PacketReader.java @@ -393,6 +393,19 @@ class PacketReader { // The server requires the client to bind a resource to the stream connection.getSASLAuthentication().bindingRequired(); } + // Set the entity caps node for the server if one is send + // See http://xmpp.org/extensions/xep-0115.html#stream + else if (parser.getName().equals("c")) { + String node = parser.getAttributeValue(null, "node"); + String ver = parser.getAttributeValue(null, "ver"); + if (ver != null && node != null) { + String capsNode = node + "#" + ver; + // In order to avoid a dependency from smack to smackx + // we have to set the services caps node in the connection + // and not directly in the EntityCapsManager + connection.setServiceCapsNode(capsNode); + } + } else if (parser.getName().equals("session")) { // The server supports sessions connection.getSASLAuthentication().sessionsSupported(); diff --git a/source/org/jivesoftware/smack/SmackConfiguration.java b/source/org/jivesoftware/smack/SmackConfiguration.java index 83f8d22f3..80f1906af 100644 --- a/source/org/jivesoftware/smack/SmackConfiguration.java +++ b/source/org/jivesoftware/smack/SmackConfiguration.java @@ -63,6 +63,11 @@ public final class SmackConfiguration { */ private static int defaultPingInterval = 1800; // 30 min (30*60) + /** + * This automatically enables EntityCaps for new connections if it is set to true + */ + private static boolean autoEnableEntityCaps = false; + private SmackConfiguration() { } @@ -115,6 +120,9 @@ public final class SmackConfiguration { else if (parser.getName().equals("defaultPingInterval")) { defaultPingInterval = parseIntProperty(parser, defaultPingInterval); } + else if (parser.getName().equals("autoEnableEntityCaps")) { + autoEnableEntityCaps = Boolean.parseBoolean(parser.nextText()); + } } eventType = parser.next(); } @@ -329,6 +337,23 @@ public final class SmackConfiguration { SmackConfiguration.defaultPingInterval = defaultPingInterval; } + /** + * Check if Entity Caps are enabled as default for every new connection + * @return + */ + public static boolean autoEnableEntityCaps() { + return autoEnableEntityCaps; + } + + /** + * Set if Entity Caps are enabled or disabled for every new connection + * + * @param true if Entity Caps should be auto enabled, false if not + */ + public static void setAutoEnableEntityCaps(boolean b) { + autoEnableEntityCaps = b; + } + private static void parseClassToLoad(XmlPullParser parser) throws Exception { String className = parser.nextText(); // Attempt to load the class so that the class can get initialized diff --git a/source/org/jivesoftware/smack/packet/IQ.java b/source/org/jivesoftware/smack/packet/IQ.java index 8b844674c..8e1f7d4ab 100644 --- a/source/org/jivesoftware/smack/packet/IQ.java +++ b/source/org/jivesoftware/smack/packet/IQ.java @@ -43,6 +43,14 @@ public abstract class IQ extends Packet { private Type type = Type.GET; + public IQ() { + super(); + } + + public IQ(IQ iq) { + super(iq); + type = iq.getType(); + } /** * Returns the type of the IQ packet. * diff --git a/source/org/jivesoftware/smack/packet/Packet.java b/source/org/jivesoftware/smack/packet/Packet.java index 883462b1b..041d8c892 100644 --- a/source/org/jivesoftware/smack/packet/Packet.java +++ b/source/org/jivesoftware/smack/packet/Packet.java @@ -90,6 +90,22 @@ public abstract class Packet { private final Map properties = new HashMap(); private XMPPError error = null; + public Packet() { + } + + public Packet(Packet p) { + packetID = p.getPacketID(); + to = p.getTo(); + from = p.getFrom(); + xmlns = p.xmlns; + error = p.error; + + // Copy extensions + for (PacketExtension pe : p.getExtensions()) { + addExtension(pe); + } + } + /** * Returns the unique ID of the packet. The returned value could be null when * ID_NOT_AVAILABLE was set as the packet's id. @@ -247,14 +263,25 @@ public abstract class Packet { } /** - * Adds a packet extension to the packet. + * Adds a packet extension to the packet. Does nothing if extension is null. * * @param extension a packet extension. */ public void addExtension(PacketExtension extension) { + if (extension == null) return; packetExtensions.add(extension); } + /** + * Adds a collection of packet extensions to the packet. Does nothing if extensions is null. + * + * @param extensions a collection of packet extensions + */ + public void addExtensions(Collection extensions) { + if (extensions == null) return; + packetExtensions.addAll(extensions); + } + /** * Removes a packet extension from the packet. * @@ -266,7 +293,7 @@ public abstract class Packet { /** * Returns the packet property with the specified name or null if the - * property doesn't exist. Property values that were orginally primitives will + * property doesn't exist. Property values that were originally primitives will * be returned as their object equivalent. For example, an int property will be * returned as an Integer, a double as a Double, etc. * @@ -456,4 +483,4 @@ public abstract class Packet { result = 31 * result + (error != null ? error.hashCode() : 0); return result; } -} \ No newline at end of file +} diff --git a/source/org/jivesoftware/smack/util/Base32Encoder.java b/source/org/jivesoftware/smack/util/Base32Encoder.java new file mode 100644 index 000000000..c7cc1d028 --- /dev/null +++ b/source/org/jivesoftware/smack/util/Base32Encoder.java @@ -0,0 +1,187 @@ +/** + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.util; + + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Base32 string encoding is useful for when filenames case-insensitive filesystems are encoded. + * Base32 representation takes roughly 20% more space then Base64. + * + * @author Florian Schmaus + * Based on code by Brian Wellington (bwelling@xbill.org) + * @see Base32 Wikipedia entry + * + */ +public class Base32Encoder implements StringEncoder { + + private static Base32Encoder instance; + private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ2345678"; + + private Base32Encoder() { + // Use getInstance() + } + + public static Base32Encoder getInstance() { + if (instance == null) { + instance = new Base32Encoder(); + } + return instance; + } + + @Override + public String decode(String str) { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + byte[] raw = str.getBytes(); + for (int i = 0; i < raw.length; i++) { + char c = (char) raw[i]; + if (!Character.isWhitespace(c)) { + c = Character.toUpperCase(c); + bs.write((byte) c); + } + } + + while (bs.size() % 8 != 0) + bs.write('8'); + + byte[] in = bs.toByteArray(); + + bs.reset(); + DataOutputStream ds = new DataOutputStream(bs); + + for (int i = 0; i < in.length / 8; i++) { + short[] s = new short[8]; + int[] t = new int[5]; + + int padlen = 8; + for (int j = 0; j < 8; j++) { + char c = (char) in[i * 8 + j]; + if (c == '8') + break; + s[j] = (short) ALPHABET.indexOf(in[i * 8 + j]); + if (s[j] < 0) + return null; + padlen--; + } + int blocklen = paddingToLen(padlen); + if (blocklen < 0) + return null; + + // all 5 bits of 1st, high 3 (of 5) of 2nd + t[0] = (s[0] << 3) | s[1] >> 2; + // lower 2 of 2nd, all 5 of 3rd, high 1 of 4th + t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4); + // lower 4 of 4th, high 4 of 5th + t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F); + // lower 1 of 5th, all 5 of 6th, high 2 of 7th + t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3); + // lower 3 of 7th, all of 8th + t[4] = ((s[6] & 0x07) << 5) | s[7]; + + try { + for (int j = 0; j < blocklen; j++) + ds.writeByte((byte) (t[j] & 0xFF)); + } catch (IOException e) { + } + } + + return new String(bs.toByteArray()); + } + + @Override + public String encode(String str) { + byte[] b = str.getBytes(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + for (int i = 0; i < (b.length + 4) / 5; i++) { + short s[] = new short[5]; + int t[] = new int[8]; + + int blocklen = 5; + for (int j = 0; j < 5; j++) { + if ((i * 5 + j) < b.length) + s[j] = (short) (b[i * 5 + j] & 0xFF); + else { + s[j] = 0; + blocklen--; + } + } + int padlen = lenToPadding(blocklen); + + // convert the 5 byte block into 8 characters (values 0-31). + + // upper 5 bits from first byte + t[0] = (byte) ((s[0] >> 3) & 0x1F); + // lower 3 bits from 1st byte, upper 2 bits from 2nd. + t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03)); + // bits 5-1 from 2nd. + t[2] = (byte) ((s[1] >> 1) & 0x1F); + // lower 1 bit from 2nd, upper 4 from 3rd + t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F)); + // lower 4 from 3rd, upper 1 from 4th. + t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01)); + // bits 6-2 from 4th + t[5] = (byte) ((s[3] >> 2) & 0x1F); + // lower 2 from 4th, upper 3 from 5th; + t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07)); + // lower 5 from 5th; + t[7] = (byte) (s[4] & 0x1F); + + // write out the actual characters. + for (int j = 0; j < t.length - padlen; j++) { + char c = ALPHABET.charAt(t[j]); + os.write(c); + } + } + return new String(os.toByteArray()); + } + + private static int lenToPadding(int blocklen) { + switch (blocklen) { + case 1: + return 6; + case 2: + return 4; + case 3: + return 3; + case 4: + return 1; + case 5: + return 0; + default: + return -1; + } + } + + private static int paddingToLen(int padlen) { + switch (padlen) { + case 6: + return 1; + case 4: + return 2; + case 3: + return 3; + case 1: + return 4; + case 0: + return 5; + default: + return -1; + } + } + +} diff --git a/source/org/jivesoftware/smack/util/Base64Encoder.java b/source/org/jivesoftware/smack/util/Base64Encoder.java new file mode 100644 index 000000000..78399b463 --- /dev/null +++ b/source/org/jivesoftware/smack/util/Base64Encoder.java @@ -0,0 +1,44 @@ +/** + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smack.util; + + +/** + * @author Florian Schmaus + */ +public class Base64Encoder implements StringEncoder { + + private static Base64Encoder instance; + + private Base64Encoder() { + // Use getInstance() + } + + public static Base64Encoder getInstance() { + if (instance == null) { + instance = new Base64Encoder(); + } + return instance; + } + + public String encode(String s) { + return Base64.encodeBytes(s.getBytes()); + } + + public String decode(String s) { + return new String(Base64.decode(s)); + } + +} diff --git a/source/org/jivesoftware/smack/util/StringEncoder.java b/source/org/jivesoftware/smack/util/StringEncoder.java new file mode 100644 index 000000000..5a15c9548 --- /dev/null +++ b/source/org/jivesoftware/smack/util/StringEncoder.java @@ -0,0 +1,38 @@ +/** + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @author Florian Schmaus + */ +package org.jivesoftware.smack.util; + +// TODO move StringEncoder, Base64Encoder and Base32Encoder to smack.util + +public interface StringEncoder { + /** + * Encodes an string to another representation + * + * @param string + * @return + */ + public String encode(String string); + + /** + * Decodes an string back to it's initial representation + * + * @param string + * @return + */ + public String decode(String string); +} diff --git a/source/org/jivesoftware/smackx/Form.java b/source/org/jivesoftware/smackx/Form.java index 8b654ab74..992c03619 100644 --- a/source/org/jivesoftware/smackx/Form.java +++ b/source/org/jivesoftware/smackx/Form.java @@ -42,17 +42,22 @@ import org.jivesoftware.smackx.packet.DataForm; * Depending of the form's type different operations are available. For example, it's only possible * to set answers if the form is of type "submit". * + * @see XEP-0004 Data Forms + * * @author Gaston Dombiak */ public class Form { - + public static final String TYPE_FORM = "form"; public static final String TYPE_SUBMIT = "submit"; public static final String TYPE_CANCEL = "cancel"; public static final String TYPE_RESULT = "result"; - + + public static final String NAMESPACE = "jabber:x:data"; + public static final String ELEMENT = "x"; + private DataForm dataForm; - + /** * Returns a new ReportedData if the packet is used for gathering data and includes an * extension that matches the elementName and namespace "x","jabber:x:data". diff --git a/source/org/jivesoftware/smackx/FormField.java b/source/org/jivesoftware/smackx/FormField.java index a10196032..44dfa8cec 100644 --- a/source/org/jivesoftware/smackx/FormField.java +++ b/source/org/jivesoftware/smackx/FormField.java @@ -299,6 +299,26 @@ public class FormField { return buf.toString(); } + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj == this) + return true; + if (obj.getClass() != getClass()) + return false; + + FormField other = (FormField) obj; + + String thisXml = toXML(); + String otherXml = other.toXML(); + + if (thisXml.equals(otherXml)) { + return true; + } else { + return false; + } + } + /** * Represents the available option of a given FormField. * @@ -354,5 +374,27 @@ public class FormField { buf.append(""); return buf.toString(); } + + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj == this) + return true; + if (obj.getClass() != getClass()) + return false; + + Option other = (Option) obj; + + if (!value.equals(other.value)) + return false; + + String thisLabel = label == null ? "" : label; + String otherLabel = other.label == null ? "" : other.label; + + if (!thisLabel.equals(otherLabel)) + return false; + + return true; + } } } diff --git a/source/org/jivesoftware/smackx/NodeInformationProvider.java b/source/org/jivesoftware/smackx/NodeInformationProvider.java index 816be4dba..68bb613d3 100644 --- a/source/org/jivesoftware/smackx/NodeInformationProvider.java +++ b/source/org/jivesoftware/smackx/NodeInformationProvider.java @@ -20,6 +20,7 @@ package org.jivesoftware.smackx; +import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smackx.packet.DiscoverInfo; import org.jivesoftware.smackx.packet.DiscoverItems; @@ -36,7 +37,7 @@ import java.util.List; * @author Gaston Dombiak */ public interface NodeInformationProvider { - + /** * Returns a list of the Items {@link org.jivesoftware.smackx.packet.DiscoverItems.Item} * defined in the node. For example, the MUC protocol specifies that an XMPP client should @@ -65,4 +66,10 @@ public interface NodeInformationProvider { */ public abstract List getNodeIdentities(); + /** + * Returns a list of the packet extensions defined in the node. + * + * @return a list of the packet extensions defined in the node. + */ + public abstract List getNodePacketExtensions(); } diff --git a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java index 8e184b765..f0e7912aa 100644 --- a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java +++ b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java @@ -26,8 +26,11 @@ import org.jivesoftware.smack.filter.PacketIDFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smackx.entitycaps.EntityCapsManager; import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; import org.jivesoftware.smackx.packet.DiscoverItems; import org.jivesoftware.smackx.packet.DataForm; @@ -47,8 +50,13 @@ import java.util.concurrent.ConcurrentHashMap; */ public class ServiceDiscoveryManager { - private static String identityName = "Smack"; - private static String identityType = "pc"; + private static final String DEFAULT_IDENTITY_NAME = "Smack"; + private static final String DEFAULT_IDENTITY_CATEGORY = "client"; + private static final String DEFAULT_IDENTITY_TYPE = "pc"; + + private static List identities = new LinkedList(); + + private EntityCapsManager capsManager; private static Map instances = new ConcurrentHashMap(); @@ -66,6 +74,7 @@ public class ServiceDiscoveryManager { new ServiceDiscoveryManager(connection); } }); + identities.add(new Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, DEFAULT_IDENTITY_TYPE)); } /** @@ -77,6 +86,7 @@ public class ServiceDiscoveryManager { */ public ServiceDiscoveryManager(Connection connection) { this.connection = connection; + init(); } @@ -98,7 +108,12 @@ public class ServiceDiscoveryManager { * in a disco request. */ public static String getIdentityName() { - return identityName; + DiscoverInfo.Identity identity = identities.get(0); + if (identity != null) { + return identity.getName(); + } else { + return null; + } } /** @@ -109,7 +124,9 @@ public class ServiceDiscoveryManager { * in a disco request. */ public static void setIdentityName(String name) { - identityName = name; + DiscoverInfo.Identity identity = identities.remove(0); + identity = new DiscoverInfo.Identity(DEFAULT_IDENTITY_CATEGORY, name, DEFAULT_IDENTITY_TYPE); + identities.add(identity); } /** @@ -121,7 +138,12 @@ public class ServiceDiscoveryManager { * disco request. */ public static String getIdentityType() { - return identityType; + DiscoverInfo.Identity identity = identities.get(0); + if (identity != null) { + return identity.getType(); + } else { + return null; + } } /** @@ -133,7 +155,22 @@ public class ServiceDiscoveryManager { * disco request. */ public static void setIdentityType(String type) { - identityType = type; + DiscoverInfo.Identity identity = identities.get(0); + if (identity != null) { + identity.setType(type); + } else { + identity = new DiscoverInfo.Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, type); + identities.add(identity); + } + } + + /** + * Returns all identities of this client as unmodifiable Collection + * + * @return + */ + public static List getIdentities() { + return Collections.unmodifiableList(identities); } /** @@ -190,13 +227,10 @@ public class ServiceDiscoveryManager { NodeInformationProvider nodeInformationProvider = getNodeInformationProvider(discoverItems.getNode()); if (nodeInformationProvider != null) { - // Specified node was found - List items = nodeInformationProvider.getNodeItems(); - if (items != null) { - for (DiscoverItems.Item item : items) { - response.addItem(item); - } - } + // Specified node was found, add node items + response.addItems(nodeInformationProvider.getNodeItems()); + // Add packet extensions + response.addExtensions(nodeInformationProvider.getNodePacketExtensions()); } else if(discoverItems.getNode() != null) { // Return error since client doesn't contain // the specified node @@ -222,22 +256,12 @@ public class ServiceDiscoveryManager { response.setTo(discoverInfo.getFrom()); response.setPacketID(discoverInfo.getPacketID()); response.setNode(discoverInfo.getNode()); - // Add the client's identity and features only if "node" is null + // Add the client's identity and features only if "node" is null + // and if the request was not send to a node. If Entity Caps are + // enabled the client's identity and features are may also added + // if the right node is chosen if (discoverInfo.getNode() == null) { - // Set this client identity - DiscoverInfo.Identity identity = new DiscoverInfo.Identity("client", - getIdentityName()); - identity.setType(getIdentityType()); - response.addIdentity(identity); - // Add the registered features to the response - synchronized (features) { - for (Iterator it = getFeatures(); it.hasNext();) { - response.addFeature(it.next()); - } - if (extendedInfo != null) { - response.addExtension(extendedInfo); - } - } + addDiscoverInfoTo(response); } else { // Disco#info was sent to a node. Check if we have information of the @@ -246,20 +270,11 @@ public class ServiceDiscoveryManager { getNodeInformationProvider(discoverInfo.getNode()); if (nodeInformationProvider != null) { // Node was found. Add node features - List features = nodeInformationProvider.getNodeFeatures(); - if (features != null) { - for(String feature : features) { - response.addFeature(feature); - } - } + response.addFeatures(nodeInformationProvider.getNodeFeatures()); // Add node identities - List identities = - nodeInformationProvider.getNodeIdentities(); - if (identities != null) { - for (DiscoverInfo.Identity identity : identities) { - response.addIdentity(identity); - } - } + response.addIdentities(nodeInformationProvider.getNodeIdentities()); + // Add packet extensions + response.addExtensions(nodeInformationProvider.getNodePacketExtensions()); } else { // Return error since specified node was not found @@ -274,6 +289,26 @@ public class ServiceDiscoveryManager { connection.addPacketListener(packetListener, packetFilter); } + /** + * Add discover info response data. + * + * @see XEP-30 Basic Protocol; Example 2 + * + * @param response the discover info response packet + */ + public void addDiscoverInfoTo(DiscoverInfo response) { + // First add the identities of the connection + response.addIdentities(identities); + + // Add the registered features to the response + synchronized (features) { + for (Iterator it = getFeatures(); it.hasNext();) { + response.addFeature(it.next()); + } + response.addExtension(extendedInfo); + } + } + /** * Returns the NodeInformationProvider responsible for providing information * (ie items) related to a given node or null if none.

@@ -334,6 +369,17 @@ public class ServiceDiscoveryManager { } } + /** + * Returns the supported features by this XMPP entity. + * + * @return a copy of the List on the supported features by this XMPP entity. + */ + public List getFeaturesList() { + synchronized (features) { + return new LinkedList(features); + } + } + /** * Registers that a new feature is supported by this XMPP entity. When this client is * queried for its information the registered features will be answered.

@@ -348,6 +394,7 @@ public class ServiceDiscoveryManager { public void addFeature(String feature) { synchronized (features) { features.add(feature); + renewEntityCapsVersion(); } } @@ -362,6 +409,7 @@ public class ServiceDiscoveryManager { public void removeFeature(String feature) { synchronized (features) { features.remove(feature); + renewEntityCapsVersion(); } } @@ -394,10 +442,36 @@ public class ServiceDiscoveryManager { */ public void setExtendedInfo(DataForm info) { extendedInfo = info; + renewEntityCapsVersion(); } /** - * Removes the dataform containing extended service discovery information + * Returns the data form that is set as extended information for this Service Discovery instance (XEP-0128) + * + * @see XEP-128: Service Discovery Extensions + * @return + */ + public DataForm getExtendedInfo() { + return extendedInfo; + } + + /** + * Returns the data form as List of PacketExtensions, or null if no data form is set. + * This representation is needed by some classes (e.g. EntityCapsManager, NodeInformationProvider) + * + * @return + */ + public List getExtendedInfoAsList() { + List res = null; + if (extendedInfo != null) { + res = new ArrayList(1); + res.add(extendedInfo); + } + return res; + } + + /** + * Removes the data form containing extended service discovery information * from the information returned by this XMPP entity.

* * Since no packet is actually sent to the server it is safe to perform this @@ -405,17 +479,45 @@ public class ServiceDiscoveryManager { */ public void removeExtendedInfo() { extendedInfo = null; + renewEntityCapsVersion(); } /** * Returns the discovered information of a given XMPP entity addressed by its JID. + * Use null as entityID to query the server * - * @param entityID the address of the XMPP entity. + * @param entityID the address of the XMPP entity or null. * @return the discovered information. * @throws XMPPException if the operation failed for some reason. */ public DiscoverInfo discoverInfo(String entityID) throws XMPPException { - return discoverInfo(entityID, null); + if (entityID == null) + return discoverInfo(null, null); + + // Check if the have it cached in the Entity Capabilities Manager + DiscoverInfo info = EntityCapsManager.getDiscoverInfoByUser(entityID); + + if (info != null) { + // We were able to retrieve the information from Entity Caps and + // avoided a disco request, hurray! + return info; + } + + // Try to get the newest node#version if it's known, otherwise null is + // returned + EntityCapsManager.NodeVerHash nvh = EntityCapsManager.getNodeVerHashByJid(entityID); + + // Discover by requesting the information from the remote entity + // Note that wee need to use NodeVer as argument for Node if it exists + info = discoverInfo(entityID, nvh != null ? nvh.getNodeVer() : null); + + // If the node version is known, store the new entry. + if (nvh != null) { + if (EntityCapsManager.verifyDiscvoerInfoVersion(nvh.getVer(), nvh.getHash(), info)) + EntityCapsManager.addDiscoverInfoByNode(nvh.getNodeVer(), info); + } + + return info; } /** @@ -423,8 +525,11 @@ public class ServiceDiscoveryManager { * note attribute. Use this message only when trying to query information which is not * directly addressable. * + * @see XEP-30 Basic Protocol + * @see XEP-30 Info Nodes + * * @param entityID the address of the XMPP entity. - * @param node the attribute that supplements the 'jid' attribute. + * @param node the optional attribute that supplements the 'jid' attribute. * @return the discovered information. * @throws XMPPException if the operation failed for some reason. */ @@ -471,7 +576,7 @@ public class ServiceDiscoveryManager { * directly addressable. * * @param entityID the address of the XMPP entity. - * @param node the attribute that supplements the 'jid' attribute. + * @param node the optional attribute that supplements the 'jid' attribute. * @return the discovered items. * @throws XMPPException if the operation failed for some reason. */ @@ -513,8 +618,21 @@ public class ServiceDiscoveryManager { */ public boolean canPublishItems(String entityID) throws XMPPException { DiscoverInfo info = discoverInfo(entityID); - return info.containsFeature("http://jabber.org/protocol/disco#publish"); - } + return canPublishItems(info); + } + + /** + * Returns true if the server supports publishing of items. A client may wish to publish items + * to the server so that the server can provide items associated to the client. These items will + * be returned by the server whenever the server receives a disco request targeted to the bare + * address of the client (i.e. user@host.com). + * + * @param DiscoverInfo the discover info packet to check. + * @return true if the server supports publishing of items. + */ + public static boolean canPublishItems(DiscoverInfo info) { + return info.containsFeature("http://jabber.org/protocol/disco#publish"); + } /** * Publishes new items to a parent entity. The item elements to publish MUST have at least @@ -565,4 +683,26 @@ public class ServiceDiscoveryManager { throw new XMPPException(result.getError()); } } -} \ No newline at end of file + + /** + * Entity Capabilities + */ + + /** + * Loads the ServiceDiscoveryManager with an EntityCapsManger + * that speeds up certain lookups + * @param manager + */ + public void setEntityCapsManager(EntityCapsManager manager) { + capsManager = manager; + } + + /** + * Updates the Entity Capabilities Verification String + * if EntityCaps is enabled + */ + private void renewEntityCapsVersion() { + if (capsManager != null && capsManager.entityCapsEnabled()) + capsManager.updateLocalEntityCaps(); + } +} diff --git a/source/org/jivesoftware/smackx/commands/AdHocCommandManager.java b/source/org/jivesoftware/smackx/commands/AdHocCommandManager.java index d80c1ac63..f32c48ec2 100755 --- a/source/org/jivesoftware/smackx/commands/AdHocCommandManager.java +++ b/source/org/jivesoftware/smackx/commands/AdHocCommandManager.java @@ -25,6 +25,7 @@ import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.Form; @@ -181,12 +182,16 @@ public class AdHocCommandManager { public List getNodeIdentities() { List answer = new ArrayList(); DiscoverInfo.Identity identity = new DiscoverInfo.Identity( - "automation", name); - identity.setType("command-node"); + "automation", name, "command-node"); answer.add(identity); return answer; } + @Override + public List getNodePacketExtensions() { + return null; + } + }); } @@ -319,6 +324,11 @@ public class AdHocCommandManager { public List getNodeIdentities() { return null; } + + @Override + public List getNodePacketExtensions() { + return null; + } }); // The packet listener and the filter for processing some AdHoc Commands diff --git a/source/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java b/source/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java new file mode 100644 index 000000000..d5d6402d2 --- /dev/null +++ b/source/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java @@ -0,0 +1,713 @@ +/** + * Copyright 2009 Jonas Ã…dahl. + * Copyright 2011-2013 Florian Schmaus + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.entitycaps; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.PacketInterceptor; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.filter.NotFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.filter.PacketExtensionFilter; +import org.jivesoftware.smack.util.Base64; +import org.jivesoftware.smack.util.Cache; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.NodeInformationProvider; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.entitycaps.cache.EntityCapsPersistentCache; +import org.jivesoftware.smackx.entitycaps.packet.CapsExtension; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DataForm; +import org.jivesoftware.smackx.packet.DiscoverInfo.Feature; +import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; +import org.jivesoftware.smackx.packet.DiscoverItems.Item; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Keeps track of entity capabilities. + * + * @author Florian Schmaus + */ +public class EntityCapsManager { + + public static final String NAMESPACE = "http://jabber.org/protocol/caps"; + public static final String ELEMENT = "c"; + + private static final String ENTITY_NODE = "http://www.igniterealtime.org/projects/smack"; + private static final Map SUPPORTED_HASHES = new HashMap(); + + protected static EntityCapsPersistentCache persistentCache; + + private static Map instances = Collections + .synchronizedMap(new WeakHashMap()); + + /** + * Map of (node + '#" + hash algorithm) to DiscoverInfo data + */ + protected static Map caps = new Cache(1000, -1); + + /** + * Map of Full JID -> DiscoverInfo/null. In case of c2s connection the + * key is formed as user@server/resource (resource is required) In case of + * link-local connection the key is formed as user@host (no resource) In + * case of a server or component the key is formed as domain + */ + protected static Map jidCaps = new Cache(10000, -1); + + static { + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(Connection connection) { + if (connection instanceof XMPPConnection) + new EntityCapsManager(connection); + } + }); + + try { + MessageDigest sha1MessageDigest = MessageDigest.getInstance("SHA-1"); + SUPPORTED_HASHES.put("sha-1", sha1MessageDigest); + } catch (NoSuchAlgorithmException e) { + // Ignore + } + } + + private WeakReference weakRefConnection; + private ServiceDiscoveryManager sdm; + private boolean entityCapsEnabled; + private String currentCapsVersion; + private boolean presenceSend = false; + private Queue lastLocalCapsVersions = new ConcurrentLinkedQueue(); + + /** + * Add DiscoverInfo to the database. + * + * @param nodeVer + * The node and verification String (e.g. + * "http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w="). + * @param info + * DiscoverInfo for the specified node. + */ + public static void addDiscoverInfoByNode(String nodeVer, DiscoverInfo info) { + caps.put(nodeVer, info); + + if (persistentCache != null) + persistentCache.addDiscoverInfoByNodePersistent(nodeVer, info); + } + + /** + * Get the Node version (node#ver) of a JID. Returns a String or null if + * EntiyCapsManager does not have any information. + * + * @param user + * the user (Full JID) + * @return the node version (node#ver) or null + */ + public static String getNodeVersionByJid(String jid) { + NodeVerHash nvh = jidCaps.get(jid); + if (nvh != null) { + return nvh.nodeVer; + } else { + return null; + } + } + + public static NodeVerHash getNodeVerHashByJid(String jid) { + return jidCaps.get(jid); + } + + /** + * Get the discover info given a user name. The discover info is returned if + * the user has a node#ver associated with it and the node#ver has a + * discover info associated with it. + * + * @param user + * user name (Full JID) + * @return the discovered info + */ + public static DiscoverInfo getDiscoverInfoByUser(String user) { + NodeVerHash nvh = jidCaps.get(user); + if (nvh == null) + return null; + + return getDiscoveryInfoByNodeVer(nvh.nodeVer); + } + + /** + * Retrieve DiscoverInfo for a specific node. + * + * @param nodeVer + * The node name (e.g. + * "http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w="). + * @return The corresponding DiscoverInfo or null if none is known. + */ + public static DiscoverInfo getDiscoveryInfoByNodeVer(String nodeVer) { + DiscoverInfo info = caps.get(nodeVer); + if (info != null) + info = new DiscoverInfo(info); + + return info; + } + + /** + * Set the persistent cache implementation + * + * @param cache + * @throws IOException + */ + public static void setPersistentCache(EntityCapsPersistentCache cache) throws IOException { + if (persistentCache != null) + throw new IllegalStateException("Entity Caps Persistent Cache was already set"); + persistentCache = cache; + persistentCache.replay(); + } + + /** + * Sets the maximum Cache size for the JID to nodeVer Cache + * + * @param maxCacheSize + */ + @SuppressWarnings("rawtypes") + public static void setJidCapsMaxCacheSize(int maxCacheSize) { + ((Cache) jidCaps).setMaxCacheSize(maxCacheSize); + } + + /** + * Sets the maximum Cache size for the nodeVer to DiscoverInfo Cache + * + * @param maxCacheSize + */ + @SuppressWarnings("rawtypes") + public static void setCapsMaxCacheSize(int maxCacheSize) { + ((Cache) caps).setMaxCacheSize(maxCacheSize); + } + + private EntityCapsManager(Connection connection) { + this.weakRefConnection = new WeakReference(connection); + this.sdm = ServiceDiscoveryManager.getInstanceFor(connection); + init(); + } + + private void init() { + Connection connection = weakRefConnection.get(); + instances.put(connection, this); + + connection.addConnectionListener(new ConnectionListener() { + public void connectionClosed() { + // Unregister this instance since the connection has been closed + presenceSend = false; + instances.remove(weakRefConnection.get()); + } + + public void connectionClosedOnError(Exception e) { + presenceSend = false; + } + + public void reconnectionFailed(Exception e) { + // ignore + } + + public void reconnectingIn(int seconds) { + // ignore + } + + public void reconnectionSuccessful() { + // ignore + } + }); + + // This calculates the local entity caps version + updateLocalEntityCaps(); + + if (SmackConfiguration.autoEnableEntityCaps()) + enableEntityCaps(); + + PacketFilter packetFilter = new AndFilter(new PacketTypeFilter(Presence.class), new PacketExtensionFilter( + ELEMENT, NAMESPACE)); + connection.addPacketListener(new PacketListener() { + // Listen for remote presence stanzas with the caps extension + // If we receive such a stanza, record the JID and nodeVer + @Override + public void processPacket(Packet packet) { + if (!entityCapsEnabled()) + return; + + CapsExtension ext = (CapsExtension) packet.getExtension(EntityCapsManager.ELEMENT, + EntityCapsManager.NAMESPACE); + + String hash = ext.getHash().toLowerCase(); + if (!SUPPORTED_HASHES.containsKey(hash)) + return; + + String from = packet.getFrom(); + String node = ext.getNode(); + String ver = ext.getVer(); + + jidCaps.put(from, new NodeVerHash(node, ver, hash)); + } + + }, packetFilter); + + packetFilter = new AndFilter(new PacketTypeFilter(Presence.class), new NotFilter(new PacketExtensionFilter( + ELEMENT, NAMESPACE))); + connection.addPacketListener(new PacketListener() { + @Override + public void processPacket(Packet packet) { + // always remove the JID from the map, even if entityCaps are + // disabled + String from = packet.getFrom(); + jidCaps.remove(from); + } + }, packetFilter); + + packetFilter = new PacketTypeFilter(Presence.class); + connection.addPacketSendingListener(new PacketListener() { + @Override + public void processPacket(Packet packet) { + presenceSend = true; + } + }, packetFilter); + + // Intercept presence packages and add caps data when intended. + // XEP-0115 specifies that a client SHOULD include entity capabilities + // with every presence notification it sends. + PacketFilter capsPacketFilter = new PacketTypeFilter(Presence.class); + PacketInterceptor packetInterceptor = new PacketInterceptor() { + public void interceptPacket(Packet packet) { + if (!entityCapsEnabled) + return; + + CapsExtension caps = new CapsExtension(ENTITY_NODE, getCapsVersion(), "sha-1"); + packet.addExtension(caps); + } + }; + connection.addPacketInterceptor(packetInterceptor, capsPacketFilter); + // It's important to do this as last action. Since it changes the + // behavior of the SDM in some ways + sdm.setEntityCapsManager(this); + } + + public static synchronized EntityCapsManager getInstanceFor(Connection connection) { + // For testing purposed forbid EntityCaps for non XMPPConnections + // it may work on BOSH connections too + if (!(connection instanceof XMPPConnection)) + return null; + + if (SUPPORTED_HASHES.size() <= 0) + return null; + + EntityCapsManager entityCapsManager = instances.get(connection); + + if (entityCapsManager == null) { + entityCapsManager = new EntityCapsManager(connection); + } + + return entityCapsManager; + } + + public void enableEntityCaps() { + // Add Entity Capabilities (XEP-0115) feature node. + sdm.addFeature(NAMESPACE); + updateLocalEntityCaps(); + entityCapsEnabled = true; + } + + public void disableEntityCaps() { + entityCapsEnabled = false; + sdm.removeFeature(NAMESPACE); + } + + public boolean entityCapsEnabled() { + return entityCapsEnabled; + } + + /** + * Remove a record telling what entity caps node a user has. + * + * @param user + * the user (Full JID) + */ + public void removeUserCapsNode(String user) { + jidCaps.remove(user); + } + + /** + * Get our own caps version. The version depends on the enabled features. A + * caps version looks like '66/0NaeaBKkwk85efJTGmU47vXI=' + * + * @return our own caps version + */ + public String getCapsVersion() { + return currentCapsVersion; + } + + /** + * Returns the local entity's NodeVer (e.g. + * "http://www.igniterealtime.org/projects/smack/#66/0NaeaBKkwk85efJTGmU47vXI= + * ) + * + * @return + */ + public String getLocalNodeVer() { + return ENTITY_NODE + '#' + getCapsVersion(); + } + + /** + * Returns true if Entity Caps are supported by a given JID + * + * @param jid + * @return + */ + public boolean areEntityCapsSupported(String jid) { + if (jid == null) + return false; + + try { + DiscoverInfo result = sdm.discoverInfo(jid); + return result.containsFeature(NAMESPACE); + } catch (XMPPException e) { + return false; + } + } + + /** + * Returns true if Entity Caps are supported by the local service/server + * + * @return + */ + public boolean areEntityCapsSupportedByServer() { + return areEntityCapsSupported(weakRefConnection.get().getServiceName()); + } + + /** + * Updates the local user Entity Caps information with the data provided + * + * If we are connected and there was already a presence send, another + * presence is send to inform others about your new Entity Caps node string. + * + * @param discoverInfo + * the local users discover info (mostly the service discovery + * features) + * @param identityType + * the local users identity type + * @param identityName + * the local users identity name + * @param extendedInfo + * the local users extended info + */ + public void updateLocalEntityCaps() { + Connection connection = weakRefConnection.get(); + + DiscoverInfo discoverInfo = new DiscoverInfo(); + discoverInfo.setType(IQ.Type.RESULT); + discoverInfo.setNode(getLocalNodeVer()); + if (connection != null) + discoverInfo.setFrom(connection.getUser()); + sdm.addDiscoverInfoTo(discoverInfo); + + currentCapsVersion = generateVerificationString(discoverInfo, "sha-1"); + addDiscoverInfoByNode(ENTITY_NODE + '#' + currentCapsVersion, discoverInfo); + if (lastLocalCapsVersions.size() > 10) { + String oldCapsVersion = lastLocalCapsVersions.poll(); + sdm.removeNodeInformationProvider(ENTITY_NODE + '#' + oldCapsVersion); + } + lastLocalCapsVersions.add(currentCapsVersion); + + caps.put(currentCapsVersion, discoverInfo); + if (connection != null) + jidCaps.put(connection.getUser(), new NodeVerHash(ENTITY_NODE, currentCapsVersion, "sha-1")); + + sdm.setNodeInformationProvider(ENTITY_NODE + '#' + currentCapsVersion, new NodeInformationProvider() { + List features = sdm.getFeaturesList(); + List identities = new LinkedList(ServiceDiscoveryManager.getIdentities()); + List packetExtensions = sdm.getExtendedInfoAsList(); + + @Override + public List getNodeItems() { + return null; + } + + @Override + public List getNodeFeatures() { + return features; + } + + @Override + public List getNodeIdentities() { + return identities; + } + + @Override + public List getNodePacketExtensions() { + return packetExtensions; + } + }); + + // Send an empty presence, and let the packet intercepter + // add a node to it. + // See http://xmpp.org/extensions/xep-0115.html#advertise + // We only send a presence packet if there was already one send + // to respect ConnectionConfiguration.isSendPresence() + if (connection != null && connection.isAuthenticated() && presenceSend) { + Presence presence = new Presence(Presence.Type.available); + connection.sendPacket(presence); + } + } + + /** + * Verify DisoverInfo and Caps Node as defined in XEP-0115 5.4 Processing + * Method + * + * @see XEP-0115 + * 5.4 Processing Method + * + * @param capsNode + * the caps node (i.e. node#ver) + * @param info + * @return true if it's valid and should be cache, false if not + */ + public static boolean verifyDiscvoerInfoVersion(String ver, String hash, DiscoverInfo info) { + // step 3.3 check for duplicate identities + if (info.containsDuplicateIdentities()) + return false; + + // step 3.4 check for duplicate features + if (info.containsDuplicateFeatures()) + return false; + + // step 3.5 check for well-formed packet extensions + if (verifyPacketExtensions(info)) + return false; + + String calculatedVer = generateVerificationString(info, hash); + + if (!ver.equals(calculatedVer)) + return false; + + return true; + } + + /** + * + * @param info + * @return true if the packet extensions is ill-formed + */ + protected static boolean verifyPacketExtensions(DiscoverInfo info) { + List foundFormTypes = new LinkedList(); + for (Iterator i = info.getExtensions().iterator(); i.hasNext();) { + PacketExtension pe = i.next(); + if (pe.getNamespace().equals(Form.NAMESPACE)) { + DataForm df = (DataForm) pe; + for (Iterator it = df.getFields(); it.hasNext();) { + FormField f = it.next(); + if (f.getVariable().equals("FORM_TYPE")) { + for (FormField fft : foundFormTypes) { + if (f.equals(fft)) + return true; + } + foundFormTypes.add(f); + } + } + } + } + return false; + } + + /** + * Generates a XEP-115 Verification String + * + * @see XEP-115 + * Verification String + * + * @param discoverInfo + * @param hash + * the used hash function + * @return The generated verification String or null if the hash is not + * supported + */ + protected static String generateVerificationString(DiscoverInfo discoverInfo, String hash) { + MessageDigest md = SUPPORTED_HASHES.get(hash.toLowerCase()); + if (md == null) + return null; + + DataForm extendedInfo = (DataForm) discoverInfo.getExtension(Form.ELEMENT, Form.NAMESPACE); + + // 1. Initialize an empty string S ('sb' in this method). + StringBuilder sb = new StringBuilder(); // Use StringBuilder as we don't + // need thread-safe StringBuffer + + // 2. Sort the service discovery identities by category and then by + // type and then by xml:lang + // (if it exists), formatted as CATEGORY '/' [TYPE] '/' [LANG] '/' + // [NAME]. Note that each slash is included even if the LANG or + // NAME is not included (in accordance with XEP-0030, the category and + // type MUST be included. + SortedSet sortedIdentities = new TreeSet(); + ; + for (Iterator it = discoverInfo.getIdentities(); it.hasNext();) + sortedIdentities.add(it.next()); + + // 3. For each identity, append the 'category/type/lang/name' to S, + // followed by the '<' character. + for (Iterator it = sortedIdentities.iterator(); it.hasNext();) { + DiscoverInfo.Identity identity = it.next(); + sb.append(identity.getCategory()); + sb.append("/"); + sb.append(identity.getType()); + sb.append("/"); + sb.append(identity.getLanguage() == null ? "" : identity.getLanguage()); + sb.append("/"); + sb.append(identity.getName() == null ? "" : identity.getName()); + sb.append("<"); + } + + // 4. Sort the supported service discovery features. + SortedSet features = new TreeSet(); + for (Iterator it = discoverInfo.getFeatures(); it.hasNext();) + features.add(it.next().getVar()); + + // 5. For each feature, append the feature to S, followed by the '<' + // character + for (String f : features) { + sb.append(f); + sb.append("<"); + } + + // only use the data form for calculation is it has a hidden FORM_TYPE + // field + // see XEP-0115 5.4 step 3.6 + if (extendedInfo != null && extendedInfo.hasHiddenFromTypeField()) { + synchronized (extendedInfo) { + // 6. If the service discovery information response includes + // XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e., + // by the XML character data of the element). + SortedSet fs = new TreeSet(new Comparator() { + public int compare(FormField f1, FormField f2) { + return f1.getVariable().compareTo(f2.getVariable()); + } + }); + + FormField ft = null; + + for (Iterator i = extendedInfo.getFields(); i.hasNext();) { + FormField f = i.next(); + if (!f.getVariable().equals("FORM_TYPE")) { + fs.add(f); + } else { + ft = f; + } + } + + // Add FORM_TYPE values + if (ft != null) { + formFieldValuesToCaps(ft.getValues(), sb); + } + + // 7. 3. For each field other than FORM_TYPE: + // 1. Append the value of the "var" attribute, followed by the + // '<' character. + // 2. Sort values by the XML character data of the + // element. + // 3. For each element, append the XML character data, + // followed by the '<' character. + for (FormField f : fs) { + sb.append(f.getVariable()); + sb.append("<"); + formFieldValuesToCaps(f.getValues(), sb); + } + } + } + // 8. Ensure that S is encoded according to the UTF-8 encoding (RFC + // 3269). + // 9. Compute the verification string by hashing S using the algorithm + // specified in the 'hash' attribute (e.g., SHA-1 as defined in RFC + // 3174). + // The hashed data MUST be generated with binary output and + // encoded using Base64 as specified in Section 4 of RFC 4648 + // (note: the Base64 output MUST NOT include whitespace and MUST set + // padding bits to zero). + byte[] digest = md.digest(sb.toString().getBytes()); + return Base64.encodeBytes(digest); + } + + private static void formFieldValuesToCaps(Iterator i, StringBuilder sb) { + SortedSet fvs = new TreeSet(); + while (i.hasNext()) { + fvs.add(i.next()); + } + for (String fv : fvs) { + sb.append(fv); + sb.append("<"); + } + } + + public static class NodeVerHash { + private String node; + private String hash; + private String ver; + private String nodeVer; + + NodeVerHash(String node, String ver, String hash) { + this.node = node; + this.ver = ver; + this.hash = hash; + nodeVer = node + "#" + ver; + } + + public String getNodeVer() { + return nodeVer; + } + + public String getNode() { + return node; + } + + public String getHash() { + return hash; + } + + public String getVer() { + return ver; + } + } +} diff --git a/source/org/jivesoftware/smackx/entitycaps/cache/EntityCapsPersistentCache.java b/source/org/jivesoftware/smackx/entitycaps/cache/EntityCapsPersistentCache.java new file mode 100644 index 000000000..4247e7b1b --- /dev/null +++ b/source/org/jivesoftware/smackx/entitycaps/cache/EntityCapsPersistentCache.java @@ -0,0 +1,38 @@ +/** + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.entitycaps.cache; + +import java.io.IOException; + +import org.jivesoftware.smackx.packet.DiscoverInfo; + +public interface EntityCapsPersistentCache { + /** + * Add an DiscoverInfo to the persistent Cache + * + * @param node + * @param info + */ + abstract void addDiscoverInfoByNodePersistent(String node, DiscoverInfo info); + + /** + * Replay the Caches data into EntityCapsManager + */ + abstract void replay() throws IOException; + + /** + * Empty the Cache + */ + abstract void emptyCache(); +} diff --git a/source/org/jivesoftware/smackx/entitycaps/cache/SimpleDirectoryPersistentCache.java b/source/org/jivesoftware/smackx/entitycaps/cache/SimpleDirectoryPersistentCache.java new file mode 100644 index 000000000..329e4dce6 --- /dev/null +++ b/source/org/jivesoftware/smackx/entitycaps/cache/SimpleDirectoryPersistentCache.java @@ -0,0 +1,193 @@ +/** + * Copyright 2011 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.entitycaps.cache; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.Base64Encoder; +import org.jivesoftware.smack.util.StringEncoder; +import org.jivesoftware.smackx.entitycaps.EntityCapsManager; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.provider.DiscoverInfoProvider; +import org.xmlpull.mxp1.MXParser; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +/** + * Simple implementation of an EntityCapsPersistentCache that uses a directory + * to store the Caps information for every known node. Every node is represented + * by an file. + * + * @author Florian Schmaus + * + */ +public class SimpleDirectoryPersistentCache implements EntityCapsPersistentCache { + + private File cacheDir; + private StringEncoder stringEncoder; + + /** + * Creates a new SimpleDirectoryPersistentCache Object. Make sure that the + * cacheDir exists and that it's an directory. + * + * If your cacheDir is case insensitive then make sure to set the + * StringEncoder to Base32. + * + * @param cacheDir + */ + public SimpleDirectoryPersistentCache(File cacheDir) { + this(cacheDir, Base64Encoder.getInstance()); + } + + /** + * Creates a new SimpleDirectoryPersistentCache Object. Make sure that the + * cacheDir exists and that it's an directory. + * + * If your cacheDir is case insensitive then make sure to set the + * StringEncoder to Base32. + * + * @param cacheDir + * @param stringEncoder + */ + public SimpleDirectoryPersistentCache(File cacheDir, StringEncoder stringEncoder) { + if (!cacheDir.exists()) + throw new IllegalStateException("Cache directory \"" + cacheDir + "\" does not exist"); + if (!cacheDir.isDirectory()) + throw new IllegalStateException("Cache directory \"" + cacheDir + "\" is not a directory"); + + this.cacheDir = cacheDir; + this.stringEncoder = stringEncoder; + } + + @Override + public void addDiscoverInfoByNodePersistent(String node, DiscoverInfo info) { + String filename = stringEncoder.encode(node); + File nodeFile = new File(cacheDir, filename); + + try { + if (nodeFile.createNewFile()) + writeInfoToFile(nodeFile, info); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void replay() throws IOException { + File[] files = cacheDir.listFiles(); + for (File f : files) { + String node = stringEncoder.decode(f.getName()); + DiscoverInfo info = restoreInfoFromFile(f); + if (info == null) + continue; + + EntityCapsManager.addDiscoverInfoByNode(node, info); + } + } + + public void emptyCache() { + File[] files = cacheDir.listFiles(); + for (File f : files) { + f.delete(); + } + } + + /** + * Writes the DiscoverInfo packet to an file + * + * @param file + * @param info + * @throws IOException + */ + private static void writeInfoToFile(File file, DiscoverInfo info) throws IOException { + DataOutputStream dos = new DataOutputStream(new FileOutputStream(file)); + try { + dos.writeUTF(info.toXML()); + } finally { + dos.close(); + } + } + + /** + * Tries to restore an DiscoverInfo packet from a file. + * + * @param file + * @return + * @throws IOException + */ + private static DiscoverInfo restoreInfoFromFile(File file) throws IOException { + DataInputStream dis = new DataInputStream(new FileInputStream(file)); + String fileContent = null; + String id; + String from; + String to; + + try { + fileContent = dis.readUTF(); + } finally { + dis.close(); + } + if (fileContent == null) + return null; + + Reader reader = new StringReader(fileContent); + XmlPullParser parser; + try { + parser = new MXParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(reader); + } catch (XmlPullParserException xppe) { + xppe.printStackTrace(); + return null; + } + + DiscoverInfo iqPacket; + IQProvider provider = new DiscoverInfoProvider(); + + // Parse the IQ, we only need the id + try { + parser.next(); + id = parser.getAttributeValue("", "id"); + from = parser.getAttributeValue("", "from"); + to = parser.getAttributeValue("", "to"); + parser.next(); + } catch (XmlPullParserException e1) { + return null; + } + + try { + iqPacket = (DiscoverInfo) provider.parseIQ(parser); + } catch (Exception e) { + return null; + } + + iqPacket.setPacketID(id); + iqPacket.setFrom(from); + iqPacket.setTo(to); + iqPacket.setType(IQ.Type.RESULT); + return iqPacket; + } +} diff --git a/source/org/jivesoftware/smackx/entitycaps/packet/CapsExtension.java b/source/org/jivesoftware/smackx/entitycaps/packet/CapsExtension.java new file mode 100644 index 000000000..a87c86c96 --- /dev/null +++ b/source/org/jivesoftware/smackx/entitycaps/packet/CapsExtension.java @@ -0,0 +1,83 @@ +/** + * Copyright 2009 Jonas Ã…dahl. + * Copyright 2011-2013 Florian Schmaus + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.entitycaps.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.entitycaps.EntityCapsManager; + +public class CapsExtension implements PacketExtension { + + private String node, ver, hash; + + public CapsExtension() { + } + + public CapsExtension(String node, String version, String hash) { + this.node = node; + this.ver = version; + this.hash = hash; + } + + public String getElementName() { + return EntityCapsManager.ELEMENT; + } + + public String getNamespace() { + return EntityCapsManager.NAMESPACE; + } + + public String getNode() { + return node; + } + + public void setNode(String node) { + this.node = node; + } + + public String getVer() { + return ver; + } + + public void setVer(String ver) { + this.ver = ver; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + /* + * + * + */ + public String toXML() { + String xml = "<" + EntityCapsManager.ELEMENT + " xmlns=\"" + EntityCapsManager.NAMESPACE + "\" " + + "hash=\"" + hash + "\" " + + "node=\"" + node + "\" " + + "ver=\"" + ver + "\"/>"; + + return xml; + } +} diff --git a/source/org/jivesoftware/smackx/entitycaps/provider/CapsExtensionProvider.java b/source/org/jivesoftware/smackx/entitycaps/provider/CapsExtensionProvider.java new file mode 100644 index 000000000..4328d21b3 --- /dev/null +++ b/source/org/jivesoftware/smackx/entitycaps/provider/CapsExtensionProvider.java @@ -0,0 +1,60 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * Copyright 2011-2013 Florian Schmaus + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.entitycaps.provider; + +import java.io.IOException; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.entitycaps.EntityCapsManager; +import org.jivesoftware.smackx.entitycaps.packet.CapsExtension; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +public class CapsExtensionProvider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws XmlPullParserException, IOException, + XMPPException { + String hash = null; + String version = null; + String node = null; + if (parser.getEventType() == XmlPullParser.START_TAG + && parser.getName().equalsIgnoreCase(EntityCapsManager.ELEMENT)) { + hash = parser.getAttributeValue(null, "hash"); + version = parser.getAttributeValue(null, "ver"); + node = parser.getAttributeValue(null, "node"); + } else { + throw new XMPPException("Malformed Caps element"); + } + + parser.next(); + + if (!(parser.getEventType() == XmlPullParser.END_TAG + && parser.getName().equalsIgnoreCase(EntityCapsManager.ELEMENT))) { + throw new XMPPException("Malformed nested Caps element"); + } + + if (hash != null && version != null && node != null) { + return new CapsExtension(node, version, hash); + } else { + throw new XMPPException("Caps elment with missing attributes"); + } + } +} diff --git a/source/org/jivesoftware/smackx/muc/MultiUserChat.java b/source/org/jivesoftware/smackx/muc/MultiUserChat.java index 2caa970a1..e0368028d 100644 --- a/source/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/source/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -52,6 +52,7 @@ import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Registration; import org.jivesoftware.smackx.Form; @@ -133,6 +134,11 @@ public class MultiUserChat { public List getNodeIdentities() { return null; } + + @Override + public List getNodePacketExtensions() { + return null; + } }); } }); diff --git a/source/org/jivesoftware/smackx/packet/DataForm.java b/source/org/jivesoftware/smackx/packet/DataForm.java index 8fe43070f..4bc1f6994 100644 --- a/source/org/jivesoftware/smackx/packet/DataForm.java +++ b/source/org/jivesoftware/smackx/packet/DataForm.java @@ -21,6 +21,7 @@ package org.jivesoftware.smackx.packet; import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.Form; import org.jivesoftware.smackx.FormField; import java.util.ArrayList; @@ -123,11 +124,11 @@ public class DataForm implements PacketExtension { } public String getElementName() { - return "x"; + return Form.ELEMENT; } public String getNamespace() { - return "jabber:x:data"; + return Form.NAMESPACE; } /** @@ -195,6 +196,21 @@ public class DataForm implements PacketExtension { } } + /** + * Returns true if this DataForm has at least one FORM_TYPE field which is + * hidden. This method is used for sanity checks. + * + * @return + */ + public boolean hasHiddenFromTypeField() { + boolean found = false; + for (FormField f : fields) { + if (f.getVariable().equals("FORM_TYPE") && f.getType() != null && f.getType().equals("hidden")) + found = true; + } + return found; + } + public String toXML() { StringBuilder buf = new StringBuilder(); buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( diff --git a/source/org/jivesoftware/smackx/packet/DiscoverInfo.java b/source/org/jivesoftware/smackx/packet/DiscoverInfo.java index 4f4597d67..e2219032d 100644 --- a/source/org/jivesoftware/smackx/packet/DiscoverInfo.java +++ b/source/org/jivesoftware/smackx/packet/DiscoverInfo.java @@ -23,8 +23,10 @@ package org.jivesoftware.smackx.packet; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.util.StringUtils; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -45,6 +47,36 @@ public class DiscoverInfo extends IQ { private final List identities = new CopyOnWriteArrayList(); private String node; + public DiscoverInfo() { + super(); + } + + /** + * Copy constructor + * + * @param d + */ + public DiscoverInfo(DiscoverInfo d) { + super(d); + + // Set node + setNode(d.getNode()); + + // Copy features + synchronized (d.features) { + for (Feature f : d.features) { + addFeature(f); + } + } + + // Copy identities + synchronized (d.identities) { + for (Identity i : d.identities) { + addIdentity(i); + } + } + } + /** * Adds a new feature to the discovered information. * @@ -54,6 +86,18 @@ public class DiscoverInfo extends IQ { addFeature(new Feature(feature)); } + /** + * Adds a collection of features to the packet. Does noting if featuresToAdd is null. + * + * @param featuresToAdd + */ + public void addFeatures(Collection featuresToAdd) { + if (featuresToAdd == null) return; + for (String feature : featuresToAdd) { + addFeature(feature); + } + } + private void addFeature(Feature feature) { synchronized (features) { features.add(feature); @@ -82,6 +126,18 @@ public class DiscoverInfo extends IQ { } } + /** + * Adds identities to the DiscoverInfo stanza + * + * @param identitiesToAdd + */ + public void addIdentities(Collection identitiesToAdd) { + if (identitiesToAdd == null) return; + synchronized (identities) { + identities.addAll(identitiesToAdd); + } + } + /** * Returns the discovered identities of an XMPP entity. * @@ -158,6 +214,40 @@ public class DiscoverInfo extends IQ { return buf.toString(); } + /** + * Test if a DiscoverInfo response contains duplicate identities. + * + * @return true if duplicate identities where found, otherwise false + */ + public boolean containsDuplicateIdentities() { + List checkedIdentities = new LinkedList(); + for (Identity i : identities) { + for (Identity i2 : checkedIdentities) { + if (i.equals(i2)) + return true; + } + checkedIdentities.add(i); + } + return false; + } + + /** + * Test if a DiscoverInfo response contains duplicate features. + * + * @return true if duplicate identities where found, otherwise false + */ + public boolean containsDuplicateFeatures() { + List checkedFeatures = new LinkedList(); + for (Feature f : features) { + for (Feature f2 : checkedFeatures) { + if (f.equals(f2)) + return true; + } + checkedFeatures.add(f); + } + return false; + } + /** * Represents the identity of a given XMPP entity. An entity may have many identities but all * the identities SHOULD have the same name.

@@ -167,21 +257,26 @@ public class DiscoverInfo extends IQ { * attributes. * */ - public static class Identity { + public static class Identity implements Comparable { private String category; private String name; private String type; + private String lang; // 'xml:lang; /** * Creates a new identity for an XMPP entity. + * 'category' and 'type' are required by + * XEP-30 XML Schemas * - * @param category the entity's category. + * @param category the entity's category (required as per XEP-30). * @param name the entity's name. + * @param type the entity's type (required as per XEP-30). */ - public Identity(String category, String name) { + public Identity(String category, String name, String type) { this.category = category; this.name = name; + this.type = type; } /** @@ -223,16 +318,106 @@ public class DiscoverInfo extends IQ { this.type = type; } + /** + * Sets the natural language (xml:lang) for this identity (optional) + * + * @param lang the xml:lang of this Identity + */ + public void setLanguage(String lang) { + this.lang = lang; + } + + /** + * Returns the identities natural language if one is set + * + * @return the value of xml:lang of this Identity + */ + public String getLanguage() { + return lang; + } + public String toXML() { StringBuilder buf = new StringBuilder(); - buf.append(""); return buf.toString(); } + + /** + * Check equality for Identity for category, type, lang and name + * in that order as defined by + * XEP-0015 5.4 Processing Method (Step 3.3) + * + */ + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj == this) + return true; + if (obj.getClass() != getClass()) + return false; + + DiscoverInfo.Identity other = (DiscoverInfo.Identity) obj; + if (!this.category.equals(other.category)) + return false; + + String otherLang = other.lang == null ? "" : other.lang; + String thisLang = lang == null ? "" : lang; + + if (!other.type.equals(type)) + return false; + if (!otherLang.equals(thisLang)) + return false; + + String otherName = other.name == null ? "" : other.name; + String thisName = name == null ? "" : other.name; + if (!thisName.equals(otherName)) + return false; + + return true; + } + + /** + * Compares and identity with another object. The comparison order is: + * Category, Type, Lang. If all three are identical the other Identity is considered equal. + * Name is not used for comparision, as defined by XEP-0115 + * + * @param obj + * @return + */ + public int compareTo(Object obj) { + + DiscoverInfo.Identity other = (DiscoverInfo.Identity) obj; + String otherLang = other.lang == null ? "" : other.lang; + String thisLang = lang == null ? "" : lang; + + if (category.equals(other.category)) { + if (type.equals(other.type)) { + if (thisLang.equals(otherLang)) { + // Don't compare on name, XEP-30 says that name SHOULD + // be equals for all identities of an entity + return 0; + } else { + return thisLang.compareTo(otherLang); + } + } else { + return type.compareTo(other.type); + } + } else { + return category.compareTo(other.category); + } + } } /** @@ -268,5 +453,17 @@ public class DiscoverInfo extends IQ { buf.append(""); return buf.toString(); } + + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj == this) + return true; + if (obj.getClass() != getClass()) + return false; + + DiscoverInfo.Feature other = (DiscoverInfo.Feature) obj; + return variable.equals(other.variable); + } } } diff --git a/source/org/jivesoftware/smackx/packet/DiscoverItems.java b/source/org/jivesoftware/smackx/packet/DiscoverItems.java index f6b6dcae1..07185e68a 100644 --- a/source/org/jivesoftware/smackx/packet/DiscoverItems.java +++ b/source/org/jivesoftware/smackx/packet/DiscoverItems.java @@ -23,6 +23,7 @@ package org.jivesoftware.smackx.packet; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.util.StringUtils; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -55,6 +56,18 @@ public class DiscoverItems extends IQ { } } + /** + * Adds a collection of items to the discovered information. Does nothing if itemsToAdd is null + * + * @param itemsToAdd + */ + public void addItems(Collection itemsToAdd) { + if (itemsToAdd == null) return; + for (Item i : itemsToAdd) { + addItem(i); + } + } + /** * Returns the discovered items of the queried XMPP entity. * diff --git a/source/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java b/source/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java index cbed6208f..d10049160 100644 --- a/source/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java +++ b/source/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java @@ -42,6 +42,7 @@ public class DiscoverInfoProvider implements IQProvider { String name = ""; String type = ""; String variable = ""; + String lang = ""; discoverInfo.setNode(parser.getAttributeValue("", "node")); while (!done) { int eventType = parser.next(); @@ -51,6 +52,7 @@ public class DiscoverInfoProvider implements IQProvider { category = parser.getAttributeValue("", "category"); name = parser.getAttributeValue("", "name"); type = parser.getAttributeValue("", "type"); + lang = parser.getAttributeValue(parser.getNamespace("xml"), "lang"); } else if (parser.getName().equals("feature")) { // Initialize the variables from the parsed XML @@ -64,8 +66,9 @@ public class DiscoverInfoProvider implements IQProvider { } else if (eventType == XmlPullParser.END_TAG) { if (parser.getName().equals("identity")) { // Create a new identity and add it to the discovered info. - identity = new DiscoverInfo.Identity(category, name); - identity.setType(type); + identity = new DiscoverInfo.Identity(category, name, type); + if (lang != null) + identity.setLanguage(lang); discoverInfo.addIdentity(identity); } if (parser.getName().equals("feature")) { diff --git a/test-unit/org/jivesoftware/smackx/bytestreams/socks5/Socks5ByteStreamManagerTest.java b/test-unit/org/jivesoftware/smackx/bytestreams/socks5/Socks5ByteStreamManagerTest.java index 0a769c27c..5c7d9493c 100644 --- a/test-unit/org/jivesoftware/smackx/bytestreams/socks5/Socks5ByteStreamManagerTest.java +++ b/test-unit/org/jivesoftware/smackx/bytestreams/socks5/Socks5ByteStreamManagerTest.java @@ -247,8 +247,7 @@ public class Socks5ByteStreamManagerTest { // build discover info for proxy containing information about NOT being a Socks5 // proxy DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID); - Identity identity = new Identity("noproxy", proxyJID); - identity.setType("bytestreams"); + Identity identity = new Identity("noproxy", proxyJID, "bytestreams"); proxyInfo.addIdentity(identity); // return the proxy identity if proxy is queried @@ -312,8 +311,7 @@ public class Socks5ByteStreamManagerTest { // build discover info for proxy containing information about NOT being a Socks5 // proxy DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID); - Identity identity = new Identity("noproxy", proxyJID); - identity.setType("bytestreams"); + Identity identity = new Identity("noproxy", proxyJID, "bytestreams"); proxyInfo.addIdentity(identity); // return the proxy identity if proxy is queried @@ -403,8 +401,7 @@ public class Socks5ByteStreamManagerTest { // build discover info for proxy containing information about being a SOCKS5 proxy DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID); - Identity identity = new Identity("proxy", proxyJID); - identity.setType("bytestreams"); + Identity identity = new Identity("proxy", proxyJID, "bytestreams"); proxyInfo.addIdentity(identity); // return the socks5 bytestream proxy identity if proxy is queried @@ -494,8 +491,7 @@ public class Socks5ByteStreamManagerTest { // build discover info for proxy containing information about being a SOCKS5 proxy DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID); - Identity identity = new Identity("proxy", proxyJID); - identity.setType("bytestreams"); + Identity identity = new Identity("proxy", proxyJID, "bytestreams"); proxyInfo.addIdentity(identity); // return the socks5 bytestream proxy identity if proxy is queried @@ -577,8 +573,7 @@ public class Socks5ByteStreamManagerTest { // build discover info for proxy containing information about being a SOCKS5 proxy DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID); - Identity identity = new Identity("proxy", proxyJID); - identity.setType("bytestreams"); + Identity identity = new Identity("proxy", proxyJID, "bytestreams"); proxyInfo.addIdentity(identity); // return the socks5 bytestream proxy identity if proxy is queried @@ -672,8 +667,7 @@ public class Socks5ByteStreamManagerTest { // build discover info for proxy containing information about being a SOCKS5 proxy DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID); - Identity identity = new Identity("proxy", proxyJID); - identity.setType("bytestreams"); + Identity identity = new Identity("proxy", proxyJID, "bytestreams"); proxyInfo.addIdentity(identity); // return the socks5 bytestream proxy identity if proxy is queried @@ -1026,7 +1020,7 @@ public class Socks5ByteStreamManagerTest { */ DiscoverInfo proxyInfo1 = Socks5PacketUtils.createDiscoverInfo("proxy2.xmpp-server", initiatorJID); - Identity identity1 = new Identity("proxy", "proxy2.xmpp-server"); + Identity identity1 = new Identity("proxy", "proxy2.xmpp-server", "bytestreams"); identity1.setType("bytestreams"); proxyInfo1.addIdentity(identity1); @@ -1036,8 +1030,7 @@ public class Socks5ByteStreamManagerTest { // build discover info for proxy containing information about being a SOCKS5 proxy DiscoverInfo proxyInfo2 = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID); - Identity identity2 = new Identity("proxy", proxyJID); - identity2.setType("bytestreams"); + Identity identity2 = new Identity("proxy", proxyJID, "bytestreams"); proxyInfo2.addIdentity(identity2); // return the SOCKS5 bytestream proxy identity if proxy is queried diff --git a/test-unit/org/jivesoftware/smackx/entitycaps/EntityCapsManagerTest.java b/test-unit/org/jivesoftware/smackx/entitycaps/EntityCapsManagerTest.java new file mode 100644 index 000000000..754bf6d3c --- /dev/null +++ b/test-unit/org/jivesoftware/smackx/entitycaps/EntityCapsManagerTest.java @@ -0,0 +1,227 @@ +package org.jivesoftware.smackx.entitycaps; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedList; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.util.Base32Encoder; +import org.jivesoftware.smack.util.Base64Encoder; +import org.jivesoftware.smack.util.StringEncoder; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.entitycaps.EntityCapsManager; +import org.jivesoftware.smackx.entitycaps.cache.EntityCapsPersistentCache; +import org.jivesoftware.smackx.entitycaps.cache.SimpleDirectoryPersistentCache; +import org.jivesoftware.smackx.packet.DataForm; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.junit.Test; + + +public class EntityCapsManagerTest { + + /** + * XEP- + * 0115 Complex Generation Example + */ + @Test + public void testComplexGenerationExample() { + DiscoverInfo di = createComplexSamplePacket(); + + String ver = EntityCapsManager.generateVerificationString(di, "sha-1"); + assertEquals("q07IKJEyjvHSyhy//CH0CxmKi8w=", ver); + } + + @Test + public void testSimpleDirectoryCacheBase64() throws IOException { + EntityCapsManager.persistentCache = null; + testSimpleDirectoryCache(Base64Encoder.getInstance()); + } + + @Test + public void testSimpleDirectoryCacheBase32() throws IOException { + EntityCapsManager.persistentCache = null; + testSimpleDirectoryCache(Base32Encoder.getInstance()); + } + + @Test + public void testVerificationDuplicateFeatures() { + DiscoverInfo di = createMalformedDiscoverInfo(); + assertTrue(di.containsDuplicateFeatures()); + } + + @Test + public void testVerificationDuplicateIdentities() { + DiscoverInfo di = createMalformedDiscoverInfo(); + assertTrue(di.containsDuplicateIdentities()); + } + + @Test + public void testVerificationDuplicateDataForm() { + DiscoverInfo di = createMalformedDiscoverInfo(); + assertTrue(EntityCapsManager.verifyPacketExtensions(di)); + } + + private void testSimpleDirectoryCache(StringEncoder stringEncoder) throws IOException { + + EntityCapsPersistentCache cache = new SimpleDirectoryPersistentCache(createTempDirectory()); + EntityCapsManager.setPersistentCache(cache); + + DiscoverInfo di = createComplexSamplePacket(); + String nodeVer = di.getNode() + "#" + EntityCapsManager.generateVerificationString(di, "sha-1"); + + // Save the data in EntityCapsManager + EntityCapsManager.addDiscoverInfoByNode(nodeVer, di); + + // Lose all the data + EntityCapsManager.caps.clear(); + + // Restore the data from the persistent Cache + cache.replay(); + + DiscoverInfo restored_di = EntityCapsManager.getDiscoveryInfoByNodeVer(nodeVer); + assertNotNull(restored_di); + assertEquals(di.toXML(), restored_di.toXML()); + } + + private static DiscoverInfo createComplexSamplePacket() { + DiscoverInfo di = new DiscoverInfo(); + di.setFrom("benvolio@capulet.lit/230193"); + di.setPacketID("disco1"); + di.setTo("juliet@capulet.lit/chamber"); + di.setType(IQ.Type.RESULT); + + Collection identities = new LinkedList(); + DiscoverInfo.Identity i = new DiscoverInfo.Identity("client", "Psi 0.11", "pc"); + i.setLanguage("en"); + identities.add(i); + i = new DiscoverInfo.Identity("client", "Ψ 0.11", "pc"); + i.setLanguage("el"); + identities.add(i); + di.addIdentities(identities); + + di.addFeature("http://jabber.org/protocol/disco#items"); + di.addFeature(EntityCapsManager.NAMESPACE); + di.addFeature("http://jabber.org/protocol/muc"); + di.addFeature("http://jabber.org/protocol/disco#info"); + + DataForm df = new DataForm("result"); + + FormField ff = new FormField("os"); + ff.addValue("Mac"); + df.addField(ff); + + ff = new FormField("FORM_TYPE"); + ff.setType("hidden"); + ff.addValue("urn:xmpp:dataforms:softwareinfo"); + df.addField(ff); + + ff = new FormField("ip_version"); + ff.addValue("ipv4"); + ff.addValue("ipv6"); + df.addField(ff); + + ff = new FormField("os_version"); + ff.addValue("10.5.1"); + df.addField(ff); + + ff = new FormField("software"); + ff.addValue("Psi"); + df.addField(ff); + + ff = new FormField("software_version"); + ff.addValue("0.11"); + df.addField(ff); + + di.addExtension(df); + return di; + } + + private static DiscoverInfo createMalformedDiscoverInfo() { + DiscoverInfo di = new DiscoverInfo(); + di.setFrom("benvolio@capulet.lit/230193"); + di.setPacketID("disco1"); + di.setTo(")juliet@capulet.lit/chamber"); + di.setType(IQ.Type.RESULT); + + Collection identities = new LinkedList(); + DiscoverInfo.Identity i = new DiscoverInfo.Identity("client", "Psi 0.11", "pc"); + i.setLanguage("en"); + identities.add(i); + i = new DiscoverInfo.Identity("client", "Ψ 0.11", "pc"); + i.setLanguage("el"); + identities.add(i); + di.addIdentities(identities); + // Failure 1: Duplicate identities + i = new DiscoverInfo.Identity("client", "Ψ 0.11", "pc"); + i.setLanguage("el"); + identities.add(i); + di.addIdentities(identities); + + di.addFeature("http://jabber.org/protocol/disco#items"); + di.addFeature(EntityCapsManager.NAMESPACE); + di.addFeature("http://jabber.org/protocol/muc"); + di.addFeature("http://jabber.org/protocol/disco#info"); + // Failure 2: Duplicate features + di.addFeature("http://jabber.org/protocol/disco#info"); + + DataForm df = new DataForm("result"); + + FormField ff = new FormField("os"); + ff.addValue("Mac"); + df.addField(ff); + + ff = new FormField("FORM_TYPE"); + ff.setType("hidden"); + ff.addValue("urn:xmpp:dataforms:softwareinfo"); + df.addField(ff); + + ff = new FormField("ip_version"); + ff.addValue("ipv4"); + ff.addValue("ipv6"); + df.addField(ff); + + ff = new FormField("os_version"); + ff.addValue("10.5.1"); + df.addField(ff); + + ff = new FormField("software"); + ff.addValue("Psi"); + df.addField(ff); + + ff = new FormField("software_version"); + ff.addValue("0.11"); + df.addField(ff); + + di.addExtension(df); + + // Failure 3: Another service discovery information form with the same + // FORM_TYPE + df = new DataForm("result"); + + ff = new FormField("FORM_TYPE"); + ff.setType("hidden"); + ff.addValue("urn:xmpp:dataforms:softwareinfo"); + df.addField(ff); + + ff = new FormField("software"); + ff.addValue("smack"); + df.addField(ff); + + di.addExtension(df); + + return di; + } + + public static File createTempDirectory() throws IOException { + String tmpdir = System.getProperty("java.io.tmpdir"); + File tmp; + tmp = File.createTempFile(tmpdir, "entityCaps"); + tmp.delete(); + tmp.mkdir(); + return tmp; + } + +} diff --git a/test-unit/org/jivesoftware/smackx/pubsub/ConfigureFormTest.java b/test-unit/org/jivesoftware/smackx/pubsub/ConfigureFormTest.java index 8f10e8e28..4b47aa2ff 100644 --- a/test-unit/org/jivesoftware/smackx/pubsub/ConfigureFormTest.java +++ b/test-unit/org/jivesoftware/smackx/pubsub/ConfigureFormTest.java @@ -50,8 +50,7 @@ public class ConfigureFormTest ThreadedDummyConnection con = new ThreadedDummyConnection(); PubSubManager mgr = new PubSubManager(con); DiscoverInfo info = new DiscoverInfo(); - Identity ident = new Identity("pubsub", null); - ident.setType("leaf"); + Identity ident = new Identity("pubsub", null, "leaf"); info.addIdentity(ident); con.addIQReply(info); @@ -78,8 +77,7 @@ public class ConfigureFormTest ThreadedDummyConnection con = new ThreadedDummyConnection(); PubSubManager mgr = new PubSubManager(con); DiscoverInfo info = new DiscoverInfo(); - Identity ident = new Identity("pubsub", null); - ident.setType("leaf"); + Identity ident = new Identity("pubsub", null, "leaf"); info.addIdentity(ident); con.addIQReply(info); diff --git a/test/org/jivesoftware/smack/ReconnectionTest.java b/test/org/jivesoftware/smack/ReconnectionTest.java index 8322ccef2..f4cbd0c32 100644 --- a/test/org/jivesoftware/smack/ReconnectionTest.java +++ b/test/org/jivesoftware/smack/ReconnectionTest.java @@ -40,7 +40,7 @@ public class ReconnectionTest extends SmackTestCase { public void testAutomaticReconnection() throws Exception { XMPPConnection connection = getConnection(0); - XMPPConnectionTestListener listener = new XMPPConnectionTestListener(); + XMPPConnectionTestListener listener = new XMPPConnectionTestListener(); connection.addConnectionListener(listener); // Simulates an error in the connection diff --git a/test/org/jivesoftware/smack/test/SmackTestCase.java b/test/org/jivesoftware/smack/test/SmackTestCase.java index 044a60cf4..fa6bb2d20 100644 --- a/test/org/jivesoftware/smack/test/SmackTestCase.java +++ b/test/org/jivesoftware/smack/test/SmackTestCase.java @@ -34,6 +34,7 @@ import junit.framework.TestCase; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.util.ConnectionUtils; import org.xmlpull.mxp1.MXParser; import org.xmlpull.v1.XmlPullParser; @@ -504,6 +505,15 @@ public abstract class SmackTestCase extends TestCase { return "config/" + fullClassName.substring(firstChar) + ".xml"; } + /** + * Subscribes all connections with each other: They all become friends + * + * @throws XMPPException + */ + protected void letsAllBeFriends() throws XMPPException { + ConnectionUtils.letsAllBeFriends(connections); + } + /** * Compares two contents of two byte arrays to make sure that they are equal * diff --git a/test/org/jivesoftware/smack/util/ConnectionUtils.java b/test/org/jivesoftware/smack/util/ConnectionUtils.java new file mode 100644 index 000000000..6d9f49106 --- /dev/null +++ b/test/org/jivesoftware/smack/util/ConnectionUtils.java @@ -0,0 +1,27 @@ +package org.jivesoftware.smack.util; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.XMPPException; + +public class ConnectionUtils { + + public static void becomeFriends(Connection con0, Connection con1) throws XMPPException { + Roster r0 = con0.getRoster(); + Roster r1 = con1.getRoster(); + r0.setSubscriptionMode(Roster.SubscriptionMode.accept_all); + r1.setSubscriptionMode(Roster.SubscriptionMode.accept_all); + r0.createEntry(con1.getUser(), "u2", null); + r1.createEntry(con0.getUser(), "u1", null); + } + + public static void letsAllBeFriends(Connection[] connections) throws XMPPException { + for (Connection c1 : connections) { + for (Connection c2 : connections) { + if (c1 == c2) + continue; + becomeFriends(c1, c2); + } + } + } +} diff --git a/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java b/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java index 9a902ab31..9ba422e3e 100644 --- a/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java +++ b/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java @@ -48,7 +48,7 @@ public class ServiceDiscoveryManagerTest extends SmackTestCase { .getInstanceFor(getConnection(0)); try { // Discover the information of another Smack client - DiscoverInfo info = discoManager.discoverInfo(getFullJID(1)); + DiscoverInfo info = discoManager.discoverInfo(getFullJID(1)); // Check the identity of the Smack client Iterator identities = info.getIdentities(); assertTrue("No identities were found", identities.hasNext()); diff --git a/test/org/jivesoftware/smackx/entitycaps/EntityCapsTest.java b/test/org/jivesoftware/smackx/entitycaps/EntityCapsTest.java new file mode 100644 index 000000000..49a81f3e4 --- /dev/null +++ b/test/org/jivesoftware/smackx/entitycaps/EntityCapsTest.java @@ -0,0 +1,147 @@ +package org.jivesoftware.smackx.entitycaps; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.filter.IQTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.packet.DiscoverInfo; + +public class EntityCapsTest extends SmackTestCase { + + private static final String DISCOVER_TEST_FEATURE = "entityCapsTest"; + + XMPPConnection con0; + XMPPConnection con1; + EntityCapsManager ecm0; + EntityCapsManager ecm1; + ServiceDiscoveryManager sdm0; + ServiceDiscoveryManager sdm1; + + private boolean discoInfoSend = false; + + public EntityCapsTest(String arg0) { + super(arg0); + } + + @Override + protected int getMaxConnections() { + return 2; + } + + protected void setUp() throws Exception { + super.setUp(); + SmackConfiguration.setAutoEnableEntityCaps(true); + SmackConfiguration.setPacketReplyTimeout(1000 * 60 * 5); + con0 = getConnection(0); + con1 = getConnection(1); + ecm0 = EntityCapsManager.getInstanceFor(getConnection(0)); + ecm1 = EntityCapsManager.getInstanceFor(getConnection(1)); + sdm0 = ServiceDiscoveryManager.getInstanceFor(con0); + sdm1 = ServiceDiscoveryManager.getInstanceFor(con1); + letsAllBeFriends(); + } + + public void testLocalEntityCaps() throws InterruptedException { + DiscoverInfo info = EntityCapsManager.getDiscoveryInfoByNodeVer(ecm1.getLocalNodeVer()); + assertFalse(info.containsFeature(DISCOVER_TEST_FEATURE)); + + dropWholeEntityCapsCache(); + + // This should cause a new presence stanza from con1 with and updated + // 'ver' String + sdm1.addFeature(DISCOVER_TEST_FEATURE); + + // Give the server some time to handle the stanza and send it to con0 + Thread.sleep(2000); + + // The presence stanza should get received by con0 and the data should + // be recorded in the map + // Note that while both connections use the same static Entity Caps + // cache, + // it's assured that *not* con1 added the data to the Entity Caps cache. + // Every time the entities features + // and identities change only a new caps 'ver' is calculated and send + // with the presence stanza + // The other connection has to receive this stanza and record the + // information in order for this test to succeed. + info = EntityCapsManager.getDiscoveryInfoByNodeVer(ecm1.getLocalNodeVer()); + assertNotNull(info); + assertTrue(info.containsFeature(DISCOVER_TEST_FEATURE)); + } + + /** + * Test if entity caps actually prevent a disco info request and reply + * + * @throws XMPPException + * + */ + public void testPreventDiscoInfo() throws XMPPException { + con0.addPacketSendingListener(new PacketListener() { + + @Override + public void processPacket(Packet packet) { + discoInfoSend = true; + } + + }, new AndFilter(new PacketTypeFilter(DiscoverInfo.class), new IQTypeFilter(IQ.Type.GET))); + + // add a bogus feature so that con1 ver won't match con0's + sdm1.addFeature(DISCOVER_TEST_FEATURE); + + dropCapsCache(); + // discover that + DiscoverInfo info = sdm0.discoverInfo(con1.getUser()); + // that discovery should cause a disco#info + assertTrue(discoInfoSend); + assertTrue(info.containsFeature(DISCOVER_TEST_FEATURE)); + discoInfoSend = false; + + // discover that + info = sdm0.discoverInfo(con1.getUser()); + // that discovery shouldn't cause a disco#info + assertFalse(discoInfoSend); + assertTrue(info.containsFeature(DISCOVER_TEST_FEATURE)); + } + + public void testCapsChanged() { + String nodeVerBefore = EntityCapsManager.getNodeVersionByJid(con1.getUser()); + sdm1.addFeature(DISCOVER_TEST_FEATURE); + String nodeVerAfter = EntityCapsManager.getNodeVersionByJid(con1.getUser()); + + assertFalse(nodeVerBefore.equals(nodeVerAfter)); + } + + public void testEntityCaps() throws XMPPException, InterruptedException { + dropWholeEntityCapsCache(); + sdm1.addFeature(DISCOVER_TEST_FEATURE); + + Thread.sleep(3000); + + DiscoverInfo info = sdm0.discoverInfo(con1.getUser()); + assertTrue(info.containsFeature(DISCOVER_TEST_FEATURE)); + + String u1ver = EntityCapsManager.getNodeVersionByJid(con1.getUser()); + assertNotNull(u1ver); + + DiscoverInfo entityInfo = EntityCapsManager.caps.get(u1ver); + assertNotNull(entityInfo); + + assertEquals(info.toXML(), entityInfo.toXML()); + } + + private static void dropWholeEntityCapsCache() { + EntityCapsManager.caps.clear(); + EntityCapsManager.jidCaps.clear(); + } + + private static void dropCapsCache() { + EntityCapsManager.caps.clear(); + } +} From 848d393b73d549208d432d3dcb1b9fefc546f67d Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 18 Mar 2013 08:42:26 +0000 Subject: [PATCH 03/24] SMACK-225 Fixed DNS SRV handling, as per RFC 2782. Added support for multiple DNS SRV resolvers namely javax and org.xbill.dns (aka dnsjava). git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13559 b35dd754-fafc-0310-a699-88a17e54d16e --- build/eclipse/classpath | 1 + build/merge/org.xbill.dns_2.1.4.jar | Bin 0 -> 304384 bytes build/resources/META-INF/smack-config.xml | 9 +- .../smack/ConnectionConfiguration.java | 54 ++- .../jivesoftware/smack/XMPPConnection.java | 72 ++-- .../org/jivesoftware/smack/util/DNSUtil.java | 318 ++++++++---------- .../smack/util/dns/DNSJavaResolver.java | 72 ++++ .../smack/util/dns/DNSResolver.java | 24 ++ .../smack/util/dns/HostAddress.java | 93 +++++ .../smack/util/dns/JavaxResolver.java | 99 ++++++ .../smack/util/dns/SRVRecord.java | 77 +++++ .../jivesoftware/smack/util/DNSUtilTest.java | 147 ++++++++ 12 files changed, 756 insertions(+), 210 deletions(-) create mode 100644 build/merge/org.xbill.dns_2.1.4.jar create mode 100644 source/org/jivesoftware/smack/util/dns/DNSJavaResolver.java create mode 100644 source/org/jivesoftware/smack/util/dns/DNSResolver.java create mode 100644 source/org/jivesoftware/smack/util/dns/HostAddress.java create mode 100644 source/org/jivesoftware/smack/util/dns/JavaxResolver.java create mode 100644 source/org/jivesoftware/smack/util/dns/SRVRecord.java create mode 100644 test-unit/org/jivesoftware/smack/util/DNSUtilTest.java diff --git a/build/eclipse/classpath b/build/eclipse/classpath index ddf552712..67dd4e3b5 100644 --- a/build/eclipse/classpath +++ b/build/eclipse/classpath @@ -28,5 +28,6 @@ + diff --git a/build/merge/org.xbill.dns_2.1.4.jar b/build/merge/org.xbill.dns_2.1.4.jar new file mode 100644 index 0000000000000000000000000000000000000000..ca8c76c81534bd4ad1bec77c47d43973108f7105 GIT binary patch literal 304384 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�!6Fc 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

*Xx^t?dJG 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 Ii2pGE59p;z!~g&Q literal 0 HcmV?d00001 diff --git a/build/resources/META-INF/smack-config.xml b/build/resources/META-INF/smack-config.xml index 6c4b891e2..1c763e0c2 100644 --- a/build/resources/META-INF/smack-config.xml +++ b/build/resources/META-INF/smack-config.xml @@ -5,15 +5,16 @@ org.jivesoftware.smackx.ServiceDiscoveryManager - org.jivesoftware.smack.PrivacyListManager + org.jivesoftware.smack.PrivacyListManager org.jivesoftware.smackx.XHTMLManager org.jivesoftware.smackx.muc.MultiUserChat org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager - org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager + org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager org.jivesoftware.smackx.filetransfer.FileTransferManager org.jivesoftware.smackx.LastActivityManager - org.jivesoftware.smack.ReconnectionManager - org.jivesoftware.smackx.commands.AdHocCommandManager + org.jivesoftware.smack.ReconnectionManager + org.jivesoftware.smackx.commands.AdHocCommandManager + org.jivesoftware.smack.util.dns.JavaxResolver diff --git a/source/org/jivesoftware/smack/ConnectionConfiguration.java b/source/org/jivesoftware/smack/ConnectionConfiguration.java index 24389ac6f..9eb7bb903 100644 --- a/source/org/jivesoftware/smack/ConnectionConfiguration.java +++ b/source/org/jivesoftware/smack/ConnectionConfiguration.java @@ -22,11 +22,15 @@ package org.jivesoftware.smack; import org.jivesoftware.smack.proxy.ProxyInfo; import org.jivesoftware.smack.util.DNSUtil; +import org.jivesoftware.smack.util.dns.HostAddress; import javax.net.SocketFactory; import javax.net.ssl.SSLContext; import javax.security.auth.callback.CallbackHandler; import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Configuration to use while establishing the connection to the server. It is possible to @@ -48,6 +52,7 @@ public class ConnectionConfiguration implements Cloneable { private String host; private int port; + protected List hostAddresses; private String truststorePath; private String truststoreType; @@ -98,12 +103,11 @@ public class ConnectionConfiguration implements Cloneable { */ public ConnectionConfiguration(String serviceName) { // Perform DNS lookup to get host and port to use - DNSUtil.HostAddress address = DNSUtil.resolveXMPPDomain(serviceName); - init(address.getHost(), address.getPort(), serviceName, - ProxyInfo.forDefaultProxy()); + hostAddresses = DNSUtil.resolveXMPPDomain(serviceName); + init(serviceName, ProxyInfo.forDefaultProxy()); } - - /** + + /** * Creates a new ConnectionConfiguration for the specified service name * with specified proxy. * A DNS SRV lookup will be performed to find out the actual host address @@ -114,8 +118,8 @@ public class ConnectionConfiguration implements Cloneable { */ public ConnectionConfiguration(String serviceName,ProxyInfo proxy) { // Perform DNS lookup to get host and port to use - DNSUtil.HostAddress address = DNSUtil.resolveXMPPDomain(serviceName); - init(address.getHost(), address.getPort(), serviceName, proxy); + hostAddresses = DNSUtil.resolveXMPPDomain(serviceName); + init(serviceName, proxy); } /** @@ -133,7 +137,8 @@ public class ConnectionConfiguration implements Cloneable { * @param serviceName the name of the service provided by an XMPP server. */ public ConnectionConfiguration(String host, int port, String serviceName) { - init(host, port, serviceName, ProxyInfo.forDefaultProxy()); + initHostAddresses(host, port); + init(serviceName, ProxyInfo.forDefaultProxy()); } /** @@ -152,7 +157,8 @@ public class ConnectionConfiguration implements Cloneable { * @param proxy the proxy through which XMPP is to be connected */ public ConnectionConfiguration(String host, int port, String serviceName, ProxyInfo proxy) { - init(host, port, serviceName, proxy); + initHostAddresses(host, port); + init(serviceName, proxy); } /** @@ -163,7 +169,8 @@ public class ConnectionConfiguration implements Cloneable { * @param port the port where the XMPP is listening. */ public ConnectionConfiguration(String host, int port) { - init(host, port, host, ProxyInfo.forDefaultProxy()); + initHostAddresses(host, port); + init(host, ProxyInfo.forDefaultProxy()); } /** @@ -175,12 +182,11 @@ public class ConnectionConfiguration implements Cloneable { * @param proxy the proxy through which XMPP is to be connected */ public ConnectionConfiguration(String host, int port, ProxyInfo proxy) { - init(host, port, host, proxy); + initHostAddresses(host, port); + init(host, proxy); } - private void init(String host, int port, String serviceName, ProxyInfo proxy) { - this.host = host; - this.port = port; + protected void init(String serviceName, ProxyInfo proxy) { this.serviceName = serviceName; this.proxy = proxy; @@ -243,6 +249,11 @@ public class ConnectionConfiguration implements Cloneable { return port; } + public void setUsedHostAddress(HostAddress hostAddress) { + this.host = hostAddress.getFQDN(); + this.port = hostAddress.getPort(); + } + /** * Returns the TLS security mode used when making the connection. By default, * the mode is {@link SecurityMode#enabled}. @@ -674,6 +685,10 @@ public class ConnectionConfiguration implements Cloneable { return this.socketFactory; } + public List getHostAddresses() { + return Collections.unmodifiableList(hostAddresses); + } + /** * An enumeration for TLS security modes that are available when making a connection * to the XMPP server. @@ -742,4 +757,15 @@ public class ConnectionConfiguration implements Cloneable { this.password = password; this.resource = resource; } + + private void initHostAddresses(String host, int port) { + hostAddresses = new ArrayList(1); + HostAddress hostAddress; + try { + hostAddress = new HostAddress(host, port); + } catch (Exception e) { + throw new IllegalStateException(e); + } + hostAddresses.add(hostAddress); + } } diff --git a/source/org/jivesoftware/smack/XMPPConnection.java b/source/org/jivesoftware/smack/XMPPConnection.java index 4024bd340..af7b66750 100644 --- a/source/org/jivesoftware/smack/XMPPConnection.java +++ b/source/org/jivesoftware/smack/XMPPConnection.java @@ -26,6 +26,7 @@ import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.dns.HostAddress; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -51,6 +52,9 @@ import java.security.KeyStore; import java.security.Provider; import java.security.Security; import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; /** * Creates a socket connection to a XMPP server. This is the default connection @@ -546,27 +550,57 @@ public class XMPPConnection extends Connection { } private void connectUsingConfiguration(ConnectionConfiguration config) throws XMPPException { - String host = config.getHost(); - int port = config.getPort(); - try { - if (config.getSocketFactory() == null) { - this.socket = new Socket(host, port); + XMPPException exception = null; + Iterator it = config.getHostAddresses().iterator(); + List failedAddresses = new LinkedList(); + boolean xmppIOError = false; + while (it.hasNext()) { + exception = null; + HostAddress hostAddress = it.next(); + String host = hostAddress.getFQDN(); + int port = hostAddress.getPort(); + try { + if (config.getSocketFactory() == null) { + this.socket = new Socket(host, port); + } + else { + this.socket = config.getSocketFactory().createSocket(host, port); + } + } catch (UnknownHostException uhe) { + String errorMessage = "Could not connect to " + host + ":" + port + "."; + exception = new XMPPException(errorMessage, new XMPPError(XMPPError.Condition.remote_server_timeout, + errorMessage), uhe); + } catch (IOException ioe) { + String errorMessage = "XMPPError connecting to " + host + ":" + port + "."; + exception = new XMPPException(errorMessage, new XMPPError(XMPPError.Condition.remote_server_error, + errorMessage), ioe); + xmppIOError = true; } - else { - this.socket = config.getSocketFactory().createSocket(host, port); + if (exception == null) { + // We found a host to connect to, break here + config.setUsedHostAddress(hostAddress); + break; + } + hostAddress.setException(exception); + failedAddresses.add(hostAddress); + if (!it.hasNext()) { + // There are no more host addresses to try + // throw an exception and report all tried + // HostAddresses in the exception + StringBuilder sb = new StringBuilder(); + for (HostAddress fha : failedAddresses) { + sb.append(fha.getErrorMessage()); + sb.append("; "); + } + XMPPError xmppError; + if (xmppIOError) { + xmppError = new XMPPError(XMPPError.Condition.remote_server_error); + } + else { + xmppError = new XMPPError(XMPPError.Condition.remote_server_timeout); + } + throw new XMPPException(sb.toString(), xmppError); } - } - catch (UnknownHostException uhe) { - String errorMessage = "Could not connect to " + host + ":" + port + "."; - throw new XMPPException(errorMessage, new XMPPError( - XMPPError.Condition.remote_server_timeout, errorMessage), - uhe); - } - catch (IOException ioe) { - String errorMessage = "XMPPError connecting to " + host + ":" - + port + "."; - throw new XMPPException(errorMessage, new XMPPError( - XMPPError.Condition.remote_server_error, errorMessage), ioe); } socketClosed = false; initConnection(); diff --git a/source/org/jivesoftware/smack/util/DNSUtil.java b/source/org/jivesoftware/smack/util/DNSUtil.java index ab3e2e44d..628d8e8f3 100644 --- a/source/org/jivesoftware/smack/util/DNSUtil.java +++ b/source/org/jivesoftware/smack/util/DNSUtil.java @@ -19,18 +19,20 @@ package org.jivesoftware.smack.util; -import java.util.Hashtable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; -import javax.naming.NamingEnumeration; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.DirContext; -import javax.naming.directory.InitialDirContext; - +import org.jivesoftware.smack.util.dns.DNSResolver; +import org.jivesoftware.smack.util.dns.HostAddress; +import org.jivesoftware.smack.util.dns.SRVRecord; /** - * Utilty class to perform DNS lookups for XMPP services. + * Utility class to perform DNS lookups for XMPP services. * * @author Matt Tucker */ @@ -40,23 +42,30 @@ public class DNSUtil { * Create a cache to hold the 100 most recently accessed DNS lookups for a period of * 10 minutes. */ - private static Map cache = new Cache(100, 1000*60*10); + private static Map> cache = new Cache>(100, 1000*60*10); - private static DirContext context; + private static DNSResolver dnsResolver = null; - static { - try { - Hashtable env = new Hashtable(); - env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); - context = new InitialDirContext(env); - } - catch (Exception e) { - // Ignore. - } + /** + * Set the DNS resolver that should be used to perform DNS lookups. + * + * @param resolver + */ + public static void setDNSResolver(DNSResolver resolver) { + dnsResolver = resolver; } /** - * Returns the host name and port that the specified XMPP server can be + * Returns the current DNS resolved used to perform DNS lookups. + * + * @return + */ + public static DNSResolver getDNSResolver() { + return dnsResolver; + } + + /** + * Returns a list of HostAddresses under which the specified XMPP server can be * reached at for client-to-server communication. A DNS lookup for a SRV * record in the form "_xmpp-client._tcp.example.com" is attempted, according * to section 14.4 of RFC 3920. If that lookup fails, a lookup in the older form @@ -67,81 +76,17 @@ public class DNSUtil { * of 5222.

* * As an example, a lookup for "example.com" may return "im.example.com:5269". - * - * Note on SRV record selection. - * We now check priority and weight, but we still don't do this correctly. - * The missing behavior is this: if we fail to reach a host based on its SRV - * record then we need to select another host from the other SRV records. - * In Smack 3.1.1 we're not going to be able to do the major system redesign to - * correct this. * * @param domain the domain. - * @return a HostAddress, which encompasses the hostname and port that the XMPP - * server can be reached at for the specified domain. + * @return List of HostAddress, which encompasses the hostname and port that the + * XMPP server can be reached at for the specified domain. */ - public static HostAddress resolveXMPPDomain(String domain) { - if (context == null) { - return new HostAddress(domain, 5222); - } - String key = "c" + domain; - // Return item from cache if it exists. - if (cache.containsKey(key)) { - HostAddress address = (HostAddress)cache.get(key); - if (address != null) { - return address; - } - } - String bestHost = domain; - int bestPort = 5222; - int bestPriority = 0; - int bestWeight = 0; - try { - Attributes dnsLookup = context.getAttributes("_xmpp-client._tcp." + domain, new String[]{"SRV"}); - Attribute srvAttribute = dnsLookup.get("SRV"); - NamingEnumeration srvRecords = (NamingEnumeration) srvAttribute.getAll(); - while(srvRecords.hasMore()) { - String srvRecord = srvRecords.next(); - String [] srvRecordEntries = srvRecord.split(" "); - int priority = Integer.parseInt(srvRecordEntries[srvRecordEntries.length - 4]); - int port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]); - int weight = Integer.parseInt(srvRecordEntries[srvRecordEntries.length - 3]); - String host = srvRecordEntries[srvRecordEntries.length-1]; - - // Randomize the weight. - weight *= Math.random() * weight; - - if ((bestPriority == 0) || (priority < bestPriority)) { - // Choose a server with the lowest priority. - bestPriority = priority; - bestWeight = weight; - bestHost = host; - bestPort = port; - } else if (priority == bestPriority) { - // When we have like priorities then randomly choose a server based on its weight - // The weights were randomized above. - if (weight > bestWeight) { - bestWeight = weight; - bestHost = host; - bestPort = port; - } - } - } - } - catch (Exception e) { - // Ignore. - } - // Host entries in DNS should end with a ".". - if (bestHost.endsWith(".")) { - bestHost = bestHost.substring(0, bestHost.length()-1); - } - HostAddress address = new HostAddress(bestHost, bestPort); - // Add item to cache. - cache.put(key, address); - return address; + public static List resolveXMPPDomain(String domain) { + return resolveDomain(domain, 'c'); } /** - * Returns the host name and port that the specified XMPP server can be + * Returns a list of HostAddresses under which the specified XMPP server can be * reached at for server-to-server communication. A DNS lookup for a SRV * record in the form "_xmpp-server._tcp.example.com" is attempted, according * to section 14.4 of RFC 3920. If that lookup fails, a lookup in the older form @@ -154,104 +99,131 @@ public class DNSUtil { * As an example, a lookup for "example.com" may return "im.example.com:5269". * * @param domain the domain. - * @return a HostAddress, which encompasses the hostname and port that the XMPP - * server can be reached at for the specified domain. + * @return List of HostAddress, which encompasses the hostname and port that the + * XMPP server can be reached at for the specified domain. */ - public static HostAddress resolveXMPPServerDomain(String domain) { - if (context == null) { - return new HostAddress(domain, 5269); - } - String key = "s" + domain; + public static List resolveXMPPServerDomain(String domain) { + return resolveDomain(domain, 's'); + } + + private static List resolveDomain(String domain, char keyPrefix) { + // Prefix the key with 's' to distinguish him from the client domain lookups + String key = keyPrefix + domain; // Return item from cache if it exists. if (cache.containsKey(key)) { - HostAddress address = (HostAddress)cache.get(key); - if (address != null) { - return address; + List addresses = cache.get(key); + if (addresses != null) { + return addresses; } } - String host = domain; - int port = 5269; - try { - Attributes dnsLookup = - context.getAttributes("_xmpp-server._tcp." + domain, new String[]{"SRV"}); - String srvRecord = (String)dnsLookup.get("SRV").get(); - String [] srvRecordEntries = srvRecord.split(" "); - port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]); - host = srvRecordEntries[srvRecordEntries.length-1]; + + if (dnsResolver == null) + throw new IllegalStateException("No DNS resolver active."); + + List addresses = new ArrayList(); + + // Step one: Do SRV lookups + String srvDomain; + if (keyPrefix == 's') { + srvDomain = "_xmpp-server._tcp." + domain; + } else if (keyPrefix == 'c') { + srvDomain = "_xmpp-client._tcp." + domain; + } else { + srvDomain = domain; } - catch (Exception e) { - // Attempt lookup with older "jabber" name. - try { - Attributes dnsLookup = - context.getAttributes("_jabber._tcp." + domain, new String[]{"SRV"}); - String srvRecord = (String)dnsLookup.get("SRV").get(); - String [] srvRecordEntries = srvRecord.split(" "); - port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]); - host = srvRecordEntries[srvRecordEntries.length-1]; - } - catch (Exception e2) { - // Ignore. - } - } - // Host entries in DNS should end with a ".". - if (host.endsWith(".")) { - host = host.substring(0, host.length()-1); - } - HostAddress address = new HostAddress(host, port); + List srvRecords = dnsResolver.lookupSRVRecords(srvDomain); + List sortedRecords = sortSRVRecords(srvRecords); + if (sortedRecords != null) + addresses.addAll(sortedRecords); + + // Step two: Add the hostname to the end of the list + addresses.add(new HostAddress(domain)); + // Add item to cache. - cache.put(key, address); - return address; + cache.put(key, addresses); + + return addresses; } /** - * Encapsulates a hostname and port. + * Sort a given list of SRVRecords as described in RFC 2782 + * Note that we follow the RFC with one exception. In a group of the same priority, only the first entry + * is calculated by random. The others are ore simply ordered by their priority. + * + * @param records + * @return */ - public static class HostAddress { + protected static List sortSRVRecords(List records) { + // RFC 2782, Usage rules: "If there is precisely one SRV RR, and its Target is "." + // (the root domain), abort." + if (records.size() == 1 && records.get(0).getFQDN().equals(".")) + return null; - private String host; - private int port; + // sorting the records improves the performance of the bisection later + Collections.sort(records); - private HostAddress(String host, int port) { - this.host = host; - this.port = port; - } - - /** - * Returns the hostname. - * - * @return the hostname. - */ - public String getHost() { - return host; - } - - /** - * Returns the port. - * - * @return the port. - */ - public int getPort() { - return port; - } - - public String toString() { - return host + ":" + port; - } - - public boolean equals(Object o) { - if (this == o) { - return true; + // create the priority buckets + SortedMap> buckets = new TreeMap>(); + for (SRVRecord r : records) { + Integer priority = r.getPriority(); + List bucket = buckets.get(priority); + // create the list of SRVRecords if it doesn't exist + if (bucket == null) { + bucket = new LinkedList(); + buckets.put(priority, bucket); } - if (!(o instanceof HostAddress)) { - return false; - } - - final HostAddress address = (HostAddress) o; - - if (!host.equals(address.host)) { - return false; - } - return port == address.port; + bucket.add(r); } + + List res = new ArrayList(records.size()); + + for (Integer priority : buckets.keySet()) { + List bucket = buckets.get(priority); + int bucketSize; + while ((bucketSize = bucket.size()) > 0) { + int[] totals = new int[bucket.size()]; + int running_total = 0; + int count = 0; + int zeroWeight = 1; + + for (SRVRecord r : bucket) { + if (r.getWeight() > 0) + zeroWeight = 0; + } + + for (SRVRecord r : bucket) { + running_total += (r.getWeight() + zeroWeight); + totals[count] = running_total; + count++; + } + int selectedPos; + if (running_total == 0) { + // If running total is 0, then all weights in this priority + // group are 0. So we simply select one of the weights randomly + // as the other 'normal' algorithm is unable to handle this case + selectedPos = (int) (Math.random() * bucketSize); + } else { + double rnd = Math.random() * running_total; + selectedPos = bisect(totals, rnd); + } + // add the SRVRecord that was randomly chosen on it's weight + // to the start of the result list + SRVRecord chosenSRVRecord = bucket.remove(selectedPos); + res.add(chosenSRVRecord); + } + } + + return res; + } + + // TODO this is not yet really bisection just a stupid linear search + private static int bisect(int[] array, double value) { + int pos = 0; + for (int element : array) { + if (value < element) + break; + pos++; + } + return pos; } } \ No newline at end of file diff --git a/source/org/jivesoftware/smack/util/dns/DNSJavaResolver.java b/source/org/jivesoftware/smack/util/dns/DNSJavaResolver.java new file mode 100644 index 000000000..91db73b1a --- /dev/null +++ b/source/org/jivesoftware/smack/util/dns/DNSJavaResolver.java @@ -0,0 +1,72 @@ +/** + * Copyright 2013 Florian Schmaus + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.util.dns; + +import java.util.ArrayList; +import java.util.List; + +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.Type; + +public class DNSJavaResolver extends DNSResolver { + + private static DNSJavaResolver instance; + + private DNSJavaResolver() { + + } + + public static DNSResolver getInstance() { + if (instance == null) { + instance = new DNSJavaResolver(); + } + return instance; + } + + @Override + public List lookupSRVRecords(String name) { + List res = new ArrayList(); + + try { + Lookup lookup = new Lookup(name, Type.SRV); + Record recs[] = lookup.run(); + if (recs == null) + return res; + + for (Record record : recs) { + org.xbill.DNS.SRVRecord srvRecord = (org.xbill.DNS.SRVRecord) record; + if (srvRecord != null && srvRecord.getTarget() != null) { + String host = srvRecord.getTarget().toString(); + int port = srvRecord.getPort(); + int priority = srvRecord.getPriority(); + int weight = srvRecord.getWeight(); + + SRVRecord r; + try { + r = new SRVRecord(host, port, priority, weight); + } catch (Exception e) { + continue; + } + res.add(r); + } + } + + } catch (Exception e) { + } + return res; + } +} diff --git a/source/org/jivesoftware/smack/util/dns/DNSResolver.java b/source/org/jivesoftware/smack/util/dns/DNSResolver.java new file mode 100644 index 000000000..2c5dd296c --- /dev/null +++ b/source/org/jivesoftware/smack/util/dns/DNSResolver.java @@ -0,0 +1,24 @@ +/** + * Copyright 2013 Florian Schmaus + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.util.dns; + +import java.util.List; + +public abstract class DNSResolver { + + public abstract List lookupSRVRecords(String name); + +} diff --git a/source/org/jivesoftware/smack/util/dns/HostAddress.java b/source/org/jivesoftware/smack/util/dns/HostAddress.java new file mode 100644 index 000000000..978a6de69 --- /dev/null +++ b/source/org/jivesoftware/smack/util/dns/HostAddress.java @@ -0,0 +1,93 @@ +/** + * Copyright 2013 Florian Schmaus + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.util.dns; + +public class HostAddress { + private String fqdn; + private int port; + private Exception exception; + + /** + * Creates a new HostAddress with the given FQDN. The port will be set to the default XMPP client port: 5222 + * + * @param fqdn + * @throws IllegalArgumentException + */ + public HostAddress(String fqdn) throws IllegalArgumentException { + if (fqdn == null) + throw new IllegalArgumentException("FQDN is null"); + if (fqdn.charAt(fqdn.length() - 1) == '.') { + this.fqdn = fqdn.substring(0, fqdn.length() - 1); + } + else { + this.fqdn = fqdn; + } + // Set port to the default port for XMPP client communication + this.port = 5222; + } + + public HostAddress(String fqdn, int port) throws IllegalArgumentException { + this(fqdn); + if (port < 0 || port > 65535) + throw new IllegalArgumentException( + "DNS SRV records weight must be a 16-bit unsiged integer (i.e. between 0-65535. Port was: " + port); + + this.port = port; + } + + public String getFQDN() { + return fqdn; + } + + public int getPort() { + return port; + } + + public void setException(Exception e) { + this.exception = e; + } + + public String toString() { + return fqdn + ":" + port; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof HostAddress)) { + return false; + } + + final HostAddress address = (HostAddress) o; + + if (!fqdn.equals(address.fqdn)) { + return false; + } + return port == address.port; + } + + public String getErrorMessage() { + String error; + if (exception == null) { + error = "No error logged"; + } + else { + error = exception.getMessage(); + } + return toString() + " Exception: " + error; + } +} diff --git a/source/org/jivesoftware/smack/util/dns/JavaxResolver.java b/source/org/jivesoftware/smack/util/dns/JavaxResolver.java new file mode 100644 index 000000000..4ea361fdf --- /dev/null +++ b/source/org/jivesoftware/smack/util/dns/JavaxResolver.java @@ -0,0 +1,99 @@ +/** + * Copyright 2013 Florian Schmaus + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.util.dns; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; + +import javax.naming.NamingEnumeration; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + +import org.jivesoftware.smack.util.DNSUtil; + +/** + * A DNS resolver (mostly for SRV records), which makes use of the API provided in the javax.* namepsace. + * + * @author Florian Schmaus + * + */ +public class JavaxResolver extends DNSResolver { + + private static JavaxResolver instance; + private static DirContext dirContext; + + static { + try { + Hashtable env = new Hashtable(); + env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); + dirContext = new InitialDirContext(env); + } catch (Exception e) { + // Ignore. + } + + // Try to set this DNS resolver as primary one + DNSUtil.setDNSResolver(maybeGetInstance()); + } + + private JavaxResolver() { + + } + + public static DNSResolver maybeGetInstance() { + if (instance == null && isSupported()) { + instance = new JavaxResolver(); + } + return instance; + } + + public static boolean isSupported() { + return dirContext != null; + } + + @Override + public List lookupSRVRecords(String name) { + List res = new ArrayList(); + + try { + Attributes dnsLookup = dirContext.getAttributes(name, new String[]{"SRV"}); + Attribute srvAttribute = dnsLookup.get("SRV"); + @SuppressWarnings("unchecked") + NamingEnumeration srvRecords = (NamingEnumeration) srvAttribute.getAll(); + while (srvRecords.hasMore()) { + String srvRecordString = srvRecords.next(); + String[] srvRecordEntries = srvRecordString.split(" "); + int priority = Integer.parseInt(srvRecordEntries[srvRecordEntries.length - 4]); + int port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length - 2]); + int weight = Integer.parseInt(srvRecordEntries[srvRecordEntries.length - 3]); + String host = srvRecordEntries[srvRecordEntries.length - 1]; + + SRVRecord srvRecord; + try { + srvRecord = new SRVRecord(host, port, priority, weight); + } catch (Exception e) { + continue; + } + res.add(srvRecord); + } + } catch (Exception e) { + + } + return res; + } +} diff --git a/source/org/jivesoftware/smack/util/dns/SRVRecord.java b/source/org/jivesoftware/smack/util/dns/SRVRecord.java new file mode 100644 index 000000000..87c6e54fc --- /dev/null +++ b/source/org/jivesoftware/smack/util/dns/SRVRecord.java @@ -0,0 +1,77 @@ +/** + * Copyright 2013 Florian Schmaus + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smack.util.dns; + +/** + * @see 65535) + throw new IllegalArgumentException( + "DNS SRV records priority must be a 16-bit unsiged integer (i.e. between 0-65535. Priority was: " + + priority); + + this.priority = priority; + this.weight = weight; + + } + + public int getPriority() { + return priority; + } + + public int getWeight() { + return weight; + } + + public int compareTo(SRVRecord other) { + // According to RFC2782, + // "[a] client MUST attempt to contact the target host with the lowest-numbered priority it can reach". + // This means that a SRV record with a higher priority is 'less' then one with a lower. + int res = other.priority - this.priority; + if (res == 0) { + res = this.weight - other.weight; + } + return res; + } + + public String toString() { + return super.toString() + " prio:" + priority + ":w:" + weight; + } +} diff --git a/test-unit/org/jivesoftware/smack/util/DNSUtilTest.java b/test-unit/org/jivesoftware/smack/util/DNSUtilTest.java new file mode 100644 index 000000000..d107f8f0d --- /dev/null +++ b/test-unit/org/jivesoftware/smack/util/DNSUtilTest.java @@ -0,0 +1,147 @@ +package org.jivesoftware.smack.util; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import java.util.ArrayList; +import java.util.List; + + +import org.jivesoftware.smack.util.dns.DNSJavaResolver; +import org.jivesoftware.smack.util.dns.DNSResolver; +import org.jivesoftware.smack.util.dns.HostAddress; +import org.jivesoftware.smack.util.dns.JavaxResolver; +import org.jivesoftware.smack.util.dns.SRVRecord; +import org.junit.Test; + +public class DNSUtilTest { + private static final String igniterealtimeDomain = "igniterealtime.org"; + private static final String igniterealtimeXMPPServer = "xmpp." + igniterealtimeDomain; + private static final int igniterealtimeClientPort = 5222; + private static final int igniterealtimeServerPort = 5269; + + @Test + public void xmppClientDomainJavaXTest() { + DNSResolver resolver = JavaxResolver.maybeGetInstance(); + assertNotNull(resolver); + DNSUtil.setDNSResolver(resolver); + xmppClientDomainTest(); + } + + @Test + public void xmppServerDomainJavaXTest() { + DNSResolver resolver = JavaxResolver.maybeGetInstance(); + assertNotNull(resolver); + DNSUtil.setDNSResolver(resolver); + xmppServerDomainTest(); + } + + @Test + public void xmppClientDomainDNSJavaTest() { + DNSResolver resolver = DNSJavaResolver.getInstance(); + assertNotNull(resolver); + DNSUtil.setDNSResolver(resolver); + xmppClientDomainTest(); + } + + @Test + public void xmppServerDomainDNSJavaTest() { + DNSResolver resolver = DNSJavaResolver.getInstance(); + assertNotNull(resolver); + DNSUtil.setDNSResolver(resolver); + xmppServerDomainTest(); + } + + @Test + public void sortSRVlowestPrioFirstTest() { + List sortedRecords = DNSUtil.sortSRVRecords(createSRVRecords()); + assertTrue(sortedRecords.get(0).getFQDN().equals("0.20.foo.bar")); + } + + @Test + public void sortSRVdistributeOverWeights() { + int weight50 = 0; + int weight20one = 0; + int weight20two = 0; + int weight10 = 0; + for (int i = 0; i < 1000; i++) { + List sortedRecords = DNSUtil.sortSRVRecords(createSRVRecords()); + String host = sortedRecords.get(1).getFQDN(); + if (host.equals("5.20.one.foo.bar")) { + weight20one++; + } else if (host.equals("5.20.two.foo.bar")) { + weight20two++; + } else if (host.equals("5.10.foo.bar")) { + weight10++; + } else if (host.equals("5.50.foo.bar")) { + weight50++; + } else { + fail("Wrong host after SRVRecord sorting"); + } + } + assertTrue(weight50 > 400 && weight50 < 600); + assertTrue(weight20one > 100 && weight20one < 300); + assertTrue(weight20two > 100 && weight20two < 300); + assertTrue(weight10 > 0&& weight10 < 200); + } + + @Test + public void sortSRVdistributeZeroWeights() { + int weightZeroOne = 0; + int weightZeroTwo = 0; + for (int i = 0; i < 1000; i++) { + List sortedRecords = DNSUtil.sortSRVRecords(createSRVRecords()); + // Remove the first 5 records with a lower priority + for (int j = 0; j < 5; j++) { + sortedRecords.remove(0); + } + String host = sortedRecords.remove(0).getFQDN(); + if (host.equals("10.0.one.foo.bar")) { + weightZeroOne++; + } else if (host.endsWith("10.0.two.foo.bar")) { + weightZeroTwo++; + } else { + fail("Wrong host after SRVRecord sorting"); + } + } + assertTrue(weightZeroOne > 400 && weightZeroOne < 600); + assertTrue(weightZeroTwo > 400 && weightZeroTwo < 600); + } + + private void xmppClientDomainTest() { + List hostAddresses = DNSUtil.resolveXMPPDomain(igniterealtimeDomain); + HostAddress ha = hostAddresses.get(0); + assertEquals(ha.getFQDN(), igniterealtimeXMPPServer); + assertEquals(ha.getPort(), igniterealtimeClientPort); + } + + private void xmppServerDomainTest() { + List hostAddresses = DNSUtil.resolveXMPPServerDomain(igniterealtimeDomain); + HostAddress ha = hostAddresses.get(0); + assertEquals(ha.getFQDN(), igniterealtimeXMPPServer); + assertEquals(ha.getPort(), igniterealtimeServerPort); + } + + private static List createSRVRecords() { + List records = new ArrayList(); + // We create one record with priority 0 that should also be tried first + // Then 4 records with priority 5 and different weights (50, 20, 20, 10) + // Then 2 records with priority 10 and weight 0 which should be treaded equal + // These records are added in a 'random' way to the list + try { + records.add(new SRVRecord("5.20.one.foo.bar", 42, 5, 20)); // Priority 5, Weight 20 + records.add(new SRVRecord("10.0.one.foo.bar", 42, 10, 0)); // Priority 10, Weight 0 + records.add(new SRVRecord("5.10.foo.bar", 42, 5, 10)); // Priority 5, Weight 10 + records.add(new SRVRecord("10.0.two.foo.bar", 42, 10, 0)); // Priority 10, Weight 0 + records.add(new SRVRecord("5.20.two.foo.bar", 42, 5, 20)); // Priority 5, Weight 20 + records.add(new SRVRecord("0.20.foo.bar", 42, 0, 20)); // Priority 0, Weight 20 + records.add(new SRVRecord("5.50.foo.bar", 42, 5, 50)); // Priority 5, Weight 50 + } catch (IllegalArgumentException e) { + // Ignore + } + assertTrue(records.size() > 0); + return records; + } +} From cb44042d3c576870d2ebc4ea94ec783a66b4824b Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 18 Mar 2013 19:56:58 +0000 Subject: [PATCH 04/24] SMACK-419 PacketWriter: Only flush if queue is empty git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13565 b35dd754-fafc-0310-a699-88a17e54d16e --- source/org/jivesoftware/smack/PacketWriter.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/org/jivesoftware/smack/PacketWriter.java b/source/org/jivesoftware/smack/PacketWriter.java index 8213bbd35..155110cfb 100644 --- a/source/org/jivesoftware/smack/PacketWriter.java +++ b/source/org/jivesoftware/smack/PacketWriter.java @@ -193,9 +193,11 @@ class PacketWriter { if (packet != null) { synchronized (writer) { writer.write(packet.toXML()); - writer.flush(); - // Keep track of the last time a stanza was sent to the server - lastActive = System.currentTimeMillis(); + if (queue.isEmpty()) { + writer.flush(); + // Keep track of the last time a stanza was sent to the server + lastActive = System.currentTimeMillis(); + } } } } From 26338a27548c30df0081913f3dd2ef294126d999 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 18 Mar 2013 19:57:28 +0000 Subject: [PATCH 05/24] SMACK-417 If both PacketReader and PacketWriter fail at the same time, connectionClosedonError() is called two times Refactored notifyConnectionError() and notifyReconnection() from PacketReader to XMPPConnection. Made PacketReader.done and PacketWriter.done volatile. Prevent duplicate connectionClosedonError() calls by making the method synchronzied and protected them with an enter guard: if (packetReader.done && packetWriter.done) return; git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13566 b35dd754-fafc-0310-a699-88a17e54d16e --- .../org/jivesoftware/smack/PacketReader.java | 46 +------------ .../org/jivesoftware/smack/PacketWriter.java | 4 +- .../jivesoftware/smack/XMPPConnection.java | 68 ++++++++++++++++--- .../jivesoftware/smack/ReconnectionTest.java | 6 +- .../jivesoftware/smack/RosterSmackTest.java | 2 +- 5 files changed, 65 insertions(+), 61 deletions(-) diff --git a/source/org/jivesoftware/smack/PacketReader.java b/source/org/jivesoftware/smack/PacketReader.java index 590dfd951..af1c7682f 100644 --- a/source/org/jivesoftware/smack/PacketReader.java +++ b/source/org/jivesoftware/smack/PacketReader.java @@ -48,7 +48,7 @@ class PacketReader { private XMPPConnection connection; private XmlPullParser parser; - private boolean done; + volatile boolean done; private String connectionID = null; private Semaphore connectionSemaphore; @@ -155,48 +155,6 @@ class PacketReader { connection.collectors.clear(); } - /** - * Sends out a notification that there was an error with the connection - * and closes the connection. - * - * @param e the exception that causes the connection close event. - */ - void notifyConnectionError(Exception e) { - done = true; - // Closes the connection temporary. A reconnection is possible - connection.shutdown(new Presence(Presence.Type.unavailable)); - // Print the stack trace to help catch the problem - e.printStackTrace(); - // Notify connection listeners of the error. - for (ConnectionListener listener : connection.getConnectionListeners()) { - try { - listener.connectionClosedOnError(e); - } - catch (Exception e2) { - // Catch and print any exception so we can recover - // from a faulty listener - e2.printStackTrace(); - } - } - } - - /** - * Sends a notification indicating that the connection was reconnected successfully. - */ - protected void notifyReconnection() { - // Notify connection listeners of the reconnection. - for (ConnectionListener listener : connection.getConnectionListeners()) { - try { - listener.reconnectionSuccessful(); - } - catch (Exception e) { - // Catch and print any exception so we can recover - // from a faulty listener - e.printStackTrace(); - } - } - } - /** * Resets the parser using the latest connection's reader. Reseting the parser is necessary * when the plain connection has been secured or when a new opening stream element is going @@ -332,7 +290,7 @@ class PacketReader { if (!(done || connection.isSocketClosed())) { // Close the connection and notify connection listeners of the // error. - notifyConnectionError(e); + connection.notifyConnectionError(e); } } } diff --git a/source/org/jivesoftware/smack/PacketWriter.java b/source/org/jivesoftware/smack/PacketWriter.java index 155110cfb..f8b5c359f 100644 --- a/source/org/jivesoftware/smack/PacketWriter.java +++ b/source/org/jivesoftware/smack/PacketWriter.java @@ -44,7 +44,7 @@ class PacketWriter { private Writer writer; private XMPPConnection connection; private final BlockingQueue queue; - private boolean done; + volatile boolean done; /** * Timestamp when the last stanza was sent to the server. This information is used @@ -245,7 +245,7 @@ class PacketWriter { // packetReader could be set to null by an concurrent disconnect() call. // Therefore Prevent NPE exceptions by checking packetReader. if (connection.packetReader != null) { - connection.packetReader.notifyConnectionError(ioe); + connection.notifyConnectionError(ioe); } } } diff --git a/source/org/jivesoftware/smack/XMPPConnection.java b/source/org/jivesoftware/smack/XMPPConnection.java index af7b66750..6512aa981 100644 --- a/source/org/jivesoftware/smack/XMPPConnection.java +++ b/source/org/jivesoftware/smack/XMPPConnection.java @@ -473,6 +473,10 @@ public class XMPPConnection extends Connection { return; } + if (!isConnected()) { + return; + } + shutdown(unavailablePresence); if (roster != null) { @@ -483,9 +487,7 @@ public class XMPPConnection extends Connection { wasAuthenticated = false; packetWriter.cleanup(); - packetWriter = null; packetReader.cleanup(); - packetReader = null; } public void sendPacket(Packet packet) { @@ -614,10 +616,8 @@ public class XMPPConnection extends Connection { */ private void initConnection() throws XMPPException { boolean isFirstInitialization = packetReader == null || packetWriter == null; - if (!isFirstInitialization) { - compressionHandler = null; - serverAckdCompression = false; - } + compressionHandler = null; + serverAckdCompression = false; // Set the reader and writer instance variables initReaderAndWriter(); @@ -661,7 +661,7 @@ public class XMPPConnection extends Connection { } } else if (!wasAuthenticated) { - packetReader.notifyReconnection(); + notifyReconnection(); } } @@ -773,7 +773,7 @@ public class XMPPConnection extends Connection { void startTLSReceived(boolean required) { if (required && config.getSecurityMode() == ConnectionConfiguration.SecurityMode.disabled) { - packetReader.notifyConnectionError(new IllegalStateException( + notifyConnectionError(new IllegalStateException( "TLS required by server but not allowed by connection configuration")); return; } @@ -787,7 +787,7 @@ public class XMPPConnection extends Connection { writer.flush(); } catch (IOException e) { - packetReader.notifyConnectionError(e); + notifyConnectionError(e); } } @@ -977,7 +977,7 @@ public class XMPPConnection extends Connection { writer.flush(); } catch (IOException e) { - packetReader.notifyConnectionError(e); + notifyConnectionError(e); } } @@ -1041,7 +1041,7 @@ public class XMPPConnection extends Connection { login(config.getUsername(), config.getPassword(), config.getResource()); } - packetReader.notifyReconnection(); + notifyReconnection(); } catch (XMPPException e) { e.printStackTrace(); @@ -1059,4 +1059,50 @@ public class XMPPConnection extends Connection { this.wasAuthenticated = wasAuthenticated; } } + + /** + * Sends out a notification that there was an error with the connection + * and closes the connection. Also prints the stack trace of the given exception + * + * @param e the exception that causes the connection close event. + */ + synchronized void notifyConnectionError(Exception e) { + // Listeners were already notified of the exception, return right here. + if (packetReader.done && packetWriter.done) return; + + packetReader.done = true; + packetWriter.done = true; + // Closes the connection temporary. A reconnection is possible + shutdown(new Presence(Presence.Type.unavailable)); + // Print the stack trace to help catch the problem + e.printStackTrace(); + // Notify connection listeners of the error. + for (ConnectionListener listener : getConnectionListeners()) { + try { + listener.connectionClosedOnError(e); + } + catch (Exception e2) { + // Catch and print any exception so we can recover + // from a faulty listener + e2.printStackTrace(); + } + } + } + + /** + * Sends a notification indicating that the connection was reconnected successfully. + */ + protected void notifyReconnection() { + // Notify connection listeners of the reconnection. + for (ConnectionListener listener : getConnectionListeners()) { + try { + listener.reconnectionSuccessful(); + } + catch (Exception e) { + // Catch and print any exception so we can recover + // from a faulty listener + e.printStackTrace(); + } + } + } } diff --git a/test/org/jivesoftware/smack/ReconnectionTest.java b/test/org/jivesoftware/smack/ReconnectionTest.java index f4cbd0c32..35c085813 100644 --- a/test/org/jivesoftware/smack/ReconnectionTest.java +++ b/test/org/jivesoftware/smack/ReconnectionTest.java @@ -44,7 +44,7 @@ public class ReconnectionTest extends SmackTestCase { connection.addConnectionListener(listener); // Simulates an error in the connection - connection.packetReader.notifyConnectionError(new Exception("Simulated Error")); + connection.notifyConnectionError(new Exception("Simulated Error")); Thread.sleep(12000); // After 10 seconds, the reconnection manager must reestablishes the connection assertEquals("The ConnectionListener.connectionStablished() notification was not fired", @@ -79,7 +79,7 @@ public class ReconnectionTest extends SmackTestCase { connection.addConnectionListener(listener); // Simulates an error in the connection - connection.packetReader.notifyConnectionError(new Exception("Simulated Error")); + connection.notifyConnectionError(new Exception("Simulated Error")); Thread.sleep(12000); // After 10 seconds, the reconnection manager must reestablishes the connection assertEquals("The ConnectionListener.connectionStablished() notification was not fired", @@ -103,7 +103,7 @@ public class ReconnectionTest extends SmackTestCase { connection.addConnectionListener(listener); // Produces a connection error - connection.packetReader.notifyConnectionError(new Exception("Simulated Error")); + connection.notifyConnectionError(new Exception("Simulated Error")); assertEquals( "An error occurs but the ConnectionListener.connectionClosedOnError(e) was not notified", true, listener.connectionClosedOnError); diff --git a/test/org/jivesoftware/smack/RosterSmackTest.java b/test/org/jivesoftware/smack/RosterSmackTest.java index 6436f69f4..8bee8124f 100644 --- a/test/org/jivesoftware/smack/RosterSmackTest.java +++ b/test/org/jivesoftware/smack/RosterSmackTest.java @@ -689,7 +689,7 @@ public class RosterSmackTest extends SmackTestCase { Thread.sleep(200); // Break the connection - getConnection(0).packetReader.notifyConnectionError(new Exception("Simulated Error")); + getConnection(0).notifyConnectionError(new Exception("Simulated Error")); Presence presence = roster.getPresence(getBareJID(1)); assertFalse("Unavailable presence not found for offline user", presence.isAvailable()); From 3891c738ad7386117d41f2f6f22d9825476c4404 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 22 Mar 2013 18:14:58 +0000 Subject: [PATCH 06/24] Added isFullJID() helper in StringUtils git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13579 b35dd754-fafc-0310-a699-88a17e54d16e --- .../org/jivesoftware/smack/util/StringUtils.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/source/org/jivesoftware/smack/util/StringUtils.java b/source/org/jivesoftware/smack/util/StringUtils.java index 39bc54945..7e3cfdc73 100644 --- a/source/org/jivesoftware/smack/util/StringUtils.java +++ b/source/org/jivesoftware/smack/util/StringUtils.java @@ -362,6 +362,20 @@ public class StringUtils { } } + /** + * Returns true if jid is a full JID (i.e. a JID with resource part). + * + * @param jid + * @return true if full JID, false otherwise + */ + public static boolean isFullJID(String jid) { + if (parseName(jid).length() <= 0 || parseServer(jid).length() <= 0 + || parseResource(jid).length() <= 0) { + return false; + } + return true; + } + /** * Escapes the node portion of a JID according to "JID Escaping" (JEP-0106). * Escaping replaces characters prohibited by node-prep with escape sequences, From a7d73993b0da00f22984adbe57491728c406897e Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 22 Mar 2013 18:15:06 +0000 Subject: [PATCH 07/24] SMACK-430 Re-activated code that throws an exception if createOutgoingFileTransfer() was called with a bare JID git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13580 b35dd754-fafc-0310-a699-88a17e54d16e --- .../filetransfer/FileTransferManager.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/source/org/jivesoftware/smackx/filetransfer/FileTransferManager.java b/source/org/jivesoftware/smackx/filetransfer/FileTransferManager.java index 664450b0d..6e413fa31 100644 --- a/source/org/jivesoftware/smackx/filetransfer/FileTransferManager.java +++ b/source/org/jivesoftware/smackx/filetransfer/FileTransferManager.java @@ -27,6 +27,7 @@ import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.packet.StreamInitiation; import java.util.ArrayList; @@ -125,18 +126,21 @@ public class FileTransferManager { * Creates an OutgoingFileTransfer to send a file to another user. * * @param userID - * The fully qualified jabber ID with resource of the user to + * The fully qualified jabber ID (i.e. full JID) with resource of the user to * send the file to. * @return The send file object on which the negotiated transfer can be run. + * @exception IllegalArgumentException if userID is null or not a full JID */ public OutgoingFileTransfer createOutgoingFileTransfer(String userID) { -// Why is this only accepting fully qualified JID? -// if (userID == null || StringUtils.parseName(userID).length() <= 0 -// || StringUtils.parseServer(userID).length() <= 0 -// || StringUtils.parseResource(userID).length() <= 0) { -// throw new IllegalArgumentException( -// "The provided user id was not fully qualified"); -// } + if (userID == null) { + throw new IllegalArgumentException("userID was null"); + } + // We need to create outgoing file transfers with a full JID since this method will later + // use XEP-0095 to negotiate the stream. This is done with IQ stanzas that need to be addressed to a full JID + // in order to reach an client entity. + else if (!StringUtils.isFullJID(userID)) { + throw new IllegalArgumentException("The provided user id was not a full JID (i.e. with resource part)"); + } return new OutgoingFileTransfer(connection.getUser(), userID, fileTransferNegotiator.getNextStreamID(), From bd70a95f8c0ba95d1e4d53a95db53b94540c3b91 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 22 Mar 2013 18:15:13 +0000 Subject: [PATCH 08/24] SMACK-384 Don't use a semaphore while waiting for PacketReader to be started. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13581 b35dd754-fafc-0310-a699-88a17e54d16e --- source/org/jivesoftware/smack/PacketReader.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/source/org/jivesoftware/smack/PacketReader.java b/source/org/jivesoftware/smack/PacketReader.java index af1c7682f..826360cf3 100644 --- a/source/org/jivesoftware/smack/PacketReader.java +++ b/source/org/jivesoftware/smack/PacketReader.java @@ -51,7 +51,6 @@ class PacketReader { volatile boolean done; private String connectionID = null; - private Semaphore connectionSemaphore; protected PacketReader(final XMPPConnection connection) { this.connection = connection; @@ -97,21 +96,17 @@ class PacketReader { * @throws XMPPException if the server fails to send an opening stream back * for more than five seconds. */ - public void startup() throws XMPPException { - connectionSemaphore = new Semaphore(1); - + synchronized public void startup() throws XMPPException { readerThread.start(); - // Wait for stream tag before returing. We'll wait a couple of seconds before + // Wait for stream tag before returning. We'll wait a couple of seconds before // giving up and throwing an error. try { - connectionSemaphore.acquire(); - // A waiting thread may be woken up before the wait time or a notify // (although this is a rare thing). Therefore, we continue waiting // until either a connectionID has been set (and hence a notify was // made) or the total wait time has elapsed. int waitTime = SmackConfiguration.getPacketReplyTimeout(); - connectionSemaphore.tryAcquire(3 * waitTime, TimeUnit.MILLISECONDS); + wait(3 * waitTime); } catch (InterruptedException ie) { // Ignore. @@ -304,8 +299,8 @@ class PacketReader { * 3) TLS negotiation was successful * */ - private void releaseConnectionIDLock() { - connectionSemaphore.release(); + synchronized private void releaseConnectionIDLock() { + notify(); } /** From 6aa195eb88efca037e0582964dfa33609a38aa1e Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 22 Mar 2013 18:15:21 +0000 Subject: [PATCH 09/24] SMACK-382 Prevent memory leak in AdHocCommandManager by only creating the Thread if it's actually needed git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13582 b35dd754-fafc-0310-a699-88a17e54d16e --- .../smackx/commands/AdHocCommandManager.java | 81 +++++++++---------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/source/org/jivesoftware/smackx/commands/AdHocCommandManager.java b/source/org/jivesoftware/smackx/commands/AdHocCommandManager.java index f32c48ec2..8f4eb65c5 100755 --- a/source/org/jivesoftware/smackx/commands/AdHocCommandManager.java +++ b/source/org/jivesoftware/smackx/commands/AdHocCommandManager.java @@ -343,46 +343,7 @@ public class AdHocCommandManager { PacketFilter filter = new PacketTypeFilter(AdHocCommandData.class); connection.addPacketListener(listener, filter); - // Create a thread to reap sessions. But, we'll only start it later when commands are - // actually registered. - sessionsSweeper = new Thread(new Runnable() { - public void run() { - while (true) { - for (String sessionId : executingCommands.keySet()) { - LocalCommand command = executingCommands.get(sessionId); - // Since the command could be removed in the meanwhile - // of getting the key and getting the value - by a - // processed packet. We must check if it still in the - // map. - if (command != null) { - long creationStamp = command.getCreationDate(); - // Check if the Session data has expired (default is - // 10 minutes) - // To remove it from the session list it waits for - // the double of the of time out time. This is to - // let - // the requester know why his execution request is - // not accepted. If the session is removed just - // after the time out, then whe the user request to - // continue the execution he will recieved an - // invalid session error and not a time out error. - if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000 * 2) { - // Remove the expired session - executingCommands.remove(sessionId); - } - } - } - try { - Thread.sleep(1000); - } - catch (InterruptedException ie) { - // Ignore. - } - } - } - - }); - sessionsSweeper.setDaemon(true); + sessionsSweeper = null; } /** @@ -486,7 +447,45 @@ public class AdHocCommandManager { response.setStatus(Status.executing); executingCommands.put(sessionId, command); // See if the session reaping thread is started. If not, start it. - if (!sessionsSweeper.isAlive()) { + if (sessionsSweeper == null) { + sessionsSweeper = new Thread(new Runnable() { + public void run() { + while (true) { + for (String sessionId : executingCommands.keySet()) { + LocalCommand command = executingCommands.get(sessionId); + // Since the command could be removed in the meanwhile + // of getting the key and getting the value - by a + // processed packet. We must check if it still in the + // map. + if (command != null) { + long creationStamp = command.getCreationDate(); + // Check if the Session data has expired (default is + // 10 minutes) + // To remove it from the session list it waits for + // the double of the of time out time. This is to + // let + // the requester know why his execution request is + // not accepted. If the session is removed just + // after the time out, then whe the user request to + // continue the execution he will recieved an + // invalid session error and not a time out error. + if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000 * 2) { + // Remove the expired session + executingCommands.remove(sessionId); + } + } + } + try { + Thread.sleep(1000); + } + catch (InterruptedException ie) { + // Ignore. + } + } + } + + }); + sessionsSweeper.setDaemon(true); sessionsSweeper.start(); } } From ca2f8efe600fab60b556fa00905b48b2af204bb3 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 23 Mar 2013 00:26:42 +0000 Subject: [PATCH 10/24] Document how an Avatar can be removed from a vCard. Add a convenience method. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13583 b35dd754-fafc-0310-a699-88a17e54d16e --- .../org/jivesoftware/smackx/packet/VCard.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/source/org/jivesoftware/smackx/packet/VCard.java b/source/org/jivesoftware/smackx/packet/VCard.java index 376044d05..db5ef76a9 100644 --- a/source/org/jivesoftware/smackx/packet/VCard.java +++ b/source/org/jivesoftware/smackx/packet/VCard.java @@ -338,22 +338,22 @@ public class VCard extends IQ { } /** - * Specify the bytes for the avatar to use. + * Removes the avatar from the vCard * - * @param bytes the bytes of the avatar. + * This is done by setting the PHOTO value to the empty string as defined in XEP-0153 + */ + public void removeAvatar() { + setAvatar(null, "image/jpeg"); + } + + /** + * Specify the bytes for the avatar to use. + * If bytes is null, then the avatar will be removed. + * + * @param bytes the bytes of the avatar, or null to remove the avatar data */ public void setAvatar(byte[] bytes) { - if (bytes == null) { - // Remove avatar (if any) from mappings - otherUnescapableFields.remove("PHOTO"); - return; - } - - // Otherwise, add to mappings. - String encodedImage = StringUtils.encodeBase64(bytes); - avatar = encodedImage; - - setField("PHOTO", "image/jpeg" + encodedImage + "", true); + setAvatar(bytes, "image/jpeg"); } /** From 638d34fd0628c36402e5805a713021d9c549ab18 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 23 Mar 2013 00:27:14 +0000 Subject: [PATCH 11/24] SMACK-413 fixed vCard parsing regarding the PHOTO element. Moved vCard test cases to unit-test where appropriate. Added testcases for vCard PHOTO parsing. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13584 b35dd754-fafc-0310-a699-88a17e54d16e --- .../org/jivesoftware/smackx/packet/VCard.java | 66 +++++++---- .../smackx/provider/VCardProvider.java | 37 +++++- .../jivesoftware/smackx/VCardUnitTest.java | 107 ++++++++++++++++++ .../smack/test/SmackTestCase.java | 2 - test/org/jivesoftware/smackx/VCardTest.java | 37 ++---- 5 files changed, 197 insertions(+), 52 deletions(-) create mode 100644 test-unit/org/jivesoftware/smackx/VCardUnitTest.java diff --git a/source/org/jivesoftware/smackx/packet/VCard.java b/source/org/jivesoftware/smackx/packet/VCard.java index db5ef76a9..9766db824 100644 --- a/source/org/jivesoftware/smackx/packet/VCard.java +++ b/source/org/jivesoftware/smackx/packet/VCard.java @@ -112,7 +112,8 @@ public class VCard extends IQ { private String organization; private String organizationUnit; - private String avatar; + private String photoMimeType; + private String photoBinval; /** * Such as DESC ROLE GEO etc.. see JEP-0054 @@ -343,12 +344,15 @@ public class VCard extends IQ { * This is done by setting the PHOTO value to the empty string as defined in XEP-0153 */ public void removeAvatar() { - setAvatar(null, "image/jpeg"); + // Remove avatar (if any) + photoBinval = null; + photoMimeType = null; } /** - * Specify the bytes for the avatar to use. + * Specify the bytes of the JPEG for the avatar to use. * If bytes is null, then the avatar will be removed. + * 'image/jpeg' will be used as MIME type. * * @param bytes the bytes of the avatar, or null to remove the avatar data */ @@ -363,27 +367,27 @@ public class VCard extends IQ { * @param mimeType the mime type of the avatar. */ public void setAvatar(byte[] bytes, String mimeType) { + // If bytes is null, remove the avatar if (bytes == null) { - // Remove avatar (if any) from mappings - otherUnescapableFields.remove("PHOTO"); + removeAvatar(); return; } // Otherwise, add to mappings. String encodedImage = StringUtils.encodeBase64(bytes); - avatar = encodedImage; - setField("PHOTO", "" + mimeType + "" + encodedImage + "", true); + setAvatar(encodedImage, mimeType); } /** - * Set the encoded avatar string. This is used by the provider. + * Specify the Avatar used for this vCard. * - * @param encodedAvatar the encoded avatar string. + * @param encodedImage the Base64 encoded image as String + * @param mimeType the MIME type of the image */ - public void setEncodedImage(String encodedAvatar) { - //TODO Move VCard and VCardProvider into a vCard package. - this.avatar = encodedAvatar; + public void setAvatar(String encodedImage, String mimeType) { + photoBinval = encodedImage; + photoMimeType = mimeType; } /** @@ -410,10 +414,19 @@ public class VCard extends IQ { * @return byte representation of avatar. */ public byte[] getAvatar() { - if (avatar == null) { + if (photoBinval == null) { return null; } - return StringUtils.decodeBase64(avatar); + return StringUtils.decodeBase64(photoBinval); + } + + /** + * Returns the MIME Type of the avatar or null if none is set + * + * @return the MIME Type of the avatar or null + */ + public String getAvatarMimeType() { + return photoMimeType; } /** @@ -570,16 +583,14 @@ public class VCard extends IQ { return sb.toString(); } - private void copyFieldsFrom(VCard result) { - if (result == null) result = new VCard(); - + private void copyFieldsFrom(VCard from) { Field[] fields = VCard.class.getDeclaredFields(); for (Field field : fields) { if (field.getDeclaringClass() == VCard.class && !Modifier.isFinal(field.getModifiers())) { try { field.setAccessible(true); - field.set(this, field.get(result)); + field.set(this, field.get(from)); } catch (IllegalAccessException e) { throw new RuntimeException("This cannot happen:" + field, e); @@ -612,6 +623,7 @@ public class VCard extends IQ { || homePhones.size() > 0 || workAddr.size() > 0 || workPhones.size() > 0 + || photoBinval != null ; } @@ -666,8 +678,11 @@ public class VCard extends IQ { if (!workAddr.equals(vCard.workAddr)) { return false; } - return workPhones.equals(vCard.workPhones); + if (photoBinval != null ? !photoBinval.equals(vCard.photoBinval) : vCard.photoBinval != null) { + return false; + } + return workPhones.equals(vCard.workPhones); } public int hashCode() { @@ -684,6 +699,7 @@ public class VCard extends IQ { result = 29 * result + (organization != null ? organization.hashCode() : 0); result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0); result = 29 * result + otherSimpleFields.hashCode(); + result = 29 * result + (photoBinval != null ? photoBinval.hashCode() : 0); return result; } @@ -716,6 +732,7 @@ public class VCard extends IQ { appendOrganization(); appendGenericFields(); + appendPhoto(); appendEmail(emailWork, "WORK"); appendEmail(emailHome, "HOME"); @@ -727,6 +744,17 @@ public class VCard extends IQ { appendAddress(homeAddr, "HOME"); } + private void appendPhoto() { + if (photoBinval == null) + return; + + appendTag("PHOTO", true, new ContentBuilder() { + public void addTagContent() { + appendTag("BINVAL", photoBinval); // No need to escape photoBinval, as it's already Base64 encoded + appendTag("TYPE", StringUtils.escapeForXML(photoMimeType)); + } + }); + } private void appendEmail(final String email, final String type) { if (email != null) { appendTag("EMAIL", true, new ContentBuilder() { diff --git a/source/org/jivesoftware/smackx/provider/VCardProvider.java b/source/org/jivesoftware/smackx/provider/VCardProvider.java index 3f07af1fc..8fa04211d 100644 --- a/source/org/jivesoftware/smackx/provider/VCardProvider.java +++ b/source/org/jivesoftware/smackx/provider/VCardProvider.java @@ -114,7 +114,7 @@ public class VCardProvider implements IQProvider { vCard.setFirstName(getTagContents("GIVEN")); vCard.setLastName(getTagContents("FAMILY")); vCard.setMiddleName(getTagContents("MIDDLE")); - vCard.setEncodedImage(getTagContents("BINVAL")); + setupPhoto(); setupEmails(); @@ -127,6 +127,41 @@ public class VCardProvider implements IQProvider { setupAddresses(); } + private void setupPhoto() { + String binval = null; + String mimetype = null; + + NodeList photo = document.getElementsByTagName("PHOTO"); + if (photo.getLength() != 1) + return; + + Node photoNode = photo.item(0); + NodeList childNodes = photoNode.getChildNodes(); + + int childNodeCount = childNodes.getLength(); + List nodes = new ArrayList(childNodeCount); + for (int i = 0; i < childNodeCount; i++) + nodes.add(childNodes.item(i)); + + String name = null; + String value = null; + for (Node n : nodes) { + name = n.getNodeName(); + value = n.getTextContent(); + if (name.equals("BINVAL")) { + binval = value; + } + else if (name.equals("TYPE")) { + mimetype = value; + } + } + + if (binval == null || mimetype == null) + return; + + vCard.setAvatar(binval, mimetype); + } + private void setupEmails() { NodeList nodes = document.getElementsByTagName("USERID"); if (nodes == null) return; diff --git a/test-unit/org/jivesoftware/smackx/VCardUnitTest.java b/test-unit/org/jivesoftware/smackx/VCardUnitTest.java new file mode 100644 index 000000000..17adf75a7 --- /dev/null +++ b/test-unit/org/jivesoftware/smackx/VCardUnitTest.java @@ -0,0 +1,107 @@ +package org.jivesoftware.smackx; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.VCard; +import org.jivesoftware.smackx.provider.VCardProvider; + +public class VCardUnitTest { + + @Test + public void testNoWorkHomeSpecifier_EMAIL() throws Throwable { + VCard card = VCardProvider.createVCardFromXML("foo@fee.www.bar"); + assertEquals("foo@fee.www.bar", card.getEmailHome()); + } + + @Test + public void testNoWorkHomeSpecifier_TEL() throws Throwable { + VCard card = VCardProvider.createVCardFromXML("3443233"); + assertEquals("3443233", card.getPhoneWork("FAX")); + } + + @Test + public void testNoWorkHomeSpecifier_ADDR() throws Throwable { + VCard card = VCardProvider.createVCardFromXML("Some streetddss"); + assertEquals("Some street", card.getAddressFieldWork("STREET")); + assertEquals("ddss", card.getAddressFieldWork("FF")); + } + + @Test + public void testFN() throws Throwable { + VCard card = VCardProvider.createVCardFromXML("kir max"); + assertEquals("kir max", card.getField("FN")); + // assertEquals("kir max", card.getFullName()); + } + + private final static String MIME_TYPE = "testtype"; + private final static String VCARD_XML = "" + getAvatarEncoded() + "" + MIME_TYPE + ""; + @Test + public void testPhoto() throws Throwable { + VCard vc = VCardProvider.createVCardFromXML(VCARD_XML); + byte[] avatar = vc.getAvatar(); + String mimeType = vc.getAvatarMimeType(); + assertEquals(mimeType, MIME_TYPE); + + byte[] expectedAvatar = getAvatarBinary(); + assertTrue(Arrays.equals(avatar, expectedAvatar)); + } + + public static byte[] getAvatarBinary() { + return StringUtils.decodeBase64(getAvatarEncoded()); + } + private static String getAvatarEncoded() { + return "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"; + } +} diff --git a/test/org/jivesoftware/smack/test/SmackTestCase.java b/test/org/jivesoftware/smack/test/SmackTestCase.java index fa6bb2d20..48e017743 100644 --- a/test/org/jivesoftware/smack/test/SmackTestCase.java +++ b/test/org/jivesoftware/smack/test/SmackTestCase.java @@ -272,8 +272,6 @@ public abstract class SmackTestCase extends TestCase { try { getConnection(i).login(currentUser, currentPassword, "Smack"); } catch (XMPPException e) { - e.printStackTrace(); - // Create the test accounts if (!getConnection(0).getAccountManager().supportsAccountCreation()) fail("Server does not support account creation"); diff --git a/test/org/jivesoftware/smackx/VCardTest.java b/test/org/jivesoftware/smackx/VCardTest.java index 08eb8b5d3..ac32a2494 100644 --- a/test/org/jivesoftware/smackx/VCardTest.java +++ b/test/org/jivesoftware/smackx/VCardTest.java @@ -23,7 +23,6 @@ import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.test.SmackTestCase; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.packet.VCard; -import org.jivesoftware.smackx.provider.VCardProvider; /** * Created by IntelliJ IDEA. @@ -81,36 +80,12 @@ public class VCardTest extends SmackTestCase { //assertEquals("Should load another user's VCard successfully", origVCard.toString(), loaded.toString()); assertEquals("Should load another user's VCard successfully", origVCard, loaded); - - } - - public void testNoWorkHomeSpecifier_EMAIL() throws Throwable { - VCard card = VCardProvider.createVCardFromXML("foo@fee.www.bar"); - assertEquals("foo@fee.www.bar", card.getEmailHome()); - } - - public void testNoWorkHomeSpecifier_TEL() throws Throwable { - VCard card = VCardProvider.createVCardFromXML("3443233"); - assertEquals("3443233", card.getPhoneWork("FAX")); - } - - public void testNoWorkHomeSpecifier_ADDR() throws Throwable { - VCard card = VCardProvider.createVCardFromXML("Some streetddss"); - assertEquals("Some street", card.getAddressFieldWork("STREET")); - assertEquals("ddss", card.getAddressFieldWork("FF")); - } - - public void testFN() throws Throwable { - VCard card = VCardProvider.createVCardFromXML("kir max"); - assertEquals("kir max", card.getField("FN")); - // assertEquals("kir max", card.getFullName()); } public void testBinaryAvatar() throws Throwable { VCard card = new VCard(); card.setAvatar(getAvatarBinary()); card.save(getConnection(0)); - System.out.println(card.getChildElementXML()); VCard loaded = new VCard(); try { @@ -120,8 +95,10 @@ public class VCardTest extends SmackTestCase { e.printStackTrace(); fail(e.getMessage()); } - System.out.println(StringUtils.encodeBase64(loaded.getAvatar())); - assertEquals("Should load own Avatar successfully", card.getAvatar(), loaded.getAvatar()); + + byte[] initialAvatar = card.getAvatar(); + byte[] loadedAvatar = loaded.getAvatar(); + assertEquals("Should load own Avatar successfully", initialAvatar, loadedAvatar); loaded = new VCard(); try { @@ -135,11 +112,11 @@ public class VCardTest extends SmackTestCase { assertEquals("Should load avatar successfully", card.getAvatar(), loaded.getAvatar()); } - private byte[] getAvatarBinary() { - return StringUtils.decodeBase64(getAvatarEnconded()); + public static byte[] getAvatarBinary() { + return StringUtils.decodeBase64(getAvatarEncoded()); } - private String getAvatarEnconded() { + public static String getAvatarEncoded() { return "/9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD/2wBDAAUDBAQEAwUE\n" + "BAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/\n" + "2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e\n" + From 37f6bf12da1f5c03712a8a6a8ce0aeba0420b2c4 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 12 May 2013 14:51:41 +0000 Subject: [PATCH 12/24] SMACK-438 Avoid NPE when the weak reference is null. Add InvitationsMonitor as strong reference within getInvitationsMonitor and return it within the block so it can't get gc'ed between put() and get(). git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13648 b35dd754-fafc-0310-a699-88a17e54d16e --- source/org/jivesoftware/smackx/muc/MultiUserChat.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/org/jivesoftware/smackx/muc/MultiUserChat.java b/source/org/jivesoftware/smackx/muc/MultiUserChat.java index e0368028d..4800011fd 100644 --- a/source/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/source/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -2590,11 +2590,13 @@ public class MultiUserChat { */ public static InvitationsMonitor getInvitationsMonitor(Connection conn) { synchronized (monitors) { - if (!monitors.containsKey(conn)) { + if (!monitors.containsKey(conn) || monitors.get(conn).get() == null) { // We need to use a WeakReference because the monitor references the // connection and this could prevent the GC from collecting the monitor // when no other object references the monitor - monitors.put(conn, new WeakReference(new InvitationsMonitor(conn))); + InvitationsMonitor ivm = new InvitationsMonitor(conn); + monitors.put(conn, new WeakReference(ivm)); + return ivm; } // Return the InvitationsMonitor that monitors the connection return monitors.get(conn).get(); From 2195d66e4d322c1c329710e56af3c1a85beee08e Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 12 May 2013 14:51:50 +0000 Subject: [PATCH 13/24] SMACK-439 git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13649 b35dd754-fafc-0310-a699-88a17e54d16e --- source/org/jivesoftware/smackx/muc/MultiUserChat.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/org/jivesoftware/smackx/muc/MultiUserChat.java b/source/org/jivesoftware/smackx/muc/MultiUserChat.java index 4800011fd..d657ee711 100644 --- a/source/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/source/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -2573,9 +2573,15 @@ public class MultiUserChat { private static class InvitationsMonitor implements ConnectionListener { // We use a WeakHashMap so that the GC can collect the monitor when the // connection is no longer referenced by any object. + // Note that when the InvitationsMonitor is used, i.e. when there are InvitationListeners, it will add a + // PacketListener to the Connection and therefore a strong reference from the Connection to the + // InvitationsMonior will exists, preventing it from beeing gc'ed. After the last InvitationListener is gone, + // the PacketListener will get removed (cancel()) allowing the garbage collection of the InvitationsMonitor + // instance. private final static Map> monitors = new WeakHashMap>(); + // We don't use a synchronized List here because it would break the semantic of (add|remove)InvitationListener private final List invitationsListeners = new ArrayList(); private Connection connection; From 4cd53e44195c2f108ee4ef70873a57dae3caca2c Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 22 Jun 2013 17:00:27 +0000 Subject: [PATCH 14/24] SMACK-425 Introduced smack.parsing.ParsingExceptionCallback, a callback invoked when a exception is thrown while parsing a stanza. Smack is now able to either rethrow the exception ulitmatly causing a disconnect *or* log/ignore the exception and resume parsing after the faulty stanza. Conflicts: source/org/jivesoftware/smack/SmackConfiguration.java git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13685 b35dd754-fafc-0310-a699-88a17e54d16e --- .../eclipse/settings/org.eclipse.jdt.ui.prefs | 3 +- .../org/jivesoftware/smack/PacketReader.java | 45 ++++++++++- .../smack/SmackConfiguration.java | 45 +++++++++++ .../jivesoftware/smack/XMPPConnection.java | 22 ++++++ .../smack/parsing/LogException.java | 51 +++++++++++++ .../parsing/ParsingExceptionCallback.java | 76 +++++++++++++++++++ .../smack/parsing/ThrowException.java | 48 ++++++++++++ .../smack/parsing/UnparsedIQ.java | 62 +++++++++++++++ .../smack/parsing/UnparsedMessage.java | 57 ++++++++++++++ .../smack/parsing/UnparsedPresence.java | 61 +++++++++++++++ .../smack/util/PacketParserUtils.java | 9 ++- .../smack/parsing/ParsingExceptionTest.java | 70 +++++++++++++++++ 12 files changed, 541 insertions(+), 8 deletions(-) create mode 100644 source/org/jivesoftware/smack/parsing/LogException.java create mode 100644 source/org/jivesoftware/smack/parsing/ParsingExceptionCallback.java create mode 100644 source/org/jivesoftware/smack/parsing/ThrowException.java create mode 100644 source/org/jivesoftware/smack/parsing/UnparsedIQ.java create mode 100644 source/org/jivesoftware/smack/parsing/UnparsedMessage.java create mode 100644 source/org/jivesoftware/smack/parsing/UnparsedPresence.java create mode 100644 test-unit/org/jivesoftware/smack/parsing/ParsingExceptionTest.java diff --git a/build/eclipse/settings/org.eclipse.jdt.ui.prefs b/build/eclipse/settings/org.eclipse.jdt.ui.prefs index fcdbc532c..dde05c864 100644 --- a/build/eclipse/settings/org.eclipse.jdt.ui.prefs +++ b/build/eclipse/settings/org.eclipse.jdt.ui.prefs @@ -1,4 +1,3 @@ -#Tue Jan 29 23:27:16 CET 2013 eclipse.preferences.version=1 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true formatter_profile=_ignite @@ -37,7 +36,7 @@ sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class= sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true -sp_cleanup.remove_trailing_whitespaces=false +sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false sp_cleanup.remove_unnecessary_casts=true diff --git a/source/org/jivesoftware/smack/PacketReader.java b/source/org/jivesoftware/smack/PacketReader.java index 826360cf3..05f8de99c 100644 --- a/source/org/jivesoftware/smack/PacketReader.java +++ b/source/org/jivesoftware/smack/PacketReader.java @@ -22,6 +22,10 @@ package org.jivesoftware.smack; import org.jivesoftware.smack.Connection.ListenerWrapper; import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.parsing.ParsingExceptionCallback; +import org.jivesoftware.smack.parsing.UnparsedIQ; +import org.jivesoftware.smack.parsing.UnparsedMessage; +import org.jivesoftware.smack.parsing.UnparsedPresence; import org.jivesoftware.smack.sasl.SASLMechanism.Challenge; import org.jivesoftware.smack.sasl.SASLMechanism.Failure; import org.jivesoftware.smack.sasl.SASLMechanism.Success; @@ -176,14 +180,49 @@ class PacketReader { int eventType = parser.getEventType(); do { if (eventType == XmlPullParser.START_TAG) { + int parserDepth = parser.getDepth(); + ParsingExceptionCallback callback = connection.getParsingExceptionCallback(); if (parser.getName().equals("message")) { - processPacket(PacketParserUtils.parseMessage(parser)); + Packet packet; + try { + packet = PacketParserUtils.parseMessage(parser); + } catch (Exception e) { + String content = PacketParserUtils.parseContentDepth(parser, parserDepth); + UnparsedMessage message = new UnparsedMessage(content, e); + if (callback != null) { + callback.messageParsingException(e, message); + } + continue; + } + processPacket(packet); } else if (parser.getName().equals("iq")) { - processPacket(PacketParserUtils.parseIQ(parser, connection)); + IQ iq; + try { + iq = PacketParserUtils.parseIQ(parser, connection); + } catch (Exception e) { + String content = PacketParserUtils.parseContentDepth(parser, parserDepth); + UnparsedIQ uniq = new UnparsedIQ(content, e); + if (callback != null) { + callback.iqParsingException(e, uniq); + } + continue; + } + processPacket(iq); } else if (parser.getName().equals("presence")) { - processPacket(PacketParserUtils.parsePresence(parser)); + Presence presence; + try { + presence = PacketParserUtils.parsePresence(parser); + } catch (Exception e) { + String content = PacketParserUtils.parseContentDepth(parser, parserDepth); + UnparsedPresence unpresence = new UnparsedPresence(content, e); + if (callback != null) { + callback.presenceParsingException(e, unpresence); + } + continue; + } + processPacket(presence); } // We found an opening stream. Record information about it, then notify // the connectionID lock so that the packet reader startup can finish. diff --git a/source/org/jivesoftware/smack/SmackConfiguration.java b/source/org/jivesoftware/smack/SmackConfiguration.java index 3ff84a0ab..cd50984b7 100644 --- a/source/org/jivesoftware/smack/SmackConfiguration.java +++ b/source/org/jivesoftware/smack/SmackConfiguration.java @@ -28,6 +28,8 @@ import java.util.Enumeration; import java.util.List; import java.util.Vector; +import org.jivesoftware.smack.parsing.ParsingExceptionCallback; +import org.jivesoftware.smack.parsing.ThrowException; import org.xmlpull.mxp1.MXParser; import org.xmlpull.v1.XmlPullParser; @@ -58,6 +60,17 @@ public final class SmackConfiguration { private static int localSocks5ProxyPort = 7777; private static int packetCollectorSize = 5000; + /** + * defaultPingInterval (in seconds) + */ + private static int defaultPingInterval = 1800; // 30 min (30*60) + + /** + * The default parsing exception callback is {@link ThrowException} which will + * throw an exception and therefore disconnect the active connection. + */ + private static ParsingExceptionCallback defaultCallback = new ThrowException(); + /** * This automatically enables EntityCaps for new connections if it is set to true */ @@ -331,6 +344,38 @@ public final class SmackConfiguration { return autoEnableEntityCaps; } +<<<<<<< HEAD +======= + /** + * Set if Entity Caps are enabled or disabled for every new connection + * + * @param true if Entity Caps should be auto enabled, false if not + */ + public static void setAutoEnableEntityCaps(boolean b) { + autoEnableEntityCaps = b; + } + + /** + * Set the default parsing exception callback for all newly created connections + * + * @param callback + * @see ParsingExceptionCallback + */ + public static void setDefaultParsingExceptionCallback(ParsingExceptionCallback callback) { + defaultCallback = callback; + } + + /** + * Returns the default parsing exception callback + * + * @return the default parsing exception callback + * @see ParsingExceptionCallback + */ + public static ParsingExceptionCallback getDefaultParsingExceptionCallback() { + return defaultCallback; + } + +>>>>>>> SMACK-425 Introduced smack.parsing.ParsingExceptionCallback, a callback invoked when a exception is thrown while parsing a stanza. Smack is now able to either rethrow the exception ulitmatly causing a disconnect *or* log/ignore the exception and resume parsing after the faulty stanza. private static void parseClassToLoad(XmlPullParser parser) throws Exception { String className = parser.nextText(); // Attempt to load the class so that the class can get initialized diff --git a/source/org/jivesoftware/smack/XMPPConnection.java b/source/org/jivesoftware/smack/XMPPConnection.java index 2c75e208a..33b1d961a 100644 --- a/source/org/jivesoftware/smack/XMPPConnection.java +++ b/source/org/jivesoftware/smack/XMPPConnection.java @@ -25,6 +25,7 @@ import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.parsing.ParsingExceptionCallback; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.dns.HostAddress; @@ -89,6 +90,8 @@ public class XMPPConnection extends Connection { private boolean anonymous = false; private boolean usingTLS = false; + private ParsingExceptionCallback parsingExceptionCallback = SmackConfiguration.getDefaultParsingExceptionCallback(); + PacketWriter packetWriter; PacketReader packetReader; @@ -202,6 +205,25 @@ public class XMPPConnection extends Connection { return user; } + /** + * Install a parsing exception callback, which will be invoked once an exception is encountered while parsing a + * stanza + * + * @param callback the callback to install + */ + public void setParsingExceptionCallback(ParsingExceptionCallback callback) { + parsingExceptionCallback = callback; + } + + /** + * Get the current active parsing exception callback. + * + * @return the active exception callback or null if there is none + */ + public ParsingExceptionCallback getParsingExceptionCallback() { + return parsingExceptionCallback; + } + @Override public synchronized void login(String username, String password, String resource) throws XMPPException { if (!isConnected()) { diff --git a/source/org/jivesoftware/smack/parsing/LogException.java b/source/org/jivesoftware/smack/parsing/LogException.java new file mode 100644 index 000000000..dd3031e9a --- /dev/null +++ b/source/org/jivesoftware/smack/parsing/LogException.java @@ -0,0 +1,51 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2013 Florian Schmaus. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smack.parsing; + +/** + * Simple parsing exception callback that only logs the encountered parsing exception to stderr. + * + * @author Florian Schmaus + * + */ +public class LogException extends ParsingExceptionCallback { + + @Override + public void messageParsingException(Exception e, UnparsedMessage message) throws Exception { + System.err.print("Smack message parsing exception: " + e.getMessage()); + e.printStackTrace(); + System.err.println("Unparsed content: " + message.getContent()); + } + + @Override + public void iqParsingException(Exception e, UnparsedIQ iq) throws Exception { + System.err.print("Smack iq parsing exception: " + e.getMessage()); + e.printStackTrace(); + System.err.println("Unparsed content: " + iq.getContent()); + } + + @Override + public void presenceParsingException(Exception e, UnparsedPresence presence) throws Exception { + System.err.print("Smack presence parsing exception: " + e.getMessage()); + e.printStackTrace(); + System.err.println("Unparsed content: " + presence.getContent()); + } +} diff --git a/source/org/jivesoftware/smack/parsing/ParsingExceptionCallback.java b/source/org/jivesoftware/smack/parsing/ParsingExceptionCallback.java new file mode 100644 index 000000000..3c57d089c --- /dev/null +++ b/source/org/jivesoftware/smack/parsing/ParsingExceptionCallback.java @@ -0,0 +1,76 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2013 Florian Schmaus. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smack.parsing; + +/** + * Base class to receive parsing exceptions. + * + * If this class is used as callback, then Smack will silently ignore the stanza that caused the parsing exception and + * place the parser after the faulty stanza. + * + * Subclasses may or may not override certain methods of this class. Each of these methods will receive the exception + * that caused the parsing error and an instance of an Unparsed Packet type. The latter can be used to inspect the + * stanza that caused the parsing error by using the getContent() (for example {@link UnparsedIQ#getContent()}) + * method. + * + * Smack provides 2 predefined ParsingExceptionCallback's: {@link LogException} and {@link ThrowException}. + * + * @author Florian Schmaus + * + */ +public abstract class ParsingExceptionCallback { + + /** + * Called when parsing an message stanza caused an exception. + * + * @param e + * the exception thrown while parsing the message stanza + * @param message + * the raw message stanza data that caused the exception + * @throws Exception + */ + public void messageParsingException(Exception e, UnparsedMessage message) throws Exception { + } + + /** + * Called when parsing an IQ stanza caused an exception. + * + * @param e + * the exception thrown while parsing the iq stanza + * @param iq + * the raw iq stanza data that caused the exception + * @throws Exception + */ + public void iqParsingException(Exception e, UnparsedIQ iq) throws Exception { + } + + /** + * Called when parsing a presence stanza caused an exception. + * + * @param e + * the exception thrown while parsing the presence stanza + * @param presence + * the raw presence stanza data that caused the exception + * @throws Exception + */ + public void presenceParsingException(Exception e, UnparsedPresence presence) throws Exception { + } +} diff --git a/source/org/jivesoftware/smack/parsing/ThrowException.java b/source/org/jivesoftware/smack/parsing/ThrowException.java new file mode 100644 index 000000000..400950346 --- /dev/null +++ b/source/org/jivesoftware/smack/parsing/ThrowException.java @@ -0,0 +1,48 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2013 Florian Schmaus. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smack.parsing; + +import org.jivesoftware.smack.ConnectionListener; + +/** + * Parsing exception callback class that simply throws the encountered parsing exception. This usually leads to an + * {@link ConnectionListener#connectionClosedOnError(Exception)} disconnect of the connection. + * + * @author Florian Schmaus + * + */ +public class ThrowException extends ParsingExceptionCallback { + + @Override + public void messageParsingException(Exception e, UnparsedMessage message) throws Exception { + throw e; + } + + @Override + public void iqParsingException(Exception e, UnparsedIQ iq) throws Exception { + throw e; + } + + @Override + public void presenceParsingException(Exception e, UnparsedPresence presence) throws Exception { + throw e; + } +} diff --git a/source/org/jivesoftware/smack/parsing/UnparsedIQ.java b/source/org/jivesoftware/smack/parsing/UnparsedIQ.java new file mode 100644 index 000000000..a9ad6a6d5 --- /dev/null +++ b/source/org/jivesoftware/smack/parsing/UnparsedIQ.java @@ -0,0 +1,62 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2013 Florian Schmaus. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smack.parsing; + +import org.jivesoftware.smack.packet.IQ; + +/** + * Representation of an unparsed IQ stanza. + * + * @author Florian Schmaus + * + */ +public class UnparsedIQ extends IQ { + private final String content; + private final Exception e; + + public UnparsedIQ(final String content, final Exception e) { + this.content = content; + this.e = e; + } + + /** + * + * @return the exception that caused the parser to fail + */ + public Exception getException() { + return e; + } + + /** + * Retrieve the raw stanza data + * + * @return the raw stanza data + */ + public String getContent() { + return content; + } + + @Override + public String getChildElementXML() { + return null; + } + +} diff --git a/source/org/jivesoftware/smack/parsing/UnparsedMessage.java b/source/org/jivesoftware/smack/parsing/UnparsedMessage.java new file mode 100644 index 000000000..e23773cf2 --- /dev/null +++ b/source/org/jivesoftware/smack/parsing/UnparsedMessage.java @@ -0,0 +1,57 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2013 Florian Schmaus. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smack.parsing; + +import org.jivesoftware.smack.packet.Message; + +/** + * Representation of an unparsed IQ stanza. + * + * @author Florian Schmaus + * + */ +public class UnparsedMessage extends Message { + private final String content; + private final Exception e; + + public UnparsedMessage(final String content, final Exception e) { + this.content = content; + this.e = e; + } + + /** + * + * @return the exception that caused the parser to fail + */ + public Exception getException() { + return e; + } + + /** + * Retrieve the raw stanza data + * + * @return the raw stanza data + */ + public String getContent() { + return content; + } + +} diff --git a/source/org/jivesoftware/smack/parsing/UnparsedPresence.java b/source/org/jivesoftware/smack/parsing/UnparsedPresence.java new file mode 100644 index 000000000..a14e0ae49 --- /dev/null +++ b/source/org/jivesoftware/smack/parsing/UnparsedPresence.java @@ -0,0 +1,61 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2013 Florian Schmaus. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smack.parsing; + +import org.jivesoftware.smack.packet.Presence; + +/** + * Representation of an unparsed IQ stanza. + * + * @author Florian Schmaus + * + */ +public class UnparsedPresence extends Presence { + private String content; + private Exception e; + + public UnparsedPresence(Type type) { + super(type); + } + + public UnparsedPresence(final String content, final Exception e) { + super(Presence.Type.error); + this.content = content; + this.e = e; + } + + /** + * + * @return the exception that caused the parser to fail + */ + public Exception getException() { + return e; + } + + /** + * Retrieve the raw stanza data + * + * @return the raw stanza data + */ + public String getContent() { + return content; + } +} diff --git a/source/org/jivesoftware/smack/util/PacketParserUtils.java b/source/org/jivesoftware/smack/util/PacketParserUtils.java index a574be3db..57d539c59 100644 --- a/source/org/jivesoftware/smack/util/PacketParserUtils.java +++ b/source/org/jivesoftware/smack/util/PacketParserUtils.java @@ -171,10 +171,13 @@ public class PacketParserUtils { */ private static String parseContent(XmlPullParser parser) throws XmlPullParserException, IOException { - StringBuffer content = new StringBuffer(); int parserDepth = parser.getDepth(); - while (!(parser.next() == XmlPullParser.END_TAG && parser - .getDepth() == parserDepth)) { + return parseContentDepth(parser, parserDepth); + } + + public static String parseContentDepth(XmlPullParser parser, int depth) throws XmlPullParserException, IOException { + StringBuffer content = new StringBuffer(); + while (!(parser.next() == XmlPullParser.END_TAG && parser.getDepth() == depth)) { content.append(parser.getText()); } return content.toString(); diff --git a/test-unit/org/jivesoftware/smack/parsing/ParsingExceptionTest.java b/test-unit/org/jivesoftware/smack/parsing/ParsingExceptionTest.java new file mode 100644 index 000000000..fd674ae0c --- /dev/null +++ b/test-unit/org/jivesoftware/smack/parsing/ParsingExceptionTest.java @@ -0,0 +1,70 @@ +package org.jivesoftware.smack.parsing; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.jivesoftware.smack.TestUtils; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; + +public class ParsingExceptionTest { + private final static ProviderManager PM = ProviderManager.getInstance(); + + private final static String EXTENSION2 = + "" + + "" + + "" + + "" + + "" + + ""; + + @Before + public void init() { + PM.addExtensionProvider(ThrowException.ELEMENT, ThrowException.NAMESPACE, new ThrowException()); + } + + @After + public void tini() { + PM.removeExtensionProvider(ThrowException.ELEMENT, ThrowException.NAMESPACE); + } + + @Test + public void consumeUnparsedInput() throws Exception { + XmlPullParser parser = TestUtils.getMessageParser( + "" + + "<" + ThrowException.ELEMENT + " xmlns='" + ThrowException.NAMESPACE + "'>" + + "" + + "" + + "" + + EXTENSION2 + + ""); + int parserDepth = parser.getDepth(); + String content = null; + try { + PacketParserUtils.parseMessage(parser); + } catch (Exception e) { + content = PacketParserUtils.parseContentDepth(parser, parserDepth); + } + assertNotNull(content); + assertEquals(content, "" + "" + EXTENSION2); + + } + + static class ThrowException implements PacketExtensionProvider { + public static final String ELEMENT = "exception"; + public static final String NAMESPACE = "http://smack.jivesoftware.org/exception"; + + @Override + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + throw new XMPPException("Test Exception"); + } + + } +} From c7d468697f158311eb583bc9925dc849da45dff8 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 22 Jun 2013 17:00:40 +0000 Subject: [PATCH 15/24] SMACK-431 Entity Capabilities are now enabled as default for new connections. Added an extensions documentation html page. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13686 b35dd754-fafc-0310-a699-88a17e54d16e --- documentation/extensions/caps.html | 63 +++++++++++++++++++ documentation/extensions/toc.html | 3 +- .../smack/SmackConfiguration.java | 2 +- 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 documentation/extensions/caps.html diff --git a/documentation/extensions/caps.html b/documentation/extensions/caps.html new file mode 100644 index 000000000..eb94e4bc1 --- /dev/null +++ b/documentation/extensions/caps.html @@ -0,0 +1,63 @@ + + +Entity Capabilities + + + + + +

+This section details the usage of Smacks implementation of Entity Capabilities. +

+XEP related: XEP-0115: Entity Capabilities + +
+ +Description

+ +Entity Capabilities is an extension to the Service Discovery (XEP-0030), which, in order to minimize network impact, caches the capabilities information of XMPP entities. + +

+ +Usage

+ +Entity Capabilities work silenty in background when enabled. If the remote XMPP entity does not support XEP-0115 but XEP-0030 then XEP-0030 mechanisms are transparently used. You can enable or disable Entity Capabilities by using EntityCapsManager.
+ +The cache used by Smack for Entity Capabilities is non-persistent as default. That is, the cache only uses memory. But it is also possible to set a persistent Entity Capabilities cache, which is recommended. +

+ +Examples

+ +Enable Entity Capabilities +
+

+
+      // Get an instance of entity caps manager for the specified connection
+      Entity CapabilitiesManager mgr = new EntityCapsManager.getInstanceFor(con);
+
+      // Enable entity capabilities
+      mgr.enableEntityCaps();
+
+
+ +Configure a persistent cache for Entity Capabilities +
+
+
+      // Get an instance of entity caps manager for the specified connection
+      Entity CapabilitiesManager mgr = new EntityCapsManager.getInstanceFor(con);
+
+      // Create an cache, see smackx.entitycaps.cache for pre-defined cache implementations
+      EntityCapsPersistentCache cache = new SimpleDirectoryPersistentCache(new File("/foo/cachedir"));
+
+      // Set the cache
+      mgr.setPersistentCache(cache);
+
+
+

+ +
+ + + + diff --git a/documentation/extensions/toc.html b/documentation/extensions/toc.html index f5d8ac778..98113ae58 100644 --- a/documentation/extensions/toc.html +++ b/documentation/extensions/toc.html @@ -22,7 +22,8 @@ Service Discovery
File Transfer
PubSub
+Entity Capabilities

- \ No newline at end of file + diff --git a/source/org/jivesoftware/smack/SmackConfiguration.java b/source/org/jivesoftware/smack/SmackConfiguration.java index cd50984b7..7bf3e0f31 100644 --- a/source/org/jivesoftware/smack/SmackConfiguration.java +++ b/source/org/jivesoftware/smack/SmackConfiguration.java @@ -74,7 +74,7 @@ public final class SmackConfiguration { /** * This automatically enables EntityCaps for new connections if it is set to true */ - private static boolean autoEnableEntityCaps = false; + private static boolean autoEnableEntityCaps = true; private SmackConfiguration() { } From 6ed6f862937b53c8764a0c03e1e7b22699ec0da3 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 22 Jun 2013 17:00:53 +0000 Subject: [PATCH 16/24] SMACK-405 Removed redundant code in XMPPConnection.shutdown() git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13687 b35dd754-fafc-0310-a699-88a17e54d16e --- .../jivesoftware/smack/XMPPConnection.java | 42 ++++--------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/source/org/jivesoftware/smack/XMPPConnection.java b/source/org/jivesoftware/smack/XMPPConnection.java index 33b1d961a..ea091b730 100644 --- a/source/org/jivesoftware/smack/XMPPConnection.java +++ b/source/org/jivesoftware/smack/XMPPConnection.java @@ -443,48 +443,22 @@ public class XMPPConnection extends Connection { // Ignore. } - // Set socketClosed to true. This will cause the PacketReader - // and PacketWriter to ingore any Exceptions that are thrown - // because of a read/write from/to a closed stream. - // It is *important* that this is done before socket.close()! + // Set socketClosed to true. This will cause the PacketReader + // and PacketWriter to ignore any Exceptions that are thrown + // because of a read/write from/to a closed stream. + // It is *important* that this is done before socket.close()! socketClosed = true; try { socket.close(); } catch (Exception e) { e.printStackTrace(); } - // In most cases the close() should be successful, so set - // connected to false here. + // In most cases the close() should be successful, so set + // connected to false here. connected = false; - // Close down the readers and writers. - if (reader != null) { - try { - // Should already be closed by the previous - // socket.close(). But just in case do it explicitly. - reader.close(); - } - catch (Throwable ignore) { /* ignore */ } - reader = null; - } - if (writer != null) { - try { - // Should already be closed by the previous - // socket.close(). But just in case do it explicitly. - writer.close(); - } - catch (Throwable ignore) { /* ignore */ } - writer = null; - } - - // Make sure that the socket is really closed - try { - // Does nothing if the socket is already closed - socket.close(); - } - catch (Exception e) { - // Ignore. - } + reader = null; + writer = null; saslAuthentication.init(); } From 2ea5ee3b26e55d632d2b803b810e1c1d00ae6f33 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 24 Jun 2013 07:25:20 +0000 Subject: [PATCH 17/24] SMACK-425 fix merge error git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13691 b35dd754-fafc-0310-a699-88a17e54d16e --- .../org/jivesoftware/smack/SmackConfiguration.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/source/org/jivesoftware/smack/SmackConfiguration.java b/source/org/jivesoftware/smack/SmackConfiguration.java index 7bf3e0f31..92f804a4c 100644 --- a/source/org/jivesoftware/smack/SmackConfiguration.java +++ b/source/org/jivesoftware/smack/SmackConfiguration.java @@ -344,17 +344,6 @@ public final class SmackConfiguration { return autoEnableEntityCaps; } -<<<<<<< HEAD -======= - /** - * Set if Entity Caps are enabled or disabled for every new connection - * - * @param true if Entity Caps should be auto enabled, false if not - */ - public static void setAutoEnableEntityCaps(boolean b) { - autoEnableEntityCaps = b; - } - /** * Set the default parsing exception callback for all newly created connections * @@ -375,7 +364,6 @@ public final class SmackConfiguration { return defaultCallback; } ->>>>>>> SMACK-425 Introduced smack.parsing.ParsingExceptionCallback, a callback invoked when a exception is thrown while parsing a stanza. Smack is now able to either rethrow the exception ulitmatly causing a disconnect *or* log/ignore the exception and resume parsing after the faulty stanza. private static void parseClassToLoad(XmlPullParser parser) throws Exception { String className = parser.nextText(); // Attempt to load the class so that the class can get initialized From c462810080ff3497cd965b0fcab6e263ea0b1f4f Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 25 Jun 2013 06:40:30 +0000 Subject: [PATCH 18/24] SMACK-448 Java7ZlibInputOutputStream: Use correct Deflater compression level git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13694 b35dd754-fafc-0310-a699-88a17e54d16e --- .../smack/compression/Java7ZlibInputOutputStream.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/org/jivesoftware/smack/compression/Java7ZlibInputOutputStream.java b/source/org/jivesoftware/smack/compression/Java7ZlibInputOutputStream.java index dc504517b..ffa65d542 100644 --- a/source/org/jivesoftware/smack/compression/Java7ZlibInputOutputStream.java +++ b/source/org/jivesoftware/smack/compression/Java7ZlibInputOutputStream.java @@ -38,7 +38,7 @@ import java.util.zip.InflaterInputStream; public class Java7ZlibInputOutputStream extends XMPPInputOutputStream { private final static Method method; private final static boolean supported; - private final static int compressionLevel = Deflater.DEFAULT_STRATEGY; + private final static int compressionLevel = Deflater.DEFAULT_COMPRESSION; static { Method m = null; From 5f793d4f4426d480238782e40b9c15c3593f2c8a Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 22 Oct 2013 14:43:13 +0000 Subject: [PATCH 19/24] SMACK-431 Reworded Entity Caps documentation, added missing frame link git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13782 b35dd754-fafc-0310-a699-88a17e54d16e --- documentation/extensions/caps.html | 2 +- documentation/extensions/intro.html | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/documentation/extensions/caps.html b/documentation/extensions/caps.html index eb94e4bc1..71e0e2f77 100644 --- a/documentation/extensions/caps.html +++ b/documentation/extensions/caps.html @@ -15,7 +15,7 @@ This section details the usage of Smacks implementation of Entity Capabilities. Description

-Entity Capabilities is an extension to the Service Discovery (XEP-0030), which, in order to minimize network impact, caches the capabilities information of XMPP entities. +Entity Capabilities is a XMPP Protocol extension, which, in order to minimize network impact, caches the capabilities of XMPP entities. Those capabilities are determined with the help of the Service Discovery Protocol (XEP-0030).

diff --git a/documentation/extensions/intro.html b/documentation/extensions/intro.html index 03dae469c..227c2a14f 100644 --- a/documentation/extensions/intro.html +++ b/documentation/extensions/intro.html @@ -75,6 +75,11 @@ XEP-0060 Generic publish and subscribe functionality. + + Entity Capabilities + XEP-0115 + Generic publish and subscribe functionality. + - \ No newline at end of file + From c4014b8ba958c733156ca069b5866adc8a3d50fa Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 22 Oct 2013 14:43:23 +0000 Subject: [PATCH 20/24] SMACK-441 ServiceDiscoveryManager identities should be non-static and kept in a Set to allow multiple identities as per XEP-0030 git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13783 b35dd754-fafc-0310-a699-88a17e54d16e --- .../smackx/ServiceDiscoveryManager.java | 65 ++++++++++--------- .../smackx/entitycaps/EntityCapsManager.java | 3 +- .../smackx/packet/DiscoverInfo.java | 11 +++- .../smackx/ServiceDiscoveryManagerTest.java | 4 +- 4 files changed, 49 insertions(+), 34 deletions(-) diff --git a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java index 9e31f6777..566294837 100644 --- a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java +++ b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java @@ -54,7 +54,8 @@ public class ServiceDiscoveryManager { private static final String DEFAULT_IDENTITY_CATEGORY = "client"; private static final String DEFAULT_IDENTITY_TYPE = "pc"; - private static List identities = new LinkedList(); + private Set identities = new HashSet(); + private DiscoverInfo.Identity identity = new Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, DEFAULT_IDENTITY_TYPE); private EntityCapsManager capsManager; @@ -74,7 +75,6 @@ public class ServiceDiscoveryManager { new ServiceDiscoveryManager(connection); } }); - identities.add(new Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, DEFAULT_IDENTITY_TYPE)); } /** @@ -86,6 +86,7 @@ public class ServiceDiscoveryManager { */ public ServiceDiscoveryManager(Connection connection) { this.connection = connection; + identities.add(identity); init(); } @@ -107,13 +108,8 @@ public class ServiceDiscoveryManager { * @return the name of the client that will be returned when asked for the client identity * in a disco request. */ - public static String getIdentityName() { - DiscoverInfo.Identity identity = identities.get(0); - if (identity != null) { - return identity.getName(); - } else { - return null; - } + public String getIdentityName() { + return identity.getName(); } /** @@ -123,10 +119,8 @@ public class ServiceDiscoveryManager { * @param name the name of the client that will be returned when asked for the client identity * in a disco request. */ - public static void setIdentityName(String name) { - DiscoverInfo.Identity identity = identities.remove(0); - identity = new DiscoverInfo.Identity(DEFAULT_IDENTITY_CATEGORY, name, DEFAULT_IDENTITY_TYPE); - identities.add(identity); + public void setIdentityName(String name) { + identity.setName(name); } /** @@ -137,13 +131,8 @@ public class ServiceDiscoveryManager { * @return the type of client that will be returned when asked for the client identity in a * disco request. */ - public static String getIdentityType() { - DiscoverInfo.Identity identity = identities.get(0); - if (identity != null) { - return identity.getType(); - } else { - return null; - } + public String getIdentityType() { + return identity.getType(); } /** @@ -154,14 +143,30 @@ public class ServiceDiscoveryManager { * @param type the type of client that will be returned when asked for the client identity in a * disco request. */ - public static void setIdentityType(String type) { - DiscoverInfo.Identity identity = identities.get(0); - if (identity != null) { - identity.setType(type); - } else { - identity = new DiscoverInfo.Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, type); - identities.add(identity); - } + public void setIdentityType(String type) { + identity.setType(type); + } + + /** + * Add an identity to the client. + * + * @param identity + */ + public void addIdentity(DiscoverInfo.Identity identity) { + identities.add(identity); + } + + /** + * Remove an identity from the client. Note that the client needs at least one identity, the default identity, which + * can not be removed. + * + * @param identity + * @return true, if successful. Otherwise the default identity was given. + */ + public boolean removeIdentity(DiscoverInfo.Identity identity) { + if (identity.equals(this.identity)) return false; + identities.remove(identity); + return true; } /** @@ -169,8 +174,8 @@ public class ServiceDiscoveryManager { * * @return */ - public static List getIdentities() { - return Collections.unmodifiableList(identities); + public Set getIdentities() { + return Collections.unmodifiableSet(identities); } /** diff --git a/source/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java b/source/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java index 1da222efd..c15f11c03 100644 --- a/source/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java +++ b/source/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java @@ -56,6 +56,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.WeakHashMap; @@ -457,9 +458,9 @@ public class EntityCapsManager { if (connection != null) jidCaps.put(connection.getUser(), new NodeVerHash(ENTITY_NODE, currentCapsVersion, "sha-1")); + final List identities = new LinkedList(ServiceDiscoveryManager.getInstanceFor(connection).getIdentities()); sdm.setNodeInformationProvider(ENTITY_NODE + '#' + currentCapsVersion, new NodeInformationProvider() { List features = sdm.getFeaturesList(); - List identities = new LinkedList(ServiceDiscoveryManager.getIdentities()); List packetExtensions = sdm.getExtendedInfoAsList(); @Override diff --git a/source/org/jivesoftware/smackx/packet/DiscoverInfo.java b/source/org/jivesoftware/smackx/packet/DiscoverInfo.java index ba873a96d..756fd7191 100644 --- a/source/org/jivesoftware/smackx/packet/DiscoverInfo.java +++ b/source/org/jivesoftware/smackx/packet/DiscoverInfo.java @@ -312,7 +312,16 @@ public class DiscoverInfo extends IQ { public String getName() { return name; } - + + /** + * Set the identity's name. + * + * @param name + */ + public void setName(String name) { + this.name = name; + } + /** * Returns the entity's type. To get the official registry of values for the * 'type' attribute refer to Jabber::Registrar diff --git a/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java b/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java index 9ba422e3e..12369f27e 100644 --- a/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java +++ b/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java @@ -53,10 +53,10 @@ public class ServiceDiscoveryManagerTest extends SmackTestCase { Iterator identities = info.getIdentities(); assertTrue("No identities were found", identities.hasNext()); Identity identity = identities.next(); - assertEquals("Name in identity is wrong", ServiceDiscoveryManager.getIdentityName(), + assertEquals("Name in identity is wrong", discoManager.getIdentityName(), identity.getName()); assertEquals("Category in identity is wrong", "client", identity.getCategory()); - assertEquals("Type in identity is wrong", ServiceDiscoveryManager.getIdentityType(), + assertEquals("Type in identity is wrong", discoManager.getIdentityType(), identity.getType()); assertFalse("More identities were found", identities.hasNext()); } From addf9ea6cd30c4d389cfd811e1e7d56f23ea1202 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 26 Oct 2013 23:50:59 +0000 Subject: [PATCH 21/24] SMACK-459 Add option to configure the default identity in ServiceDiscoveryManager git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13790 b35dd754-fafc-0310-a699-88a17e54d16e --- .../smackx/ServiceDiscoveryManager.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java index 566294837..688f9a3a6 100644 --- a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java +++ b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java @@ -53,9 +53,11 @@ public class ServiceDiscoveryManager { private static final String DEFAULT_IDENTITY_NAME = "Smack"; private static final String DEFAULT_IDENTITY_CATEGORY = "client"; private static final String DEFAULT_IDENTITY_TYPE = "pc"; + private static DiscoverInfo.Identity defaultIdentity = new Identity(DEFAULT_IDENTITY_CATEGORY, + DEFAULT_IDENTITY_NAME, DEFAULT_IDENTITY_TYPE); private Set identities = new HashSet(); - private DiscoverInfo.Identity identity = new Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, DEFAULT_IDENTITY_TYPE); + private DiscoverInfo.Identity identity = defaultIdentity; private EntityCapsManager capsManager; @@ -77,6 +79,16 @@ public class ServiceDiscoveryManager { }); } + /** + * Set the default identity all new connections will have. If unchanged the default identity is an + * identity where category is set to 'client', type is set to 'pc' and name is set to 'Smack'. + * + * @param identity + */ + public static void setDefaultIdentity(DiscoverInfo.Identity identity) { + defaultIdentity = identity; + } + /** * Creates a new ServiceDiscoveryManager for a given Connection. This means that the * service manager will respond to any service discovery request that the connection may From 6fa2cbee68fc9e7be8ed902596bd3cadc9423153 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 31 Oct 2013 20:24:02 +0000 Subject: [PATCH 22/24] SMACK-459 actually add the defaultIdentity to the result of getIdentities() git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13796 b35dd754-fafc-0310-a699-88a17e54d16e --- source/org/jivesoftware/smackx/ServiceDiscoveryManager.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java index 688f9a3a6..2b4f23f5b 100644 --- a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java +++ b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java @@ -187,7 +187,10 @@ public class ServiceDiscoveryManager { * @return */ public Set getIdentities() { - return Collections.unmodifiableSet(identities); + Set res = new HashSet(identities); + // Add the default identity that must exist + res.add(defaultIdentity); + return res; } /** From 385cfac9fa3dcf53ad17857d10937b306ba3602c Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 31 Oct 2013 20:28:40 +0000 Subject: [PATCH 23/24] SMACK-459 Make it user proof by returning an unmodifiable set again git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13797 b35dd754-fafc-0310-a699-88a17e54d16e --- source/org/jivesoftware/smackx/ServiceDiscoveryManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java index 2b4f23f5b..a42e5e1bf 100644 --- a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java +++ b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java @@ -190,7 +190,7 @@ public class ServiceDiscoveryManager { Set res = new HashSet(identities); // Add the default identity that must exist res.add(defaultIdentity); - return res; + return Collections.unmodifiableSet(res); } /** From 717d39c8fc0eb8fccd17e65a44b8b8cc3eddebf6 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 4 Nov 2013 20:58:36 +0000 Subject: [PATCH 24/24] Fix caps support (SMACK-361, SMACK-459). SMACK-459 splitted the SDM identities over two member variables which are joined by getIdentities(), therefore this method must be used to calculated the hash for entity caps. Also recalculate the hash if an identity is added/remvoed/changed. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13801 b35dd754-fafc-0310-a699-88a17e54d16e --- source/org/jivesoftware/smackx/ServiceDiscoveryManager.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java index a42e5e1bf..cd1d705e3 100644 --- a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java +++ b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java @@ -133,6 +133,7 @@ public class ServiceDiscoveryManager { */ public void setIdentityName(String name) { identity.setName(name); + renewEntityCapsVersion(); } /** @@ -157,6 +158,7 @@ public class ServiceDiscoveryManager { */ public void setIdentityType(String type) { identity.setType(type); + renewEntityCapsVersion(); } /** @@ -166,6 +168,7 @@ public class ServiceDiscoveryManager { */ public void addIdentity(DiscoverInfo.Identity identity) { identities.add(identity); + renewEntityCapsVersion(); } /** @@ -178,6 +181,7 @@ public class ServiceDiscoveryManager { public boolean removeIdentity(DiscoverInfo.Identity identity) { if (identity.equals(this.identity)) return false; identities.remove(identity); + renewEntityCapsVersion(); return true; } @@ -318,7 +322,7 @@ public class ServiceDiscoveryManager { */ public void addDiscoverInfoTo(DiscoverInfo response) { // First add the identities of the connection - response.addIdentities(identities); + response.addIdentities(getIdentities()); // Add the registered features to the response synchronized (features) {