Skip to content
This repository has been archived by the owner on Jan 25, 2018. It is now read-only.

XEP-0198 implementation #116

Merged
merged 2 commits into from
Mar 26, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 35 additions & 20 deletions src/info/guardianproject/otr/app/im/plugin/xmpp/XmppConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,16 @@
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.UnknownPacket;
import org.jivesoftware.smack.packet.Presence.Mode;
import org.jivesoftware.smack.packet.Presence.Type;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.proxy.ProxyInfo.ProxyType;
import org.jivesoftware.smackx.packet.VCard;
import org.xmlpull.v1.XmlPullParser;

import android.content.ContentResolver;
import android.content.Context;
Expand All @@ -79,7 +84,7 @@
public class XmppConnection extends ImConnection implements CallbackHandler
{

private final static String TAG = "Gibberbot.XmppConnection";
final static String TAG = "Gibberbot.XmppConnection";
private final static boolean DEBUG_ENABLED = false;

private XmppContactList mContactListManager;
Expand All @@ -88,6 +93,7 @@ public class XmppConnection extends ImConnection implements CallbackHandler
// watch out, this is a different XMPPConnection class than XmppConnection! ;)
// Synchronized by executor thread
private MyXMPPConnection mConnection;
private XmppStreamHandler mStreamHandler;

private XmppChatSessionManager mSessionManager;
private ConnectionConfiguration mConfig;
Expand Down Expand Up @@ -134,6 +140,8 @@ public XmppConnection(Context context) {

// Create a single threaded executor. This will serialize actions on the underlying connection.
createExecutor();

XmppStreamHandler.addExtensionProviders();
}

private void createExecutor() {
Expand Down Expand Up @@ -530,9 +538,6 @@ private void initConnection(String userName, final String password,



mConnection.connect();


//debug(TAG,"is secure connection? " + mConnection.isSecureConnection());
//debug(TAG,"is using TLS? " + mConnection.isUsingTLS());

Expand Down Expand Up @@ -577,6 +582,8 @@ public void processPacket(Packet packet) {
}
}, new PacketTypeFilter(org.jivesoftware.smack.packet.Presence.class));

mConnection.connect();

mConnection.addConnectionListener(new ConnectionListener() {
/**
* Called from smack when connect() is fully successful
Expand Down Expand Up @@ -677,7 +684,10 @@ public void connectionCreated(Connection arg0) {

this.mPassword = password;
this.mResource = xmppResource;

mStreamHandler = new XmppStreamHandler(mConnection);
mConnection.login(mUsername, mPassword, mResource);
mStreamHandler.notifyInitialLogin();

org.jivesoftware.smack.packet.Presence presence =
new org.jivesoftware.smack.packet.Presence(org.jivesoftware.smack.packet.Presence.Type.available);
Expand Down Expand Up @@ -1554,21 +1564,26 @@ private void reconnect() {
Log.i(TAG, "reconnect");
clearPing();
try {
mConnection.connect();
if (!mConnection.isAuthenticated()) {
// This can happen if a reconnect failed and the smack connection now has wasAuthenticated = false.
// It can also happen if auth exception was swallowed by smack.
// Try to login manually.

Log.e(TAG, "authentication did not happen in connect() - login manually");
mConnection.login(mUsername, mPassword, mResource);

// Make sure
if (!mConnection.isAuthenticated())
throw new XMPPException("manual auth failed");
// Manually set the state since manual auth doesn't notify listeners
mNeedReconnect = false;
setState(LOGGED_IN, null);
if (mStreamHandler.isResumePossible()) {
// Connect without binding, will automatically trigger a resume
mConnection.connect(false);
} else {
mConnection.connect();
if (!mConnection.isAuthenticated()) {
// This can happen if a reconnect failed and the smack connection now has wasAuthenticated = false.
// It can also happen if auth exception was swallowed by smack.
// Try to login manually.

Log.e(TAG, "authentication did not happen in connect() - login manually");
mConnection.login(mUsername, mPassword, mResource);

// Make sure
if (!mConnection.isAuthenticated())
throw new XMPPException("manual auth failed");
// Manually set the state since manual auth doesn't notify listeners
mNeedReconnect = false;
setState(LOGGED_IN, null);
}
}
} catch (XMPPException e) {
mConnection.shutdown();
Expand All @@ -1594,7 +1609,7 @@ protected void setState(int state, ImErrorInfo error) {
super.setState(state, error);
}

public void debug (String tag, String msg)
public static void debug (String tag, String msg)
{
Log.d(tag, msg);
}
Expand Down
202 changes: 202 additions & 0 deletions src/info/guardianproject/otr/app/im/plugin/xmpp/XmppStreamHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package info.guardianproject.otr.app.im.plugin.xmpp;

import info.guardianproject.otr.app.im.plugin.xmpp.XmppConnection.MyXMPPConnection;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.UnknownPacket;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.util.StringUtils;
import org.xmlpull.v1.XmlPullParser;

public class XmppStreamHandler {
private static final String URN_SM_2 = "urn:xmpp:sm:2";
private MyXMPPConnection mConnection;
private boolean isSmAvailable = false;
private boolean isSmEnabled = false;
private long previousIncomingStanzaCount = -1;
private String sessionId;
private long incomingStanzaCount = 0;

public XmppStreamHandler(MyXMPPConnection connection) {
mConnection = connection;
startListening();
}

public boolean isResumePossible() {
return sessionId != null;
}

public static void addExtensionProviders() {
addSimplePacketExtension("sm", URN_SM_2);
addSimplePacketExtension("r", URN_SM_2);
addSimplePacketExtension("a", URN_SM_2);
addSimplePacketExtension("enabled", URN_SM_2);
addSimplePacketExtension("resumed", URN_SM_2);
addSimplePacketExtension("failed", URN_SM_2);
}

public void notifyInitialLogin() {
if (isSmAvailable) {
sendEnablePacket();
}
}

private void sendEnablePacket() {
if (sessionId != null) {
// TODO binding
StreamHandlingPacket resumePacket = new StreamHandlingPacket("resume", URN_SM_2);
resumePacket.addAttribute("h", String.valueOf(previousIncomingStanzaCount));
resumePacket.addAttribute("previd", sessionId);
mConnection.sendPacket(resumePacket);
} else {
StreamHandlingPacket enablePacket = new StreamHandlingPacket("enable", URN_SM_2);
enablePacket.addAttribute("resume", "true");
mConnection.sendPacket(enablePacket);
}
}

private void startListening() {
mConnection.addConnectionListener(new ConnectionListener() {
public void reconnectionSuccessful() {
if (isSmAvailable) {
sendEnablePacket();
} else {
isSmEnabled = false;
sessionId = null;
}
}

public void reconnectionFailed(Exception e) {}

public void reconnectingIn(int seconds) {}

public void connectionClosedOnError(Exception e) {
if (isSmEnabled && sessionId != null) {
previousIncomingStanzaCount = incomingStanzaCount;
}
isSmEnabled = false;
isSmAvailable = false;
}

public void connectionClosed() {
previousIncomingStanzaCount = -1;
}});

mConnection.addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
incomingStanzaCount++;
debug("" + incomingStanzaCount + " : " + packet.toXML());
if (packet instanceof Message && ((Message)packet).getBody().contains("skip")) {
debug("skip");
incomingStanzaCount--;
}
if (packet instanceof StreamHandlingPacket) {
StreamHandlingPacket shPacket = (StreamHandlingPacket)packet;
String name = shPacket.getElementName();
if ("sm".equals(name)) {
isSmAvailable = true;
} else if ("r".equals(name)) {
incomingStanzaCount--;
StreamHandlingPacket ackPacket = new StreamHandlingPacket("a", URN_SM_2);
ackPacket.addAttribute("h", String.valueOf(incomingStanzaCount));
mConnection.sendPacket(ackPacket);
} else if ("a".equals(name)) {
// ignore for now
} else if ("enabled".equals(name)) {
incomingStanzaCount = 0;
isSmEnabled = true;
mConnection.getRoster().setOfflineOnError(false);
String resume = shPacket.getAttribute("resume");
if ("true".equals(resume) || "1".equals(resume)) {
sessionId = shPacket.getAttribute("id");
}
} else if ("resumed".equals(name)) {
incomingStanzaCount = previousIncomingStanzaCount;
isSmEnabled = true;
} else if ("failed".equals(name)) {
// Failed, shutdown and the parent will retry
mConnection.getRoster().setOfflineOnError(true);
mConnection.getRoster().setOfflinePresences();
sessionId = null;
mConnection.shutdown();
// isSmEnabled is already false
}
}
}},
new PacketFilter() { public boolean accept(Packet packet) { return true; }});
}

private static void addSimplePacketExtension(final String name, final String namespace) {
ProviderManager.getInstance().addExtensionProvider(name, namespace,
new PacketExtensionProvider() {
public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
StreamHandlingPacket packet = new StreamHandlingPacket(name, namespace);
int attributeCount = parser.getAttributeCount();
for (int i = 0 ; i < attributeCount ; i++) {
packet.addAttribute(parser.getAttributeName(i), parser.getAttributeValue(i));
}
return packet;
}
});
}

private void debug(String message) {
XmppConnection.debug(XmppConnection.TAG, message);
}

static class StreamHandlingPacket extends UnknownPacket {
private String name;
private String namespace;
Map<String, String> attributes;

StreamHandlingPacket(String name, String namespace) {
this.name = name;
this.namespace = namespace;
attributes = Collections.emptyMap();
}

public void addAttribute(String name, String value) {
if (attributes == Collections.EMPTY_MAP)
attributes = new HashMap<String, String>();
attributes.put(name, value);
}

public String getAttribute(String name) {
return attributes.get(name);
}

public String getNamespace() {
return namespace;
}

public String getElementName() {
return name;
}

public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<").append(getElementName());

// TODO Xmlns??
if (getNamespace() != null) {
buf.append(" xmlns=\"").append(getNamespace()).append("\"");
}
for (String key : attributes.keySet()) {
buf.append(" ").append(key).append("=\"").append(StringUtils.escapeForXML(attributes.get(key))).append("\"");
}
buf.append("/>");
return buf.toString();
}

}
}