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;
10import java.util.Hashtable;
11
12import javax.net.ssl.SSLSocket;
13import javax.net.ssl.SSLSocketFactory;
14
15import org.xmlpull.v1.XmlPullParserException;
16
17import android.os.PowerManager;
18import android.util.Log;
19import de.gultsch.chat.entities.Account;
20import de.gultsch.chat.utils.SASL;
21import de.gultsch.chat.xml.Element;
22import de.gultsch.chat.xml.Tag;
23import de.gultsch.chat.xml.XmlReader;
24import de.gultsch.chat.xml.TagWriter;
25
26public class XmppConnection implements Runnable {
27
28 protected Account account;
29 private static final String LOGTAG = "xmppService";
30
31 private PowerManager.WakeLock wakeLock;
32
33 private SecureRandom random = new SecureRandom();
34
35 private Socket socket;
36 private XmlReader tagReader;
37 private TagWriter tagWriter;
38
39 private boolean isTlsEncrypted = false;
40 private boolean isAuthenticated = false;
41 private boolean shouldUseTLS = false;
42 private boolean shouldReConnect = true;
43 private boolean shouldBind = true;
44 private boolean shouldAuthenticate = true;
45 private Element streamFeatures;
46
47 private static final int PACKET_IQ = 0;
48 private static final int PACKET_MESSAGE = 1;
49 private static final int PACKET_PRESENCE = 2;
50
51 private Hashtable<String, OnIqPacketReceived> iqPacketCallbacks = new Hashtable<String, OnIqPacketReceived>();
52 private OnPresencePacketReceived presenceListener = null;
53 private OnIqPacketReceived unregisteredIqListener = null;
54 private OnMessagePacketReceived messageListener = null;
55
56 private String resource = null;
57
58 public XmppConnection(Account account, PowerManager pm) {
59 this.account = account;
60 wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
61 "XmppConnection");
62 tagReader = new XmlReader(wakeLock);
63 tagWriter = new TagWriter();
64 }
65
66 protected void connect() {
67 try {
68 socket = new Socket(account.getServer(), 5222);
69 Log.d(LOGTAG, "starting new socket");
70 OutputStream out = socket.getOutputStream();
71 tagWriter.setOutputStream(out);
72 InputStream in = socket.getInputStream();
73 tagReader.setInputStream(in);
74 tagWriter.beginDocument();
75 sendStartStream();
76 Tag nextTag;
77 while ((nextTag = tagReader.readTag()) != null) {
78 if (nextTag.isStart("stream")) {
79 processStream(nextTag);
80 } else {
81 Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName());
82 return;
83 }
84 }
85 } catch (UnknownHostException e) {
86 Log.d(LOGTAG, "error during connect. unknown host");
87 return;
88 } catch (IOException e) {
89 Log.d(LOGTAG, "error during connect. io exception. falscher port?");
90 return;
91 } catch (XmlPullParserException e) {
92 Log.d(LOGTAG,"xml exception "+e.getMessage());
93 return;
94 }
95 }
96
97 @Override
98 public void run() {
99 while(shouldReConnect) {
100 connect();
101 try {
102 Thread.sleep(30000);
103 } catch (InterruptedException e) {
104 // TODO Auto-generated catch block
105 e.printStackTrace();
106 }
107 }
108 }
109
110 private void processStream(Tag currentTag) throws XmlPullParserException,
111 IOException {
112 Log.d(LOGTAG, "process Stream");
113 Tag nextTag;
114 while (!(nextTag = tagReader.readTag()).isEnd("stream")) {
115 if (nextTag.isStart("error")) {
116 processStreamError(nextTag);
117 } else if (nextTag.isStart("features")) {
118 processStreamFeatures(nextTag);
119 } else if (nextTag.isStart("proceed")) {
120 switchOverToTls(nextTag);
121 } else if (nextTag.isStart("success")) {
122 isAuthenticated = true;
123 Log.d(LOGTAG,"read success tag in stream. reset again");
124 tagReader.readTag();
125 tagReader.reset();
126 sendStartStream();
127 processStream(tagReader.readTag());
128 } else if (nextTag.isStart("iq")) {
129 processIq(nextTag);
130 } else if (nextTag.isStart("message")) {
131 processMessage(nextTag);
132 } else if (nextTag.isStart("presence")) {
133 processPresence(nextTag);
134 } else {
135 Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()
136 + " as child of " + currentTag.getName());
137 }
138 }
139 }
140
141 private Element processPacket(Tag currentTag, int packetType) throws XmlPullParserException, IOException {
142 Element element;
143 switch (packetType) {
144 case PACKET_IQ:
145 element = new IqPacket();
146 break;
147 case PACKET_MESSAGE:
148 element = new MessagePacket();
149 break;
150 case PACKET_PRESENCE:
151 element = new PresencePacket();
152 break;
153 default:
154 return null;
155 }
156 element.setAttributes(currentTag.getAttributes());
157 Tag nextTag = tagReader.readTag();
158 while(!nextTag.isEnd(element.getName())) {
159 if (!nextTag.isNo()) {
160 Element child = tagReader.readElement(nextTag);
161 element.addChild(child);
162 }
163 nextTag = tagReader.readTag();
164 }
165 return element;
166 }
167
168
169 private IqPacket processIq(Tag currentTag) throws XmlPullParserException, IOException {
170 IqPacket packet = (IqPacket) processPacket(currentTag,PACKET_IQ);
171 if (iqPacketCallbacks.containsKey(packet.getId())) {
172 iqPacketCallbacks.get(packet.getId()).onIqPacketReceived(account,packet);
173 iqPacketCallbacks.remove(packet.getId());
174 } else if (this.unregisteredIqListener != null) {
175 this.unregisteredIqListener.onIqPacketReceived(account,packet);
176 }
177 return packet;
178 }
179
180 private void processMessage(Tag currentTag) throws XmlPullParserException, IOException {
181 MessagePacket packet = (MessagePacket) processPacket(currentTag, PACKET_MESSAGE);
182 if (this.messageListener != null) {
183 this.messageListener.onMessagePacketReceived(account,packet);
184 }
185 }
186
187 private void processPresence(Tag currentTag) throws XmlPullParserException, IOException {
188 PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE);
189 if (this.presenceListener != null) {
190 this.presenceListener.onPresencePacketReceived(account,packet);
191 }
192 }
193
194 private void sendStartTLS() throws XmlPullParserException, IOException {
195 Tag startTLS = Tag.empty("starttls");
196 startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
197 Log.d(LOGTAG, "sending starttls");
198 tagWriter.writeTag(startTLS).flush();
199 }
200
201 private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
202 IOException {
203 Tag nextTag = tagReader.readTag(); // should be proceed end tag
204 Log.d(LOGTAG, "now switch to ssl");
205 SSLSocket sslSocket;
206 try {
207 sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory
208 .getDefault()).createSocket(socket, socket.getInetAddress()
209 .getHostAddress(), socket.getPort(), true);
210 tagReader.setInputStream(sslSocket.getInputStream());
211 Log.d(LOGTAG, "reset inputstream");
212 tagWriter.setOutputStream(sslSocket.getOutputStream());
213 Log.d(LOGTAG, "switch over seemed to work");
214 isTlsEncrypted = true;
215 sendStartStream();
216 processStream(tagReader.readTag());
217 } catch (IOException e) {
218 Log.d(LOGTAG, "error on ssl" + e.getMessage());
219 }
220 }
221
222 private void sendSaslAuth() throws IOException, XmlPullParserException {
223 String saslString = SASL.plain(account.getUsername(),
224 account.getPassword());
225 Element auth = new Element("auth");
226 auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
227 auth.setAttribute("mechanism", "PLAIN");
228 auth.setContent(saslString);
229 Log.d(LOGTAG,"sending sasl "+auth.toString());
230 tagWriter.writeElement(auth);
231 tagWriter.flush();
232 }
233
234 private void processStreamFeatures(Tag currentTag)
235 throws XmlPullParserException, IOException {
236 this.streamFeatures = tagReader.readElement(currentTag);
237 Log.d(LOGTAG,"process stream features "+streamFeatures);
238 if (this.streamFeatures.hasChild("starttls")&&shouldUseTLS) {
239 sendStartTLS();
240 }
241 if (this.streamFeatures.hasChild("mechanisms")&&shouldAuthenticate) {
242 sendSaslAuth();
243 }
244 if (this.streamFeatures.hasChild("bind")&&shouldBind) {
245 sendBindRequest();
246 if (this.streamFeatures.hasChild("session")) {
247 IqPacket startSession = new IqPacket(IqPacket.TYPE_SET);
248 Element session = new Element("session");
249 session.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-session");
250 session.setContent("");
251 startSession.addChild(session);
252 sendIqPacket(startSession, null);
253 tagWriter.writeElement(startSession);
254 tagWriter.flush();
255 }
256 Element presence = new Element("presence");
257
258 tagWriter.writeElement(presence);
259 tagWriter.flush();
260 }
261 }
262
263 private void sendBindRequest() throws IOException {
264 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
265 Element bind = new Element("bind");
266 bind.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-bind");
267 iq.addChild(bind);
268 this.sendIqPacket(iq, new OnIqPacketReceived() {
269 @Override
270 public void onIqPacketReceived(Account account, IqPacket packet) {
271 resource = packet.findChild("bind").findChild("jid").getContent().split("/")[1];
272 Log.d(LOGTAG,"answer for our bind was: "+packet.toString());
273 Log.d(LOGTAG,"new resource is "+resource);
274 }
275 });
276 }
277
278 private void processStreamError(Tag currentTag) {
279 Log.d(LOGTAG, "processStreamError");
280 }
281
282 private void sendStartStream() throws IOException {
283 Tag stream = Tag.start("stream");
284 stream.setAttribute("from", account.getJid());
285 stream.setAttribute("to", account.getServer());
286 stream.setAttribute("version", "1.0");
287 stream.setAttribute("xml:lang", "en");
288 stream.setAttribute("xmlns", "jabber:client");
289 stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
290 tagWriter.writeTag(stream).flush();
291 }
292
293 private String nextRandomId() {
294 return new BigInteger(50, random).toString(32);
295 }
296
297 public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) throws IOException {
298 String id = nextRandomId();
299 packet.setAttribute("id",id);
300 tagWriter.writeElement(packet);
301 if (callback != null) {
302 iqPacketCallbacks.put(id, callback);
303 }
304 tagWriter.flush();
305 Log.d(LOGTAG,"sending: "+packet.toString());
306 }
307
308 public void sendMessagePacket(MessagePacket packet) throws IOException {
309 tagWriter.writeElement(packet);
310 tagWriter.flush();
311 }
312
313 public void sendPresencePacket(PresencePacket packet) throws IOException {
314 tagWriter.writeElement(packet);
315 tagWriter.flush();
316 }
317
318 public void setOnMessagePacketReceivedListener(OnMessagePacketReceived listener) {
319 this.messageListener = listener;
320 }
321
322 public void setOnUnregisteredIqPacketReceivedListener(OnIqPacketReceived listener) {
323 this.unregisteredIqListener = listener;
324 }
325
326 public void setOnPresencePacketReceivedListener(OnPresencePacketReceived listener) {
327 this.presenceListener = listener;
328 }
329}