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