JingleSocks5Transport.java

  1package eu.siacs.conversations.xmpp.jingle;
  2
  3import android.os.PowerManager;
  4import android.util.Log;
  5
  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.security.MessageDigest;
 13import java.security.NoSuchAlgorithmException;
 14
 15import eu.siacs.conversations.Config;
 16import eu.siacs.conversations.entities.DownloadableFile;
 17import eu.siacs.conversations.persistance.FileBackend;
 18import eu.siacs.conversations.services.AbstractConnectionManager;
 19import eu.siacs.conversations.utils.CryptoHelper;
 20import eu.siacs.conversations.utils.SocksSocketFactory;
 21import eu.siacs.conversations.utils.WakeLockHelper;
 22import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
 23
 24public class JingleSocks5Transport extends JingleTransport {
 25    private final JingleCandidate candidate;
 26    private final JingleConnection connection;
 27    private final String destination;
 28    private OutputStream outputStream;
 29    private InputStream inputStream;
 30    private boolean isEstablished = false;
 31    private boolean activated = false;
 32    private Socket socket;
 33
 34    JingleSocks5Transport(JingleConnection jingleConnection, JingleCandidate candidate) {
 35        final MessageDigest messageDigest;
 36        try {
 37            messageDigest = MessageDigest.getInstance("SHA-1");
 38        } catch (NoSuchAlgorithmException e) {
 39            throw new AssertionError(e);
 40        }
 41        this.candidate = candidate;
 42        this.connection = jingleConnection;
 43        final StringBuilder destBuilder = new StringBuilder();
 44        if (jingleConnection.getFtVersion() == Content.Version.FT_3) {
 45            Log.d(Config.LOGTAG, this.connection.getAccount().getJid().asBareJid() + ": using session Id instead of transport Id for proxy destination");
 46            destBuilder.append(jingleConnection.getSessionId());
 47        } else {
 48            destBuilder.append(jingleConnection.getTransportId());
 49        }
 50        if (candidate.isOurs()) {
 51            destBuilder.append(jingleConnection.getAccount().getJid());
 52            destBuilder.append(jingleConnection.getCounterPart());
 53        } else {
 54            destBuilder.append(jingleConnection.getCounterPart());
 55            destBuilder.append(jingleConnection.getAccount().getJid());
 56        }
 57        messageDigest.reset();
 58        this.destination = CryptoHelper.bytesToHex(messageDigest.digest(destBuilder.toString().getBytes()));
 59    }
 60
 61    public void connect(final OnTransportConnected callback) {
 62        new Thread(() -> {
 63            try {
 64                final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect();
 65                if (useTor) {
 66                    socket = SocksSocketFactory.createSocketOverTor(candidate.getHost(), candidate.getPort());
 67                } else {
 68                    socket = new Socket();
 69                    SocketAddress address = new InetSocketAddress(candidate.getHost(), candidate.getPort());
 70                    socket.connect(address, Config.SOCKET_TIMEOUT * 1000);
 71                }
 72                inputStream = socket.getInputStream();
 73                outputStream = socket.getOutputStream();
 74                SocksSocketFactory.createSocksConnection(socket, destination, 0);
 75                isEstablished = true;
 76                callback.established();
 77            } catch (IOException e) {
 78                callback.failed();
 79            }
 80        }).start();
 81
 82    }
 83
 84    public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
 85        new Thread(() -> {
 86            InputStream fileInputStream = null;
 87            final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_" + connection.getSessionId());
 88            try {
 89                wakeLock.acquire();
 90                MessageDigest digest = MessageDigest.getInstance("SHA-1");
 91                digest.reset();
 92                fileInputStream = connection.getFileInputStream();
 93                if (fileInputStream == null) {
 94                    Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": could not create input stream");
 95                    callback.onFileTransferAborted();
 96                    return;
 97                }
 98                final InputStream innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream);
 99                long size = file.getExpectedSize();
100                long transmitted = 0;
101                int count;
102                byte[] buffer = new byte[8192];
103                while ((count = innerInputStream.read(buffer)) > 0) {
104                    outputStream.write(buffer, 0, count);
105                    digest.update(buffer, 0, count);
106                    transmitted += count;
107                    connection.updateProgress((int) ((((double) transmitted) / size) * 100));
108                }
109                outputStream.flush();
110                file.setSha1Sum(digest.digest());
111                if (callback != null) {
112                    callback.onFileTransmitted(file);
113                }
114            } catch (Exception e) {
115                Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": " + e.getMessage());
116                callback.onFileTransferAborted();
117            } finally {
118                FileBackend.close(fileInputStream);
119                WakeLockHelper.release(wakeLock);
120            }
121        }).start();
122
123    }
124
125    public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
126        new Thread(() -> {
127            OutputStream fileOutputStream = null;
128            final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_" + connection.getSessionId());
129            try {
130                wakeLock.acquire();
131                MessageDigest digest = MessageDigest.getInstance("SHA-1");
132                digest.reset();
133                //inputStream.skip(45);
134                socket.setSoTimeout(30000);
135                fileOutputStream = connection.getFileOutputStream();
136                if (fileOutputStream == null) {
137                    callback.onFileTransferAborted();
138                    Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": could not create output stream");
139                    return;
140                }
141                double size = file.getExpectedSize();
142                long remainingSize = file.getExpectedSize();
143                byte[] buffer = new byte[8192];
144                int count;
145                while (remainingSize > 0) {
146                    count = inputStream.read(buffer);
147                    if (count == -1) {
148                        callback.onFileTransferAborted();
149                        Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": file ended prematurely with " + remainingSize + " bytes remaining");
150                        return;
151                    } else {
152                        fileOutputStream.write(buffer, 0, count);
153                        digest.update(buffer, 0, count);
154                        remainingSize -= count;
155                    }
156                    connection.updateProgress((int) (((size - remainingSize) / size) * 100));
157                }
158                fileOutputStream.flush();
159                fileOutputStream.close();
160                file.setSha1Sum(digest.digest());
161                callback.onFileTransmitted(file);
162            } catch (Exception e) {
163                Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": " + e.getMessage());
164                callback.onFileTransferAborted();
165            } finally {
166                WakeLockHelper.release(wakeLock);
167                FileBackend.close(fileOutputStream);
168                FileBackend.close(inputStream);
169            }
170        }).start();
171    }
172
173    public boolean isProxy() {
174        return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
175    }
176
177    public boolean needsActivation() {
178        return (this.isProxy() && !this.activated);
179    }
180
181    public void disconnect() {
182        FileBackend.close(inputStream);
183        FileBackend.close(outputStream);
184        FileBackend.close(socket);
185    }
186
187    public boolean isEstablished() {
188        return this.isEstablished;
189    }
190
191    public JingleCandidate getCandidate() {
192        return this.candidate;
193    }
194
195    public void setActivated(boolean activated) {
196        this.activated = activated;
197    }
198}