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