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