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