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