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