first draft on xml parser and communication. a long way to go. code definitly not perfect. will refactor asap

This commit is contained in:
Daniel Gultsch 2014-01-30 16:42:35 +01:00
parent ad11dab635
commit 6c5c3ac2de
10 changed files with 616 additions and 1 deletions

View file

@ -10,6 +10,8 @@
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_PROFILE"/> <uses-permission android:name="android.permission.READ_PROFILE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application <application
android:allowBackup="true" android:allowBackup="true"

View file

@ -1,22 +1,43 @@
package de.gultsch.chat.services; package de.gultsch.chat.services;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.List; import java.util.List;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import de.gultsch.chat.entities.Account; import de.gultsch.chat.entities.Account;
import de.gultsch.chat.entities.Contact; import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation; import de.gultsch.chat.entities.Conversation;
import de.gultsch.chat.entities.Message; import de.gultsch.chat.entities.Message;
import de.gultsch.chat.persistance.DatabaseBackend; import de.gultsch.chat.persistance.DatabaseBackend;
import de.gultsch.chat.xml.Tag;
import de.gultsch.chat.xml.XmlReader;
import de.gultsch.chat.xmpp.XmppConnection;
import android.app.Service; import android.app.Service;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log; import android.util.Log;
public class XmppConnectionService extends Service { public class XmppConnectionService extends Service {
protected static final String LOGTAG = "xmppConnection"; protected static final String LOGTAG = "xmppService";
protected DatabaseBackend databaseBackend; protected DatabaseBackend databaseBackend;
public long startDate;
private List<Account> accounts;
public boolean connectionRunnig = false;
private final IBinder mBinder = new XmppConnectionBinder(); private final IBinder mBinder = new XmppConnectionBinder();
@ -26,9 +47,27 @@ public class XmppConnectionService extends Service {
} }
} }
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(LOGTAG,"recieved start command. been running for "+((System.currentTimeMillis() - startDate) / 1000)+"s");
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (!connectionRunnig) {
for(Account account : accounts) {
Log.d(LOGTAG,"connection wasnt running");
XmppConnection connection = new XmppConnection(account, pm);
Thread thread = new Thread(connection);
thread.start();
}
connectionRunnig = true;
}
return START_STICKY;
}
@Override @Override
public void onCreate() { public void onCreate() {
databaseBackend = DatabaseBackend.getInstance(getApplicationContext()); databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
this.accounts = databaseBackend.getAccounts();
startDate = System.currentTimeMillis();
} }
@Override @Override

View file

