Create a core-util module with some common utilities.

This commit is contained in:
Greyson Parrelli
2020-12-04 18:31:58 -05:00
parent 831cd2f297
commit 8e93bf9075
958 changed files with 1879 additions and 2035 deletions

View File

@@ -1,41 +0,0 @@
package org.thoughtcrime.securesms.logging;
import android.annotation.SuppressLint;
@SuppressLint("LogNotSignal")
public final class AndroidLogger extends Log.Logger {
@Override
public void v(String tag, String message, Throwable t) {
android.util.Log.v(tag, message, t);
}
@Override
public void d(String tag, String message, Throwable t) {
android.util.Log.d(tag, message, t);
}
@Override
public void i(String tag, String message, Throwable t) {
android.util.Log.i(tag, message, t);
}
@Override
public void w(String tag, String message, Throwable t) {
android.util.Log.w(tag, message, t);
}
@Override
public void e(String tag, String message, Throwable t) {
android.util.Log.e(tag, message, t);
}
@Override
public void wtf(String tag, String message, Throwable t) {
android.util.Log.wtf(tag, message, t);
}
@Override
public void blockUntilAllWritesFinished() {
}
}

View File

@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.logging;
import org.signal.core.util.logging.Log;
import org.whispersystems.libsignal.logging.SignalProtocolLogger;
public class CustomSignalProtocolLogger implements SignalProtocolLogger {

View File

@@ -1,13 +0,0 @@
package org.thoughtcrime.securesms.logging;
public class GrowingBuffer {
private byte[] buffer;
public byte[] get(int minLength) {
if (buffer == null || buffer.length < minLength) {
buffer = new byte[minLength];
}
return buffer;
}
}

View File

@@ -1,150 +0,0 @@
package org.thoughtcrime.securesms.logging;
import android.annotation.SuppressLint;
import androidx.annotation.MainThread;
@SuppressLint("LogNotSignal")
public final class Log {
private static Logger[] loggers;
@MainThread
public static void initialize(Logger... loggers) {
Log.loggers = loggers;
}
public static void v(String tag, String message) {
v(tag, message, null);
}
public static void d(String tag, String message) {
d(tag, message, null);
}
public static void i(String tag, String message) {
i(tag, message, null);
}
public static void w(String tag, String message) {
w(tag, message, null);
}
public static void e(String tag, String message) {
e(tag, message, null);
}
public static void wtf(String tag, String message) {
wtf(tag, message, null);
}
public static void v(String tag, Throwable t) {
v(tag, null, t);
}
public static void d(String tag, Throwable t) {
d(tag, null, t);
}
public static void i(String tag, Throwable t) {
i(tag, null, t);
}
public static void w(String tag, Throwable t) {
w(tag, null, t);
}
public static void e(String tag, Throwable t) {
e(tag, null, t);
}
public static void wtf(String tag, Throwable t) {
wtf(tag, null, t);
}
public static void v(String tag, String message, Throwable t) {
if (loggers != null) {
for (Logger logger : loggers) {
logger.v(tag, message, t);
}
} else {
android.util.Log.v(tag, message, t);
}
}
public static void d(String tag, String message, Throwable t) {
if (loggers != null) {
for (Logger logger : loggers) {
logger.d(tag, message, t);
}
} else {
android.util.Log.d(tag, message, t);
}
}
public static void i(String tag, String message, Throwable t) {
if (loggers != null) {
for (Logger logger : loggers) {
logger.i(tag, message, t);
}
} else {
android.util.Log.i(tag, message, t);
}
}
public static void w(String tag, String message, Throwable t) {
if (loggers != null) {
for (Logger logger : loggers) {
logger.w(tag, message, t);
}
} else {
android.util.Log.w(tag, message, t);
}
}
public static void e(String tag, String message, Throwable t) {
if (loggers != null) {
for (Logger logger : loggers) {
logger.e(tag, message, t);
}
} else {
android.util.Log.e(tag, message, t);
}
}
public static void wtf(String tag, String message, Throwable t) {
if (loggers != null) {
for (Logger logger : loggers) {
logger.wtf(tag, message, t);
}
} else {
android.util.Log.wtf(tag, message, t);
}
}
public static String tag(Class<?> clazz) {
String simpleName = clazz.getSimpleName();
if (simpleName.length() > 23) {
return simpleName.substring(0, 23);
}
return simpleName;
}
public static void blockUntilAllWritesFinished() {
if (loggers != null) {
for (Logger logger : loggers) {
logger.blockUntilAllWritesFinished();
}
}
}
public static abstract class Logger {
public abstract void v(String tag, String message, Throwable t);
public abstract void d(String tag, String message, Throwable t);
public abstract void i(String tag, String message, Throwable t);
public abstract void w(String tag, String message, Throwable t);
public abstract void e(String tag, String message, Throwable t);
public abstract void wtf(String tag, String message, Throwable t);
public abstract void blockUntilAllWritesFinished();
}
}

View File

@@ -1,137 +0,0 @@
package org.thoughtcrime.securesms.logging;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.util.Conversions;
import org.thoughtcrime.securesms.util.Util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
class LogFile {
public static class Writer {
private final byte[] ivBuffer = new byte[16];
private final GrowingBuffer ciphertextBuffer = new GrowingBuffer();
private final byte[] secret;
private final File file;
private final Cipher cipher;
private final BufferedOutputStream outputStream;
Writer(@NonNull byte[] secret, @NonNull File file) throws IOException {
this.secret = secret;
this.file = file;
this.outputStream = new BufferedOutputStream(new FileOutputStream(file, true));
try {
this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new AssertionError(e);
}
}
void writeEntry(@NonNull String entry) throws IOException {
new SecureRandom().nextBytes(ivBuffer);
byte[] plaintext = entry.getBytes();
try {
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer));
int cipherLength = cipher.getOutputSize(plaintext.length);
byte[] ciphertext = ciphertextBuffer.get(cipherLength);
cipherLength = cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
outputStream.write(ivBuffer);
outputStream.write(Conversions.intToByteArray(cipherLength));
outputStream.write(ciphertext, 0, cipherLength);
outputStream.flush();
} catch (ShortBufferException | InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
throw new AssertionError(e);
}
}
long getLogSize() {
return file.length();
}
void close() {
Util.close(outputStream);
}
}
static class Reader {
private final byte[] ivBuffer = new byte[16];
private final byte[] intBuffer = new byte[4];
private final GrowingBuffer ciphertextBuffer = new GrowingBuffer();
private final byte[] secret;
private final Cipher cipher;
private final BufferedInputStream inputStream;
Reader(@NonNull byte[] secret, @NonNull File file) throws IOException {
this.secret = secret;
this.inputStream = new BufferedInputStream(new FileInputStream(file));
try {
this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new AssertionError(e);
}
}
String readAll() throws IOException {
StringBuilder builder = new StringBuilder();
String entry;
while ((entry = readEntry()) != null) {
builder.append(entry).append('\n');
}
return builder.toString();
}
private String readEntry() throws IOException {
try {
Util.readFully(inputStream, ivBuffer);
Util.readFully(inputStream, intBuffer);
int length = Conversions.byteArrayToInt(intBuffer);
byte[] ciphertext = ciphertextBuffer.get(length);
Util.readFully(inputStream, ciphertext, length);
try {
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer));
byte[] plaintext = cipher.doFinal(ciphertext, 0, length);
return new String(plaintext);
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
} catch (EOFException e) {
return null;
}
}
}
}

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.logging;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.KeyStoreHelper;
@@ -11,9 +12,9 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.IOException;
import java.security.SecureRandom;
class LogSecretProvider {
public class LogSecretProvider {
static byte[] getOrCreateAttachmentSecret(@NonNull Context context) {
public static byte[] getOrCreateAttachmentSecret(@NonNull Context context) {
String unencryptedSecret = TextSecurePreferences.getLogUnencryptedSecret(context);
String encryptedSecret = TextSecurePreferences.getLogEncryptedSecret(context);

View File

@@ -1,247 +0,0 @@
package org.thoughtcrime.securesms.logging;
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.annotation.AnyThread;
import androidx.annotation.WorkerThread;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@SuppressLint("LogNotSignal")
public final class PersistentLogger extends Log.Logger {
private static final String TAG = PersistentLogger.class.getSimpleName();
private static final String LOG_V = "V";
private static final String LOG_D = "D";
private static final String LOG_I = "I";
private static final String LOG_W = "W";
private static final String LOG_E = "E";
private static final String LOG_WTF = "A";
private static final String VERSION_TAG = "[" + BuildConfig.VERSION_NAME + "]";
private static final String LOG_DIRECTORY = "log";
private static final String FILENAME_PREFIX = "log-";
private static final int MAX_LOG_FILES = 7;
private static final int MAX_LOG_SIZE = 300 * 1024;
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS zzz");
private final Context context;
private final Executor executor;
private final byte[] secret;
private LogFile.Writer writer;
public PersistentLogger(Context context) {
this.context = context.getApplicationContext();
this.secret = LogSecretProvider.getOrCreateAttachmentSecret(context);
this.executor = Executors.newSingleThreadExecutor(r -> {
Thread thread = new Thread(r, "signal-PersistentLogger");
thread.setPriority(Thread.MIN_PRIORITY);
return thread;
});
executor.execute(this::initializeWriter);
}
@Override
public void v(String tag, String message, Throwable t) {
write(LOG_V, tag, message, t);
}
@Override
public void d(String tag, String message, Throwable t) {
write(LOG_D, tag, message, t);
}
@Override
public void i(String tag, String message, Throwable t) {
write(LOG_I, tag, message, t);
}
@Override
public void w(String tag, String message, Throwable t) {
write(LOG_W, tag, message, t);
}
@Override
public void e(String tag, String message, Throwable t) {
write(LOG_E, tag, message, t);
}
@Override
public void wtf(String tag, String message, Throwable t) {
write(LOG_WTF, tag, message, t);
}
@Override
public void blockUntilAllWritesFinished() {
CountDownLatch latch = new CountDownLatch(1);
executor.execute(latch::countDown);
try {
latch.await();
} catch (InterruptedException e) {
android.util.Log.w(TAG, "Failed to wait for all writes.");
}
}
@WorkerThread
public ListenableFuture<CharSequence> getLogs() {
final SettableFuture<CharSequence> future = new SettableFuture<>();
executor.execute(() -> {
StringBuilder builder = new StringBuilder();
try {
File[] logs = getSortedLogFiles();
for (int i = logs.length - 1; i >= 0; i--) {
try {
LogFile.Reader reader = new LogFile.Reader(secret, logs[i]);
builder.append(reader.readAll());
} catch (IOException e) {
android.util.Log.w(TAG, "Failed to read log at index " + i + ". Removing reference.");
logs[i].delete();
}
}
future.set(builder);
} catch (NoExternalStorageException e) {
future.setException(e);
}
});
return future;
}
@WorkerThread
private void initializeWriter() {
try {
writer = new LogFile.Writer(secret, getOrCreateActiveLogFile());
} catch (NoExternalStorageException | IOException e) {
android.util.Log.e(TAG, "Failed to initialize writer.", e);
}
}
@AnyThread
private void write(String level, String tag, String message, Throwable t) {
executor.execute(() -> {
try {
if (writer == null) {
return;
}
if (writer.getLogSize() >= MAX_LOG_SIZE) {
writer.close();
writer = new LogFile.Writer(secret, createNewLogFile());
trimLogFilesOverMax();
}
for (String entry : buildLogEntries(level, tag, message, t)) {
writer.writeEntry(entry);
}
} catch (NoExternalStorageException e) {
android.util.Log.w(TAG, "Cannot persist logs.", e);
} catch (IOException e) {
android.util.Log.w(TAG, "Failed to write line. Deleting all logs and starting over.");
deleteAllLogs();
initializeWriter();
}
});
}
private void trimLogFilesOverMax() throws NoExternalStorageException {
File[] logs = getSortedLogFiles();
if (logs.length > MAX_LOG_FILES) {
for (int i = MAX_LOG_FILES; i < logs.length; i++) {
logs[i].delete();
}
}
}
private void deleteAllLogs() {
try {
File[] logs = getSortedLogFiles();
for (File log : logs) {
log.delete();
}
} catch (NoExternalStorageException e) {
android.util.Log.w(TAG, "Was unable to delete logs.", e);
}
}
private File getOrCreateActiveLogFile() throws NoExternalStorageException {
File[] logs = getSortedLogFiles();
if (logs.length > 0) {
return logs[0];
}
return createNewLogFile();
}
private File createNewLogFile() throws NoExternalStorageException {
return new File(getOrCreateLogDirectory(), FILENAME_PREFIX + System.currentTimeMillis());
}
private File[] getSortedLogFiles() throws NoExternalStorageException {
File[] logs = getOrCreateLogDirectory().listFiles();
if (logs != null) {
Arrays.sort(logs, (o1, o2) -> o2.getName().compareTo(o1.getName()));
return logs;
}
return new File[0];
}
private File getOrCreateLogDirectory() throws NoExternalStorageException {
File logDir = new File(context.getCacheDir(), LOG_DIRECTORY);
if (!logDir.exists() && !logDir.mkdir()) {
throw new NoExternalStorageException("Unable to create log directory.");
}
return logDir;
}
private List<String> buildLogEntries(String level, String tag, String message, Throwable t) {
List<String> entries = new LinkedList<>();
Date date = new Date();
entries.add(buildEntry(level, tag, message, date));
if (t != null) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
t.printStackTrace(new PrintStream(outputStream));
String trace = new String(outputStream.toByteArray());
String[] lines = trace.split("\\n");
for (String line : lines) {
entries.add(buildEntry(level, tag, line, date));
}
}
return entries;
}
private String buildEntry(String level, String tag, String message, Date date) {
return VERSION_TAG + ' ' +DATE_FORMAT.format(date) + ' ' + level + ' ' + tag + ": " + message;
}
}

View File

@@ -1,26 +0,0 @@
package org.thoughtcrime.securesms.logging;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
public class SignalUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private static final String TAG = SignalUncaughtExceptionHandler.class.getSimpleName();
private final Thread.UncaughtExceptionHandler originalHandler;
public SignalUncaughtExceptionHandler(@NonNull Thread.UncaughtExceptionHandler originalHandler) {
this.originalHandler = originalHandler;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Log.e(TAG, "", e);
SignalStore.blockUntilAllWritesFinished();
Log.blockUntilAllWritesFinished();
ApplicationDependencies.getJobManager().flush();
originalHandler.uncaughtException(t, e);
}
}