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