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