XmppConnection.java

  1package de.gultsch.chat.xmpp;
  2
  3import java.io.IOException;
  4import java.io.InputStream;
  5import java.io.OutputStream;
  6import java.math.BigInteger;
  7import java.net.Socket;
  8import java.net.UnknownHostException;
  9import java.security.SecureRandom;
 10
 11import javax.net.ssl.SSLSocket;
 12import javax.net.ssl.SSLSocketFactory;
 13
 14import org.xmlpull.v1.XmlPullParserException;
 15
 16import android.os.PowerManager;
 17import android.util.Log;
 18import de.gultsch.chat.entities.Account;
 19import de.gultsch.chat.utils.SASL;
 20import de.gultsch.chat.xml.Element;
 21import de.gultsch.chat.xml.Tag;
 22import de.gultsch.chat.xml.XmlReader;
 23import de.gultsch.chat.xml.TagWriter;
 24
 25public class XmppConnection implements Runnable {
 26
 27	protected Account account;
 28	private static final String LOGTAG = "xmppService";
 29
 30	private PowerManager.WakeLock wakeLock;
 31
 32	private SecureRandom random = new SecureRandom();
 33	
 34	private Socket socket;
 35	private XmlReader tagReader;
 36	private TagWriter tagWriter;
 37
 38	private boolean isTlsEncrypted = true;
 39	private boolean isAuthenticated = false;
 40	
 41	private static final int PACKET_IQ = 0;
 42	private static final int PACKET_MESSAGE = 1;
 43	private static final int PACKET_PRESENCE = 2;
 44
 45	public XmppConnection(Account account, PowerManager pm) {
 46		this.account = account;
 47		wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
 48				"XmppConnection");
 49		tagReader = new XmlReader(wakeLock);
 50		tagWriter = new TagWriter();
 51	}
 52
 53	protected void connect() {
 54		try {
 55			socket = new Socket(account.getServer(), 5222);
 56			Log.d(LOGTAG, "starting new socket");
 57			OutputStream out = socket.getOutputStream();
 58			tagWriter.setOutputStream(out);
 59			InputStream in = socket.getInputStream();
 60			tagReader.setInputStream(in);
 61		} catch (UnknownHostException e) {
 62			Log.d(LOGTAG, "error during connect. unknown host");
 63		} catch (IOException e) {
 64			Log.d(LOGTAG, "error during connect. io exception. falscher port?");
 65		}
 66	}
 67
 68	@Override
 69	public void run() {
 70		connect();
 71		try {
 72			tagWriter.beginDocument();
 73			sendStartStream();
 74			Tag nextTag;
 75			while ((nextTag = tagReader.readTag()) != null) {
 76				if (nextTag.isStart("stream")) {
 77					processStream(nextTag);
 78				} else {
 79					Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName());
 80				}
 81			}
 82		} catch (XmlPullParserException e) {
 83			Log.d(LOGTAG,
 84					"xml error during normal read. maybe missformed xml? "
 85							+ e.getMessage());
 86		} catch (IOException e) {
 87			Log.d(LOGTAG, "io exception during read. connection lost?");
 88		}
 89	}
 90
 91	private void processStream(Tag currentTag) throws XmlPullParserException,
 92			IOException {
 93		Log.d(LOGTAG, "process Stream");
 94		Tag nextTag;
 95		while ((nextTag = tagReader.readTag()) != null) {
 96			if (nextTag.isStart("error")) {
 97				processStreamError(nextTag);
 98			} else if (nextTag.isStart("features")) {
 99				processStreamFeatures(nextTag);
100				if (!isTlsEncrypted) {
101					sendStartTLS();
102				}
103				if ((!isAuthenticated) && (isTlsEncrypted)) {
104					sendSaslAuth();
105				}
106				if ((isAuthenticated)&&(isTlsEncrypted)) {
107					sendBindRequest();
108				}
109			} else if (nextTag.isStart("proceed")) {
110				switchOverToTls(nextTag);
111			} else if (nextTag.isStart("success")) {
112				isAuthenticated = true;
113				Log.d(LOGTAG,"read success tag in stream. reset again");
114				tagReader.readTag();
115				tagReader.reset();
116				sendStartStream();
117				processStream(tagReader.readTag());
118			} else if (nextTag.isStart("iq")) {
119				Log.d(LOGTAG,processIq(nextTag).toString());
120			} else if (nextTag.isStart("message")) {
121				Log.d(LOGTAG,processMessage(nextTag).toString());
122			} else if (nextTag.isStart("presence")) {
123				Log.d(LOGTAG,processPresence(nextTag).toString());
124			} else if (nextTag.isEnd("stream")) {
125				break;
126			} else {
127				Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()
128						+ " as child of " + currentTag.getName());
129			}
130		}
131	}
132	
133	private Element processPacket(Tag currentTag, int packetType) throws XmlPullParserException, IOException {
134		Element element;
135		switch (packetType) {
136		case PACKET_IQ:
137			element = new IqPacket();
138			break;
139		case PACKET_MESSAGE:
140			element = new MessagePacket();
141			break;
142		case PACKET_PRESENCE:
143			element = new PresencePacket();
144			break;
145		default:
146			return null;
147		}
148		element.setAttributes(currentTag.getAttributes());
149		Tag nextTag = tagReader.readTag();
150		while(!nextTag.isEnd(element.getName())) {
151			if (!nextTag.isNo()) {
152				Element child = tagReader.readElement(nextTag);
153				element.addChild(child);
154			}
155			nextTag = tagReader.readTag();
156		}
157		return element;
158	}
159	
160
161	private IqPacket processIq(Tag currentTag) throws XmlPullParserException, IOException {
162		return (IqPacket) processPacket(currentTag,PACKET_IQ);
163	}
164	
165	private MessagePacket processMessage(Tag currentTag) throws XmlPullParserException, IOException {
166		return (MessagePacket) processPacket(currentTag, PACKET_MESSAGE);
167	}
168	
169	private PresencePacket processPresence(Tag currentTag) throws XmlPullParserException, IOException {
170		return (PresencePacket) processPacket(currentTag, PACKET_PRESENCE);
171	}
172
173	private void sendStartTLS() throws XmlPullParserException, IOException {
174		Tag startTLS = Tag.empty("starttls");
175		startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
176		Log.d(LOGTAG, "sending starttls");
177		tagWriter.writeTag(startTLS).flush();
178	}
179
180	private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
181			IOException {
182		Tag nextTag = tagReader.readTag(); // should be proceed end tag
183		Log.d(LOGTAG, "now switch to ssl");
184		SSLSocket sslSocket;
185		try {
186			sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory
187					.getDefault()).createSocket(socket, socket.getInetAddress()
188					.getHostAddress(), socket.getPort(), true);
189			tagReader.setInputStream(sslSocket.getInputStream());
190			Log.d(LOGTAG, "reset inputstream");
191			tagWriter.setOutputStream(sslSocket.getOutputStream());
192			Log.d(LOGTAG, "switch over seemed to work");
193			isTlsEncrypted = true;
194			sendStartStream();
195			processStream(tagReader.readTag());
196		} catch (IOException e) {
197			Log.d(LOGTAG, "error on ssl" + e.getMessage());
198		}
199	}
200
201	private void sendSaslAuth() throws IOException, XmlPullParserException {
202		String saslString = SASL.plain(account.getUsername(),
203				account.getPassword());
204		Element auth = new Element("auth");
205		auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
206		auth.setAttribute("mechanism", "PLAIN");
207		auth.setContent(saslString);
208		Log.d(LOGTAG,"sending sasl "+auth.toString());
209		tagWriter.writeElement(auth);
210		tagWriter.flush();
211	}
212
213	private void processStreamFeatures(Tag currentTag)
214			throws XmlPullParserException, IOException {
215		Log.d(LOGTAG, "processStreamFeatures");
216		
217		Element streamFeatures = new Element("features");
218		
219		Tag nextTag = tagReader.readTag();
220		while(!nextTag.isEnd("features")) {
221			Element element = tagReader.readElement(nextTag);
222			streamFeatures.addChild(element);
223			nextTag = tagReader.readTag();
224		}
225		Log.d(LOGTAG,streamFeatures.toString());
226	}
227
228	private void sendBindRequest() throws IOException {
229		IqPacket iq = new IqPacket(nextRandomId(),IqPacket.TYPE_SET);
230		Element bind = new Element("bind");
231		bind.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-bind");
232		iq.addChild(bind);
233		//Element resource = new Element("resource");
234		//resource.setContent("mobile");
235		//bind.addChild(resource);
236		Log.d(LOGTAG,"sending bind request: "+iq.toString());
237		tagWriter.writeElement(iq);
238		tagWriter.flush();
239		
240		
241		//technically not bind stuff
242		IqPacket startSession = new IqPacket(this.nextRandomId(), IqPacket.TYPE_SET);
243		Element session = new Element("session");
244		session.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-session");
245		session.setContent("");
246		startSession.addChild(session);
247		
248		tagWriter.writeElement(startSession);
249		tagWriter.flush();
250		
251		Element presence = new Element("presence");
252		
253		tagWriter.writeElement(presence);
254		tagWriter.flush();
255		
256	}
257
258	private void processStreamError(Tag currentTag) {
259		Log.d(LOGTAG, "processStreamError");
260	}
261
262	private void sendStartStream() throws IOException {
263		Tag stream = Tag.start("stream");
264		stream.setAttribute("from", account.getJid());
265		stream.setAttribute("to", account.getServer());
266		stream.setAttribute("version", "1.0");
267		stream.setAttribute("xml:lang", "en");
268		stream.setAttribute("xmlns", "jabber:client");
269		stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
270		tagWriter.writeTag(stream).flush();
271	}
272
273	private String nextRandomId() {
274		return new BigInteger(50, random).toString(32);
275	}
276}