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;
 24import eu.siacs.conversations.utils.SocksSocketFactory;
 25
 26public class JingleSocks5Transport extends JingleTransport {
 27	private JingleCandidate candidate;
 28	private JingleConnection connection;
 29	private String destination;
 30	private OutputStream outputStream;
 31	private InputStream inputStream;
 32	private boolean isEstablished = false;
 33	private boolean activated = false;
 34	protected Socket socket;
 35
 36	public JingleSocks5Transport(JingleConnection jingleConnection,
 37			JingleCandidate candidate) {
 38		this.candidate = candidate;
 39		this.connection = jingleConnection;
 40		try {
 41			MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
 42			StringBuilder destBuilder = new StringBuilder();
 43			destBuilder.append(jingleConnection.getSessionId());
 44			if (candidate.isOurs()) {
 45				destBuilder.append(jingleConnection.getAccount().getJid());
 46				destBuilder.append(jingleConnection.getCounterPart());
 47			} else {
 48				destBuilder.append(jingleConnection.getCounterPart());
 49				destBuilder.append(jingleConnection.getAccount().getJid());
 50			}
 51			mDigest.reset();
 52			this.destination = CryptoHelper.bytesToHex(mDigest
 53					.digest(destBuilder.toString().getBytes()));
 54		} catch (NoSuchAlgorithmException e) {
 55
 56		}
 57	}
 58
 59	public void connect(final OnTransportConnected callback) {
 60		new Thread(new Runnable() {
 61
 62			@Override
 63			public void run() {
 64				try {
 65					final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect();
 66					if (useTor) {
 67						socket = SocksSocketFactory.createSocketOverTor(candidate.getHost(),candidate.getPort());
 68					} else {
 69						socket = new Socket();
 70						SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
 71						socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
 72					}
 73					inputStream = socket.getInputStream();
 74					outputStream = socket.getOutputStream();
 75					SocksSocketFactory.createSocksConnection(socket,destination,0);
 76					isEstablished = true;
 77					callback.established();
 78				} catch (IOException e) {
 79					callback.failed();
 80				}
 81			}
 82		}).start();
 83
 84	}
 85
 86	public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
 87		new Thread(new Runnable() {
 88
 89			@Override
 90			public void run() {
 91				InputStream fileInputStream = null;
 92				final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_"+connection.getSessionId());
 93				try {
 94					wakeLock.acquire();
 95					MessageDigest digest = MessageDigest.getInstance("SHA-1");
 96					digest.reset();
 97					fileInputStream = connection.getFileInputStream();
 98					if (fileInputStream == null) {
 99						Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
100						callback.onFileTransferAborted();
101						return;
102					}
103					long size = file.getExpectedSize();
104					long transmitted = 0;
105					int count;
106					byte[] buffer = new byte[8192];
107					while ((count = fileInputStream.read(buffer)) > 0) {
108						outputStream.write(buffer, 0, count);
109						digest.update(buffer, 0, count);
110						transmitted += count;
111						connection.updateProgress((int) ((((double) transmitted) / size) * 100));
112					}
113					outputStream.flush();
114					file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
115					if (callback != null) {
116						callback.onFileTransmitted(file);
117					}
118				} catch (Exception e) {
119					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
120					callback.onFileTransferAborted();
121				} finally {
122					FileBackend.close(fileInputStream);
123					wakeLock.release();
124				}
125			}
126		}).start();
127
128	}
129
130	public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
131		new Thread(new Runnable() {
132
133			@Override
134			public void run() {
135				OutputStream fileOutputStream = null;
136				final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_"+connection.getSessionId());
137				try {
138					wakeLock.acquire();
139					MessageDigest digest = MessageDigest.getInstance("SHA-1");
140					digest.reset();
141					//inputStream.skip(45);
142					socket.setSoTimeout(30000);
143					file.getParentFile().mkdirs();
144					file.createNewFile();
145					fileOutputStream = connection.getFileOutputStream();
146					if (fileOutputStream == null) {
147						callback.onFileTransferAborted();
148						Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
149						return;
150					}
151					double size = file.getExpectedSize();
152					long remainingSize = file.getExpectedSize();
153					byte[] buffer = new byte[8192];
154					int count;
155					while (remainingSize > 0) {
156						count = inputStream.read(buffer);
157						if (count == -1) {
158							callback.onFileTransferAborted();
159							Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining");
160							return;
161						} else {
162							fileOutputStream.write(buffer, 0, count);
163							digest.update(buffer, 0, count);
164							remainingSize -= count;
165						}
166						connection.updateProgress((int) (((size - remainingSize) / size) * 100));
167					}
168					fileOutputStream.flush();
169					fileOutputStream.close();
170					file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
171					callback.onFileTransmitted(file);
172				} catch (Exception e) {
173					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
174					callback.onFileTransferAborted();
175				} finally {
176					wakeLock.release();
177					FileBackend.close(fileOutputStream);
178					FileBackend.close(inputStream);
179				}
180			}
181		}).start();
182	}
183
184	public boolean isProxy() {
185		return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
186	}
187
188	public boolean needsActivation() {
189		return (this.isProxy() && !this.activated);
190	}
191
192	public void disconnect() {
193		FileBackend.close(inputStream);
194		FileBackend.close(outputStream);
195		FileBackend.close(socket);
196	}
197
198	public boolean isEstablished() {
199		return this.isEstablished;
200	}
201
202	public JingleCandidate getCandidate() {
203		return this.candidate;
204	}
205
206	public void setActivated(boolean activated) {
207		this.activated = activated;
208	}
209}