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 (UnknownHostException e) {
 79					callback.failed();
 80				} catch (IOException e) {
 81					callback.failed();
 82				}
 83			}
 84		}).start();
 85
 86	}
 87
 88	public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
 89		new Thread(new Runnable() {
 90
 91			@Override
 92			public void run() {
 93				InputStream fileInputStream = null;
 94				final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_"+connection.getSessionId());
 95				try {
 96					wakeLock.acquire();
 97					MessageDigest digest = MessageDigest.getInstance("SHA-1");
 98					digest.reset();
 99					fileInputStream = connection.getFileInputStream();
100					if (fileInputStream == null) {
101						Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
102						callback.onFileTransferAborted();
103						return;
104					}
105					long size = file.getExpectedSize();
106					long transmitted = 0;
107					int count;
108					byte[] buffer = new byte[8192];
109					while ((count = fileInputStream.read(buffer)) > 0) {
110						outputStream.write(buffer, 0, count);
111						digest.update(buffer, 0, count);
112						transmitted += count;
113						connection.updateProgress((int) ((((double) transmitted) / size) * 100));
114					}
115					outputStream.flush();
116					file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
117					if (callback != null) {
118						callback.onFileTransmitted(file);
119					}
120				} catch (FileNotFoundException e) {
121					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
122					callback.onFileTransferAborted();
123				} catch (IOException e) {
124					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
125					callback.onFileTransferAborted();
126				} catch (NoSuchAlgorithmException e) {
127					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
128					callback.onFileTransferAborted();
129				} finally {
130					FileBackend.close(fileInputStream);
131					wakeLock.release();
132				}
133			}
134		}).start();
135
136	}
137
138	public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
139		new Thread(new Runnable() {
140
141			@Override
142			public void run() {
143				OutputStream fileOutputStream = null;
144				final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_"+connection.getSessionId());
145				try {
146					wakeLock.acquire();
147					MessageDigest digest = MessageDigest.getInstance("SHA-1");
148					digest.reset();
149					//inputStream.skip(45);
150					socket.setSoTimeout(30000);
151					file.getParentFile().mkdirs();
152					file.createNewFile();
153					fileOutputStream = connection.getFileOutputStream();
154					if (fileOutputStream == null) {
155						callback.onFileTransferAborted();
156						Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
157						return;
158					}
159					double size = file.getExpectedSize();
160					long remainingSize = file.getExpectedSize();
161					byte[] buffer = new byte[8192];
162					int count;
163					while (remainingSize > 0) {
164						count = inputStream.read(buffer);
165						if (count == -1) {
166							callback.onFileTransferAborted();
167							Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining");
168							return;
169						} else {
170							fileOutputStream.write(buffer, 0, count);
171							digest.update(buffer, 0, count);
172							remainingSize -= count;
173						}
174						connection.updateProgress((int) (((size - remainingSize) / size) * 100));
175					}
176					fileOutputStream.flush();
177					fileOutputStream.close();
178					file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
179					callback.onFileTransmitted(file);
180				} catch (FileNotFoundException e) {
181					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
182					callback.onFileTransferAborted();
183				} catch (IOException e) {
184					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
185					callback.onFileTransferAborted();
186				} catch (NoSuchAlgorithmException e) {
187					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
188					callback.onFileTransferAborted();
189				} finally {
190					wakeLock.release();
191					FileBackend.close(fileOutputStream);
192					FileBackend.close(inputStream);
193				}
194			}
195		}).start();
196	}
197
198	public boolean isProxy() {
199		return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
200	}
201
202	public boolean needsActivation() {
203		return (this.isProxy() && !this.activated);
204	}
205
206	public void disconnect() {
207		FileBackend.close(inputStream);
208		FileBackend.close(outputStream);
209		FileBackend.close(socket);
210	}
211
212	public boolean isEstablished() {
213		return this.isEstablished;
214	}
215
216	public JingleCandidate getCandidate() {
217		return this.candidate;
218	}
219
220	public void setActivated(boolean activated) {
221		this.activated = activated;
222	}
223}