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