diff options
Diffstat (limited to 'src/eu/siacs/conversations/xmpp/XmppConnection.java')
-rw-r--r-- | src/eu/siacs/conversations/xmpp/XmppConnection.java | 251 |
1 files changed, 174 insertions, 77 deletions
diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java index ba7a9245..36920929 100644 --- a/src/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java @@ -31,6 +31,8 @@ import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; import android.util.Log; +import android.util.SparseArray; +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; @@ -47,6 +49,8 @@ import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import eu.siacs.conversations.xmpp.stanzas.PresencePacket; +import eu.siacs.conversations.xmpp.stanzas.csi.ActivePacket; +import eu.siacs.conversations.xmpp.stanzas.csi.InactivePacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket; @@ -55,7 +59,6 @@ import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket; public class XmppConnection implements Runnable { protected Account account; - private static final String LOGTAG = "xmppService"; private WakeLock wakeLock; @@ -64,7 +67,7 @@ public class XmppConnection implements Runnable { private Socket socket; private XmlReader tagReader; private TagWriter tagWriter; - + private Features features = new Features(this); private boolean shouldBind = true; @@ -74,7 +77,8 @@ public class XmppConnection implements Runnable { private String streamId = null; private int smVersion = 3; - + private SparseArray<String> messageReceipts = new SparseArray<String>(); + private boolean usingCompression = false; private int stanzasReceived = 0; @@ -98,14 +102,15 @@ public class XmppConnection implements Runnable { private OnMessagePacketReceived messageListener = null; private OnStatusChanged statusListener = null; private OnBindListener bindListener = null; + private OnMessageAcknowledged acknowledgedListener = null; private MemorizingTrustManager mMemorizingTrustManager; public XmppConnection(Account account, XmppConnectionService service) { this.mRandom = service.getRNG(); this.mMemorizingTrustManager = service.getMemorizingTrustManager(); this.account = account; - this.wakeLock = service.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - account.getJid()); + this.wakeLock = service.getPowerManager().newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, account.getJid()); tagWriter = new TagWriter(); } @@ -128,7 +133,7 @@ public class XmppConnection implements Runnable { } protected void connect() { - Log.d(LOGTAG, account.getJid() + ": connecting"); + Log.d(Config.LOGTAG, account.getJid() + ": connecting"); usingCompression = false; lastConnect = SystemClock.elapsedRealtime(); lastPingSent = SystemClock.elapsedRealtime(); @@ -142,7 +147,7 @@ public class XmppConnection implements Runnable { this.changeStatus(Account.STATUS_CONNECTING); Bundle namePort = DNSHelper.getSRVRecord(account.getServer()); if ("timeout".equals(namePort.getString("error"))) { - Log.d(LOGTAG, account.getJid() + ": dns timeout"); + Log.d(Config.LOGTAG, account.getJid() + ": dns timeout"); this.changeStatus(Account.STATUS_OFFLINE); return; } @@ -151,13 +156,14 @@ public class XmppConnection implements Runnable { int srvRecordPort = namePort.getInt("port"); if (srvRecordServer != null) { if (srvIpServer != null) { - Log.d(LOGTAG, account.getJid() + ": using values from dns " - + srvRecordServer + "[" + srvIpServer + "]:" - + srvRecordPort); + Log.d(Config.LOGTAG, account.getJid() + + ": using values from dns " + srvRecordServer + + "[" + srvIpServer + "]:" + srvRecordPort); socket = new Socket(srvIpServer, srvRecordPort); } else { - Log.d(LOGTAG, account.getJid() + ": using values from dns " - + srvRecordServer + ":" + srvRecordPort); + Log.d(Config.LOGTAG, account.getJid() + + ": using values from dns " + srvRecordServer + + ":" + srvRecordPort); socket = new Socket(srvRecordServer, srvRecordPort); } } else { @@ -175,7 +181,8 @@ public class XmppConnection implements Runnable { processStream(nextTag); break; } else { - Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()); + Log.d(Config.LOGTAG, + "found unexpected tag: " + nextTag.getName()); return; } } @@ -185,27 +192,39 @@ public class XmppConnection implements Runnable { } catch (UnknownHostException e) { this.changeStatus(Account.STATUS_SERVER_NOT_FOUND); if (wakeLock.isHeld()) { - try { wakeLock.release();} catch (RuntimeException re) {} + try { + wakeLock.release(); + } catch (RuntimeException re) { + } } return; } catch (IOException e) { this.changeStatus(Account.STATUS_OFFLINE); if (wakeLock.isHeld()) { - try { wakeLock.release();} catch (RuntimeException re) {} + try { + wakeLock.release(); + } catch (RuntimeException re) { + } } return; } catch (NoSuchAlgorithmException e) { this.changeStatus(Account.STATUS_OFFLINE); - Log.d(LOGTAG, "compression exception " + e.getMessage()); + Log.d(Config.LOGTAG, "compression exception " + e.getMessage()); if (wakeLock.isHeld()) { - try { wakeLock.release();} catch (RuntimeException re) {} + try { + wakeLock.release(); + } catch (RuntimeException re) { + } } return; } catch (XmlPullParserException e) { this.changeStatus(Account.STATUS_OFFLINE); - Log.d(LOGTAG, "xml exception " + e.getMessage()); + Log.d(Config.LOGTAG, "xml exception " + e.getMessage()); if (wakeLock.isHeld()) { - try { wakeLock.release();} catch (RuntimeException re) {} + try { + wakeLock.release(); + } catch (RuntimeException re) { + } } return; } @@ -230,7 +249,7 @@ public class XmppConnection implements Runnable { } else if (nextTag.isStart("compressed")) { switchOverToZLib(nextTag); } else if (nextTag.isStart("success")) { - Log.d(LOGTAG, account.getJid() + ": logged in"); + Log.d(Config.LOGTAG, account.getJid() + ": logged in"); tagReader.readTag(); tagReader.reset(); sendStartStream(); @@ -245,18 +264,18 @@ public class XmppConnection implements Runnable { response.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); response.setContent(CryptoHelper.saslDigestMd5(account, - challange,mRandom)); + challange, mRandom)); tagWriter.writeElement(response); } else if (nextTag.isStart("enabled")) { - this.stanzasSent = 0; Element enabled = tagReader.readElement(nextTag); if ("true".equals(enabled.getAttribute("resume"))) { this.streamId = enabled.getAttribute("id"); - Log.d(LOGTAG, account.getJid() + ": stream managment(" - + smVersion + ") enabled (resumable)"); + Log.d(Config.LOGTAG, account.getJid() + + ": stream managment(" + smVersion + + ") enabled (resumable)"); } else { - Log.d(LOGTAG, account.getJid() + ": stream managment(" - + smVersion + ") enabled"); + Log.d(Config.LOGTAG, account.getJid() + + ": stream managment(" + smVersion + ") enabled"); } this.lastSessionStarted = SystemClock.elapsedRealtime(); this.stanzasReceived = 0; @@ -264,9 +283,30 @@ public class XmppConnection implements Runnable { tagWriter.writeStanzaAsync(r); } else if (nextTag.isStart("resumed")) { lastPaketReceived = SystemClock.elapsedRealtime(); - Log.d(LOGTAG, account.getJid() + ": session resumed"); - tagReader.readElement(nextTag); - sendPing(); + Element resumed = tagReader.readElement(nextTag); + String h = resumed.getAttribute("h"); + try { + int serverCount = Integer.parseInt(h); + if (serverCount != stanzasSent) { + Log.d(Config.LOGTAG, account.getJid() + + ": session resumed with lost packages"); + stanzasSent = serverCount; + } else { + Log.d(Config.LOGTAG, account.getJid() + + ": 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 (NumberFormatException e) { + + } changeStatus(Account.STATUS_ONLINE); } else if (nextTag.isStart("r")) { tagReader.readElement(nextTag); @@ -276,12 +316,17 @@ public class XmppConnection implements Runnable { Element ack = tagReader.readElement(nextTag); lastPaketReceived = SystemClock.elapsedRealtime(); int serverSequence = Integer.parseInt(ack.getAttribute("h")); - if (serverSequence > this.stanzasSent) { - this.stanzasSent = serverSequence; + String msgId = this.messageReceipts.get(serverSequence); + if (msgId != null) { + if (this.acknowledgedListener != null) { + this.acknowledgedListener.onMessageAcknowledged( + account, msgId); + } + this.messageReceipts.remove(serverSequence); } } else if (nextTag.isStart("failed")) { tagReader.readElement(nextTag); - Log.d(LOGTAG, account.getJid() + ": resumption failed"); + Log.d(Config.LOGTAG, account.getJid() + ": resumption failed"); streamId = null; if (account.getStatus() != Account.STATUS_ONLINE) { sendBindRequest(); @@ -321,7 +366,7 @@ public class XmppConnection implements Runnable { } element.setAttributes(currentTag.getAttributes()); Tag nextTag = tagReader.readTag(); - if (nextTag==null) { + if (nextTag == null) { throw new IOException("interrupted mid tag"); } while (!nextTag.isEnd(element.getName())) { @@ -335,7 +380,7 @@ public class XmppConnection implements Runnable { element.addChild(child); } nextTag = tagReader.readTag(); - if (nextTag==null) { + if (nextTag == null) { throw new IOException("interrupted mid tag"); } } @@ -420,7 +465,7 @@ public class XmppConnection implements Runnable { .setInputStream(new ZLibInputStream(tagReader.getInputStream())); sendStartStream(); - Log.d(LOGTAG, account.getJid() + ": compression enabled"); + Log.d(Config.LOGTAG, account.getJid() + ": compression enabled"); usingCompression = true; processStream(tagReader.readTag()); } @@ -436,23 +481,30 @@ public class XmppConnection implements Runnable { tagReader.readTag(); try { SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, new X509TrustManager[] { this.mMemorizingTrustManager }, mRandom); + sc.init(null, + new X509TrustManager[] { this.mMemorizingTrustManager }, + mRandom); SSLSocketFactory factory = sc.getSocketFactory(); - - HostnameVerifier verifier = this.mMemorizingTrustManager.wrapHostnameVerifier(new org.apache.http.conn.ssl.StrictHostnameVerifier()); + + HostnameVerifier verifier = this.mMemorizingTrustManager + .wrapHostnameVerifier(new org.apache.http.conn.ssl.StrictHostnameVerifier()); SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true); - - if (verifier != null && !verifier.verify(account.getServer(), sslSocket.getSession())) { - Log.d(LOGTAG, account.getJid() + ": host mismatch in TLS connection"); + + if (verifier != null + && !verifier.verify(account.getServer(), + sslSocket.getSession())) { + Log.d(Config.LOGTAG, account.getJid() + + ": host mismatch in TLS connection"); sslSocket.close(); throw new IOException(); } tagReader.setInputStream(sslSocket.getInputStream()); tagWriter.setOutputStream(sslSocket.getOutputStream()); sendStartStream(); - Log.d(LOGTAG, account.getJid() + ": TLS connection established"); + Log.d(Config.LOGTAG, account.getJid() + + ": TLS connection established"); processStream(tagReader.readTag()); sslSocket.close(); } catch (NoSuchAlgorithmException e1) { @@ -578,7 +630,7 @@ public class XmppConnection implements Runnable { changeStatus(Account.STATUS_REGISTRATION_CONFLICT); } else { changeStatus(Account.STATUS_REGISTRATION_FAILED); - Log.d(LOGTAG, packet.toString()); + Log.d(Config.LOGTAG, packet.toString()); } disconnect(true); } @@ -586,7 +638,7 @@ public class XmppConnection implements Runnable { } else { changeStatus(Account.STATUS_REGISTRATION_FAILED); disconnect(true); - Log.d(LOGTAG, account.getJid() + Log.d(Config.LOGTAG, account.getJid() + ": could not register. instructions are" + instructions.getContent()); } @@ -602,18 +654,23 @@ public class XmppConnection implements Runnable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { Element bind = packet.findChild("bind"); - if (bind!=null) { + if (bind != null) { Element jid = bind.findChild("jid"); - if (jid!=null) { + if (jid != null) { account.setResource(jid.getContent().split("/")[1]); if (streamFeatures.hasChild("sm", "urn:xmpp:sm:3")) { smVersion = 3; EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); - } else if (streamFeatures.hasChild("sm", "urn:xmpp:sm:2")) { + stanzasSent = 0; + messageReceipts.clear(); + } else if (streamFeatures.hasChild("sm", + "urn:xmpp:sm:2")) { smVersion = 2; EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); + stanzasSent = 0; + messageReceipts.clear(); } sendServiceDiscoveryInfo(account.getServer()); sendServiceDiscoveryItems(account.getServer()); @@ -630,7 +687,8 @@ public class XmppConnection implements Runnable { } }); if (this.streamFeatures.hasChild("session")) { - Log.d(LOGTAG, account.getJid() + ": sending deprecated session"); + Log.d(Config.LOGTAG, account.getJid() + + ": sending deprecated session"); IqPacket startSession = new IqPacket(IqPacket.TYPE_SET); startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); @@ -695,18 +753,23 @@ public class XmppConnection implements Runnable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (!packet.hasChild("error")) { - Log.d(LOGTAG, account.getJid() + Log.d(Config.LOGTAG, account.getJid() + ": successfully enabled carbons"); } else { - Log.d(LOGTAG, account.getJid() + Log.d(Config.LOGTAG, account.getJid() + ": error enableing carbons " + packet.toString()); } } }); } - private void processStreamError(Tag currentTag) { - Log.d(LOGTAG, "processStreamError"); + private void processStreamError(Tag currentTag) throws XmlPullParserException, IOException { + Element streamError = tagReader.readElement(currentTag); + if (streamError!=null && streamError.hasChild("conflict")) { + String resource = account.getResource().split("\\.")[0]; + account.setResource(resource+"."+nextRandomId()); + Log.d(Config.LOGTAG,account.getJid()+": switching resource due to conflict ("+account.getResource()+")"); + } } private void sendStartStream() throws IOException { @@ -748,12 +811,21 @@ public class XmppConnection implements Runnable { public void sendPresencePacket(PresencePacket packet) { this.sendPacket(packet, null); } - + private synchronized void sendPacket(final AbstractStanza packet, PacketReceived callback) { - // TODO dont increment stanza count if packet = request packet or ack; - ++stanzasSent; + if (packet.getName().equals("iq") || packet.getName().equals("message") + || packet.getName().equals("presence")) { + ++stanzasSent; + } tagWriter.writeStanzaAsync(packet); + if (packet instanceof MessagePacket && packet.getId() != null + && this.streamId != null) { + Log.d(Config.LOGTAG, "request delivery report for stanza " + + stanzasSent); + this.messageReceipts.put(stanzasSent, packet.getId()); + tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion)); + } if (callback != null) { if (packet.getId() == null) { packet.setId(nextRandomId()); @@ -802,9 +874,13 @@ public class XmppConnection implements Runnable { this.bindListener = listener; } + public void setOnMessageAcknowledgeListener(OnMessageAcknowledged listener) { + this.acknowledgedListener = listener; + } + public void disconnect(boolean force) { changeStatus(Account.STATUS_OFFLINE); - Log.d(LOGTAG, "disconnecting"); + Log.d(Config.LOGTAG, "disconnecting"); try { if (force) { socket.close(); @@ -818,20 +894,21 @@ public class XmppConnection implements Runnable { tagWriter.finish(); try { while (!tagWriter.finished()) { - Log.d(LOGTAG, "not yet finished"); + Log.d(Config.LOGTAG, "not yet finished"); Thread.sleep(100); } tagWriter.writeTag(Tag.end("stream:stream")); } catch (IOException e) { - Log.d(LOGTAG, "io exception during disconnect"); + Log.d(Config.LOGTAG, + "io exception during disconnect"); } catch (InterruptedException e) { - Log.d(LOGTAG, "interrupted"); + Log.d(Config.LOGTAG, "interrupted"); } } } }).start(); } catch (IOException e) { - Log.d(LOGTAG, "io exception during disconnect"); + Log.d(Config.LOGTAG, "io exception during disconnect"); } } @@ -844,10 +921,10 @@ public class XmppConnection implements Runnable { } return items; } - + public String findDiscoItemByFeature(String feature) { List<String> items = findDiscoItemsByFeature(feature); - if (items.size()>=1) { + if (items.size() >= 1) { return items.get(0); } return null; @@ -856,7 +933,7 @@ public class XmppConnection implements Runnable { public void r() { this.tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); } - + public String getMucServer() { return findDiscoItemByFeature("http://jabber.org/protocol/muc"); } @@ -870,28 +947,29 @@ public class XmppConnection implements Runnable { public int getAttempt() { return this.attempt; } - + public Features getFeatures() { return this.features; } - + public class Features { XmppConnection connection; + public Features(XmppConnection connection) { this.connection = connection; } - + private boolean hasDiscoFeature(String server, String feature) { if (!connection.disco.containsKey(server)) { return false; } return connection.disco.get(server).contains(feature); } - + public boolean carbons() { return hasDiscoFeature(account.getServer(), "urn:xmpp:carbons:2"); } - + public boolean sm() { if (connection.streamFeatures == null) { return false; @@ -899,11 +977,21 @@ public class XmppConnection implements Runnable { return connection.streamFeatures.hasChild("sm"); } } - + + public boolean csi() { + if (connection.streamFeatures == null) { + return false; + } else { + return connection.streamFeatures.hasChild("csi", + "urn:xmpp:csi:0"); + } + } + public boolean pubsub() { - return hasDiscoFeature(account.getServer(), "http://jabber.org/protocol/pubsub#publish"); + return hasDiscoFeature(account.getServer(), + "http://jabber.org/protocol/pubsub#publish"); } - + public boolean rosterVersioning() { if (connection.streamFeatures == null) { return false; @@ -911,11 +999,12 @@ public class XmppConnection implements Runnable { return connection.streamFeatures.hasChild("ver"); } } - + public boolean streamhost() { - return connection.findDiscoItemByFeature("http://jabber.org/protocol/bytestreams") != null; + return connection + .findDiscoItemByFeature("http://jabber.org/protocol/bytestreams") != null; } - + public boolean compression() { return connection.usingCompression; } @@ -930,16 +1019,24 @@ public class XmppConnection implements Runnable { } return System.currentTimeMillis() - diff; } - + public long getLastConnect() { return this.lastConnect; } - + public long getLastPingSent() { return this.lastPingSent; } - + public long getLastPacketReceived() { return this.lastPaketReceived; } + + public void sendActive() { + this.sendPacket(new ActivePacket(), null); + } + + public void sendInactive() { + this.sendPacket(new InactivePacket(), null); + } } |