JingleSocks5Transport.java

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