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