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 (IOException e) {
79 callback.failed();
80 }
81 }
82 }).start();
83
84 }
85
86 public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
87 new Thread(new Runnable() {
88
89 @Override
90 public void run() {
91 InputStream fileInputStream = null;
92 final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_"+connection.getSessionId());
93 try {
94 wakeLock.acquire();
95 MessageDigest digest = MessageDigest.getInstance("SHA-1");
96 digest.reset();
97 fileInputStream = connection.getFileInputStream();
98 if (fileInputStream == null) {
99 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
100 callback.onFileTransferAborted();
101 return;
102 }
103 long size = file.getExpectedSize();
104 long transmitted = 0;
105 int count;
106 byte[] buffer = new byte[8192];
107 while ((count = fileInputStream.read(buffer)) > 0) {
108 outputStream.write(buffer, 0, count);
109 digest.update(buffer, 0, count);
110 transmitted += count;
111 connection.updateProgress((int) ((((double) transmitted) / size) * 100));
112 }
113 outputStream.flush();
114 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
115 if (callback != null) {
116 callback.onFileTransmitted(file);
117 }
118 } catch (Exception e) {
119 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
120 callback.onFileTransferAborted();
121 } finally {
122 FileBackend.close(fileInputStream);
123 wakeLock.release();
124 }
125 }
126 }).start();
127
128 }
129
130 public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
131 new Thread(new Runnable() {
132
133 @Override
134 public void run() {
135 OutputStream fileOutputStream = null;
136 final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_"+connection.getSessionId());
137 try {
138 wakeLock.acquire();
139 MessageDigest digest = MessageDigest.getInstance("SHA-1");
140 digest.reset();
141 //inputStream.skip(45);
142 socket.setSoTimeout(30000);
143 file.getParentFile().mkdirs();
144 file.createNewFile();
145 fileOutputStream = connection.getFileOutputStream();
146 if (fileOutputStream == null) {
147 callback.onFileTransferAborted();
148 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
149 return;
150 }
151 double size = file.getExpectedSize();
152 long remainingSize = file.getExpectedSize();
153 byte[] buffer = new byte[8192];
154 int count;
155 while (remainingSize > 0) {
156 count = inputStream.read(buffer);
157 if (count == -1) {
158 callback.onFileTransferAborted();
159 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining");
160 return;
161 } else {
162 fileOutputStream.write(buffer, 0, count);
163 digest.update(buffer, 0, count);
164 remainingSize -= count;
165 }
166 connection.updateProgress((int) (((size - remainingSize) / size) * 100));
167 }
168 fileOutputStream.flush();
169 fileOutputStream.close();
170 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
171 callback.onFileTransmitted(file);
172 } catch (Exception e) {
173 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
174 callback.onFileTransferAborted();
175 } finally {
176 wakeLock.release();
177 FileBackend.close(fileOutputStream);
178 FileBackend.close(inputStream);
179 }
180 }
181 }).start();
182 }
183
184 public boolean isProxy() {
185 return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
186 }
187
188 public boolean needsActivation() {
189 return (this.isProxy() && !this.activated);
190 }
191
192 public void disconnect() {
193 FileBackend.close(inputStream);
194 FileBackend.close(outputStream);
195 FileBackend.close(socket);
196 }
197
198 public boolean isEstablished() {
199 return this.isEstablished;
200 }
201
202 public JingleCandidate getCandidate() {
203 return this.candidate;
204 }
205
206 public void setActivated(boolean activated) {
207 this.activated = activated;
208 }
209}