JingleSocks5Transport.java

  1package eu.siacs.conversations.xmpp.jingle;
  2
  3import android.os.PowerManager;
  4import android.util.Log;
  5
  6import java.io.FileNotFoundException;
  7import java.io.IOException;
  8import java.io.InputStream;
  9import java.io.OutputStream;
 10import java.net.InetAddress;
 11import java.net.InetSocketAddress;
 12import java.net.Proxy;
 13import java.net.Socket;
 14import java.net.SocketAddress;
 15import java.net.UnknownHostException;
 16import java.security.MessageDigest;
 17import java.security.NoSuchAlgorithmException;
 18import java.util.Arrays;
 19
 20import eu.siacs.conversations.Config;
 21import eu.siacs.conversations.entities.DownloadableFile;
 22import eu.siacs.conversations.persistance.FileBackend;
 23import eu.siacs.conversations.utils.CryptoHelper;
 24
 25public class JingleSocks5Transport extends JingleTransport {
 26	private JingleCandidate candidate;
 27	private JingleConnection connection;
 28	private String destination;
 29	private OutputStream outputStream;
 30	private InputStream inputStream;
 31	private boolean isEstablished = false;
 32	private boolean activated = false;
 33	protected Socket socket;
 34
 35	public JingleSocks5Transport(JingleConnection jingleConnection,
 36			JingleCandidate candidate) {
 37		this.candidate = candidate;
 38		this.connection = jingleConnection;
 39		try {
 40			MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
 41			StringBuilder destBuilder = new StringBuilder();
 42			destBuilder.append(jingleConnection.getSessionId());
 43			if (candidate.isOurs()) {
 44				destBuilder.append(jingleConnection.getAccount().getJid());
 45				destBuilder.append(jingleConnection.getCounterPart());
 46			} else {
 47				destBuilder.append(jingleConnection.getCounterPart());
 48				destBuilder.append(jingleConnection.getAccount().getJid());
 49			}
 50			mDigest.reset();
 51			this.destination = CryptoHelper.bytesToHex(mDigest
 52					.digest(destBuilder.toString().getBytes()));
 53		} catch (NoSuchAlgorithmException e) {
 54
 55		}
 56	}
 57
 58	public void connect(final OnTransportConnected callback) {
 59		new Thread(new Runnable() {
 60
 61			@Override
 62			public void run() {
 63				try {
 64					final boolean useTor = connection.getConnectionManager().getXmppConnectionService().useTorToConnect();
 65					final Proxy TOR_PROXY = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(InetAddress.getLocalHost(), 9050));
 66					socket = useTor ? new Socket(TOR_PROXY) : new Socket();
 67					SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
 68					socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
 69					inputStream = socket.getInputStream();
 70					outputStream = socket.getOutputStream();
 71					byte[] login = { 0x05, 0x01, 0x00 };
 72					byte[] expectedReply = { 0x05, 0x00 };
 73					byte[] reply = new byte[2];
 74					outputStream.write(login);
 75					inputStream.read(reply);
 76					final String connect = Character.toString('\u0005')
 77							+ '\u0001' + '\u0000' + '\u0003' + '\u0028'
 78							+ destination + '\u0000' + '\u0000';
 79					if (Arrays.equals(reply, expectedReply)) {
 80						outputStream.write(connect.getBytes());
 81						byte[] result = new byte[2];
 82						inputStream.read(result);
 83						int status = result[1];
 84						if (status == 0) {
 85							isEstablished = true;
 86							callback.established();
 87						} else {
 88							callback.failed();
 89						}
 90					} else {
 91						socket.close();
 92						callback.failed();
 93					}
 94				} catch (UnknownHostException e) {
 95					callback.failed();
 96				} catch (IOException e) {
 97					callback.failed();
 98				}
 99			}
100		}).start();
101
102	}
103
104	public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
105		new Thread(new Runnable() {
106
107			@Override
108			public void run() {
109				InputStream fileInputStream = null;
110				final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_"+connection.getSessionId());
111				try {
112					wakeLock.acquire();
113					MessageDigest digest = MessageDigest.getInstance("SHA-1");
114					digest.reset();
115					fileInputStream = connection.getFileInputStream();
116					if (fileInputStream == null) {
117						Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
118						callback.onFileTransferAborted();
119						return;
120					}
121					long size = file.getExpectedSize();
122					long transmitted = 0;
123					int count;
124					byte[] buffer = new byte[8192];
125					while ((count = fileInputStream.read(buffer)) > 0) {
126						outputStream.write(buffer, 0, count);
127						digest.update(buffer, 0, count);
128						transmitted += count;
129						connection.updateProgress((int) ((((double) transmitted) / size) * 100));
130					}
131					outputStream.flush();
132					file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
133					if (callback != null) {
134						callback.onFileTransmitted(file);
135					}
136				} catch (FileNotFoundException e) {
137					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
138					callback.onFileTransferAborted();
139				} catch (IOException e) {
140					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
141					callback.onFileTransferAborted();
142				} catch (NoSuchAlgorithmException e) {
143					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
144					callback.onFileTransferAborted();
145				} finally {
146					FileBackend.close(fileInputStream);
147					wakeLock.release();
148				}
149			}
150		}).start();
151
152	}
153
154	public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
155		new Thread(new Runnable() {
156
157			@Override
158			public void run() {
159				OutputStream fileOutputStream = null;
160				final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_"+connection.getSessionId());
161				try {
162					wakeLock.acquire();
163					MessageDigest digest = MessageDigest.getInstance("SHA-1");
164					digest.reset();
165					inputStream.skip(45);
166					socket.setSoTimeout(30000);
167					file.getParentFile().mkdirs();
168					file.createNewFile();
169					fileOutputStream = connection.getFileOutputStream();
170					if (fileOutputStream == null) {
171						callback.onFileTransferAborted();
172						Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
173						return;
174					}
175					double size = file.getExpectedSize();
176					long remainingSize = file.getExpectedSize();
177					byte[] buffer = new byte[8192];
178					int count;
179					while (remainingSize > 0) {
180						count = inputStream.read(buffer);
181						if (count == -1) {
182							callback.onFileTransferAborted();
183							Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining");
184							return;
185						} else {
186							fileOutputStream.write(buffer, 0, count);
187							digest.update(buffer, 0, count);
188							remainingSize -= count;
189						}
190						connection.updateProgress((int) (((size - remainingSize) / size) * 100));
191					}
192					fileOutputStream.flush();
193					fileOutputStream.close();
194					file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
195					callback.onFileTransmitted(file);
196				} catch (FileNotFoundException e) {
197					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
198					callback.onFileTransferAborted();
199				} catch (IOException e) {
200					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
201					callback.onFileTransferAborted();
202				} catch (NoSuchAlgorithmException e) {
203					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
204					callback.onFileTransferAborted();
205				} finally {
206					wakeLock.release();
207					FileBackend.close(fileOutputStream);
208					FileBackend.close(inputStream);
209				}
210			}
211		}).start();
212	}
213
214	public boolean isProxy() {
215		return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
216	}
217
218	public boolean needsActivation() {
219		return (this.isProxy() && !this.activated);
220	}
221
222	public void disconnect() {
223		FileBackend.close(inputStream);
224		FileBackend.close(outputStream);
225		FileBackend.close(socket);
226	}
227
228	public boolean isEstablished() {
229		return this.isEstablished;
230	}
231
232	public JingleCandidate getCandidate() {
233		return this.candidate;
234	}
235
236	public void setActivated(boolean activated) {
237		this.activated = activated;
238	}
239}