AbstractConnectionManager.java

  1package eu.siacs.conversations.services;
  2
  3import android.content.Context;
  4import android.os.FileUtils;
  5import android.os.PowerManager;
  6import android.os.SystemClock;
  7import android.util.Log;
  8
  9import org.bouncycastle.crypto.engines.AESEngine;
 10import org.bouncycastle.crypto.io.CipherInputStream;
 11import org.bouncycastle.crypto.io.CipherOutputStream;
 12import org.bouncycastle.crypto.modes.AEADBlockCipher;
 13import org.bouncycastle.crypto.modes.GCMBlockCipher;
 14import org.bouncycastle.crypto.params.AEADParameters;
 15import org.bouncycastle.crypto.params.KeyParameter;
 16
 17import java.io.FileInputStream;
 18import java.io.FileNotFoundException;
 19import java.io.FileOutputStream;
 20import java.io.IOException;
 21import java.io.InputStream;
 22import java.io.OutputStream;
 23import java.security.InvalidAlgorithmParameterException;
 24import java.security.InvalidKeyException;
 25import java.security.NoSuchAlgorithmException;
 26import java.security.NoSuchProviderException;
 27import java.util.concurrent.atomic.AtomicLong;
 28
 29import javax.annotation.Nullable;
 30import javax.crypto.NoSuchPaddingException;
 31
 32import eu.siacs.conversations.Config;
 33import eu.siacs.conversations.R;
 34import eu.siacs.conversations.entities.DownloadableFile;
 35import eu.siacs.conversations.utils.Compatibility;
 36import okhttp3.MediaType;
 37import okhttp3.RequestBody;
 38import okio.Buffer;
 39import okio.BufferedSink;
 40import okio.ForwardingSink;
 41import okio.Okio;
 42import okio.Sink;
 43import okio.Source;
 44
 45import static eu.siacs.conversations.entities.Transferable.VALID_CRYPTO_EXTENSIONS;
 46
 47public class AbstractConnectionManager {
 48
 49    private static final int UI_REFRESH_THRESHOLD = 250;
 50    private static final AtomicLong LAST_UI_UPDATE_CALL = new AtomicLong(0);
 51    protected XmppConnectionService mXmppConnectionService;
 52
 53    public AbstractConnectionManager(XmppConnectionService service) {
 54        this.mXmppConnectionService = service;
 55    }
 56
 57    public static InputStream upgrade(DownloadableFile file, InputStream is) {
 58        if (file.getKey() != null && file.getIv() != null) {
 59            AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
 60            cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
 61            return new CipherInputStream(is, cipher);
 62        } else {
 63            return is;
 64        }
 65    }
 66
 67
 68    //For progress tracking see:
 69    //https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Progress.java
 70
 71    public static RequestBody requestBody(final DownloadableFile file, final ProgressListener progressListener) {
 72        return new RequestBody() {
 73
 74            @Override
 75            public long contentLength() {
 76                return file.getSize() + (file.getKey() != null ? 16 : 0);
 77            }
 78
 79            @Nullable
 80            @Override
 81            public MediaType contentType() {
 82                return MediaType.parse(file.getMimeType());
 83            }
 84
 85            @Override
 86            public void writeTo(final BufferedSink sink) throws IOException {
 87                long transmitted = 0;
 88                try (final Source source = Okio.source(upgrade(file, new FileInputStream(file)))) {
 89                    long read;
 90                    while ((read = source.read(sink.buffer(), 8196)) != -1) {
 91                        transmitted += read;
 92                        sink.flush();
 93                        progressListener.onProgress(transmitted);
 94                    }
 95                }
 96            }
 97        };
 98    }
 99
100    public interface ProgressListener {
101        void onProgress(long progress);
102    }
103
104    public static OutputStream createOutputStream(DownloadableFile file, boolean append, boolean decrypt) {
105        FileOutputStream os;
106        try {
107            os = new FileOutputStream(file, append);
108            if (file.getKey() == null || !decrypt) {
109                return os;
110            }
111        } catch (FileNotFoundException e) {
112            Log.d(Config.LOGTAG, "unable to create output stream", e);
113            return null;
114        }
115        try {
116            AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
117            cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
118            return new CipherOutputStream(os, cipher);
119        } catch (Exception e) {
120            Log.d(Config.LOGTAG, "unable to create cipher output stream", e);
121            return null;
122        }
123    }
124
125    public XmppConnectionService getXmppConnectionService() {
126        return this.mXmppConnectionService;
127    }
128
129    public long getAutoAcceptFileSize() {
130        return this.mXmppConnectionService.getLongPreference("auto_accept_file_size", R.integer.auto_accept_filesize);
131    }
132
133    public boolean hasStoragePermission() {
134        return Compatibility.hasStoragePermission(mXmppConnectionService);
135    }
136
137    public void updateConversationUi(boolean force) {
138        synchronized (LAST_UI_UPDATE_CALL) {
139            if (force || SystemClock.elapsedRealtime() - LAST_UI_UPDATE_CALL.get() >= UI_REFRESH_THRESHOLD) {
140                LAST_UI_UPDATE_CALL.set(SystemClock.elapsedRealtime());
141                mXmppConnectionService.updateConversationUi();
142            }
143        }
144    }
145
146    public PowerManager.WakeLock createWakeLock(final Thread thread) {
147        return createWakeLock("conversations:" + thread.getName());
148    }
149
150    public PowerManager.WakeLock createWakeLock(final String name) {
151        final PowerManager powerManager = (PowerManager) mXmppConnectionService.getSystemService(Context.POWER_SERVICE);
152        return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
153    }
154
155    public static class Extension {
156        public final String main;
157        public final String secondary;
158
159        private Extension(String main, String secondary) {
160            this.main = main;
161            this.secondary = secondary;
162        }
163
164        public String getExtension() {
165            if (VALID_CRYPTO_EXTENSIONS.contains(main)) {
166                return secondary;
167            } else {
168                return main;
169            }
170        }
171
172        public static Extension of(String path) {
173            //TODO accept List<String> pathSegments
174            final int pos = path.lastIndexOf('/');
175            final String filename = path.substring(pos + 1).toLowerCase();
176            final String[] parts = filename.split("\\.");
177            final String main = parts.length >= 2 ? parts[parts.length - 1] : null;
178            final String secondary = parts.length >= 3 ? parts[parts.length - 2] : null;
179            return new Extension(main, secondary);
180        }
181    }
182}