aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Gultsch <daniel.gultsch@rwth-aachen.de>2014-01-30 16:42:35 +0100
committerDaniel Gultsch <daniel.gultsch@rwth-aachen.de>2014-01-30 16:42:35 +0100
commit6c5c3ac2decac75ec3208d47912e67c4e1a33548 (patch)
treec6cdcc5d76608369da08eb76891c48ffefbb9a48
parentad11dab6359a1eb2b6921d36117093066999fb96 (diff)
first draft on xml parser and communication. a long way to go. code definitly not perfect. will refactor asap
-rw-r--r--AndroidManifest.xml2
-rw-r--r--src/de/gultsch/chat/services/XmppConnectionService.java41
-rw-r--r--src/de/gultsch/chat/ui/XmppActivity.java1
-rw-r--r--src/de/gultsch/chat/utils/SASL.java24
-rw-r--r--src/de/gultsch/chat/xml/Element.java65
-rw-r--r--src/de/gultsch/chat/xml/Tag.java99
-rw-r--r--src/de/gultsch/chat/xml/TagWriter.java46
-rw-r--r--src/de/gultsch/chat/xml/XmlReader.java91
-rw-r--r--src/de/gultsch/chat/xmpp/IqPacket.java26
-rw-r--r--src/de/gultsch/chat/xmpp/XmppConnection.java222
10 files changed, 616 insertions, 1 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a3529cbd..2b65b3a7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -10,6 +10,8 @@
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_PROFILE"/>
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
android:allowBackup="true"
diff --git a/src/de/gultsch/chat/services/XmppConnectionService.java b/src/de/gultsch/chat/services/XmppConnectionService.java
index b8a54523..d078714d 100644
--- a/src/de/gultsch/chat/services/XmppConnectionService.java
+++ b/src/de/gultsch/chat/services/XmppConnectionService.java
@@ -1,22 +1,43 @@
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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import de.gultsch.chat.entities.Account;
import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation;
import de.gultsch.chat.entities.Message;
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.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
+import android.os.PowerManager;
import android.util.Log;
public class XmppConnectionService extends Service {
- protected static final String LOGTAG = "xmppConnection";
+ protected static final String LOGTAG = "xmppService";
protected DatabaseBackend databaseBackend;
+
+ public long startDate;
+
+ private List<Account> accounts;
+
+ public boolean connectionRunnig = false;
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
public void onCreate() {
databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
+ this.accounts = databaseBackend.getAccounts();
+ startDate = System.currentTimeMillis();
}
@Override
diff --git a/src/de/gultsch/chat/ui/XmppActivity.java b/src/de/gultsch/chat/ui/XmppActivity.java
index ce65ab5e..66c92b72 100644
--- a/src/de/gultsch/chat/ui/XmppActivity.java
+++ b/src/de/gultsch/chat/ui/XmppActivity.java
@@ -31,6 +31,7 @@ public abstract class XmppActivity extends Activity {
@Override
protected void onStart() {
+ startService(new Intent(this, XmppConnectionService.class));
super.onStart();
if (!xmppConnectionServiceBound) {
Intent intent = new Intent(this, XmppConnectionService.class);
diff --git a/src/de/gultsch/chat/utils/SASL.java b/src/de/gultsch/chat/utils/SASL.java
new file mode 100644
index 00000000..266f0cb2
--- /dev/null
+++ b/src/de/gultsch/chat/utils/SASL.java
@@ -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);
+ }
+}
diff --git a/src/de/gultsch/chat/xml/Element.java b/src/de/gultsch/chat/xml/Element.java
new file mode 100644
index 00000000..d6d1b23d
--- /dev/null
+++ b/src/de/gultsch/chat/xml/Element.java
@@ -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;
+ }
+}
diff --git a/src/de/gultsch/chat/xml/Tag.java b/src/de/gultsch/chat/xml/Tag.java
new file mode 100644
index 00000000..275229cf
--- /dev/null
+++ b/src/de/gultsch/chat/xml/Tag.java
@@ -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;
+ }
+}
diff --git a/src/de/gultsch/chat/xml/TagWriter.java b/src/de/gultsch/chat/xml/TagWriter.java
new file mode 100644
index 00000000..35f27477
--- /dev/null
+++ b/src/de/gultsch/chat/xml/TagWriter.java
@@ -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());
+ }
+}
diff --git a/src/de/gultsch/chat/xml/XmlReader.java b/src/de/gultsch/chat/xml/XmlReader.java
new file mode 100644
index 00000000..e086c8ae
--- /dev/null
+++ b/src/de/gultsch/chat/xml/XmlReader.java
@@ -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;
+ }
+}
diff --git a/src/de/gultsch/chat/xmpp/IqPacket.java b/src/de/gultsch/chat/xmpp/IqPacket.java
new file mode 100644
index 00000000..062bf1c0
--- /dev/null
+++ b/src/de/gultsch/chat/xmpp/IqPacket.java
@@ -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;
+ }
+ }
+
+}
diff --git a/src/de/gultsch/chat/xmpp/XmppConnection.java b/src/de/gultsch/chat/xmpp/XmppConnection.java
new file mode 100644
index 00000000..942033a1
--- /dev/null
+++ b/src/de/gultsch/chat/xmpp/XmppConnection.java
@@ -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);
+ }
+}