JingleSocks5Transport.java

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