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