@ -31,6 +31,7 @@ public abstract class XmppActivity extends Activity {
@Override @Override
protected void onStart() { protected void onStart() {
startService(new Intent(this, XmppConnectionService.class));
super.onStart(); super.onStart();
if (!xmppConnectionServiceBound) { if (!xmppConnectionServiceBound) {
Intent intent = new Intent(this, XmppConnectionService.class); Intent intent = new Intent(this, XmppConnectionService.class);

View file

@ -0,0 +1,24 @@
package de.gultsch.chat.utils;
import android.util.Base64;
public class SASL {
public static String plain(String username, String password) {
byte[] userBytes = username.getBytes();
int userLenght = userBytes.length;
byte[] passwordBytes = password.getBytes();
byte[] saslBytes = new byte[userBytes.length+passwordBytes.length+2];
saslBytes[0] = 0x0;
for(int i = 1; i < saslBytes.length; ++i) {
if (i<=userLenght) {
saslBytes[i] = userBytes[i-1];
} else if (i==userLenght+1) {
saslBytes[i] = 0x0;
} else {
saslBytes[i] = passwordBytes[i-(userLenght+2)];
}
}
return Base64.encodeToString(saslBytes, Base64.DEFAULT);
}
}

View file

@ -0,0 +1,65 @@
package de.gultsch.chat.xml;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
public class Element {
protected String name;
protected Hashtable<String, String> attributes = new Hashtable<String, String>();
protected String content;
protected List<Element> children = new ArrayList<Element>();
public Element(String name) {
this.name = name;
}
public Element addChild(Element child) {
this.content = null;
children.add(child);
return this;
}
public Element setContent(String content) {
this.content = content;
this.children.clear();
return this;
}
public Element setAttribute(String name, String value) {
this.attributes.put(name, value);
return this;
}
public Element setAttributes(Hashtable<String, String> attributes) {
this.attributes = attributes;
return this;
}
public String toString() {
StringBuilder elementOutput = new StringBuilder();
if ((content==null)&&(children.size() == 0)) {
Tag emptyTag = Tag.empty(name);
emptyTag.setAtttributes(this.attributes);
elementOutput.append(emptyTag.toString());
} else {
Tag startTag = Tag.start(name);
startTag.setAtttributes(this.attributes);
elementOutput.append(startTag);
if (content!=null) {
elementOutput.append(content);
} else {
for(Element child : children) {
elementOutput.append(child.toString());
}
}
Tag endTag = Tag.end(name);
elementOutput.append(endTag);
}
return elementOutput.toString();
}
public String getName() {
return name;
}
}

View file

@ -0,0 +1,99 @@
package de.gultsch.chat.xml;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
public class Tag {
public static final int NO = -1;
public static final int START = 0;
public static final int END = 1;
public static final int EMPTY = 2;
protected int type;
protected String name;
protected Hashtable<String, String> attributes = new Hashtable<String, String>();
protected Tag(int type, String name) {
this.type = type;
this.name = name;
}
public static Tag no(String text) {
return new Tag(NO,text);
}
public static Tag start(String name) {
return new Tag(START,name);
}
public static Tag end(String name) {
return new Tag(END,name);
}
public static Tag empty(String name) {
return new Tag(EMPTY,name);
}
public String getName() {
return name;
}
public String getAttribute(String attrName) {
return this.attributes.get(attrName);
}
public Tag setAttribute(String attrName, String attrValue) {
this.attributes.put(attrName, attrValue);
return this;
}
public Tag setAtttributes(Hashtable<String, String> attributes) {
this.attributes = attributes;
return this;
}
public boolean isStart(String needle) {
return (this.type == START) && (this.name.equals(needle));
}
public boolean isEnd(String needle) {
return (this.type == END) && (this.name.equals(needle));
}
public boolean isNo() {
return (this.type == NO);
}
public String toString() {
StringBuilder tagOutput = new StringBuilder();
tagOutput.append('<');
if (type==END) {
tagOutput.append('/');
}
tagOutput.append(name);
if(type!=END) {
Set<Entry<String, String>> attributeSet = attributes.entrySet();
Iterator<Entry<String, String>> it = attributeSet.iterator();
while(it.hasNext()) {
Entry<String,String> entry = it.next();
tagOutput.append(' ');
tagOutput.append(entry.getKey());
tagOutput.append("=\"");
tagOutput.append(entry.getValue());
tagOutput.append('"');
}
}
if (type==EMPTY) {
tagOutput.append('/');
}
tagOutput.append('>');
return tagOutput.toString();
}
public Hashtable<String, String> getAttributes() {
return this.attributes;
}
}

View file

@ -0,0 +1,46 @@
package de.gultsch.chat.xml;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import android.util.Log;
public class TagWriter {
OutputStreamWriter writer;
public TagWriter() {
}
public TagWriter(OutputStream out) {
this.setOutputStream(out);
}
public void setOutputStream(OutputStream out) {
this.writer = new OutputStreamWriter(out);
}
public TagWriter beginDocument() throws IOException {
writer.write("<?xml version='1.0'?>");
return this;
}
public TagWriter writeTag(Tag tag) throws IOException {
writer.write(tag.toString());
return this;
}
public void flush() throws IOException {
writer.flush();
}
public void writeString(String string) throws IOException {
writer.write(string);
}
public void writeElement(Element element) throws IOException {
writer.write(element.toString());
}
}

View file

@ -0,0 +1,91 @@
package de.gultsch.chat.xml;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.util.Xml;
public class XmlReader {
private static final String LOGTAG = "xmppService";
private XmlPullParser parser;
private PowerManager.WakeLock wakeLock;
private InputStream is;
public XmlReader(WakeLock wakeLock) {
this.parser = Xml.newPullParser();
try {
this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,true);
} catch (XmlPullParserException e) {
Log.d(LOGTAG,"error setting namespace feature on parser");
}
this.wakeLock = wakeLock;
}
public void setInputStream(InputStream inputStream) {
this.is = inputStream;
try {
parser.setInput(new InputStreamReader(this.is));
} catch (XmlPullParserException e) {
Log.d(LOGTAG,"error setting input stream");
}
}
public void reset() {
try {
parser.setInput(new InputStreamReader(this.is));
} catch (XmlPullParserException e) {
Log.d(LOGTAG,"error resetting input stream");
}
}
public Tag readTag() throws XmlPullParserException, IOException {
if (wakeLock.isHeld()) {
//Log.d(LOGTAG,"there was a wake lock. releasing it till next event");
wakeLock.release(); //release wake look while waiting on next parser event
}
while(parser.next() != XmlPullParser.END_DOCUMENT) {
//Log.d(LOGTAG,"found new event. acquiring wake lock");
wakeLock.acquire();
if (parser.getEventType() == XmlPullParser.START_TAG) {
Tag tag = Tag.start(parser.getName());
for(int i = 0; i < parser.getAttributeCount(); ++i) {
tag.setAttribute(parser.getAttributeName(i), parser.getAttributeValue(i));
}
return tag;
} else if (parser.getEventType() == XmlPullParser.END_TAG) {
Tag tag = Tag.end(parser.getName());
return tag;
} else if (parser.getEventType() == XmlPullParser.TEXT) {
Tag tag = Tag.no(parser.getText());
return tag;
}
}
if (wakeLock.isHeld()) {
wakeLock.release();
}
return null; //end document;
}
public Element readElement(Tag currentTag) throws XmlPullParserException, IOException {
Element element = new Element(currentTag.getName());
element.setAttributes(currentTag.getAttributes());
Tag nextTag = this.readTag();
if(nextTag.isNo()) {
element.setContent(nextTag.getName());
nextTag = this.readTag();
}
while(!nextTag.isEnd(element.getName())) {
Element child = this.readElement(nextTag);
element.addChild(child);
nextTag = this.readTag();
}
return element;
}
}

View file

@ -0,0 +1,26 @@
package de.gultsch.chat.xmpp;
import de.gultsch.chat.xml.Element;
public class IqPacket extends Element {
public static final int TYPE_SET = 0;
public static final int TYPE_RESULT = 1;
private IqPacket(String name) {
super(name);
}
public IqPacket(String id, int type) {
super("iq");
this.setAttribute("id",id);
switch (type) {
case TYPE_SET:
this.setAttribute("type", "set");
break;
default:
break;
}
}
}

View file

@ -0,0 +1,222 @@
package de.gultsch.chat.xmpp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.xmlpull.v1.XmlPullParserException;
import android.os.PowerManager;
import android.util.Log;
import de.gultsch.chat.entities.Account;
import de.gultsch.chat.utils.SASL;
import de.gultsch.chat.xml.Element;
import de.gultsch.chat.xml.Tag;
import de.gultsch.chat.xml.XmlReader;
import de.gultsch.chat.xml.TagWriter;
public class XmppConnection implements Runnable {
protected Account account;
private static final String LOGTAG = "xmppService";
private PowerManager.WakeLock wakeLock;
private SecureRandom random = new SecureRandom();
private Socket socket;
private XmlReader tagReader;
private TagWriter tagWriter;
private boolean isTlsEncrypted = false;
private boolean isAuthenticated = false;
public XmppConnection(Account account, PowerManager pm) {
this.account = account;
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"XmppConnection");
tagReader = new XmlReader(wakeLock);
tagWriter = new TagWriter();
}
protected void connect() {
try {
socket = new Socket(account.getServer(), 5222);
Log.d(LOGTAG, "starting new socket");
OutputStream out = socket.getOutputStream();
tagWriter.setOutputStream(out);
InputStream in = socket.getInputStream();
tagReader.setInputStream(in);
} catch (UnknownHostException e) {
Log.d(LOGTAG, "error during connect. unknown host");
} catch (IOException e) {
Log.d(LOGTAG, "error during connect. io exception. falscher port?");
}
}
@Override
public void run() {
connect();
try {
tagWriter.beginDocument();
sendStartStream();
Tag nextTag;
while ((nextTag = tagReader.readTag()) != null) {
if (nextTag.isStart("stream")) {
processStream(nextTag);
} else {
Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName());
}
}
} catch (XmlPullParserException e) {
Log.d(LOGTAG,
"xml error during normal read. maybe missformed xml? "
+ e.getMessage());
} catch (IOException e) {
Log.d(LOGTAG, "io exception during read. connection lost?");
}
}
private void processStream(Tag currentTag) throws XmlPullParserException,
IOException {
Log.d(LOGTAG, "process Stream");
Tag nextTag;
while ((nextTag = tagReader.readTag()) != null) {
if (nextTag.isStart("error")) {
processStreamError(nextTag);
} else if (nextTag.isStart("features")) {
processStreamFeatures(nextTag);
if (!isTlsEncrypted) {
sendStartTLS();
}
if ((!isAuthenticated) && (isTlsEncrypted)) {
sendSaslAuth();
}
if ((isAuthenticated)&&(isTlsEncrypted)) {
sendBindRequest();
}
} else if (nextTag.isStart("proceed")) {
switchOverToTls(nextTag);
} else if (nextTag.isStart("success")) {
isAuthenticated = true;
Log.d(LOGTAG,"read success tag in stream. reset again");
tagReader.readTag();
tagReader.reset();
sendStartStream();
processStream(tagReader.readTag());
} else if (nextTag.isStart("iq")) {
processIq(nextTag);
} else if (nextTag.isEnd("stream")) {
break;
} else {
Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()
+ " as child of " + currentTag.getName());
}
}
}
private void processIq(Tag currentTag) throws XmlPullParserException, IOException {
int typ = -1;
if (currentTag.getAttribute("type").equals("result")) {
typ = IqPacket.TYPE_RESULT;
}
IqPacket iq = new IqPacket(currentTag.getAttribute("id"),typ);
Tag nextTag = tagReader.readTag();
while(!nextTag.isEnd("iq")) {
Element element = tagReader.readElement(nextTag);
iq.addChild(element);
nextTag = tagReader.readTag();
}
Log.d(LOGTAG,"this is what i understood: "+iq.toString());
}
private void sendStartTLS() throws XmlPullParserException, IOException {
Tag startTLS = Tag.empty("starttls");
startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
Log.d(LOGTAG, "sending starttls");
tagWriter.writeTag(startTLS).flush();
}
private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
IOException {
Tag nextTag = tagReader.readTag(); // should be proceed end tag
Log.d(LOGTAG, "now switch to ssl");
SSLSocket sslSocket;
try {
sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory
.getDefault()).createSocket(socket, socket.getInetAddress()
.getHostAddress(), socket.getPort(), true);
tagReader.setInputStream(sslSocket.getInputStream());
Log.d(LOGTAG, "reset inputstream");
tagWriter.setOutputStream(sslSocket.getOutputStream());
Log.d(LOGTAG, "switch over seemed to work");
isTlsEncrypted = true;
sendStartStream();
processStream(tagReader.readTag());
} catch (IOException e) {
Log.d(LOGTAG, "error on ssl" + e.getMessage());
}
}
private void sendSaslAuth() throws IOException, XmlPullParserException {
String saslString = SASL.plain(account.getUsername(),
account.getPassword());
Element auth = new Element("auth");
auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
auth.setAttribute("mechanism", "PLAIN");
auth.setContent(saslString);
Log.d(LOGTAG,"sending sasl "+auth.toString());
tagWriter.writeElement(auth);
tagWriter.flush();
}
private void processStreamFeatures(Tag currentTag)
throws XmlPullParserException, IOException {
Log.d(LOGTAG, "processStreamFeatures");
Element streamFeatures = new Element("features");
Tag nextTag = tagReader.readTag();
while(!nextTag.isEnd("features")) {
Element element = tagReader.readElement(nextTag);
streamFeatures.addChild(element);
nextTag = tagReader.readTag();
}
}
private void sendBindRequest() throws IOException {
IqPacket iq = new IqPacket(nextRandomId(),IqPacket.TYPE_SET);
Element bind = new Element("bind");
bind.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-bind");
iq.addChild(bind);
Log.d(LOGTAG,"sending bind request: "+iq.toString());
tagWriter.writeElement(iq);
tagWriter.flush();
}
private void processStreamError(Tag currentTag) {
Log.d(LOGTAG, "processStreamError");
}
private void sendStartStream() throws IOException {
Tag stream = Tag.start("stream");
stream.setAttribute("from", account.getJid());
stream.setAttribute("to", account.getServer());
stream.setAttribute("version", "1.0");
stream.setAttribute("xml:lang", "en");
stream.setAttribute("xmlns", "jabber:client");
stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
tagWriter.writeTag(stream).flush();
}
private String nextRandomId() {
return new BigInteger(50, random).toString(32);
}
}