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.InetSocketAddress;
11import java.net.Socket;
12import java.net.SocketAddress;
13import java.net.UnknownHostException;
14import java.security.MessageDigest;
15import java.security.NoSuchAlgorithmException;
16import java.util.Arrays;
17
18import eu.siacs.conversations.Config;
19import eu.siacs.conversations.entities.DownloadableFile;
20import eu.siacs.conversations.persistance.FileBackend;
21import eu.siacs.conversations.utils.CryptoHelper;
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 protected Socket socket;
32
33 public JingleSocks5Transport(JingleConnection jingleConnection,
34 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 destBuilder.append(jingleConnection.getSessionId());
41 if (candidate.isOurs()) {
42 destBuilder.append(jingleConnection.getAccount().getJid());
43 destBuilder.append(jingleConnection.getCounterPart());
44 } else {
45 destBuilder.append(jingleConnection.getCounterPart());
46 destBuilder.append(jingleConnection.getAccount().getJid());
47 }
48 mDigest.reset();
49 this.destination = CryptoHelper.bytesToHex(mDigest
50 .digest(destBuilder.toString().getBytes()));
51 } catch (NoSuchAlgorithmException e) {
52
53 }
54 }
55
56 public void connect(final OnTransportConnected callback) {
57 new Thread(new Runnable() {
58
59 @Override
60 public void run() {
61 try {
62 socket = new Socket();
63 SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
64 socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
65 inputStream = socket.getInputStream();
66 outputStream = socket.getOutputStream();
67 byte[] login = { 0x05, 0x01, 0x00 };
68 byte[] expectedReply = { 0x05, 0x00 };
69 byte[] reply = new byte[2];
70 outputStream.write(login);
71 inputStream.read(reply);
72 final String connect = Character.toString('\u0005')
73 + '\u0001' + '\u0000' + '\u0003' + '\u0028'
74 + destination + '\u0000' + '\u0000';
75 if (Arrays.equals(reply, expectedReply)) {
76 outputStream.write(connect.getBytes());
77 byte[] result = new byte[2];
78 inputStream.read(result);
79 int status = result[1];
80 if (status == 0) {
81 isEstablished = true;
82 callback.established();
83 } else {
84 callback.failed();
85 }
86 } else {
87 socket.close();
88 callback.failed();
89 }
90 } catch (UnknownHostException e) {
91 callback.failed();
92 } catch (IOException e) {
93 callback.failed();
94 }
95 }
96 }).start();
97
98 }
99
100 public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
101 new Thread(new Runnable() {
102
103 @Override
104 public void run() {
105 InputStream fileInputStream = null;
106 final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_"+connection.getSessionId());
107 try {
108 wakeLock.acquire();
109 MessageDigest digest = MessageDigest.getInstance("SHA-1");
110 digest.reset();
111 fileInputStream = connection.getFileInputStream();
112 if (fileInputStream == null) {
113 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
114 callback.onFileTransferAborted();
115 return;
116 }
117 long size = file.getExpectedSize();
118 long transmitted = 0;
119 int count;
120 byte[] buffer = new byte[8192];
121 while ((count = fileInputStream.read(buffer)) > 0) {
122 outputStream.write(buffer, 0, count);
123 digest.update(buffer, 0, count);
124 transmitted += count;
125 connection.updateProgress((int) ((((double) transmitted) / size) * 100));
126 }
127 outputStream.flush();
128 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
129 if (callback != null) {
130 callback.onFileTransmitted(file);
131 }
132 } catch (FileNotFoundException e) {
133 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
134 callback.onFileTransferAborted();
135 } catch (IOException e) {
136 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
137 callback.onFileTransferAborted();
138 } catch (NoSuchAlgorithmException e) {
139 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
140 callback.onFileTransferAborted();
141 } finally {
142 FileBackend.close(fileInputStream);
143 wakeLock.release();
144 }
145 }
146 }).start();
147
148 }
149
150 public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
151 new Thread(new Runnable() {
152
153 @Override
154 public void run() {
155 OutputStream fileOutputStream = null;
156 final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_"+connection.getSessionId());
157 try {
158 wakeLock.acquire();
159 MessageDigest digest = MessageDigest.getInstance("SHA-1");
160 digest.reset();
161 inputStream.skip(45);
162 socket.setSoTimeout(30000);
163 file.getParentFile().mkdirs();
164 file.createNewFile();
165 fileOutputStream = connection.getFileOutputStream();
166 if (fileOutputStream == null) {
167 callback.onFileTransferAborted();
168 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
169 return;
170 }
171 double size = file.getExpectedSize();
172 long remainingSize = file.getExpectedSize();
173 byte[] buffer = new byte[8192];
174 int count;
175 while (remainingSize > 0) {
176 count = inputStream.read(buffer);
177 if (count == -1) {
178 callback.onFileTransferAborted();
179 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining");
180 return;
181 } else {
182 fileOutputStream.write(buffer, 0, count);
183 digest.update(buffer, 0, count);
184 remainingSize -= count;
185 }
186 connection.updateProgress((int) (((size - remainingSize) / size) * 100));
187 }
188 fileOutputStream.flush();
189 fileOutputStream.close();
190 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
191 callback.onFileTransmitted(file);
192 } catch (FileNotFoundException e) {
193 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
194 callback.onFileTransferAborted();
195 } catch (IOException e) {
196 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
197 callback.onFileTransferAborted();
198 } catch (NoSuchAlgorithmException e) {
199 Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
200 callback.onFileTransferAborted();
201 } finally {
202 wakeLock.release();
203 FileBackend.close(fileOutputStream);
204 FileBackend.close(inputStream);
205 }
206 }
207 }).start();
208 }
209
210 public boolean isProxy() {
211 return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
212 }
213
214 public boolean needsActivation() {
215 return (this.isProxy() && !this.activated);
216 }
217
218 public void disconnect() {
219 FileBackend.close(inputStream);
220 FileBackend.close(outputStream);
221 FileBackend.close(socket);
222 }
223
224 public boolean isEstablished() {
225 return this.isEstablished;
226 }
227
228 public JingleCandidate getCandidate() {
229 return this.candidate;
230 }
231
232 public void setActivated(boolean activated) {
233 this.activated = activated;
234 }
235}