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.InetSocketAddress;
 11import java.net.Socket;
 12import java.net.SocketAddress;
 13import java.net.UnknownHostException;
 14import java.security.MessageDigest;
 15import java.security.NoSuchAlgorithmException;
 16import java.util.Arrays;
 17
 18import eu.siacs.conversations.Config;
 19import eu.siacs.conversations.entities.DownloadableFile;
 20import eu.siacs.conversations.persistance.FileBackend;
 21import eu.siacs.conversations.utils.CryptoHelper;
 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	protected Socket socket;
 32
 33	public JingleSocks5Transport(JingleConnection jingleConnection,
 34			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			destBuilder.append(jingleConnection.getSessionId());
 41			if (candidate.isOurs()) {
 42				destBuilder.append(jingleConnection.getAccount().getJid());
 43				destBuilder.append(jingleConnection.getCounterPart());
 44			} else {
 45				destBuilder.append(jingleConnection.getCounterPart());
 46				destBuilder.append(jingleConnection.getAccount().getJid());
 47			}
 48			mDigest.reset();
 49			this.destination = CryptoHelper.bytesToHex(mDigest
 50					.digest(destBuilder.toString().getBytes()));
 51		} catch (NoSuchAlgorithmException e) {
 52
 53		}
 54	}
 55
 56	public void connect(final OnTransportConnected callback) {
 57		new Thread(new Runnable() {
 58
 59			@Override
 60			public void run() {
 61				try {
 62					socket = new Socket();
 63					SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
 64					socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
 65					inputStream = socket.getInputStream();
 66					outputStream = socket.getOutputStream();
 67					byte[] login = { 0x05, 0x01, 0x00 };
 68					byte[] expectedReply = { 0x05, 0x00 };
 69					byte[] reply = new byte[2];
 70					outputStream.write(login);
 71					inputStream.read(reply);
 72					final String connect = Character.toString('\u0005')
 73							+ '\u0001' + '\u0000' + '\u0003' + '\u0028'
 74							+ destination + '\u0000' + '\u0000';
 75					if (Arrays.equals(reply, expectedReply)) {
 76						outputStream.write(connect.getBytes());
 77						byte[] result = new byte[2];
 78						inputStream.read(result);
 79						int status = result[1];
 80						if (status == 0) {
 81							isEstablished = true;
 82							callback.established();
 83						} else {
 84							callback.failed();
 85						}
 86					} else {
 87						socket.close();
 88						callback.failed();
 89					}
 90				} catch (UnknownHostException e) {
 91					callback.failed();
 92				} catch (IOException e) {
 93					callback.failed();
 94				}
 95			}
 96		}).start();
 97
 98	}
 99
100	public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
101		new Thread(new Runnable() {
102
103			@Override
104			public void run() {
105				InputStream fileInputStream = null;
106				final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_"+connection.getSessionId());
107				try {
108					wakeLock.acquire();
109					MessageDigest digest = MessageDigest.getInstance("SHA-1");
110					digest.reset();
111					fileInputStream = connection.getFileInputStream();
112					if (fileInputStream == null) {
113						Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
114						callback.onFileTransferAborted();
115						return;
116					}
117					long size = file.getExpectedSize();
118					long transmitted = 0;
119					int count;
120					byte[] buffer = new byte[8192];
121					while ((count = fileInputStream.read(buffer)) > 0) {
122						outputStream.write(buffer, 0, count);
123						digest.update(buffer, 0, count);
124						transmitted += count;
125						connection.updateProgress((int) ((((double) transmitted) / size) * 100));
126					}
127					outputStream.flush();
128					file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
129					if (callback != null) {
130						callback.onFileTransmitted(file);
131					}
132				} catch (FileNotFoundException e) {
133					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
134					callback.onFileTransferAborted();
135				} catch (IOException e) {
136					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
137					callback.onFileTransferAborted();
138				} catch (NoSuchAlgorithmException e) {
139					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
140					callback.onFileTransferAborted();
141				} finally {
142					FileBackend.close(fileInputStream);
143					wakeLock.release();
144				}
145			}
146		}).start();
147
148	}
149
150	public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
151		new Thread(new Runnable() {
152
153			@Override
154			public void run() {
155				OutputStream fileOutputStream = null;
156				final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_"+connection.getSessionId());
157				try {
158					wakeLock.acquire();
159					MessageDigest digest = MessageDigest.getInstance("SHA-1");
160					digest.reset();
161					inputStream.skip(45);
162					socket.setSoTimeout(30000);
163					file.getParentFile().mkdirs();
164					file.createNewFile();
165					fileOutputStream = connection.getFileOutputStream();
166					if (fileOutputStream == null) {
167						callback.onFileTransferAborted();
168						Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
169						return;
170					}
171					double size = file.getExpectedSize();
172					long remainingSize = file.getExpectedSize();
173					byte[] buffer = new byte[8192];
174					int count;
175					while (remainingSize > 0) {
176						count = inputStream.read(buffer);
177						if (count == -1) {
178							callback.onFileTransferAborted();
179							Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining");
180							return;
181						} else {
182							fileOutputStream.write(buffer, 0, count);
183							digest.update(buffer, 0, count);
184							remainingSize -= count;
185						}
186						connection.updateProgress((int) (((size - remainingSize) / size) * 100));
187					}
188					fileOutputStream.flush();
189					fileOutputStream.close();
190					file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
191					callback.onFileTransmitted(file);
192				} catch (FileNotFoundException e) {
193					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
194					callback.onFileTransferAborted();
195				} catch (IOException e) {
196					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
197					callback.onFileTransferAborted();
198				} catch (NoSuchAlgorithmException e) {
199					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
200					callback.onFileTransferAborted();
201				} finally {
202					wakeLock.release();
203					FileBackend.close(fileOutputStream);
204					FileBackend.close(inputStream);
205				}
206			}
207		}).start();
208	}
209
210	public boolean isProxy() {
211		return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
212	}
213
214	public boolean needsActivation() {
215		return (this.isProxy() && !this.activated);
216	}
217
218	public void disconnect() {
219		FileBackend.close(inputStream);
220		FileBackend.close(outputStream);
221		FileBackend.close(socket);
222	}
223
224	public boolean isEstablished() {
225		return this.isEstablished;
226	}
227
228	public JingleCandidate getCandidate() {
229		return this.candidate;
230	}
231
232	public void setActivated(boolean activated) {
233		this.activated = activated;
234	}
235}