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