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