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