summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Young <consult.awy@gmail.com>2014-11-13 06:42:37 (GMT)
committerAlan Young <consult.awy@gmail.com>2015-04-10 06:50:58 (GMT)
commit7d2101b93b6253c3ea15b663f7f3dc385cb21364 (patch)
treed81338baf0d117e83cdc07f882cbedd9471e834d /src/com/isode/stroke/disco
parenta20ca7ba40d837abe228462be0aba5d32d6831e3 (diff)
downloadstroke-7d2101b93b6253c3ea15b663f7f3dc385cb21364.zip
stroke-7d2101b93b6253c3ea15b663f7f3dc385cb21364.tar.bz2
Checkpoint - A bunch of initial stuff for Android
MemoryStorages, Storages NickManager, NickResolver CryptoProvider, Hash, SafeByteArray, JavaCryptoProvider CapsInfoGenerator, CapsManager, CapsMemoryStorage, CapsProvider, CapsStorage, CapsInfo CapsInfoSerializer, CapsInfoParser ClientDiscoManager, DiscoInfoResponder, EntityCapsManager, EntityCapsProvider GetDiscoInfoRequest ChatState, Idle Presence, PayloadAddingPresenceSender, PresenceOracle, SubscriptionManager StatusSerializer, StatusShowSerializer, StatusParser, StatusShowParser, Replace, ReplaceParser, ReplaceSerializer SecurityLabel, SecurityLabelsCatalog, GetSecurityLabelsCatalogRequest VCard, GetVCardRequest, SetVCardRequest, VCardManager, VCardMemoryStorage, VCardStorage RosterMemoryStorage, RosterPushResponder, RosterStorage, SetRosterRequest XMPPRoster, XMPPRosterController, XMPPRosterImpl, XMPPRosterItem GetRosterRequest, SetResponder Add parsers and serializers for Idle, VCard, PrivateStorage & Stroage. Add parser for Subject. Add impromptu flag to MUCInvitation. Update copyrights. Change-Id: I9949f506b70e60b3a64f1dadde8f9b235b322e1d
Diffstat (limited to 'src/com/isode/stroke/disco')
-rw-r--r--src/com/isode/stroke/disco/CapsInfoGenerator.java73
-rw-r--r--src/com/isode/stroke/disco/CapsManager.java144
-rw-r--r--src/com/isode/stroke/disco/CapsMemoryStorage.java26
-rw-r--r--src/com/isode/stroke/disco/CapsProvider.java14
-rw-r--r--src/com/isode/stroke/disco/CapsStorage.java12
-rw-r--r--src/com/isode/stroke/disco/ClientDiscoManager.java76
-rw-r--r--src/com/isode/stroke/disco/DiscoInfoResponder.java54
-rw-r--r--src/com/isode/stroke/disco/EntityCapsManager.java93
-rw-r--r--src/com/isode/stroke/disco/EntityCapsProvider.java21
-rw-r--r--src/com/isode/stroke/disco/GetDiscoInfoRequest.java32
10 files changed, 545 insertions, 0 deletions
diff --git a/src/com/isode/stroke/disco/CapsInfoGenerator.java b/src/com/isode/stroke/disco/CapsInfoGenerator.java
new file mode 100644
index 0000000..2bbd843
--- /dev/null
+++ b/src/com/isode/stroke/disco/CapsInfoGenerator.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2010-2015, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.disco;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.elements.CapsInfo;
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.Form;
+import com.isode.stroke.elements.FormField;
+import com.isode.stroke.stringcodecs.Base64;
+
+public class CapsInfoGenerator {
+ private String node_;
+ private CryptoProvider crypto_;
+
+ private final static Comparator<FormField> compareFields = new Comparator<FormField>() {
+ @Override
+ public int compare(FormField lhs, FormField rhs) {
+ return lhs.getName().compareTo(rhs.getName());
+ }
+ };
+
+ CapsInfoGenerator(final String node, CryptoProvider crypto) {
+ this.node_ = node;
+ this.crypto_ = crypto;
+ }
+
+ CapsInfo generateCapsInfo(final DiscoInfo discoInfo) {
+ String serializedCaps = "";
+
+ List<DiscoInfo.Identity> identities = discoInfo.getIdentities();
+ Collections.sort(identities);
+ for (final DiscoInfo.Identity identity : identities) {
+ serializedCaps += identity.getCategory() + "/" + identity.getType()
+ + "/" + identity.getLanguage() + "/" + identity.getName()
+ + "<";
+ }
+
+ List<String> features = discoInfo.getFeatures();
+ Collections.sort(features);
+ for (final String feature : features) {
+ serializedCaps += feature + "<";
+ }
+
+ for (Form extension : discoInfo.getExtensions()) {
+ serializedCaps += extension.getFormType() + "<";
+ List<FormField> fields = extension.getFields();
+ Collections.sort(fields, compareFields);
+ for (FormField field : fields) {
+ if ("FORM_TYPE".equals(field.getName())) {
+ continue;
+ }
+ serializedCaps += field.getName() + "<";
+ List<String> values = field.getValues();
+ Collections.sort(values);
+ for (final String value : values) {
+ serializedCaps += value + "<";
+ }
+ }
+ }
+
+ String version = Base64.encode(crypto_ .getSHA1Hash(new ByteArray(serializedCaps)));
+ return new CapsInfo(node_, version, "sha-1");
+ }
+
+}
diff --git a/src/com/isode/stroke/disco/CapsManager.java b/src/com/isode/stroke/disco/CapsManager.java
new file mode 100644
index 0000000..2e4c45b
--- /dev/null
+++ b/src/com/isode/stroke/disco/CapsManager.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2010-2015, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.disco;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.isode.stroke.client.StanzaChannel;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.elements.CapsInfo;
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.Presence;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Slot2;
+
+public class CapsManager extends CapsProvider {
+
+ private final IQRouter iqRouter;
+ private final CryptoProvider crypto;
+ private final CapsStorage capsStorage;
+ private boolean warnOnInvalidHash;
+ private Set<String> requestedDiscoInfos = new HashSet<String>();
+ private Set<CapsPair> failingCaps = new HashSet<CapsPair>();
+ private Map<String, Set<CapsPair>> fallbacks = new HashMap<String, Set<CapsPair>>();
+
+ private class CapsPair {
+ JID jid;
+ String node;
+
+ CapsPair(JID j, String n) {jid = j; node = n;}
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof CapsPair)) return false;
+ CapsPair o1 = (CapsPair) o;
+ return jid.equals(o1.jid) && node.equals(o1.node);
+ }
+
+ @Override public int hashCode() {return jid.hashCode() * 5 + node.hashCode();}
+ }
+
+ public CapsManager(CapsStorage capsStorage, StanzaChannel stanzaChannel,
+ IQRouter iqRouter, CryptoProvider crypto) {
+ this.iqRouter = iqRouter;
+ this.crypto = crypto;
+ this.capsStorage = capsStorage;
+ this.warnOnInvalidHash = true;
+
+ stanzaChannel.onPresenceReceived.connect(new Slot1<Presence>() {
+ @Override
+ public void call(Presence p1) {
+ handlePresenceReceived(p1);
+ }
+ });
+ stanzaChannel.onAvailableChanged.connect(new Slot1<Boolean>() {
+ @Override
+ public void call(Boolean p1) {
+ handleStanzaChannelAvailableChanged(p1);
+ }
+ });
+ }
+
+ private void handlePresenceReceived(Presence presence) {
+ CapsInfo capsInfo = presence.getPayload(new CapsInfo());
+ if (capsInfo == null || !capsInfo.getHash().equals("sha-1")
+ || presence.getPayload(new ErrorPayload()) != null) {
+ return;
+ }
+ String hash = capsInfo.getVersion();
+ if (capsStorage.getDiscoInfo(hash) != null) {
+ return;
+ }
+ if (failingCaps.contains(new CapsPair(presence.getFrom(), hash))) {
+ return;
+ }
+ if (requestedDiscoInfos.contains(hash)) {
+ Set<CapsPair> fallback = fallbacks.get(hash);
+ if (fallback == null) fallbacks.put(hash, fallback = new HashSet<CapsPair>());
+ fallback.add(new CapsPair(presence.getFrom(), capsInfo.getNode()));
+ return;
+ }
+ requestDiscoInfo(presence.getFrom(), capsInfo.getNode(), hash);
+ }
+
+ private void handleStanzaChannelAvailableChanged(boolean available) {
+ if (available) {
+ failingCaps.clear();
+ fallbacks.clear();
+ requestedDiscoInfos.clear();
+ }
+ }
+
+ private void handleDiscoInfoReceived(final JID from, final String hash, DiscoInfo discoInfo, ErrorPayload error) {
+ requestedDiscoInfos.remove(hash);
+ if (error != null || discoInfo == null
+ || !new CapsInfoGenerator("", crypto).generateCapsInfo(discoInfo).getVersion().equals(hash)) {
+ if (warnOnInvalidHash && error == null && discoInfo != null) {
+// std.cerr << "Warning: Caps from " << from.toString() << " do not verify" << std.endl;
+ }
+ failingCaps.add(new CapsPair(from, hash));
+ Set<CapsPair> i = fallbacks.get(hash);
+ if (i != null && !i.isEmpty()) {
+ CapsPair fallbackAndNode = i.iterator().next();
+ i.remove(fallbackAndNode);
+ requestDiscoInfo(fallbackAndNode.jid, fallbackAndNode.node, hash);
+ }
+ return;
+ }
+ fallbacks.remove(hash);
+ capsStorage.setDiscoInfo(hash, discoInfo);
+ onCapsAvailable.emit(hash);
+ }
+
+ private void requestDiscoInfo(final JID jid, final String node, final String hash) {
+ GetDiscoInfoRequest request = GetDiscoInfoRequest.create(jid, node
+ + "#" + hash, iqRouter);
+ request.onResponse.connect(new Slot2<DiscoInfo, ErrorPayload>() {
+ @Override
+ public void call(DiscoInfo p1, ErrorPayload p2) {
+ handleDiscoInfoReceived(jid, hash, p1, p2);
+ }
+ });
+ requestedDiscoInfos.add(hash);
+ request.send();
+ }
+
+ @Override
+ DiscoInfo getCaps(String hash) {
+ return capsStorage.getDiscoInfo(hash);
+ }
+
+ // Mainly for testing purposes
+ void setWarnOnInvalidHash(boolean b) {
+ warnOnInvalidHash = b;
+ }
+
+}
diff --git a/src/com/isode/stroke/disco/CapsMemoryStorage.java b/src/com/isode/stroke/disco/CapsMemoryStorage.java
new file mode 100644
index 0000000..81e0e77
--- /dev/null
+++ b/src/com/isode/stroke/disco/CapsMemoryStorage.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010-2015, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.disco;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.isode.stroke.elements.DiscoInfo;
+
+public class CapsMemoryStorage implements CapsStorage {
+ private Map<String, DiscoInfo> caps = new HashMap<String, DiscoInfo>();
+
+ @Override
+ public DiscoInfo getDiscoInfo(String s) {
+ return caps.get(s);
+ }
+
+ @Override
+ public void setDiscoInfo(String s, DiscoInfo d) {
+ caps.put(s, d);
+
+ }
+
+}
diff --git a/src/com/isode/stroke/disco/CapsProvider.java b/src/com/isode/stroke/disco/CapsProvider.java
new file mode 100644
index 0000000..d83eebc
--- /dev/null
+++ b/src/com/isode/stroke/disco/CapsProvider.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010-2015, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.disco;
+
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.signals.Signal1;
+
+public abstract class CapsProvider {
+ abstract DiscoInfo getCaps(final String hash);
+
+ public final Signal1<String> onCapsAvailable = new Signal1<String>();
+}
diff --git a/src/com/isode/stroke/disco/CapsStorage.java b/src/com/isode/stroke/disco/CapsStorage.java
new file mode 100644
index 0000000..07c1cd7
--- /dev/null
+++ b/src/com/isode/stroke/disco/CapsStorage.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2010-2015, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.disco;
+
+import com.isode.stroke.elements.DiscoInfo;
+
+public interface CapsStorage {
+ DiscoInfo getDiscoInfo(final String s);
+ void setDiscoInfo(final String s, DiscoInfo d);
+}
diff --git a/src/com/isode/stroke/disco/ClientDiscoManager.java b/src/com/isode/stroke/disco/ClientDiscoManager.java
new file mode 100644
index 0000000..3770fbc
--- /dev/null
+++ b/src/com/isode/stroke/disco/ClientDiscoManager.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2010-2015, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.disco;
+
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.elements.CapsInfo;
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.presence.PayloadAddingPresenceSender;
+import com.isode.stroke.presence.PresenceSender;
+import com.isode.stroke.queries.IQRouter;
+
+public class ClientDiscoManager {
+ private PayloadAddingPresenceSender presenceSender;
+ private CryptoProvider crypto;
+ private DiscoInfoResponder discoInfoResponder;
+ private String capsNode;
+ private CapsInfo capsInfo;
+
+ /**
+ * Constructs the manager
+ *
+ * \param iqRouter the router on which requests will be answered \param
+ * presenceSender the presence sender to which all outgoing presence (with
+ * caps information) will be sent.
+ */
+ public ClientDiscoManager(IQRouter iqRouter, PresenceSender presenceSender,
+ CryptoProvider crypto) {
+ this.crypto = crypto;
+ discoInfoResponder = new DiscoInfoResponder(iqRouter);
+ discoInfoResponder.start();
+ this.presenceSender = new PayloadAddingPresenceSender(presenceSender);
+ }
+
+ void delete() {
+ discoInfoResponder.stop();
+ }
+
+ /**
+ * Needs to be called before calling setDiscoInfo().
+ */
+ public void setCapsNode(final String node) {
+ capsNode = node;
+ }
+
+ /**
+ * Sets the capabilities of the client.
+ */
+ public void setDiscoInfo(final DiscoInfo discoInfo) {
+ capsInfo = new CapsInfoGenerator(capsNode, crypto).generateCapsInfo(discoInfo);
+ discoInfoResponder.clearDiscoInfo();
+ discoInfoResponder.setDiscoInfo(discoInfo);
+ discoInfoResponder.setDiscoInfo(
+ capsInfo.getNode() + "#" + capsInfo.getVersion(), discoInfo);
+ presenceSender.setPayload(capsInfo);
+ }
+
+ /**
+ * Returns the presence sender through which all outgoing presence should be
+ * sent. The manager will add the necessary caps information, and forward it
+ * to the presence sender passed at construction time.
+ */
+ public PresenceSender getPresenceSender() {
+ return presenceSender;
+ }
+
+ /**
+ * Called when the client is connected. This resets the presence sender,
+ * such that it assumes initial presence hasn't been sent yet.
+ */
+ void handleConnected() {
+ presenceSender.reset();
+ }
+
+}
diff --git a/src/com/isode/stroke/disco/DiscoInfoResponder.java b/src/com/isode/stroke/disco/DiscoInfoResponder.java
new file mode 100644
index 0000000..c0edca2
--- /dev/null
+++ b/src/com/isode/stroke/disco/DiscoInfoResponder.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010-2015, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.disco;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.queries.GetResponder;
+import com.isode.stroke.queries.IQRouter;
+
+public class DiscoInfoResponder extends GetResponder<DiscoInfo> {
+ private DiscoInfo info_;
+ private Map<String, DiscoInfo> nodeInfo_ = new HashMap<String, DiscoInfo>();
+
+ public DiscoInfoResponder(IQRouter router) {
+ super(new DiscoInfo(), router);
+ }
+
+ void clearDiscoInfo() {
+ info_ = new DiscoInfo();
+ nodeInfo_.clear();
+ }
+
+ void setDiscoInfo(final DiscoInfo info) {
+ info_ = info;
+ }
+
+ void setDiscoInfo(final String node, final DiscoInfo info) {
+ DiscoInfo newInfo = info;
+ newInfo.setNode(node);
+ nodeInfo_.put(node, newInfo);
+ }
+
+ protected boolean handleGetRequest(final JID from, final JID j, final String id, DiscoInfo info) {
+ if (info.getNode().isEmpty()) {
+ sendResponse(from, id, info_);
+ }
+ else {
+ DiscoInfo i = nodeInfo_.get(info.getNode());
+ if (i != null) {
+ sendResponse(from, id, i);
+ }
+ else {
+ sendError(from, id, ErrorPayload.Condition.ItemNotFound, ErrorPayload.Type.Cancel);
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/isode/stroke/disco/EntityCapsManager.java b/src/com/isode/stroke/disco/EntityCapsManager.java
new file mode 100644
index 0000000..a41ec11
--- /dev/null
+++ b/src/com/isode/stroke/disco/EntityCapsManager.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2010-2015, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.disco;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.isode.stroke.client.StanzaChannel;
+import com.isode.stroke.elements.CapsInfo;
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.Presence;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.signals.Slot1;
+
+public class EntityCapsManager extends EntityCapsProvider {
+ private final CapsProvider capsProvider;
+ private final Map<JID, String> caps = new HashMap<JID, String>();
+
+ public EntityCapsManager(CapsProvider capsProvider, StanzaChannel stanzaChannel) {
+ this.capsProvider = capsProvider;
+
+ stanzaChannel.onPresenceReceived.connect(new Slot1<Presence>() {
+ @Override
+ public void call(Presence p1) {
+ handlePresenceReceived(p1);
+ }
+ });
+ stanzaChannel.onAvailableChanged.connect(new Slot1<Boolean>() {
+ @Override
+ public void call(Boolean p1) {
+ handleStanzaChannelAvailableChanged(p1);
+ }
+ });
+ capsProvider.onCapsAvailable.connect(new Slot1<String>() {
+ @Override
+ public void call(String p1) {
+ handleCapsAvailable(p1);
+ }
+ });
+ }
+
+ private void handlePresenceReceived(Presence presence) {
+ JID from = presence.getFrom();
+ if (presence.isAvailable()) {
+ CapsInfo capsInfo = presence.getPayload(new CapsInfo());
+ if (capsInfo == null || !capsInfo.getHash().equals("sha-1") || presence.getPayload(new ErrorPayload()) != null) {
+ return;
+ }
+ String hash = capsInfo.getVersion();
+ String i = caps.get(from);
+ if (!hash.equals(i)) {
+ caps.put(from, hash);
+ DiscoInfo disco = capsProvider.getCaps(hash);
+ if (disco != null || i != null) {
+ onCapsChanged.emit(from);
+ }
+ }
+ }
+ else {
+ if (caps.remove(from) != null) {
+ onCapsChanged.emit(from);
+ }
+ }
+ }
+
+ private void handleStanzaChannelAvailableChanged(boolean available) {
+ if (available) {
+ for (JID i : caps.keySet()) {
+ onCapsChanged.emit(i);
+ }
+ caps.clear();
+ }
+ }
+
+ private void handleCapsAvailable(final String hash) {
+ // TODO: Use Boost.Bimap ?
+ for (JID i : caps.keySet()) {
+ if (caps.get(i).equals(hash)) {
+ onCapsChanged.emit(i);
+ }
+ }
+ }
+
+ public DiscoInfo getCaps(final JID jid) {
+ if (caps.containsKey(jid)) {
+ return capsProvider.getCaps(caps.get(jid));
+ }
+ return new DiscoInfo();
+ }
+}
diff --git a/src/com/isode/stroke/disco/EntityCapsProvider.java b/src/com/isode/stroke/disco/EntityCapsProvider.java
new file mode 100644
index 0000000..fd30173
--- /dev/null
+++ b/src/com/isode/stroke/disco/EntityCapsProvider.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010-2015, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.disco;
+
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.signals.Signal1;
+
+public abstract class EntityCapsProvider {
+ /**
+ * Returns the service discovery information of the given JID.
+ */
+ public abstract DiscoInfo getCaps(final JID jid);
+
+ /**
+ * Emitted when the capabilities of a JID changes.
+ */
+ public final Signal1<JID> onCapsChanged = new Signal1<JID>();
+}
diff --git a/src/com/isode/stroke/disco/GetDiscoInfoRequest.java b/src/com/isode/stroke/disco/GetDiscoInfoRequest.java
new file mode 100644
index 0000000..f820b2f
--- /dev/null
+++ b/src/com/isode/stroke/disco/GetDiscoInfoRequest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010-2015, Isode Limited, London, England.
+ * All rights reserved.
+ */
+package com.isode.stroke.disco;
+
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.queries.IQRouter;
+
+public class GetDiscoInfoRequest extends GenericRequest<DiscoInfo> {
+
+ public static GetDiscoInfoRequest create(final JID jid, IQRouter router) {
+ return new GetDiscoInfoRequest(jid, router);
+ }
+
+ public static GetDiscoInfoRequest create(final JID jid, final String node, IQRouter router) {
+ return new GetDiscoInfoRequest(jid, node, router);
+ }
+
+ private GetDiscoInfoRequest(final JID jid, IQRouter router) {
+ super(IQ.Type.Get, jid, new DiscoInfo(), router);
+ }
+
+ private GetDiscoInfoRequest(final JID jid, final String node, IQRouter router) {
+ this(jid, router);
+ getPayloadGeneric().setNode(node);
+ }
+
+}