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 public XmppConnection(Account account, PowerManager pm) {
57 this.account = account;
58 wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
59 "XmppConnection");
60 tagReader = new XmlReader(wakeLock);
61 tagWriter = new TagWriter();
62 }
63
64 protected void connect() {
65 try {
66 socket = new Socket(account.getServer(), 5222);
67 Log.d(LOGTAG, "starting new socket");
68 OutputStream out = socket.getOutputStream();
69 tagWriter.setOutputStream(out);
70 InputStream in = socket.getInputStream();
71 tagReader.setInputStream(in);
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 return;
81 }
82 }
83 } catch (UnknownHostException e) {
84 Log.d(LOGTAG, "error during connect. unknown host");
85 return;
86 } catch (IOException e) {
87 Log.d(LOGTAG, "error during connect. io exception. falscher port?");
88 return;
89 } catch (XmlPullParserException e) {
90 Log.d(LOGTAG,"xml exception "+e.getMessage());
91 return;
92 }
93 }
94
95 @Override
96 public void run() {
97 while(shouldReConnect) {
98 connect();
99 }
100 }
101
102 private void processStream(Tag currentTag) throws XmlPullParserException,
103 IOException {
104 Log.d(LOGTAG, "process Stream");
105 Tag nextTag;
106 while (!(nextTag = tagReader.readTag()).isEnd("stream")) {
107 if (nextTag.isStart("error")) {
108 processStreamError(nextTag);
109 } else if (nextTag.isStart("features")) {
110 processStreamFeatures(nextTag);
111 } else if (nextTag.isStart("proceed")) {
112 switchOverToTls(nextTag);
113 } else if (nextTag.isStart("success")) {
114 isAuthenticated = true;
115 Log.d(LOGTAG,"read success tag in stream. reset again");
116 tagReader.readTag();
117 tagReader.reset();
118 sendStartStream();
119 processStream(tagReader.readTag());
120 } else if (nextTag.isStart("iq")) {
121 processIq(nextTag);
122 } else if (nextTag.isStart("message")) {
123 processMessage(nextTag);
124 } else if (nextTag.isStart("presence")) {
125 processPresence(nextTag);
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 IqPacket packet = (IqPacket) processPacket(currentTag,PACKET_IQ);
163 if (iqPacketCallbacks.containsKey(packet.getId())) {
164 iqPacketCallbacks.get(packet.getId()).onIqPacketReceived(account,packet);
165 iqPacketCallbacks.remove(packet.getId());
166 } else if (this.unregisteredIqListener != null) {
167 this.unregisteredIqListener.onIqPacketReceived(account,packet);
168 }
169 return packet;
170 }
171
172 private void processMessage(Tag currentTag) throws XmlPullParserException, IOException {
173 MessagePacket packet = (MessagePacket) processPacket(currentTag, PACKET_MESSAGE);
174 if (this.messageListener != null) {
175 this.messageListener.onMessagePacketReceived(account,packet);
176 }
177 }
178
179 private void processPresence(Tag currentTag) throws XmlPullParserException, IOException {
180 PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE);
181 if (this.presenceListener != null) {
182 this.presenceListener.onPresencePacketReceived(account,packet);
183 }
184 }
185
186 private void sendStartTLS() throws XmlPullParserException, IOException {
187 Tag startTLS = Tag.empty("starttls");
188 startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
189 Log.d(LOGTAG, "sending starttls");
190 tagWriter.writeTag(startTLS).flush();
191 }
192
193 private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
194 IOException {
195 Tag nextTag = tagReader.readTag(); // should be proceed end tag
196 Log.d(LOGTAG, "now switch to ssl");
197 SSLSocket sslSocket;
198 try {
199 sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory
200 .getDefault()).createSocket(socket, socket.getInetAddress()
201 .getHostAddress(), socket.getPort(), true);
202 tagReader.setInputStream(sslSocket.getInputStream());
203 Log.d(LOGTAG, "reset inputstream");
204 tagWriter.setOutputStream(sslSocket.getOutputStream());
205 Log.d(LOGTAG, "switch over seemed to work");
206 isTlsEncrypted = true;
207 sendStartStream();
208 processStream(tagReader.readTag());
209 } catch (IOException e) {
210 Log.d(LOGTAG, "error on ssl" + e.getMessage());
211 }
212 }
213
214 private void sendSaslAuth() throws IOException, XmlPullParserException {
215 String saslString = SASL.plain(account.getUsername(),
216 account.getPassword());
217 Element auth = new Element("auth");
218 auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
219 auth.setAttribute("mechanism", "PLAIN");
220 auth.setContent(saslString);
221 Log.d(LOGTAG,"sending sasl "+auth.toString());
222 tagWriter.writeElement(auth);
223 tagWriter.flush();
224 }
225
226 private void processStreamFeatures(Tag currentTag)
227 throws XmlPullParserException, IOException {
228 this.streamFeatures = tagReader.readElement(currentTag);
229 Log.d(LOGTAG,"process stream features "+streamFeatures);
230 if (this.streamFeatures.hasChild("starttls")&&shouldUseTLS) {
231 sendStartTLS();
232 }
233 if (this.streamFeatures.hasChild("mechanisms")&&shouldAuthenticate) {
234 sendSaslAuth();
235 }
236 if (this.streamFeatures.hasChild("bind")&&shouldBind) {
237 sendBindRequest();
238 if (this.streamFeatures.hasChild("session")) {
239 IqPacket startSession = new IqPacket(IqPacket.TYPE_SET);
240 Element session = new Element("session");
241 session.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-session");
242 session.setContent("");
243 startSession.addChild(session);
244 sendIqPacket(startSession, null);
245 tagWriter.writeElement(startSession);
246 tagWriter.flush();
247 }
248 Element presence = new Element("presence");
249
250 tagWriter.writeElement(presence);
251 tagWriter.flush();
252 }
253 }
254
255 private void sendBindRequest() throws IOException {
256 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
257 Element bind = new Element("bind");
258 bind.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-bind");
259 iq.addChild(bind);
260 this.sendIqPacket(iq, new OnIqPacketReceived() {
261 @Override
262 public void onIqPacketReceived(Account account, IqPacket packet) {
263 Log.d(LOGTAG,"answer for our bind was: "+packet.toString());
264 }
265 });
266 }
267
268 private void processStreamError(Tag currentTag) {
269 Log.d(LOGTAG, "processStreamError");
270 }
271
272 private void sendStartStream() throws IOException {
273 Tag stream = Tag.start("stream");
274 stream.setAttribute("from", account.getJid());
275 stream.setAttribute("to", account.getServer());
276 stream.setAttribute("version", "1.0");
277 stream.setAttribute("xml:lang", "en");
278 stream.setAttribute("xmlns", "jabber:client");
279 stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
280 tagWriter.writeTag(stream).flush();
281 }
282
283 private String nextRandomId() {
284 return new BigInteger(50, random).toString(32);
285 }
286
287 public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) throws IOException {
288 String id = nextRandomId();
289 packet.setAttribute("id",id);
290 tagWriter.writeElement(packet);
291 if (callback != null) {
292 iqPacketCallbacks.put(id, callback);
293 }
294 Log.d(LOGTAG,"sending: "+packet.toString());
295 }
296
297 public void sendMessagePacket(MessagePacket packet) throws IOException {
298 tagWriter.writeElement(packet);
299 }
300
301 public void sendPresencePacket(PresencePacket packet) throws IOException {
302 tagWriter.writeElement(packet);
303 }
304
305 public void setOnMessagePacketReceivedListener(OnMessagePacketReceived listener) {
306 this.messageListener = listener;
307 }
308
309 public void setOnUnregisteredIqPacketReceivedListener(OnIqPacketReceived listener) {
310 this.unregisteredIqListener = listener;
311 }
312
313 public void setOnPresencePacketReceivedListener(OnPresencePacketReceived listener) {
314 this.presenceListener = listener;
315 }
316}