aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/thedevstack/conversationsplus/xmpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/thedevstack/conversationsplus/xmpp')
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/OnKeyStatusUpdated.java7
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java1167
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/chatstate/ChatState.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Data.java20
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Field.java33
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jid/Jid.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java168
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java8
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleInbandTransport.java20
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleSocks5Transport.java78
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnFileTransmissionStatusChanged.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnJinglePacketReceived.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnPrimaryCandidateFound.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/stanzas/Content.java5
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/pep/Avatar.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractAcknowledgeableStanza.java31
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractStanza.java8
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java12
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java9
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/PresencePacket.java2
22 files changed, 1032 insertions, 559 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/OnKeyStatusUpdated.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/OnKeyStatusUpdated.java
new file mode 100644
index 00000000..47bcff9e
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/OnKeyStatusUpdated.java
@@ -0,0 +1,7 @@
+package de.thedevstack.conversationsplus.xmpp;
+
+import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService;
+
+public interface OnKeyStatusUpdated {
+ public void onKeyStatusUpdated(AxolotlService.FetchStatus report);
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java
index d5ab9f90..29be84f5 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java
@@ -1,66 +1,80 @@
package de.thedevstack.conversationsplus.xmpp;
-import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
+import android.security.KeyChain;
+import android.util.Base64;
+import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
-import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.math.BigInteger;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
+import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
-import java.util.LinkedList;
+import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeSet;
import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
+import de.duenndns.ssl.MemorizingTrustManager;
import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.dto.SrvRecord;
import de.thedevstack.conversationsplus.Config;
+import de.thedevstack.conversationsplus.crypto.XmppDomainVerifier;
import de.thedevstack.conversationsplus.crypto.sasl.DigestMd5;
+import de.thedevstack.conversationsplus.crypto.sasl.External;
import de.thedevstack.conversationsplus.crypto.sasl.Plain;
import de.thedevstack.conversationsplus.crypto.sasl.SaslMechanism;
import de.thedevstack.conversationsplus.crypto.sasl.ScramSha1;
-import de.thedevstack.conversationsplus.dto.SrvRecord;
import de.thedevstack.conversationsplus.entities.Account;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.entities.ServiceDiscoveryResult;
import de.thedevstack.conversationsplus.generator.IqGenerator;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
-import de.thedevstack.conversationsplus.utils.CryptoHelper;
import de.thedevstack.conversationsplus.utils.DNSHelper;
+import de.thedevstack.conversationsplus.utils.SSLSocketHelper;
import de.thedevstack.conversationsplus.utils.Xmlns;
import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xml.Tag;
import de.thedevstack.conversationsplus.xml.TagWriter;
import de.thedevstack.conversationsplus.xml.XmlReader;
+import de.thedevstack.conversationsplus.xmpp.forms.Data;
+import de.thedevstack.conversationsplus.xmpp.forms.Field;
import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException;
import de.thedevstack.conversationsplus.xmpp.jid.Jid;
import de.thedevstack.conversationsplus.xmpp.jingle.OnJinglePacketReceived;
import de.thedevstack.conversationsplus.xmpp.jingle.stanzas.JinglePacket;
+import de.thedevstack.conversationsplus.xmpp.stanzas.AbstractAcknowledgeableStanza;
import de.thedevstack.conversationsplus.xmpp.stanzas.AbstractStanza;
import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket;
@@ -83,14 +97,14 @@ public class XmppConnection implements Runnable {
private XmlReader tagReader;
private TagWriter tagWriter;
private final Features features = new Features(this);
- private boolean shouldBind = true;
+ private boolean needsBinding = true;
private boolean shouldAuthenticate = true;
private Element streamFeatures;
- private final HashMap<Jid, Info> disco = new HashMap<>();
+ private final HashMap<Jid, ServiceDiscoveryResult> disco = new HashMap<>();
private String streamId = null;
private int smVersion = 3;
- private final SparseArray<String> messageReceipts = new SparseArray<>();
+ private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>();
private int stanzasReceived = 0;
private int stanzasSent = 0;
@@ -98,8 +112,12 @@ public class XmppConnection implements Runnable {
private long lastPingSent = 0;
private long lastConnect = 0;
private long lastSessionStarted = 0;
+ private long lastDiscoStarted = 0;
+ private int mPendingServiceDiscoveries = 0;
+ private final ArrayList<String> mPendingServiceDiscoveriesIds = new ArrayList<>();
+ private boolean mInteractive = false;
private int attempt = 0;
- private final Map<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>();
+ private final Hashtable<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>();
private OnPresencePacketReceived presenceListener = null;
private OnJinglePacketReceived jingleListener = null;
private OnIqPacketReceived unregisteredIqListener = null;
@@ -112,6 +130,68 @@ public class XmppConnection implements Runnable {
private SaslMechanism saslMechanism;
+ private X509KeyManager mKeyManager = new X509KeyManager() {
+ @Override
+ public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
+ return account.getPrivateKeyAlias();
+ }
+
+ @Override
+ public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
+ return null;
+ }
+
+ @Override
+ public X509Certificate[] getCertificateChain(String alias) {
+ try {
+ return KeyChain.getCertificateChain(mXmppConnectionService, alias);
+ } catch (Exception e) {
+ return new X509Certificate[0];
+ }
+ }
+
+ @Override
+ public String[] getClientAliases(String s, Principal[] principals) {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getServerAliases(String s, Principal[] principals) {
+ return new String[0];
+ }
+
+ @Override
+ public PrivateKey getPrivateKey(String alias) {
+ try {
+ return KeyChain.getPrivateKey(mXmppConnectionService, alias);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ };
+ private Identity mServerIdentity = Identity.UNKNOWN;
+
+ private OnIqPacketReceived createPacketReceiveHandler() {
+ return new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ account.setOption(Account.OPTION_REGISTER,
+ false);
+ changeStatus(Account.State.REGISTRATION_SUCCESSFUL);
+ } else if (packet.hasChild("error")
+ && (packet.findChild("error")
+ .hasChild("conflict"))) {
+ changeStatus(Account.State.REGISTRATION_CONFLICT);
+ } else {
+ changeStatus(Account.State.REGISTRATION_FAILED);
+ Log.d(Config.LOGTAG, packet.toString());
+ }
+ disconnect(true);
+ }
+ };
+ }
+
public XmppConnection(final Account account, final XmppConnectionService service) {
this.account = account;
this.wakeLock = service.getPowerManager().newWakeLock(
@@ -143,59 +223,92 @@ public class XmppConnection implements Runnable {
features.encryptionEnabled = false;
lastConnect = SystemClock.elapsedRealtime();
lastPingSent = SystemClock.elapsedRealtime();
+ lastDiscoStarted = Long.MAX_VALUE;
this.attempt++;
+ switch (account.getJid().getDomainpart()) {
+ case "chat.facebook.com":
+ mServerIdentity = Identity.FACEBOOK;
+ break;
+ case "nimbuzz.com":
+ mServerIdentity = Identity.NIMBUZZ;
+ break;
+ default:
+ mServerIdentity = Identity.UNKNOWN;
+ break;
+ }
try {
- shouldAuthenticate = shouldBind = !account.isOptionSet(Account.OPTION_REGISTER);
+ shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER);
tagReader = new XmlReader(wakeLock);
tagWriter = new TagWriter();
- packetCallbacks.clear();
this.changeStatus(Account.State.CONNECTING);
- if (DNSHelper.isIp(account.getServer().toString())) {
+ final boolean extended = mXmppConnectionService.showExtendedConnectionOptions();
+ if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
+ socket = new Socket();
+ try {
+ socket.connect(new InetSocketAddress(account.getHostname(), account.getPort()), Config.SOCKET_TIMEOUT * 1000);
+ } catch (IOException e) {
+ throw new UnknownHostException();
+ }
+ startXmpp();
+ } else if (DNSHelper.isIp(account.getServer().toString())) {
socket = new Socket();
try {
socket.connect(new InetSocketAddress(account.getServer().toString(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000);
} catch (IOException e) {
throw new UnknownHostException();
}
+ startXmpp();
} else {
- socket = new Socket();
- TreeSet<SrvRecord> result = DNSHelper.querySrvRecord(account.getServer());
- if (!result.isEmpty()) {
- for (SrvRecord record : result) {
- try {
- socket.connect(new InetSocketAddress(record.getName(), record.getPort()), Config.SOCKET_TIMEOUT * 1000);
- break;
- } catch (IOException e) {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
- }
- }
- }
- if (result.isEmpty() || !socket.isConnected()){
+ final TreeSet<SrvRecord> srvRecords = DNSHelper.querySrvRecord(account.getServer());
+ if (srvRecords.isEmpty()) {
+ socket = new Socket();
try {
socket.connect(new InetSocketAddress(account.getServer().getDomainpart(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000);
} catch (IOException e) {
throw new UnknownHostException();
}
+ startXmpp();
+ } else {
+ for (SrvRecord srvRecord : srvRecords) {
+ // if tls is true, encryption is implied and must not be started
+ features.encryptionEnabled = srvRecord.isUseTls();
+ TlsFactoryVerifier tlsFactoryVerifier = null;
+ if (features.encryptionEnabled) {
+ try {
+ tlsFactoryVerifier = getTlsFactoryVerifier();
+ socket = tlsFactoryVerifier.factory.createSocket();
+
+ if (socket == null) {
+ throw new IOException("could not initialize ssl socket");
+ }
+
+ SSLSocketHelper.setSecurity((SSLSocket) socket);
+ SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart());
+ SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client");
+ } catch (SecurityException e) {
+ throw e;
+ } catch (KeyManagementException e) {
+ Logging.e("connection-init", "Error while creating TLS verifier factory: " + e.getMessage(), e);
+ throw new SecurityException();
+ }
+ } else {
+ socket = new Socket();
+ }
+
+ socket.connect(new InetSocketAddress(srvRecord.getName(), srvRecord.getPort()), Config.SOCKET_TIMEOUT * 1000);
+
+ if (null != tlsFactoryVerifier && !tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
+ throw new SecurityException();
+ }
+
+ if (startXmpp()) {
+ break; // successfully connected to server that speaks xmpp
+ }
+ }
}
}
- final OutputStream out = socket.getOutputStream();
- tagWriter.setOutputStream(out);
- final InputStream in = socket.getInputStream();
- tagReader.setInputStream(in);
- tagWriter.beginDocument();
- sendStartStream();
- Tag nextTag;
- while ((nextTag = tagReader.readTag()) != null) {
- if (nextTag.isStart("stream")) {
- processStream(nextTag);
- break;
- } else {
- throw new IOException("unknown tag on connect");
- }
- }
- if (socket.isConnected()) {
- socket.close();
- }
+ processStream();
} catch (final IncompatibleServerException e) {
this.changeStatus(Account.State.INCOMPATIBLE_SERVER);
} catch (final SecurityException e) {
@@ -209,13 +322,7 @@ public class XmppConnection implements Runnable {
this.changeStatus(Account.State.OFFLINE);
this.attempt--; //don't count attempt when reconnecting instantly anyway
} finally {
- if (socket != null) {
- try {
- socket.close();
- } catch (IOException e) {
-
- }
- }
+ forceCloseSocket();
if (wakeLock.isHeld()) {
try {
wakeLock.release();
@@ -225,165 +332,213 @@ public class XmppConnection implements Runnable {
}
}
- @Override
- public void run() {
- try {
- if (socket != null) {
- socket.close();
+ /**
+ * Starts xmpp protocol, call after connecting to socket
+ * @return true if server returns with valid xmpp, false otherwise
+ * @throws IOException Unknown tag on connect
+ * @throws XmlPullParserException Bad Xml
+ * @throws NoSuchAlgorithmException Other error
+ */
+ private boolean startXmpp() throws IOException, XmlPullParserException, NoSuchAlgorithmException {
+ tagWriter.setOutputStream(socket.getOutputStream());
+ tagReader.setInputStream(socket.getInputStream());
+ tagWriter.beginDocument();
+ sendStartStream();
+ Tag nextTag;
+ while ((nextTag = tagReader.readTag()) != null) {
+ if (nextTag.isStart("stream")) {
+ return true;
+ } else {
+ throw new IOException("unknown tag on connect");
+ }
+ }
+ if (socket.isConnected()) {
+ socket.close();
+ }
+ return false;
+ }
+
+ private static class TlsFactoryVerifier {
+ private final SSLSocketFactory factory;
+ private final HostnameVerifier verifier;
+
+ public TlsFactoryVerifier(final SSLSocketFactory factory, final HostnameVerifier verifier) throws IOException {
+ this.factory = factory;
+ this.verifier = verifier;
+ if (factory == null || verifier == null) {
+ throw new IOException("could not setup ssl");
}
- } catch (final IOException ignored) {
+ }
+ }
+ private TlsFactoryVerifier getTlsFactoryVerifier() throws NoSuchAlgorithmException, KeyManagementException, IOException {
+ final SSLContext sc = SSLSocketHelper.getSSLContext();
+ MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager();
+ KeyManager[] keyManager;
+ if (account.getPrivateKeyAlias() != null && account.getPassword().isEmpty()) {
+ keyManager = new KeyManager[]{mKeyManager};
+ } else {
+ keyManager = null;
+ }
+ sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()}, mXmppConnectionService.getRNG());
+ final SSLSocketFactory factory = sc.getSocketFactory();
+ final HostnameVerifier verifier;
+ if (mInteractive) {
+ verifier = trustManager.wrapHostnameVerifier(new XmppDomainVerifier());
+ } else {
+ verifier = trustManager.wrapHostnameVerifierNonInteractive(new XmppDomainVerifier());
}
+
+ return new TlsFactoryVerifier(factory, verifier);
+ }
+
+ @Override
+ public void run() {
+ forceCloseSocket();
connect();
}
- private void processStream(final Tag currentTag) throws XmlPullParserException,
- IOException, NoSuchAlgorithmException {
- Tag nextTag = tagReader.readTag();
-
- while ((nextTag != null) && (!nextTag.isEnd("stream"))) {
- if (nextTag.isStart("error")) {
- processStreamError(nextTag);
- } else if (nextTag.isStart("features")) {
- processStreamFeatures(nextTag);
- } else if (nextTag.isStart("proceed")) {
- switchOverToTls(nextTag);
- } else if (nextTag.isStart("success")) {
- final String challenge = tagReader.readElement(nextTag).getContent();
- try {
- saslMechanism.getResponse(challenge);
- } catch (final SaslMechanism.AuthenticationException e) {
- disconnect(true);
- Logging.e(Config.LOGTAG, String.valueOf(e));
- }
- Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in");
- account.setKey(Account.PINNED_MECHANISM_KEY,
- String.valueOf(saslMechanism.getPriority()));
- tagReader.reset();
- sendStartStream();
- processStream(tagReader.readTag());
- break;
- } else if (nextTag.isStart("failure")) {
- throw new UnauthorizedException();
- } else if (nextTag.isStart("challenge")) {
- final String challenge = tagReader.readElement(nextTag).getContent();
- final Element response = new Element("response");
- response.setAttribute("xmlns",
- "urn:ietf:params:xml:ns:xmpp-sasl");
- try {
- response.setContent(saslMechanism.getResponse(challenge));
- } catch (final SaslMechanism.AuthenticationException e) {
- // TODO: Send auth abort tag.
- Logging.e(Config.LOGTAG, e.toString());
- }
- tagWriter.writeElement(response);
- } else if (nextTag.isStart("enabled")) {
- final Element enabled = tagReader.readElement(nextTag);
- if ("true".equals(enabled.getAttribute("resume"))) {
- this.streamId = enabled.getAttribute("id");
- Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": stream managment(" + smVersion
- + ") enabled (resumable)");
- } else {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": stream managment(" + smVersion + ") enabled");
- }
- this.lastSessionStarted = SystemClock.elapsedRealtime();
- this.stanzasReceived = 0;
- final RequestPacket r = new RequestPacket(smVersion);
- tagWriter.writeStanzaAsync(r);
- } else if (nextTag.isStart("resumed")) {
- lastPacketReceived = SystemClock.elapsedRealtime();
- final Element resumed = tagReader.readElement(nextTag);
- final String h = resumed.getAttribute("h");
- try {
- final int serverCount = Integer.parseInt(h);
- if (serverCount != stanzasSent) {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": session resumed with lost packages");
- stanzasSent = serverCount;
- } else {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": session resumed");
- }
- if (acknowledgedListener != null) {
- for (int i = 0; i < messageReceipts.size(); ++i) {
- if (serverCount >= messageReceipts.keyAt(i)) {
- acknowledgedListener.onMessageAcknowledged(
- account, messageReceipts.valueAt(i));
- }
- }
- }
- messageReceipts.clear();
- } catch (final NumberFormatException ignored) {
- }
- sendServiceDiscoveryInfo(account.getServer());
- sendServiceDiscoveryInfo(account.getJid().toBareJid());
- sendServiceDiscoveryItems(account.getServer());
- sendInitialPing();
- } else if (nextTag.isStart("r")) {
- tagReader.readElement(nextTag);
- if (Config.EXTENDED_SM_LOGGING) {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": acknowledging stanza #" + this.stanzasReceived);
- }
- final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion);
- tagWriter.writeStanzaAsync(ack);
- } else if (nextTag.isStart("a")) {
- final Element ack = tagReader.readElement(nextTag);
- lastPacketReceived = SystemClock.elapsedRealtime();
- try {
- final int serverSequence = Integer.parseInt(ack.getAttribute("h"));
- if (Config.EXTENDED_SM_LOGGING) {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + serverSequence);
- }
- final String msgId = this.messageReceipts.get(serverSequence);
- if (msgId != null) {
- if (this.acknowledgedListener != null) {
- this.acknowledgedListener.onMessageAcknowledged(
- account, msgId);
- }
- this.messageReceipts.remove(serverSequence);
- }
- } catch (NumberFormatException e) {
- Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": server send ack without sequence number");
- }
- } else if (nextTag.isStart("failed")) {
- tagReader.readElement(nextTag);
- Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": resumption failed");
- streamId = null;
- if (account.getStatus() != Account.State.ONLINE) {
- sendBindRequest();
- }
- } else if (nextTag.isStart("iq")) {
- processIq(nextTag);
- } else if (nextTag.isStart("message")) {
- processMessage(nextTag);
- } else if (nextTag.isStart("presence")) {
- processPresence(nextTag);
- }
- nextTag = tagReader.readTag();
- }
- if (account.getStatus() == Account.State.ONLINE) {
- account. setStatus(Account.State.OFFLINE);
- if (statusListener != null) {
- statusListener.onStatusChanged(account);
- }
+ private void processStream() throws XmlPullParserException, IOException, NoSuchAlgorithmException {
+ Tag nextTag = tagReader.readTag();
+ while (nextTag != null && !nextTag.isEnd("stream")) {
+ if (nextTag.isStart("error")) {
+ processStreamError(nextTag);
+ } else if (nextTag.isStart("features")) {
+ processStreamFeatures(nextTag);
+ } else if (nextTag.isStart("proceed")) {
+ switchOverToTls(nextTag);
+ } else if (nextTag.isStart("success")) {
+ final String challenge = tagReader.readElement(nextTag).getContent();
+ try {
+ saslMechanism.getResponse(challenge);
+ } catch (final SaslMechanism.AuthenticationException e) {
+ disconnect(true);
+ Log.e(Config.LOGTAG, String.valueOf(e));
+ }
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in");
+ account.setKey(Account.PINNED_MECHANISM_KEY,
+ String.valueOf(saslMechanism.getPriority()));
+ tagReader.reset();
+ sendStartStream();
+ final Tag tag = tagReader.readTag();
+ if (tag != null && tag.isStart("stream")) {
+ processStream();
+ } else {
+ throw new IOException("server didn't restart stream after successful auth");
+ }
+ break;
+ } else if (nextTag.isStart("failure")) {
+ throw new UnauthorizedException();
+ } else if (nextTag.isStart("challenge")) {
+ final String challenge = tagReader.readElement(nextTag).getContent();
+ final Element response = new Element("response");
+ response.setAttribute("xmlns",
+ "urn:ietf:params:xml:ns:xmpp-sasl");
+ try {
+ response.setContent(saslMechanism.getResponse(challenge));
+ } catch (final SaslMechanism.AuthenticationException e) {
+ // TODO: Send auth abort tag.
+ Log.e(Config.LOGTAG, e.toString());
+ }
+ tagWriter.writeElement(response);
+ } else if (nextTag.isStart("enabled")) {
+ final Element enabled = tagReader.readElement(nextTag);
+ if ("true".equals(enabled.getAttribute("resume"))) {
+ this.streamId = enabled.getAttribute("id");
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
+ + ": stream managment(" + smVersion
+ + ") enabled (resumable)");
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
+ + ": stream management(" + smVersion + ") enabled");
+ }
+ this.stanzasReceived = 0;
+ final RequestPacket r = new RequestPacket(smVersion);
+ tagWriter.writeStanzaAsync(r);
+ } else if (nextTag.isStart("resumed")) {
+ lastPacketReceived = SystemClock.elapsedRealtime();
+ final Element resumed = tagReader.readElement(nextTag);
+ final String h = resumed.getAttribute("h");
+ try {
+ final int serverCount = Integer.parseInt(h);
+ if (serverCount != stanzasSent) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
+ + ": session resumed with lost packages");
+ stanzasSent = serverCount;
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": session resumed");
+ }
+ acknowledgeStanzaUpTo(serverCount);
+ ArrayList<AbstractAcknowledgeableStanza> failedStanzas = new ArrayList<>();
+ for(int i = 0; i < this.mStanzaQueue.size(); ++i) {
+ failedStanzas.add(mStanzaQueue.valueAt(i));
+ }
+ mStanzaQueue.clear();
+ Log.d(Config.LOGTAG,"resending "+failedStanzas.size()+" stanzas");
+ for(AbstractAcknowledgeableStanza packet : failedStanzas) {
+ if (packet instanceof MessagePacket) {
+ MessagePacket message = (MessagePacket) packet;
+ mXmppConnectionService.markMessage(account,
+ message.getTo().toBareJid(),
+ message.getId(),
+ Message.STATUS_UNSEND);
}
+ sendPacket(packet);
+ }
+ } catch (final NumberFormatException ignored) {
+ }
+ Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": online with resource " + account.getResource());
+ changeStatus(Account.State.ONLINE);
+ } else if (nextTag.isStart("r")) {
+ tagReader.readElement(nextTag);
+ if (Config.EXTENDED_SM_LOGGING) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": acknowledging stanza #" + this.stanzasReceived);
+ }
+ final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion);
+ tagWriter.writeStanzaAsync(ack);
+ } else if (nextTag.isStart("a")) {
+ final Element ack = tagReader.readElement(nextTag);
+ lastPacketReceived = SystemClock.elapsedRealtime();
+ try {
+ final int serverSequence = Integer.parseInt(ack.getAttribute("h"));
+ acknowledgeStanzaUpTo(serverSequence);
+ } catch (NumberFormatException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server send ack without sequence number");
+ }
+ } else if (nextTag.isStart("failed")) {
+ tagReader.readElement(nextTag);
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": resumption failed");
+ resetStreamId();
+ if (account.getStatus() != Account.State.ONLINE) {
+ sendBindRequest();
+ }
+ } else if (nextTag.isStart("iq")) {
+ processIq(nextTag);
+ } else if (nextTag.isStart("message")) {
+ processMessage(nextTag);
+ } else if (nextTag.isStart("presence")) {
+ processPresence(nextTag);
+ }
+ nextTag = tagReader.readTag();
+ }
+ throw new IOException("reached end of stream. last tag was "+nextTag);
}
- private void sendInitialPing() {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": sending intial ping");
- final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
- iq.setFrom(account.getJid());
- iq.addChild("ping", "urn:xmpp:ping");
- this.sendIqPacket(iq, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(final Account account, final IqPacket packet) {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": online with resource " + account.getResource());
- changeStatus(Account.State.ONLINE);
+ private void acknowledgeStanzaUpTo(int serverCount) {
+ for (int i = 0; i < mStanzaQueue.size(); ++i) {
+ if (serverCount >= mStanzaQueue.keyAt(i)) {
+ if (Config.EXTENDED_SM_LOGGING) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + mStanzaQueue.keyAt(i));
+ }
+ AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i);
+ if (stanza instanceof MessagePacket && acknowledgedListener != null) {
+ MessagePacket packet = (MessagePacket) stanza;
+ acknowledgedListener.onMessageAcknowledged(account, packet.getId());
+ }
+ mStanzaQueue.removeAt(i);
+ i--;
}
- });
+ }
}
private Element processPacket(final Tag currentTag, final int packetType)
@@ -446,26 +601,32 @@ public class XmppConnection implements Runnable {
this.jingleListener.onJinglePacketReceived(account,(JinglePacket) packet);
}
} else {
- if (packetCallbacks.containsKey(packet.getId())) {
- final Pair<IqPacket, OnIqPacketReceived> packetCallbackDuple = packetCallbacks.get(packet.getId());
- // Packets to the server should have responses from the server
- if (packetCallbackDuple.first.toServer(account)) {
- if (packet.fromServer(account)) {
- packetCallbackDuple.second.onIqPacketReceived(account, packet);
- packetCallbacks.remove(packet.getId());
- } else {
- Logging.e(Config.LOGTAG,account.getJid().toBareJid().toString()+": ignoring spoofed iq packet");
- }
- } else {
- if (packet.getFrom().equals(packetCallbackDuple.first.getTo())) {
- packetCallbackDuple.second.onIqPacketReceived(account, packet);
- packetCallbacks.remove(packet.getId());
+ OnIqPacketReceived callback = null;
+ synchronized (this.packetCallbacks) {
+ if (packetCallbacks.containsKey(packet.getId())) {
+ final Pair<IqPacket, OnIqPacketReceived> packetCallbackDuple = packetCallbacks.get(packet.getId());
+ // Packets to the server should have responses from the server
+ if (packetCallbackDuple.first.toServer(account)) {
+ if (packet.fromServer(account) || mServerIdentity == Identity.FACEBOOK) {
+ callback = packetCallbackDuple.second;
+ packetCallbacks.remove(packet.getId());
+ } else {
+ Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet");
+ }
} else {
- Logging.e(Config.LOGTAG,account.getJid().toBareJid().toString()+": ignoring spoofed iq packet");
+ if (packet.getFrom().equals(packetCallbackDuple.first.getTo())) {
+ callback = packetCallbackDuple.second;
+ packetCallbacks.remove(packet.getId());
+ } else {
+ Logging.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet");
+ }
}
+ } else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) {
+ callback = this.unregisteredIqListener;
}
- } else if (packet.getType() == IqPacket.TYPE.GET|| packet.getType() == IqPacket.TYPE.SET) {
- this.unregisteredIqListener.onIqPacketReceived(account, packet);
+ }
+ if (callback != null) {
+ callback.onIqPacketReceived(account,packet);
}
}
}
@@ -486,53 +647,44 @@ public class XmppConnection implements Runnable {
tagWriter.writeTag(startTLS);
}
+
+
private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException {
tagReader.readTag();
try {
- final SSLContext sc = SSLContext.getInstance("TLS");
- sc.init(null,new X509TrustManager[]{this.mXmppConnectionService.getMemorizingTrustManager()},mXmppConnectionService.getRNG());
- final SSLSocketFactory factory = sc.getSocketFactory();
- final HostnameVerifier verifier = this.mXmppConnectionService.getMemorizingTrustManager().wrapHostnameVerifier(new StrictHostnameVerifier());
+ final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
final InetAddress address = socket == null ? null : socket.getInetAddress();
- if (factory == null || address == null || verifier == null) {
+ if (address == null) {
throw new IOException("could not setup ssl");
}
- final SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,address.getHostAddress(), socket.getPort(),true);
+ final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true);
if (sslSocket == null) {
throw new IOException("could not initialize ssl socket");
}
- final String[] supportProtocols;
- final Collection<String> supportedProtocols = new LinkedList<>(
- Arrays.asList(sslSocket.getSupportedProtocols()));
- supportedProtocols.remove("SSLv3");
- supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]);
-
- sslSocket.setEnabledProtocols(supportProtocols);
-
- final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
- sslSocket.getSupportedCipherSuites());
- //Logging.d(Config.LOGTAG, "Using ciphers: " + Arrays.toString(cipherSuites));
- if (cipherSuites.length > 0) {
- sslSocket.setEnabledCipherSuites(cipherSuites);
- }
+ SSLSocketHelper.setSecurity(sslSocket);
- if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) {
- Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed");
+ if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), sslSocket.getSession())) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed");
throw new SecurityException();
}
tagReader.setInputStream(sslSocket.getInputStream());
tagWriter.setOutputStream(sslSocket.getOutputStream());
sendStartStream();
- Logging.d(Config.LOGTAG, account.getJid().toBareJid()+ ": TLS connection established");
+ Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": TLS connection established");
features.encryptionEnabled = true;
- processStream(tagReader.readTag());
+ final Tag tag = tagReader.readTag();
+ if (tag != null && tag.isStart("stream")) {
+ processStream();
+ } else {
+ throw new IOException("server didn't restart stream after STARTTLS");
+ }
sslSocket.close();
} catch (final NoSuchAlgorithmException | KeyManagementException e1) {
- Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed");
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
throw new SecurityException();
}
}
@@ -542,21 +694,26 @@ public class XmppConnection implements Runnable {
this.streamFeatures = tagReader.readElement(currentTag);
if (this.streamFeatures.hasChild("starttls") && !features.encryptionEnabled) {
sendStartTLS();
- } else if (this.streamFeatures.hasChild("register")
- && account.isOptionSet(Account.OPTION_REGISTER)
- && features.encryptionEnabled) {
- sendRegistryRequest();
+ } else if (this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) {
+ if (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS) {
+ sendRegistryRequest();
+ } else {
+ throw new IncompatibleServerException();
+ }
} else if (!this.streamFeatures.hasChild("register")
&& account.isOptionSet(Account.OPTION_REGISTER)) {
changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED);
disconnect(true);
} else if (this.streamFeatures.hasChild("mechanisms")
- && shouldAuthenticate && features.encryptionEnabled) {
+ && shouldAuthenticate
+ && (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS)) {
final List<String> mechanisms = extractMechanisms(streamFeatures
.findChild("mechanisms"));
final Element auth = new Element("auth");
auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
- if (mechanisms.contains("SCRAM-SHA-1")) {
+ if (mechanisms.contains("EXTERNAL") && account.getPrivateKeyAlias() != null) {
+ saslMechanism = new External(tagWriter, account, mXmppConnectionService.getRNG());
+ } else if (mechanisms.contains("SCRAM-SHA-1")) {
saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG());
} else if (mechanisms.contains("PLAIN")) {
saslMechanism = new Plain(tagWriter, account);
@@ -586,19 +743,18 @@ public class XmppConnection implements Runnable {
} else {
throw new IncompatibleServerException();
}
- } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:"
- + smVersion)
- && streamId != null) {
+ } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + smVersion) && streamId != null) {
if (Config.EXTENDED_SM_LOGGING) {
Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived);
}
final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion);
this.tagWriter.writeStanzaAsync(resume);
- } else if (this.streamFeatures.hasChild("bind") && shouldBind) {
- sendBindRequest();
- } else {
- disconnect(true);
- changeStatus(Account.State.INCOMPATIBLE_SERVER);
+ } else if (needsBinding) {
+ if (this.streamFeatures.hasChild("bind")) {
+ sendBindRequest();
+ } else {
+ throw new IncompatibleServerException();
+ }
}
}
@@ -611,6 +767,15 @@ public class XmppConnection implements Runnable {
return mechanisms;
}
+ public void sendCaptchaRegistryRequest(String id, Data data) {
+ if (data == null) {
+ setAccountCreationFailed("");
+ } else {
+ IqPacket request = getIqGenerator().generateCreateAccountWithCaptcha(account, id, data);
+ sendIqPacket(request, createPacketReceiveHandler());
+ }
+ }
+
private void sendRegistryRequest() {
final IqPacket register = new IqPacket(IqPacket.TYPE.GET);
register.query("jabber:iq:register");
@@ -619,59 +784,95 @@ public class XmppConnection implements Runnable {
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
- final Element instructions = packet.query().findChild("instructions");
- if (packet.query().hasChild("username")
+ boolean failed = false;
+ if (packet.getType() == IqPacket.TYPE.RESULT
+ && packet.query().hasChild("username")
&& (packet.query().hasChild("password"))) {
final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
final Element username = new Element("username").setContent(account.getUsername());
final Element password = new Element("password").setContent(account.getPassword());
register.query("jabber:iq:register").addChild(username);
register.query().addChild(password);
- sendIqPacket(register, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(final Account account, final IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- account.setOption(Account.OPTION_REGISTER,
- false);
- changeStatus(Account.State.REGISTRATION_SUCCESSFUL);
- } else if (packet.hasChild("error")
- && (packet.findChild("error")
- .hasChild("conflict"))) {
- changeStatus(Account.State.REGISTRATION_CONFLICT);
- } else {
- changeStatus(Account.State.REGISTRATION_FAILED);
- Logging.d(Config.LOGTAG, packet.toString());
- }
- disconnect(true);
+ sendIqPacket(register, createPacketReceiveHandler());
+ } else if (packet.getType() == IqPacket.TYPE.RESULT
+ && (packet.query().hasChild("x", "jabber:x:data"))) {
+ final Data data = Data.parse(packet.query().findChild("x", "jabber:x:data"));
+ final Element blob = packet.query().findChild("data", "urn:xmpp:bob");
+ final String id = packet.getId();
+
+ Bitmap captcha = null;
+ if (blob != null) {
+ try {
+ final String base64Blob = blob.getContent();
+ final byte[] strBlob = Base64.decode(base64Blob, Base64.DEFAULT);
+ InputStream stream = new ByteArrayInputStream(strBlob);
+ captcha = BitmapFactory.decodeStream(stream);
+ } catch (Exception e) {
+ //ignored
}
- });
+ } else {
+ try {
+ Field url = data.getFieldByName("url");
+ String urlString = url.findChildContent("value");
+ URL uri = new URL(urlString);
+ captcha = BitmapFactory.decodeStream(uri.openConnection().getInputStream());
+ } catch (IOException e) {
+ Logging.e(Config.LOGTAG, e.toString());
+ }
+ }
+
+ if (captcha != null) {
+ failed = !mXmppConnectionService.displayCaptchaRequest(account, id, data, captcha);
+ }
} else {
- changeStatus(Account.State.REGISTRATION_FAILED);
- disconnect(true);
- Logging.d(Config.LOGTAG, account.getJid().toBareJid()
- + ": could not register. instructions are"
- + (instructions != null ? instructions.getContent() : ""));
+ failed = true;
+ }
+
+ if (failed) {
+ final Element instructions = packet.query().findChild("instructions");
+ setAccountCreationFailed((instructions != null) ? instructions.getContent() : "");
}
}
});
}
+ private void setAccountCreationFailed(String instructions) {
+ changeStatus(Account.State.REGISTRATION_FAILED);
+ disconnect(true);
+ Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ + ": could not register. instructions are"
+ + instructions);
+ }
+
+ public void resetEverything() {
+ resetStreamId();
+ clearIqCallbacks();
+ mStanzaQueue.clear();
+ synchronized (this.disco) {
+ disco.clear();
+ }
+ }
+
private void sendBindRequest() {
- while(!mXmppConnectionService.areMessagesInitialized()) {
+ while(!mXmppConnectionService.areMessagesInitialized() && socket != null && !socket.isClosed()) {
try {
Thread.sleep(500);
} catch (final InterruptedException ignored) {
}
}
+ needsBinding = false;
+ clearIqCallbacks();
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind")
.addChild("resource").setContent(account.getResource());
this.sendUnmodifiedIqPacket(iq, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ return;
+ }
final Element bind = packet.findChild("bind");
- if (bind != null) {
+ if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) {
final Element jid = bind.findChild("jid");
if (jid != null && jid.getContent() != null) {
try {
@@ -685,24 +886,71 @@ public class XmppConnection implements Runnable {
sendPostBindInitialization();
}
} else {
+ Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure");
disconnect(true);
}
} else {
+ Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure");
disconnect(true);
}
}
});
}
+ private void clearIqCallbacks() {
+ final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.TIMEOUT);
+ final ArrayList<OnIqPacketReceived> callbacks = new ArrayList<>();
+ synchronized (this.packetCallbacks) {
+ if (this.packetCallbacks.size() == 0) {
+ return;
+ }
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": clearing "+this.packetCallbacks.size()+" iq callbacks");
+ final Iterator<Pair<IqPacket, OnIqPacketReceived>> iterator = this.packetCallbacks.values().iterator();
+ while (iterator.hasNext()) {
+ Pair<IqPacket, OnIqPacketReceived> entry = iterator.next();
+ callbacks.add(entry.second);
+ iterator.remove();
+ }
+ }
+ for(OnIqPacketReceived callback : callbacks) {
+ callback.onIqPacketReceived(account,failurePacket);
+ }
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": done clearing iq callbacks. " + this.packetCallbacks.size() + " left");
+ }
+
+ public void sendDiscoTimeout() {
+ final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.ERROR); //don't use timeout
+ final ArrayList<OnIqPacketReceived> callbacks = new ArrayList<>();
+ synchronized (this.mPendingServiceDiscoveriesIds) {
+ for(String id : mPendingServiceDiscoveriesIds) {
+ synchronized (this.packetCallbacks) {
+ Pair<IqPacket, OnIqPacketReceived> pair = this.packetCallbacks.remove(id);
+ if (pair != null) {
+ callbacks.add(pair.second);
+ }
+ }
+ }
+ this.mPendingServiceDiscoveriesIds.clear();
+ }
+ if (callbacks.size() > 0) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": sending disco timeout");
+ resetStreamId(); //we don't want to live with this for ever
+ }
+ for(OnIqPacketReceived callback : callbacks) {
+ callback.onIqPacketReceived(account,failurePacket);
+ }
+ }
+
private void sendStartSession() {
final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET);
- startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session");
+ startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session");
this.sendUnmodifiedIqPacket(startSession, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.RESULT) {
sendPostBindInitialization();
- } else {
+ } else if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions");
disconnect(true);
}
}
@@ -720,56 +968,96 @@ public class XmppConnection implements Runnable {
final EnablePacket enable = new EnablePacket(smVersion);
tagWriter.writeStanzaAsync(enable);
stanzasSent = 0;
- messageReceipts.clear();
+ mStanzaQueue.clear();
}
features.carbonsEnabled = false;
features.blockListRequested = false;
- disco.clear();
- sendServiceDiscoveryInfo(account.getServer());
- sendServiceDiscoveryInfo(account.getJid().toBareJid());
+ synchronized (this.disco) {
+ this.disco.clear();
+ }
+ mPendingServiceDiscoveries = mServerIdentity == Identity.NIMBUZZ ? 1 : 0;
+ lastDiscoStarted = SystemClock.elapsedRealtime();
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": starting service discovery");
+ mXmppConnectionService.scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode());
sendServiceDiscoveryItems(account.getServer());
- if (bindListener != null) {
- bindListener.onBind(account);
+ Element caps = streamFeatures.findChild("c");
+ final String hash = caps == null ? null : caps.getAttribute("hash");
+ final String ver = caps == null ? null : caps.getAttribute("ver");
+ ServiceDiscoveryResult discoveryResult = null;
+ if (hash != null && ver != null) {
+ discoveryResult = mXmppConnectionService.databaseBackend.findDiscoveryResult(hash, ver);
}
- sendInitialPing();
+ if (discoveryResult == null) {
+ sendServiceDiscoveryInfo(account.getServer());
+ } else {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server caps came from cache");
+ disco.put(account.getServer(), discoveryResult);
+ }
+ sendServiceDiscoveryInfo(account.getJid().toBareJid());
+ this.lastSessionStarted = SystemClock.elapsedRealtime();
}
private void sendServiceDiscoveryInfo(final Jid jid) {
- if (disco.containsKey(jid)) {
- if (account.getServer().equals(jid)) {
- enableAdvancedStreamFeatures();
- }
- } else {
- final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
- iq.setTo(jid);
- iq.query("http://jabber.org/protocol/disco#info");
- this.sendIqPacket(iq, new OnIqPacketReceived() {
+ if (mServerIdentity != Identity.NIMBUZZ) {
+ mPendingServiceDiscoveries++;
+ }
+ final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
+ iq.setTo(jid);
+ iq.query("http://jabber.org/protocol/disco#info");
+ String id = this.sendIqPacket(iq, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(final Account account, final IqPacket packet) {
- final List<Element> elements = packet.query().getChildren();
- final Info info = new Info();
- for (final Element element : elements) {
- if (element.getName().equals("identity")) {
- String type = element.getAttribute("type");
- String category = element.getAttribute("category");
- if (type != null && category != null) {
- info.identities.add(new Pair<>(category,type));
- }
- } else if (element.getName().equals("feature")) {
- info.features.add(element.getAttribute("var"));
+ @Override
+ public void onIqPacketReceived(final Account account, final IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ boolean advancedStreamFeaturesLoaded;
+ synchronized (XmppConnection.this.disco) {
+ ServiceDiscoveryResult result = new ServiceDiscoveryResult(packet);
+ for (final ServiceDiscoveryResult.Identity id : result.getIdentities()) {
+ if (mServerIdentity == Identity.UNKNOWN && id.getType().equals("im") &&
+ id.getCategory().equals("server") && id.getName() != null &&
+ jid.equals(account.getServer())) {
+ switch (id.getName()) {
+ case "Prosody":
+ mServerIdentity = Identity.PROSODY;
+ break;
+ case "ejabberd":
+ mServerIdentity = Identity.EJABBERD;
+ break;
+ case "Slack-XMPP":
+ mServerIdentity = Identity.SLACK;
+ break;
+ }
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server name: " + id.getName());
+ }
+ }
+ if (jid.equals(account.getServer())) {
+ mXmppConnectionService.databaseBackend.insertDiscoveryResult(result);
}
+ disco.put(jid, result);
+ advancedStreamFeaturesLoaded = disco.containsKey(account.getServer())
+ && disco.containsKey(account.getJid().toBareJid());
}
- disco.put(jid, info);
-
- if (account.getServer().equals(jid)) {
+ if (advancedStreamFeaturesLoaded && (jid.equals(account.getServer()) || jid.equals(account.getJid().toBareJid()))) {
enableAdvancedStreamFeatures();
- for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) {
- listener.onAdvancedStreamFeaturesAvailable(account);
+ }
+ } else {
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString());
+ }
+ if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
+ mPendingServiceDiscoveries--;
+ if (mPendingServiceDiscoveries == 0) {
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": done with service discovery");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource());
+ if (bindListener != null) {
+ bindListener.onBind(account);
}
+ changeStatus(Account.State.ONLINE);
}
}
- });
+ }
+ });
+ synchronized (this.mPendingServiceDiscoveriesIds) {
+ this.mPendingServiceDiscoveriesIds.add(id);
}
}
@@ -778,9 +1066,12 @@ public class XmppConnection implements Runnable {
sendEnableCarbons();
}
if (getFeatures().blocking() && !features.blockListRequested) {
- Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": Requesting block list");
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Requesting block list");
this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser());
}
+ for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) {
+ listener.onAdvancedStreamFeaturesAvailable(account);
+ }
}
private void sendServiceDiscoveryItems(final Jid server) {
@@ -791,14 +1082,18 @@ public class XmppConnection implements Runnable {
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
- final List<Element> elements = packet.query().getChildren();
- for (final Element element : elements) {
- if (element.getName().equals("item")) {
- final Jid jid = element.getAttributeAsJid("jid");
- if (jid != null && !jid.equals(account.getServer())) {
- sendServiceDiscoveryInfo(jid);
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ final List<Element> elements = packet.query().getChildren();
+ for (final Element element : elements) {
+ if (element.getName().equals("item")) {
+ final Jid jid = element.getAttributeAsJid("jid");
+ if (jid != null && !jid.equals(account.getServer())) {
+ sendServiceDiscoveryInfo(jid);
+ }
}
}
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco items of " + server);
}
}
});
@@ -832,12 +1127,13 @@ public class XmppConnection implements Runnable {
Logging.d(Config.LOGTAG,
account.getJid().toBareJid() + ": switching resource due to conflict ("
+ account.getResource() + ")");
+ } else if (streamError != null) {
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": stream error "+streamError.toString());
}
}
private void sendStartStream() throws IOException {
final Tag stream = Tag.start("stream:stream");
- stream.setAttribute("from", account.getJid().toBareJid().toString());
stream.setAttribute("to", account.getServer().toString());
stream.setAttribute("version", "1.0");
stream.setAttribute("xml:lang", "en");
@@ -850,24 +1146,23 @@ public class XmppConnection implements Runnable {
return new BigInteger(50, mXmppConnectionService.getRNG()).toString(32);
}
- public void sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) {
+ public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) {
packet.setFrom(account.getJid());
- this.sendUnmodifiedIqPacket(packet,callback);
-
+ return this.sendUnmodifiedIqPacket(packet, callback);
}
- private synchronized void sendUnmodifiedIqPacket(final IqPacket packet, final OnIqPacketReceived callback) {
+ private synchronized String sendUnmodifiedIqPacket(final IqPacket packet, final OnIqPacketReceived callback) {
if (packet.getId() == null) {
final String id = nextRandomId();
packet.setAttribute("id", id);
}
if (callback != null) {
- if (packet.getId() == null) {
- packet.setId(nextRandomId());
+ synchronized (this.packetCallbacks) {
+ packetCallbacks.put(packet.getId(), new Pair<>(packet, callback));
}
- packetCallbacks.put(packet.getId(), new Pair<>(packet, callback));
}
this.sendPacket(packet);
+ return packet.getId();
}
public void sendMessagePacket(final MessagePacket packet) {
@@ -884,24 +1179,22 @@ public class XmppConnection implements Runnable {
disconnect(true);
return;
}
- final String name = packet.getName();
- if (name.equals("iq") || name.equals("message") || name.equals("presence")) {
- ++stanzasSent;
- }
tagWriter.writeStanzaAsync(packet);
- if (packet instanceof MessagePacket && packet.getId() != null && getFeatures().sm()) {
- if (Config.EXTENDED_SM_LOGGING) {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent);
+ if (packet instanceof AbstractAcknowledgeableStanza) {
+ AbstractAcknowledgeableStanza stanza = (AbstractAcknowledgeableStanza) packet;
+ ++stanzasSent;
+ this.mStanzaQueue.put(stanzasSent, stanza);
+ if (stanza instanceof MessagePacket && stanza.getId() != null && getFeatures().sm()) {
+ if (Config.EXTENDED_SM_LOGGING) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent);
+ }
+ tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion));
}
- this.messageReceipts.put(stanzasSent, packet.getId());
- tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion));
}
}
public void sendPing() {
- if (streamFeatures.hasChild("sm")) {
- tagWriter.writeStanzaAsync(new RequestPacket(smVersion));
- } else {
+ if (!r()) {
final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
iq.setFrom(account.getJid());
iq.addChild("ping", "urn:xmpp:ping");
@@ -948,37 +1241,71 @@ public class XmppConnection implements Runnable {
}
}
- public void disconnect(final boolean force) {
- Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting");
- try {
- if (force) {
- socket.close();
- return;
- }
+ public void waitForPush() {
+ if (tagWriter.isActive()) {
+ tagWriter.finish();
new Thread(new Runnable() {
-
@Override
public void run() {
- if (tagWriter.isActive()) {
- tagWriter.finish();
- try {
- while (!tagWriter.finished()) {
- Logging.d(Config.LOGTAG, "not yet finished");
- Thread.sleep(100);
- }
- tagWriter.writeTag(Tag.end("stream:stream"));
- socket.close();
- } catch (final IOException e) {
- Logging.d(Config.LOGTAG,
- "io exception during disconnect");
- } catch (final InterruptedException e) {
- Logging.d(Config.LOGTAG, "interrupted");
+ try {
+ while(!tagWriter.finished()) {
+ Thread.sleep(10);
}
+ socket.close();
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
}
}
}).start();
- } catch (final IOException e) {
- Logging.d(Config.LOGTAG, "io exception during disconnect");
+ } else {
+ forceCloseSocket();
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
+ }
+ }
+
+ private void forceCloseSocket() {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": exception during force close ("+e.getMessage()+")");
+ }
+ }
+ }
+
+ public void disconnect(final boolean force) {
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force="+Boolean.valueOf(force));
+ if (force) {
+ forceCloseSocket();
+ return;
+ } else {
+ if (tagWriter.isActive()) {
+ tagWriter.finish();
+ try {
+ int i = 0;
+ boolean warned = false;
+ while (!tagWriter.finished() && socket.isConnected() && i <= 10) {
+ if (!warned) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid()+": waiting for tag writer to finish");
+ warned = true;
+ }
+ Thread.sleep(200);
+ i++;
+ }
+ if (warned) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": tag writer has finished");
+ }
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closing stream");
+ tagWriter.writeTag(Tag.end("stream:stream"));
+ } catch (final IOException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": io exception during disconnect ("+e.getMessage()+")");
+ } catch (final InterruptedException e) {
+ Log.d(Config.LOGTAG, "interrupted");
+ }
+ }
}
}
@@ -987,13 +1314,15 @@ public class XmppConnection implements Runnable {
}
public List<Jid> findDiscoItemsByFeature(final String feature) {
- final List<Jid> items = new ArrayList<>();
- for (final Entry<Jid, Info> cursor : disco.entrySet()) {
- if (cursor.getValue().features.contains(feature)) {
- items.add(cursor.getKey());
+ synchronized (this.disco) {
+ final List<Jid> items = new ArrayList<>();
+ for (final Entry<Jid, ServiceDiscoveryResult> cursor : this.disco.entrySet()) {
+ if (cursor.getValue().getFeatures().contains(feature)) {
+ items.add(cursor.getKey());
+ }
}
+ return items;
}
- return items;
}
public Jid findDiscoItemByFeature(final String feature) {
@@ -1004,17 +1333,24 @@ public class XmppConnection implements Runnable {
return null;
}
- public void r() {
- this.tagWriter.writeStanzaAsync(new RequestPacket(smVersion));
+ public boolean r() {
+ if (getFeatures().sm()) {
+ this.tagWriter.writeStanzaAsync(new RequestPacket(smVersion));
+ return true;
+ } else {
+ return false;
+ }
}
public String getMucServer() {
- for (final Entry<Jid, Info> cursor : disco.entrySet()) {
- final Info value = cursor.getValue();
- if (value.features.contains("http://jabber.org/protocol/muc")
- && !value.features.contains("jabber:iq:gateway")
- && !value.identities.contains(new Pair<>("conference","irc"))) {
- return cursor.getKey().toString();
+ synchronized (this.disco) {
+ for (final Entry<Jid, ServiceDiscoveryResult> cursor : disco.entrySet()) {
+ final ServiceDiscoveryResult value = cursor.getValue();
+ if (value.getFeatures().contains("http://jabber.org/protocol/muc")
+ && !value.getFeatures().contains("jabber:iq:gateway")
+ && !value.hasIdentity("conference", "irc")) {
+ return cursor.getKey().toString();
+ }
}
}
return null;
@@ -1052,6 +1388,9 @@ public class XmppConnection implements Runnable {
return this.lastPingSent;
}
+ public long getLastDiscoStarted() {
+ return this.lastDiscoStarted;
+ }
public long getLastPacketReceived() {
return this.lastPacketReceived;
}
@@ -1069,9 +1408,12 @@ public class XmppConnection implements Runnable {
this.lastConnect = 0;
}
- private class Info {
- public final ArrayList<String> features = new ArrayList<>();
- public final ArrayList<Pair<String,String>> identities = new ArrayList<>();
+ public void setInteractive(boolean interactive) {
+ this.mInteractive = interactive;
+ }
+
+ public Identity getServerIdentity() {
+ return mServerIdentity;
}
private class UnauthorizedException extends IOException {
@@ -1086,6 +1428,15 @@ public class XmppConnection implements Runnable {
}
+ public enum Identity {
+ FACEBOOK,
+ SLACK,
+ EJABBERD,
+ PROSODY,
+ NIMBUZZ,
+ UNKNOWN
+ }
+
public class Features {
XmppConnection connection;
private boolean carbonsEnabled = false;
@@ -1097,8 +1448,10 @@ public class XmppConnection implements Runnable {
}
private boolean hasDiscoFeature(final Jid server, final String feature) {
- return connection.disco.containsKey(server) &&
- connection.disco.get(server).features.contains(feature);
+ synchronized (XmppConnection.this.disco) {
+ return connection.disco.containsKey(server) &&
+ connection.disco.get(server).getFeatures().contains(feature);
+ }
}
public boolean carbons() {
@@ -1123,26 +1476,26 @@ public class XmppConnection implements Runnable {
}
public boolean pep() {
- final Pair<String,String> needle = new Pair<>("pubsub","pep");
- Info info = disco.get(account.getServer());
- if (info != null && info.identities.contains(needle)) {
- return true;
- } else {
- info = disco.get(account.getJid().toBareJid());
- return info != null && info.identities.contains(needle);
+ synchronized (XmppConnection.this.disco) {
+ final Pair<String, String> needle = new Pair<>("pubsub", "pep");
+ ServiceDiscoveryResult info = disco.get(account.getServer());
+ if (info != null && info.hasIdentity("pubsub", "pep")) {
+ return true;
+ } else {
+ info = disco.get(account.getJid().toBareJid());
+ return info != null && info.hasIdentity("pubsub", "pep");
+ }
}
}
public boolean mam() {
- if (hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:mam:0")) {
- return true;
- } else {
- return hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0");
- }
+ return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:mam:0")
+ || hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0");
}
- public boolean advancedStreamFeaturesLoaded() {
- return disco.containsKey(account.getServer());
+ public boolean push() {
+ return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:push:0")
+ || hasDiscoFeature(account.getServer(), "urn:xmpp:push:0");
}
public boolean rosterVersioning() {
@@ -1154,7 +1507,7 @@ public class XmppConnection implements Runnable {
}
public boolean httpUpload() {
- return findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD).size() > 0;
+ return !Config.DISABLE_HTTP_UPLOAD && findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD).size() > 0;
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java
index bb7e557e..1574604e 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java
@@ -1,9 +1,9 @@
package de.thedevstack.conversationsplus.xmpp.avatar;
+import de.thedevstack.conversationsplus.xmpp.pubsub.PubSubPacketGenerator;
import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xmpp.jid.Jid;
import de.thedevstack.conversationsplus.xmpp.pep.Avatar;
-import de.thedevstack.conversationsplus.xmpp.pubsub.PubSubPacketGenerator;
import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
/**
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java
index 26d5202f..0867524b 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java
@@ -1,7 +1,7 @@
package de.thedevstack.conversationsplus.xmpp.avatar;
-import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xmpp.pubsub.PubSubPacketParser;
+import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
/**
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/chatstate/ChatState.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatstate/ChatState.java
index 929d485a..323d99c2 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/chatstate/ChatState.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatstate/ChatState.java
@@ -4,7 +4,7 @@ import de.thedevstack.conversationsplus.xml.Element;
public enum ChatState {
- ACTIVE, INACTIVE, GONE, COMPOSING, PAUSED, mIncomingChatState;
+ ACTIVE, INACTIVE, GONE, COMPOSING, PAUSED;
public static ChatState parse(Element element) {
final String NAMESPACE = "http://jabber.org/protocol/chatstates";
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Data.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Data.java
index 591d8ada..cb612a07 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Data.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Data.java
@@ -53,16 +53,16 @@ public class Data extends Element {
public void submit() {
this.setAttribute("type","submit");
- removeNonFieldChildren();
+ removeUnnecessaryChildren();
for(Field field : getFields()) {
field.removeNonValueChildren();
}
}
- private void removeNonFieldChildren() {
+ private void removeUnnecessaryChildren() {
for(Iterator<Element> iterator = this.children.iterator(); iterator.hasNext();) {
Element element = iterator.next();
- if (!element.getName().equals("field")) {
+ if (!element.getName().equals("field") && !element.getName().equals("title")) {
iterator.remove();
}
}
@@ -76,10 +76,20 @@ public class Data extends Element {
}
public void setFormType(String formType) {
- this.put("FORM_TYPE",formType);
+ this.put("FORM_TYPE", formType);
}
public String getFormType() {
- return this.getAttribute("FORM_TYPE");
+ String type = getValue("FORM_TYPE");
+ return type == null ? "" : type;
+ }
+
+ public String getValue(String name) {
+ Field field = this.getFieldByName(name);
+ return field == null ? null : field.getValue();
+ }
+
+ public String getTitle() {
+ return findChildContent("title");
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Field.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Field.java
index 9eb2d08d..88b3155c 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Field.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Field.java
@@ -1,7 +1,9 @@
package de.thedevstack.conversationsplus.xmpp.forms;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
import de.thedevstack.conversationsplus.xml.Element;
@@ -16,7 +18,7 @@ public class Field extends Element {
super("field");
}
- public String getName() {
+ public String getFieldName() {
return this.getAttribute("var");
}
@@ -47,4 +49,33 @@ public class Field extends Element {
field.setChildren(element.getChildren());
return field;
}
+
+ public String getValue() {
+ return findChildContent("value");
+ }
+
+ public List<String> getValues() {
+ List<String> values = new ArrayList<>();
+ for(Element child : getChildren()) {
+ if ("value".equals(child.getName())) {
+ String content = child.getContent();
+ if (content != null) {
+ values.add(content);
+ }
+ }
+ }
+ return values;
+ }
+
+ public String getLabel() {
+ return getAttribute("label");
+ }
+
+ public String getType() {
+ return getAttribute("type");
+ }
+
+ public boolean isRequired() {
+ return hasChild("required");
+ }
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jid/Jid.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jid/Jid.java
index 5aaf1563..bb32e821 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jid/Jid.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jid/Jid.java
@@ -176,7 +176,7 @@ public final class Jid {
return resourcepart.isEmpty() ? this : fromParts(localpart, domainpart, "");
} catch (final InvalidJidException e) {
// This should never happen.
- return null;
+ throw new AssertionError("Jid " + this.toString() + " invalid");
}
}
@@ -185,7 +185,7 @@ public final class Jid {
return resourcepart.isEmpty() && localpart.isEmpty() ? this : fromString(getDomainpart());
} catch (final InvalidJidException e) {
// This should never happen.
- return null;
+ throw new AssertionError("Jid " + this.toString() + " invalid");
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
index 7885cc0f..c4ceeb4e 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
@@ -1,5 +1,12 @@
package de.thedevstack.conversationsplus.xmpp.jingle;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Pair;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
@@ -8,22 +15,23 @@ import java.util.Locale;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.SystemClock;
-
import de.thedevstack.android.logcat.Logging;
-import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
+import de.thedevstack.conversationsplus.Config;
+import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService;
+import de.thedevstack.conversationsplus.crypto.axolotl.OnMessageCreatedCallback;
+import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlMessage;
import de.thedevstack.conversationsplus.entities.Account;
import de.thedevstack.conversationsplus.entities.Conversation;
-import de.thedevstack.conversationsplus.entities.Transferable;
import de.thedevstack.conversationsplus.entities.DownloadableFile;
-import de.thedevstack.conversationsplus.entities.TransferablePlaceholder;
import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.entities.Transferable;
+import de.thedevstack.conversationsplus.entities.TransferablePlaceholder;
import de.thedevstack.conversationsplus.persistance.FileBackend;
+import de.thedevstack.conversationsplus.services.AbstractConnectionManager;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
-import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived;
import de.thedevstack.conversationsplus.xmpp.jid.Jid;
@@ -63,20 +71,24 @@ public class JingleConnection implements Transferable {
private String contentCreator;
private int mProgress = 0;
- private long mLastGuiRefresh = 0;
private boolean receivedCandidate = false;
private boolean sentCandidate = false;
private boolean acceptedAutomatically = false;
+ private XmppAxolotlMessage mXmppAxolotlMessage;
+
private JingleTransport transport = null;
+ private OutputStream mFileOutputStream;
+ private InputStream mFileInputStream;
+
private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.ERROR) {
+ if (packet.getType() != IqPacket.TYPE.RESULT) {
fail();
}
}
@@ -88,15 +100,13 @@ public class JingleConnection implements Transferable {
public void onFileTransmitted(DownloadableFile file) {
if (responder.equals(account.getJid())) {
sendSuccess();
+ MessageUtil.updateFileParams(message);
+ mXmppConnectionService.databaseBackend.createMessage(message);
+ mXmppConnectionService.markMessage(message,Message.STATUS_RECEIVED);
if (acceptedAutomatically) {
message.markUnread();
- JingleConnection.this.mXmppConnectionService
- .getNotificationService().push(message);
+ JingleConnection.this.mXmppConnectionService.getNotificationService().push(message);
}
- MessageUtil.updateFileParams(message);
- mXmppConnectionService.databaseBackend.createMessage(message);
- mXmppConnectionService.markMessage(message,
- Message.STATUS_RECEIVED);
} else {
if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
file.delete();
@@ -107,6 +117,8 @@ public class JingleConnection implements Transferable {
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file));
mXmppConnectionService.sendBroadcast(intent);
+ } else {
+ account.getPgpDecryptionService().add(message);
}
}
@@ -117,6 +129,14 @@ public class JingleConnection implements Transferable {
}
};
+ public InputStream getFileInputStream() {
+ return this.mFileInputStream;
+ }
+
+ public OutputStream getFileOutputStream() {
+ return this.mFileOutputStream;
+ }
+
private OnProxyActivated onProxyActivated = new OnProxyActivated() {
@Override
@@ -198,7 +218,26 @@ public class JingleConnection implements Transferable {
mXmppConnectionService.sendIqPacket(account,response,null);
}
- public void init(Message message) {
+ public void init(final Message message) {
+ if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
+ Conversation conversation = message.getConversation();
+ conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation.getContact(), new OnMessageCreatedCallback() {
+ @Override
+ public void run(XmppAxolotlMessage xmppAxolotlMessage) {
+ if (xmppAxolotlMessage != null) {
+ init(message, xmppAxolotlMessage);
+ } else {
+ fail();
+ }
+ }
+ });
+ } else {
+ init(message, null);
+ }
+ }
+
+ private void init(Message message, XmppAxolotlMessage xmppAxolotlMessage) {
+ this.mXmppAxolotlMessage = xmppAxolotlMessage;
this.contentCreator = "initiator";
this.contentName = this.mJingleConnectionManager.nextRandomId();
this.message = message;
@@ -271,13 +310,16 @@ public class JingleConnection implements Transferable {
this.contentCreator = content.getAttribute("creator");
this.contentName = content.getAttribute("name");
this.transportId = content.getTransportId();
- this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
- .getChildren()));
+ this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
this.fileOffer = packet.getJingleContent().getFileOffer();
mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null);
if (fileOffer != null) {
+ Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX);
+ if (encrypted != null) {
+ this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().toBareJid());
+ }
Element fileSize = fileOffer.findChild("size");
Element fileNameElement = fileOffer.findChild("name");
if (fileNameElement != null) {
@@ -323,10 +365,9 @@ public class JingleConnection implements Transferable {
message.setBody(Long.toString(size));
conversation.add(message);
mXmppConnectionService.updateConversationUi();
- if (size <= ConversationsPlusPreferences.autoAcceptFileSize()
- && mXmppConnectionService.isDownloadAllowedInConnection()) {
- Logging.d(Config.LOGTAG, "auto accepting file from "
- + packet.getFrom());
+ if (mJingleConnectionManager.hasStoragePermission()
+ && size < ConversationsPlusPreferences.autoAcceptFileSize()) {
+ Logging.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom());
this.acceptedAutomatically = true;
this.sendAccept();
} else {
@@ -336,21 +377,36 @@ public class JingleConnection implements Transferable {
+ size
+ " allowed size:"
+ ConversationsPlusPreferences.autoAcceptFileSize());
- this.mXmppConnectionService.getNotificationService()
- .push(message);
+ this.mXmppConnectionService.getNotificationService().push(message);
}
this.file = FileBackend.getFile(message, false);
- if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+ if (mXmppAxolotlMessage != null) {
+ XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage);
+ if (transportMessage != null) {
+ message.setEncryption(Message.ENCRYPTION_AXOLOTL);
+ this.file.setKey(transportMessage.getKey());
+ this.file.setIv(transportMessage.getIv());
+ message.setAxolotlFingerprint(transportMessage.getFingerprint());
+ } else {
+ Logging.d(Config.LOGTAG,"could not process KeyTransportMessage");
+ }
+ } else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
byte[] key = conversation.getSymmetricKey();
if (key == null) {
this.sendCancel();
this.fail();
return;
} else {
- this.file.setKey(key);
+ this.file.setKeyAndIv(key);
}
}
- this.file.setExpectedSize(size);
+ this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file,message.getEncryption() == Message.ENCRYPTION_AXOLOTL);
+ if (message.getEncryption() == Message.ENCRYPTION_OTR && Config.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE) {
+ this.file.setExpectedSize((size / 16 + 1) * 16);
+ } else {
+ this.file.setExpectedSize(size);
+ }
+ Logging.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize());
} else {
this.sendCancel();
this.fail();
@@ -367,17 +423,34 @@ public class JingleConnection implements Transferable {
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
content.setTransportId(this.transportId);
this.file = FileBackend.getFile(message, false);
- if (message.getEncryption() == Message.ENCRYPTION_OTR) {
- Conversation conversation = this.message.getConversation();
- if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
- Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key");
- cancel();
+ Pair<InputStream,Integer> pair;
+ try {
+ if (message.getEncryption() == Message.ENCRYPTION_OTR) {
+ Conversation conversation = this.message.getConversation();
+ if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not set symmetric key");
+ cancel();
+ }
+ this.file.setKeyAndIv(conversation.getSymmetricKey());
+ pair = AbstractConnectionManager.createInputStream(this.file, false);
+ this.file.setExpectedSize(pair.second);
+ content.setFileOffer(this.file, true);
+ } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
+ this.file.setKey(mXmppAxolotlMessage.getInnerKey());
+ this.file.setIv(mXmppAxolotlMessage.getIV());
+ pair = AbstractConnectionManager.createInputStream(this.file, true);
+ this.file.setExpectedSize(pair.second);
+ content.setFileOffer(this.file, false).addChild(mXmppAxolotlMessage.toElement());
+ } else {
+ pair = AbstractConnectionManager.createInputStream(this.file, false);
+ this.file.setExpectedSize(pair.second);
+ content.setFileOffer(this.file, false);
}
- content.setFileOffer(this.file, true);
- this.file.setKey(conversation.getSymmetricKey());
- } else {
- content.setFileOffer(this.file, false);
+ } catch (FileNotFoundException e) {
+ cancel();
+ return;
}
+ this.mFileInputStream = pair.first;
this.transportId = this.mJingleConnectionManager.nextRandomId();
content.setTransportId(this.transportId);
content.socks5transport().setChildren(getCandidatesAsElements());
@@ -386,7 +459,7 @@ public class JingleConnection implements Transferable {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() != IqPacket.TYPE.ERROR) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer");
mJingleStatus = JINGLE_STATUS_INITIATED;
mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED);
@@ -571,12 +644,11 @@ public class JingleConnection implements Transferable {
@Override
public void onIqPacketReceived(Account account,
IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.ERROR) {
+ if (packet.getType() != IqPacket.TYPE.RESULT) {
onProxyActivated.failed();
} else {
onProxyActivated.success();
- sendProxyActivated(connection
- .getCandidate().getCid());
+ sendProxyActivated(connection.getCandidate().getCid());
}
}
});
@@ -675,8 +747,7 @@ public class JingleConnection implements Transferable {
JinglePacket answer = bootstrapPacket("transport-accept");
Content content = new Content("initiator", "a-file-offer");
content.setTransportId(this.transportId);
- content.ibbTransport().setAttribute("block-size",
- Integer.toString(this.ibbBlockSize));
+ content.ibbTransport().setAttribute("block-size",this.ibbBlockSize);
answer.setContent(content);
this.sendJinglePacket(answer);
return true;
@@ -749,6 +820,8 @@ public class JingleConnection implements Transferable {
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
this.transport.disconnect();
}
+ StreamUtil.close(mFileInputStream);
+ StreamUtil.close(mFileOutputStream);
if (this.message != null) {
if (this.responder.equals(account.getJid())) {
this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
@@ -902,10 +975,7 @@ public class JingleConnection implements Transferable {
public void updateProgress(int i) {
this.mProgress = i;
- if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) {
- this.mLastGuiRefresh = SystemClock.elapsedRealtime();
- mXmppConnectionService.updateConversationUi();
- }
+ mXmppConnectionService.updateConversationUi();
}
interface OnProxyActivated {
@@ -957,4 +1027,8 @@ public class JingleConnection implements Transferable {
public int getProgress() {
return this.mProgress;
}
+
+ public AbstractConnectionManager getConnectionManager() {
+ return this.mJingleConnectionManager;
+ }
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java
index 861082d8..3284a936 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java
@@ -1,23 +1,23 @@
package de.thedevstack.conversationsplus.xmpp.jingle;
+import android.annotation.SuppressLint;
+
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
-import android.annotation.SuppressLint;
import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.entities.Transferable;
import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.entities.Transferable;
import de.thedevstack.conversationsplus.services.AbstractConnectionManager;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
import de.thedevstack.conversationsplus.utils.Xmlns;
import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived;
-import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException;
import de.thedevstack.conversationsplus.xmpp.jid.Jid;
import de.thedevstack.conversationsplus.xmpp.jingle.stanzas.JinglePacket;
import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
@@ -83,7 +83,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
public void getPrimaryCandidate(Account account,
final OnPrimaryCandidateFound listener) {
- if (Config.NO_PROXY_LOOKUP) {
+ if (Config.DISABLE_PROXY_LOOKUP) {
listener.onPrimaryCandidateFound(false, null);
return;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleInbandTransport.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleInbandTransport.java
index 84d40b3e..7c830d0b 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleInbandTransport.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleInbandTransport.java
@@ -1,5 +1,7 @@
package de.thedevstack.conversationsplus.xmpp.jingle;
+import android.util.Base64;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -7,15 +9,12 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
-import android.util.Base64;
-
import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.entities.Account;
import de.thedevstack.conversationsplus.entities.DownloadableFile;
-import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.utils.CryptoHelper;
-import de.thedevstack.conversationsplus.utils.StreamUtil;
import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived;
import de.thedevstack.conversationsplus.xmpp.jid.Jid;
@@ -75,7 +74,7 @@ public class JingleInbandTransport extends JingleTransport {
@Override
public void onIqPacketReceived(Account account,
IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.ERROR) {
+ if (packet.getType() != IqPacket.TYPE.RESULT) {
callback.failed();
} else {
callback.established();
@@ -94,7 +93,7 @@ public class JingleInbandTransport extends JingleTransport {
digest.reset();
file.getParentFile().mkdirs();
file.createNewFile();
- this.fileOutputStream = file.createOutputStream();
+ this.fileOutputStream = connection.getFileOutputStream();
if (this.fileOutputStream == null) {
Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
callback.onFileTransferAborted();
@@ -113,15 +112,11 @@ public class JingleInbandTransport extends JingleTransport {
this.onFileTransmissionStatusChanged = callback;
this.file = file;
try {
- if (this.file.getKey() != null) {
- this.remainingSize = (this.file.getSize() / 16 + 1) * 16;
- } else {
- this.remainingSize = this.file.getSize();
- }
+ this.remainingSize = this.file.getExpectedSize();
this.fileSize = this.remainingSize;
this.digest = MessageDigest.getInstance("SHA-1");
this.digest.reset();
- fileInputStream = this.file.createInputStream();
+ fileInputStream = connection.getFileInputStream();
if (fileInputStream == null) {
Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
callback.onFileTransferAborted();
@@ -181,6 +176,7 @@ public class JingleInbandTransport extends JingleTransport {
data.setAttribute("sid", this.sessionId);
data.setContent(base64);
this.account.getXmppConnection().sendIqPacket(iq, this.onAckReceived);
+ this.account.getXmppConnection().r(); //don't fill up stanza queue too much
this.seq++;
if (this.remainingSize > 0) {
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleSocks5Transport.java
index 999180c5..9ea268d5 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleSocks5Transport.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleSocks5Transport.java
@@ -1,5 +1,7 @@
package de.thedevstack.conversationsplus.xmpp.jingle;
+import android.os.PowerManager;
+
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -10,14 +12,13 @@ import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.entities.DownloadableFile;
-import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.utils.CryptoHelper;
-import de.thedevstack.conversationsplus.utils.StreamUtil;
+import de.thedevstack.conversationsplus.utils.SocksSocketFactory;
public class JingleSocks5Transport extends JingleTransport {
private JingleCandidate candidate;
@@ -61,31 +62,12 @@ public class JingleSocks5Transport extends JingleTransport {
socket = new Socket();
SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
+
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
- byte[] login = { 0x05, 0x01, 0x00 };
- byte[] expectedReply = { 0x05, 0x00 };
- byte[] reply = new byte[2];
- outputStream.write(login);
- inputStream.read(reply);
- final String connect = Character.toString('\u0005')
- + '\u0001' + '\u0000' + '\u0003' + '\u0028'
- + destination + '\u0000' + '\u0000';
- if (Arrays.equals(reply, expectedReply)) {
- outputStream.write(connect.getBytes());
- byte[] result = new byte[2];
- inputStream.read(result);
- int status = result[1];
- if (status == 0) {
- isEstablished = true;
- callback.established();
- } else {
- callback.failed();
- }
- } else {
- socket.close();
- callback.failed();
- }
+ SocksSocketFactory.createSocksConnection(socket,destination,0);
+ isEstablished = true;
+ callback.established();
} catch (UnknownHostException e) {
callback.failed();
} catch (IOException e) {
@@ -96,23 +78,24 @@ public class JingleSocks5Transport extends JingleTransport {
}
- public void send(final DownloadableFile file,
- final OnFileTransmissionStatusChanged callback) {
+ public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
new Thread(new Runnable() {
@Override
public void run() {
InputStream fileInputStream = null;
+ final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_"+connection.getSessionId());
try {
+ wakeLock.acquire();
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
- fileInputStream = file.createInputStream();
+ fileInputStream = connection.getFileInputStream();
if (fileInputStream == null) {
Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
callback.onFileTransferAborted();
return;
}
- long size = file.getSize();
+ long size = file.getExpectedSize();
long transmitted = 0;
int count;
byte[] buffer = new byte[8192];
@@ -138,6 +121,7 @@ public class JingleSocks5Transport extends JingleTransport {
callback.onFileTransferAborted();
} finally {
StreamUtil.close(fileInputStream);
+ wakeLock.release();
}
}
}).start();
@@ -150,14 +134,16 @@ public class JingleSocks5Transport extends JingleTransport {
@Override
public void run() {
OutputStream fileOutputStream = null;
+ final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_"+connection.getSessionId());
try {
+ wakeLock.acquire();
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
- inputStream.skip(45);
+ //inputStream.skip(45);
socket.setSoTimeout(30000);
file.getParentFile().mkdirs();
file.createNewFile();
- fileOutputStream = file.createOutputStream();
+ fileOutputStream = connection.getFileOutputStream();
if (fileOutputStream == null) {
callback.onFileTransferAborted();
Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
@@ -166,7 +152,7 @@ public class JingleSocks5Transport extends JingleTransport {
double size = file.getExpectedSize();
long remainingSize = file.getExpectedSize();
byte[] buffer = new byte[8192];
- int count = buffer.length;
+ int count;
while (remainingSize > 0) {
count = inputStream.read(buffer);
if (count == -1) {
@@ -194,7 +180,9 @@ public class JingleSocks5Transport extends JingleTransport {
Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} finally {
+ wakeLock.release();
StreamUtil.close(fileOutputStream);
+ StreamUtil.close(inputStream);
}
}
}).start();
@@ -209,27 +197,9 @@ public class JingleSocks5Transport extends JingleTransport {
}
public void disconnect() {
- if (this.outputStream != null) {
- try {
- this.outputStream.close();
- } catch (IOException e) {
-
- }
- }
- if (this.inputStream != null) {
- try {
- this.inputStream.close();
- } catch (IOException e) {
-
- }
- }
- if (this.socket != null) {
- try {
- this.socket.close();
- } catch (IOException e) {
-
- }
- }
+ StreamUtil.close(inputStream);
+ StreamUtil.close(outputStream);
+ StreamUtil.close(socket);
}
public boolean isEstablished() {
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnFileTransmissionStatusChanged.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnFileTransmissionStatusChanged.java
index 86daae81..762b4551 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnFileTransmissionStatusChanged.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnFileTransmissionStatusChanged.java
@@ -3,7 +3,7 @@ package de.thedevstack.conversationsplus.xmpp.jingle;
import de.thedevstack.conversationsplus.entities.DownloadableFile;
public interface OnFileTransmissionStatusChanged {
- public void onFileTransmitted(DownloadableFile file);
+ void onFileTransmitted(DownloadableFile file);
- public void onFileTransferAborted();
+ void onFileTransferAborted();
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnJinglePacketReceived.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnJinglePacketReceived.java
index d6dda138..c87fd400 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnJinglePacketReceived.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnJinglePacketReceived.java
@@ -5,5 +5,5 @@ import de.thedevstack.conversationsplus.xmpp.PacketReceived;
import de.thedevstack.conversationsplus.xmpp.jingle.stanzas.JinglePacket;
public interface OnJinglePacketReceived extends PacketReceived {
- public void onJinglePacketReceived(Account account, JinglePacket packet);
+ void onJinglePacketReceived(Account account, JinglePacket packet);
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnPrimaryCandidateFound.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnPrimaryCandidateFound.java
index be1f1d02..4fe31d3b 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnPrimaryCandidateFound.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnPrimaryCandidateFound.java
@@ -1,6 +1,5 @@
package de.thedevstack.conversationsplus.xmpp.jingle;
public interface OnPrimaryCandidateFound {
- public void onPrimaryCandidateFound(boolean success,
- JingleCandidate canditate);
+ void onPrimaryCandidateFound(boolean success, JingleCandidate canditate);
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/stanzas/Content.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/stanzas/Content.java
index 40eec6f1..336a8c14 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/stanzas/Content.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/stanzas/Content.java
@@ -25,17 +25,18 @@ public class Content extends Element {
this.transportId = sid;
}
- public void setFileOffer(DownloadableFile actualFile, boolean otr) {
+ public Element setFileOffer(DownloadableFile actualFile, boolean otr) {
Element description = this.addChild("description",
"urn:xmpp:jingle:apps:file-transfer:3");
Element offer = description.addChild("offer");
Element file = offer.addChild("file");
- file.addChild("size").setContent(Long.toString(actualFile.getSize()));
+ file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize()));
if (otr) {
file.addChild("name").setContent(actualFile.getName() + ".otr");
} else {
file.addChild("name").setContent(actualFile.getName());
}
+ return file;
}
public Element getFileOffer() {
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/pep/Avatar.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/pep/Avatar.java
index 31fda8fd..9c1c8b9c 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/pep/Avatar.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/pep/Avatar.java
@@ -1,10 +1,10 @@
package de.thedevstack.conversationsplus.xmpp.pep;
+import android.util.Base64;
+
import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xmpp.jid.Jid;
-import android.util.Base64;
-
public class Avatar {
public enum Origin { PEP, VCARD };
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractAcknowledgeableStanza.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractAcknowledgeableStanza.java
new file mode 100644
index 00000000..49d9f799
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractAcknowledgeableStanza.java
@@ -0,0 +1,31 @@
+package de.thedevstack.conversationsplus.xmpp.stanzas;
+
+import de.thedevstack.conversationsplus.xml.Element;
+
+abstract public class AbstractAcknowledgeableStanza extends AbstractStanza {
+
+ protected AbstractAcknowledgeableStanza(String name) {
+ super(name);
+ }
+
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+
+ public void setId(final String id) {
+ setAttribute("id", id);
+ }
+
+ public Element getError() {
+ Element error = findChild("error");
+ if (error != null) {
+ for(Element element : error.getChildren()) {
+ if (!element.getName().equals("text")) {
+ return element;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractStanza.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractStanza.java
index dff5b58d..78c71541 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractStanza.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractStanza.java
@@ -18,10 +18,6 @@ public class AbstractStanza extends Element {
return getAttributeAsJid("from");
}
- public String getId() {
- return this.getAttribute("id");
- }
-
public void setTo(final Jid to) {
if (to != null) {
setAttribute("to", to.toString());
@@ -34,10 +30,6 @@ public class AbstractStanza extends Element {
}
}
- public void setId(final String id) {
- setAttribute("id", id);
- }
-
public boolean fromServer(final Account account) {
return getFrom() == null
|| getFrom().equals(account.getServer())
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java
index fb08556b..d86831e0 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java
@@ -2,14 +2,15 @@ package de.thedevstack.conversationsplus.xmpp.stanzas;
import de.thedevstack.conversationsplus.xml.Element;
-public class IqPacket extends AbstractStanza {
+public class IqPacket extends AbstractAcknowledgeableStanza {
- public static enum TYPE {
+ public enum TYPE {
ERROR,
SET,
RESULT,
GET,
- INVALID
+ INVALID,
+ TIMEOUT
}
public IqPacket(final TYPE type) {
@@ -39,6 +40,9 @@ public class IqPacket extends AbstractStanza {
public TYPE getType() {
final String type = getAttribute("type");
+ if (type == null) {
+ return TYPE.INVALID;
+ }
switch (type) {
case "error":
return TYPE.ERROR;
@@ -48,6 +52,8 @@ public class IqPacket extends AbstractStanza {
return TYPE.SET;
case "get":
return TYPE.GET;
+ case "timeout":
+ return TYPE.TIMEOUT;
default:
return TYPE.INVALID;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java
index 638ba5f4..e14cdacd 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java
@@ -2,12 +2,10 @@ package de.thedevstack.conversationsplus.xmpp.stanzas;
import android.util.Pair;
-import java.text.ParseException;
-
import de.thedevstack.conversationsplus.parser.AbstractParser;
import de.thedevstack.conversationsplus.xml.Element;
-public class MessagePacket extends AbstractStanza {
+public class MessagePacket extends AbstractAcknowledgeableStanza {
public static final int TYPE_CHAT = 0;
public static final int TYPE_NORMAL = 2;
public static final int TYPE_GROUPCHAT = 3;
@@ -29,6 +27,11 @@ public class MessagePacket extends AbstractStanza {
this.children.add(0, body);
}
+ public void setAxolotlMessage(Element axolotlMessage) {
+ this.children.remove(findChild("body"));
+ this.children.add(0, axolotlMessage);
+ }
+
public void setType(int type) {
switch (type) {
case TYPE_CHAT:
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/PresencePacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/PresencePacket.java
index dfe40f87..5fd21b3b 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/PresencePacket.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/PresencePacket.java
@@ -1,6 +1,6 @@
package de.thedevstack.conversationsplus.xmpp.stanzas;
-public class PresencePacket extends AbstractStanza {
+public class PresencePacket extends AbstractAcknowledgeableStanza {
public PresencePacket() {
super("presence");