mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-23 11:15:44 +00:00
Add a core-util-jvm module.
This is basically a location where we can put common utils that can also be imported by libsignal-service (which is java-only, no android dependency).
This commit is contained in:
committed by
Nicholas Tinsley
parent
6be1413d7d
commit
7d5786ea93
@@ -1,78 +0,0 @@
|
||||
package org.signal.core.util;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A set of utilities to make working with Bitmasks easier.
|
||||
*/
|
||||
public final class Bitmask {
|
||||
|
||||
/**
|
||||
* Reads a bitmasked boolean from a long at the requested position.
|
||||
*/
|
||||
public static boolean read(long value, int position) {
|
||||
return read(value, position, 1) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a bitmasked value from a long at the requested position.
|
||||
*
|
||||
* @param value The value your are reading state from
|
||||
* @param position The position you'd like to read from
|
||||
* @param flagBitSize How many bits are in each flag
|
||||
* @return The value at the requested position
|
||||
*/
|
||||
public static long read(long value, int position, int flagBitSize) {
|
||||
checkArgument(flagBitSize >= 0, "Must have a positive bit size! size: " + flagBitSize);
|
||||
|
||||
int bitsToShift = position * flagBitSize;
|
||||
checkArgument(bitsToShift + flagBitSize <= 64 && position >= 0, String.format(Locale.US, "Your position is out of bounds! position: %d, flagBitSize: %d", position, flagBitSize));
|
||||
|
||||
long shifted = value >>> bitsToShift;
|
||||
long mask = twoToThe(flagBitSize) - 1;
|
||||
|
||||
return shifted & mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value at the specified position in a single-bit bitmasked long.
|
||||
*/
|
||||
public static long update(long existing, int position, boolean value) {
|
||||
return update(existing, position, 1, value ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the value in a bitmasked long.
|
||||
*
|
||||
* @param existing The existing state of the bitmask
|
||||
* @param position The position you'd like to update
|
||||
* @param flagBitSize How many bits are in each flag
|
||||
* @param value The value you'd like to set at the specified position
|
||||
* @return The updated bitmask
|
||||
*/
|
||||
public static long update(long existing, int position, int flagBitSize, long value) {
|
||||
checkArgument(flagBitSize >= 0, "Must have a positive bit size! size: " + flagBitSize);
|
||||
checkArgument(value >= 0, "Value must be positive! value: " + value);
|
||||
checkArgument(value < twoToThe(flagBitSize), String.format(Locale.US, "Value is larger than you can hold for the given bitsize! value: %d, flagBitSize: %d", value, flagBitSize));
|
||||
|
||||
int bitsToShift = position * flagBitSize;
|
||||
checkArgument(bitsToShift + flagBitSize <= 64 && position >= 0, String.format(Locale.US, "Your position is out of bounds! position: %d, flagBitSize: %d", position, flagBitSize));
|
||||
|
||||
long clearMask = ~((twoToThe(flagBitSize) - 1) << bitsToShift);
|
||||
long cleared = existing & clearMask;
|
||||
long shiftedValue = value << bitsToShift;
|
||||
|
||||
return cleared | shiftedValue;
|
||||
}
|
||||
|
||||
/** Simple method to do 2^n. Giving it a name just so it's clear what's happening. */
|
||||
private static long twoToThe(long n) {
|
||||
return 1 << n;
|
||||
}
|
||||
|
||||
private static void checkArgument(boolean state, String message) {
|
||||
if (!state) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.util
|
||||
|
||||
inline val Long.bytes: ByteSize
|
||||
get() = ByteSize(this)
|
||||
|
||||
inline val Long.kibiBytes: ByteSize
|
||||
get() = (this * 1024).bytes
|
||||
|
||||
inline val Long.mebiBytes: ByteSize
|
||||
get() = (this * 1024).kibiBytes
|
||||
|
||||
inline val Long.gibiBytes: ByteSize
|
||||
get() = (this * 1024).mebiBytes
|
||||
|
||||
class ByteSize(val bytes: Long) {
|
||||
val inWholeBytes: Long
|
||||
get() = bytes
|
||||
|
||||
val inWholeKibiBytes: Long
|
||||
get() = bytes / 1024
|
||||
|
||||
val inWholeMebiBytes: Long
|
||||
get() = inWholeKibiBytes / 1024
|
||||
|
||||
val inWholeGibiBytes: Long
|
||||
get() = inWholeMebiBytes / 1024
|
||||
|
||||
val inKibiBytes: Float
|
||||
get() = bytes / 1024f
|
||||
|
||||
val inMebiBytes: Float
|
||||
get() = inKibiBytes / 1024f
|
||||
|
||||
val inGibiBytes: Float
|
||||
get() = inMebiBytes / 1024f
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package org.signal.core.util;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.FloatRange;
|
||||
|
||||
public final class ColorUtil {
|
||||
|
||||
private ColorUtil() {}
|
||||
|
||||
public static int blendARGB(@ColorInt int color1,
|
||||
@ColorInt int color2,
|
||||
@FloatRange(from = 0.0, to = 1.0) float ratio)
|
||||
{
|
||||
final float inverseRatio = 1 - ratio;
|
||||
|
||||
float a = alpha(color1) * inverseRatio + alpha(color2) * ratio;
|
||||
float r = red(color1) * inverseRatio + red(color2) * ratio;
|
||||
float g = green(color1) * inverseRatio + green(color2) * ratio;
|
||||
float b = blue(color1) * inverseRatio + blue(color2) * ratio;
|
||||
|
||||
return argb((int) a, (int) r, (int) g, (int) b);
|
||||
}
|
||||
|
||||
private static int alpha(int color) {
|
||||
return color >>> 24;
|
||||
}
|
||||
|
||||
private static int red(int color) {
|
||||
return (color >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
private static int green(int color) {
|
||||
return (color >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
private static int blue(int color) {
|
||||
return color & 0xFF;
|
||||
}
|
||||
|
||||
private static int argb(int alpha, int red, int green, int blue) {
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.util
|
||||
|
||||
/**
|
||||
* Rounds a number to the specified number of places. e.g.
|
||||
*
|
||||
* 1.123456f.roundedString(2) = 1.12
|
||||
* 1.123456f.roundedString(5) = 1.12346
|
||||
*/
|
||||
fun Double.roundedString(places: Int): String {
|
||||
return String.format("%.${places}f", this)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.util
|
||||
|
||||
/**
|
||||
* Rounds a number to the specified number of places. e.g.
|
||||
*
|
||||
* 1.123456f.roundedString(2) = 1.12
|
||||
* 1.123456f.roundedString(5) = 1.12346
|
||||
*/
|
||||
fun Float.roundedString(places: Int): String {
|
||||
return String.format("%.${places}f", this)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.text.Spanned;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
|
||||
|
||||
/**
|
||||
* This filter will constrain edits not to make the number of character breaks of the text
|
||||
* greater than the specified maximum.
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.signal.core.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Utility for generating hex dumps.
|
||||
*/
|
||||
public class Hex {
|
||||
|
||||
private final static int HEX_DIGITS_START = 10;
|
||||
private final static int ASCII_TEXT_START = HEX_DIGITS_START + (16*2 + (16/2));
|
||||
|
||||
final static String EOL = System.getProperty("line.separator");
|
||||
|
||||
private final static char[] HEX_DIGITS = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
||||
};
|
||||
|
||||
public static String toString(byte[] bytes) {
|
||||
return toString(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public static String toString(byte[] bytes, int offset, int length) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; i < length; i++) {
|
||||
appendHexChar(buf, bytes[offset + i]);
|
||||
buf.append(' ');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String toStringCondensed(byte[] bytes) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i=0;i<bytes.length;i++) {
|
||||
appendHexChar(buf, bytes[i]);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static byte[] fromStringCondensed(String encoded) throws IOException {
|
||||
final char[] data = encoded.toCharArray();
|
||||
final int len = data.length;
|
||||
|
||||
if ((len & 0x01) != 0) {
|
||||
throw new IOException("Odd number of characters.");
|
||||
}
|
||||
|
||||
final byte[] out = new byte[len >> 1];
|
||||
|
||||
// two characters form the hex value.
|
||||
for (int i = 0, j = 0; j < len; i++) {
|
||||
int f = Character.digit(data[j], 16) << 4;
|
||||
j++;
|
||||
f = f | Character.digit(data[j], 16);
|
||||
j++;
|
||||
out[i] = (byte) (f & 0xFF);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static byte[] fromStringOrThrow(String encoded) {
|
||||
try {
|
||||
return fromStringCondensed(encoded);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String dump(byte[] bytes) {
|
||||
return dump(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public static String dump(byte[] bytes, int offset, int length) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
int lines = ((length - 1) / 16) + 1;
|
||||
int lineOffset;
|
||||
int lineLength;
|
||||
|
||||
for (int i = 0; i < lines; i++) {
|
||||
lineOffset = (i * 16) + offset;
|
||||
lineLength = Math.min(16, (length - (i * 16)));
|
||||
appendDumpLine(buf, i, bytes, lineOffset, lineLength);
|
||||
buf.append(EOL);
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static void appendDumpLine(StringBuffer buf, int line, byte[] bytes, int lineOffset, int lineLength) {
|
||||
buf.append(HEX_DIGITS[(line >> 28) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 24) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 20) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 16) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 12) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 8) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 4) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line ) & 0xf]);
|
||||
buf.append(": ");
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int idx = i + lineOffset;
|
||||
if (i < lineLength) {
|
||||
int b = bytes[idx];
|
||||
appendHexChar(buf, b);
|
||||
} else {
|
||||
buf.append(" ");
|
||||
}
|
||||
if ((i % 2) == 1) {
|
||||
buf.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16 && i < lineLength; i++) {
|
||||
int idx = i + lineOffset;
|
||||
int b = bytes[idx];
|
||||
if (b >= 0x20 && b <= 0x7e) {
|
||||
buf.append((char)b);
|
||||
} else {
|
||||
buf.append('.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendHexChar(StringBuffer buf, int b) {
|
||||
buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
|
||||
buf.append(HEX_DIGITS[b & 0xf]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.signal.core.util
|
||||
|
||||
import java.util.Optional
|
||||
|
||||
fun <E> Optional<E>.or(other: Optional<E>): Optional<E> {
|
||||
return if (this.isPresent) {
|
||||
this
|
||||
} else {
|
||||
other
|
||||
}
|
||||
}
|
||||
|
||||
fun <E> Optional<E>.isAbsent(): Boolean {
|
||||
return !isPresent
|
||||
}
|
||||
|
||||
fun <E : Any> E?.toOptional(): Optional<E> {
|
||||
return Optional.ofNullable(this)
|
||||
}
|
||||
|
||||
fun <E> Optional<E>.orNull(): E? {
|
||||
return orElse(null)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.signal.core.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public final class SetUtil {
|
||||
private SetUtil() {}
|
||||
|
||||
public static <E> Set<E> intersection(Collection<E> a, Collection<E> b) {
|
||||
Set<E> intersection = new LinkedHashSet<>(a);
|
||||
intersection.retainAll(b);
|
||||
return intersection;
|
||||
}
|
||||
|
||||
public static <E> Set<E> difference(Collection<E> a, Collection<E> b) {
|
||||
Set<E> difference = new LinkedHashSet<>(a);
|
||||
difference.removeAll(b);
|
||||
return difference;
|
||||
}
|
||||
|
||||
public static <E> Set<E> union(Set<E> a, Set<E> b) {
|
||||
Set<E> result = new LinkedHashSet<>(a);
|
||||
result.addAll(b);
|
||||
return result;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <E> HashSet<E> newHashSet(E... elements) {
|
||||
return new HashSet<>(Arrays.asList(elements));
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package org.signal.core.util
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import kotlin.time.Duration.Companion.nanoseconds
|
||||
import kotlin.time.DurationUnit
|
||||
|
||||
/**
|
||||
* Simple utility to easily track the time a multi-step operation takes via splits.
|
||||
*
|
||||
* e.g.
|
||||
*
|
||||
* ```kotlin
|
||||
* val stopwatch = Stopwatch("my-event")
|
||||
* stopwatch.split("split-1")
|
||||
* stopwatch.split("split-2")
|
||||
* stopwatch.split("split-3")
|
||||
* stopwatch.stop(TAG)
|
||||
* ```
|
||||
*/
|
||||
class Stopwatch @JvmOverloads constructor(private val title: String, private val decimalPlaces: Int = 0) {
|
||||
private val startTimeNanos: Long = System.nanoTime()
|
||||
private val splits: MutableList<Split> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Create a new split between now and the last event.
|
||||
*/
|
||||
fun split(label: String) {
|
||||
val now = System.nanoTime()
|
||||
|
||||
val previousTime = if (splits.isEmpty()) {
|
||||
startTimeNanos
|
||||
} else {
|
||||
splits.last().nanoTime
|
||||
}
|
||||
|
||||
splits += Split(
|
||||
nanoTime = now,
|
||||
durationNanos = now - previousTime,
|
||||
label = label
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the stopwatch and logs the results with the provided tag.
|
||||
*/
|
||||
fun stop(tag: String) {
|
||||
Log.d(tag, stopAndGetLogString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to [stop], but instead of logging directly, this will return the log string.
|
||||
*/
|
||||
fun stopAndGetLogString(): String {
|
||||
val now = System.nanoTime()
|
||||
|
||||
splits += Split(
|
||||
nanoTime = now,
|
||||
durationNanos = now - startTimeNanos,
|
||||
label = "total"
|
||||
)
|
||||
|
||||
val splitString = splits
|
||||
.joinToString(separator = ", ", transform = { it.displayString(decimalPlaces) })
|
||||
|
||||
return "[$title] $splitString"
|
||||
}
|
||||
|
||||
private data class Split(val nanoTime: Long, val durationNanos: Long, val label: String) {
|
||||
fun displayString(decimalPlaces: Int): String {
|
||||
val timeMs: String = durationNanos.nanoseconds.toDouble(DurationUnit.MILLISECONDS).roundedString(decimalPlaces)
|
||||
return "$label: $timeMs"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package org.signal.core.util.logging;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
|
||||
@SuppressLint("LogNotSignal")
|
||||
public final class AndroidLogger extends Log.Logger {
|
||||
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
package org.signal.core.util.logging;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* A way to treat N loggers as one. Wraps a bunch of other loggers and forwards the method calls to
|
||||
* all of them.
|
||||
*/
|
||||
class CompoundLogger extends Log.Logger {
|
||||
|
||||
private final Log.Logger[] loggers;
|
||||
|
||||
CompoundLogger(@NonNull Log.Logger... loggers) {
|
||||
this.loggers = loggers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void v(String tag, String message, Throwable t, boolean keepLonger) {
|
||||
for (Log.Logger logger : loggers) {
|
||||
logger.v(tag, message, t, keepLonger);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void d(String tag, String message, Throwable t, boolean keepLonger) {
|
||||
for (Log.Logger logger : loggers) {
|
||||
logger.d(tag, message, t, keepLonger);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void i(String tag, String message, Throwable t, boolean keepLonger) {
|
||||
for (Log.Logger logger : loggers) {
|
||||
logger.i(tag, message, t, keepLonger);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void w(String tag, String message, Throwable t, boolean keepLonger) {
|
||||
for (Log.Logger logger : loggers) {
|
||||
logger.w(tag, message, t, keepLonger);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void e(String tag, String message, Throwable t, boolean keepLonger) {
|
||||
for (Log.Logger logger : loggers) {
|
||||
logger.e(tag, message, t, keepLonger);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void v(String tag, String message, Throwable t) {
|
||||
for (Log.Logger logger :loggers) {
|
||||
logger.v(tag, message, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void d(String tag, String message, Throwable t) {
|
||||
for (Log.Logger logger :loggers) {
|
||||
logger.d(tag, message, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void i(String tag, String message, Throwable t) {
|
||||
for (Log.Logger logger :loggers) {
|
||||
logger.i(tag, message, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void w(String tag, String message, Throwable t) {
|
||||
for (Log.Logger logger :loggers) {
|
||||
logger.w(tag, message, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void e(String tag, String message, Throwable t) {
|
||||
for (Log.Logger logger :loggers) {
|
||||
logger.e(tag, message, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void v(String tag, String message) {
|
||||
for (Log.Logger logger :loggers) {
|
||||
logger.v(tag, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void d(String tag, String message) {
|
||||
for (Log.Logger logger :loggers) {
|
||||
logger.d(tag, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void i(String tag, String message) {
|
||||
for (Log.Logger logger :loggers) {
|
||||
logger.i(tag, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void w(String tag, String message) {
|
||||
for (Log.Logger logger :loggers) {
|
||||
logger.w(tag, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void e(String tag, String message) {
|
||||
for (Log.Logger logger :loggers) {
|
||||
logger.e(tag, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
for (Log.Logger logger : loggers) {
|
||||
logger.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
package org.signal.core.util.logging;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@SuppressLint("LogNotSignal")
|
||||
public final class Log {
|
||||
|
||||
private static final Logger NOOP_LOGGER = new NoopLogger();
|
||||
|
||||
private static InternalCheck internalCheck;
|
||||
private static Logger logger = new AndroidLogger();
|
||||
|
||||
/**
|
||||
* @param internalCheck A checker that will indicate if this is an internal user
|
||||
* @param loggers A list of loggers that will be given every log statement.
|
||||
*/
|
||||
@MainThread
|
||||
public static void initialize(@NonNull InternalCheck internalCheck, Logger... loggers) {
|
||||
Log.internalCheck = internalCheck;
|
||||
Log.logger = new CompoundLogger(loggers);
|
||||
}
|
||||
|
||||
public static void initialize(Logger... loggers) {
|
||||
initialize(() -> false, 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 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 v(String tag, String message, Throwable t) {
|
||||
logger.v(tag, message, t);
|
||||
}
|
||||
|
||||
public static void d(String tag, String message, Throwable t) {
|
||||
logger.d(tag, message, t);
|
||||
}
|
||||
|
||||
public static void i(String tag, String message, Throwable t) {
|
||||
logger.i(tag, message, t);
|
||||
}
|
||||
|
||||
public static void w(String tag, String message, Throwable t) {
|
||||
logger.w(tag, message, t);
|
||||
}
|
||||
|
||||
public static void e(String tag, String message, Throwable t) {
|
||||
logger.e(tag, message, t);
|
||||
}
|
||||
|
||||
public static void v(String tag, String message, boolean keepLonger) {
|
||||
logger.v(tag, message, keepLonger);
|
||||
}
|
||||
|
||||
public static void d(String tag, String message, boolean keepLonger) {
|
||||
logger.d(tag, message, keepLonger);
|
||||
}
|
||||
|
||||
public static void i(String tag, String message, boolean keepLonger) {
|
||||
logger.i(tag, message, keepLonger);
|
||||
}
|
||||
|
||||
public static void w(String tag, String message, boolean keepLonger) {
|
||||
logger.w(tag, message, keepLonger);
|
||||
}
|
||||
|
||||
public static void e(String tag, String message, boolean keepLonger) {
|
||||
logger.e(tag, message, keepLonger);
|
||||
}
|
||||
|
||||
public static void v(String tag, String message, Throwable t, boolean keepLonger) {
|
||||
logger.v(tag, message, t, keepLonger);
|
||||
}
|
||||
|
||||
public static void d(String tag, String message, Throwable t, boolean keepLonger) {
|
||||
logger.d(tag, message, t, keepLonger);
|
||||
}
|
||||
|
||||
public static void i(String tag, String message, Throwable t, boolean keepLonger) {
|
||||
logger.i(tag, message, t, keepLonger);
|
||||
}
|
||||
|
||||
public static void w(String tag, String message, Throwable t, boolean keepLonger) {
|
||||
logger.w(tag, message, t, keepLonger);
|
||||
}
|
||||
|
||||
public static void e(String tag, String message, Throwable t, boolean keepLonger) {
|
||||
logger.e(tag, message, t, keepLonger);
|
||||
}
|
||||
|
||||
public static @NonNull String tag(Class<?> clazz) {
|
||||
String simpleName = clazz.getSimpleName();
|
||||
if (simpleName.length() > 23) {
|
||||
return simpleName.substring(0, 23);
|
||||
}
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Important: This is not something that can be used to log PII. Instead, it's intended use is for
|
||||
* logs that might be too verbose or otherwise unnecessary for public users.
|
||||
*
|
||||
* @return The normal logger if this is an internal user, or a no-op logger if it isn't.
|
||||
*/
|
||||
public static Logger internal() {
|
||||
if (internalCheck.isInternal()) {
|
||||
return logger;
|
||||
} else {
|
||||
return NOOP_LOGGER;
|
||||
}
|
||||
}
|
||||
|
||||
public static void blockUntilAllWritesFinished() {
|
||||
logger.flush();
|
||||
}
|
||||
|
||||
public static abstract class Logger {
|
||||
|
||||
public abstract void v(String tag, String message, Throwable t, boolean keepLonger);
|
||||
public abstract void d(String tag, String message, Throwable t, boolean keepLonger);
|
||||
public abstract void i(String tag, String message, Throwable t, boolean keepLonger);
|
||||
public abstract void w(String tag, String message, Throwable t, boolean keepLonger);
|
||||
public abstract void e(String tag, String message, Throwable t, boolean keepLonger);
|
||||
public abstract void flush();
|
||||
|
||||
public void v(String tag, String message, boolean keepLonger) {
|
||||
v(tag, message, null, keepLonger);
|
||||
}
|
||||
|
||||
public void d(String tag, String message, boolean keepLonger) {
|
||||
d(tag, message, null, keepLonger);
|
||||
}
|
||||
|
||||
public void i(String tag, String message, boolean keepLonger) {
|
||||
i(tag, message, null, keepLonger);
|
||||
}
|
||||
|
||||
public void w(String tag, String message, boolean keepLonger) {
|
||||
w(tag, message, null, keepLonger);
|
||||
}
|
||||
|
||||
public void e(String tag, String message, boolean keepLonger) {
|
||||
e(tag, message, null, keepLonger);
|
||||
}
|
||||
|
||||
public void v(String tag, String message, Throwable t) {
|
||||
v(tag, message, t, false);
|
||||
}
|
||||
|
||||
public void d(String tag, String message, Throwable t) {
|
||||
d(tag, message, t, false);
|
||||
}
|
||||
|
||||
public void i(String tag, String message, Throwable t) {
|
||||
i(tag, message, t, false);
|
||||
}
|
||||
|
||||
public void w(String tag, String message, Throwable t) {
|
||||
w(tag, message, t, false);
|
||||
}
|
||||
|
||||
public void e(String tag, String message, Throwable t) {
|
||||
e(tag, message, t, false);
|
||||
}
|
||||
|
||||
public void v(String tag, String message) {
|
||||
v(tag, message, null);
|
||||
}
|
||||
|
||||
public void d(String tag, String message) {
|
||||
d(tag, message, null);
|
||||
}
|
||||
|
||||
public void i(String tag, String message) {
|
||||
i(tag, message, null);
|
||||
}
|
||||
|
||||
public void w(String tag, String message) {
|
||||
w(tag, message, null);
|
||||
}
|
||||
|
||||
public void e(String tag, String message) {
|
||||
e(tag, message, null);
|
||||
}
|
||||
}
|
||||
|
||||
public interface InternalCheck {
|
||||
boolean isInternal();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.signal.core.util.logging;
|
||||
|
||||
/**
|
||||
* A logger that does nothing.
|
||||
*/
|
||||
class NoopLogger extends Log.Logger {
|
||||
@Override
|
||||
public void v(String tag, String message, Throwable t, boolean keepLonger) { }
|
||||
|
||||
@Override
|
||||
public void d(String tag, String message, Throwable t, boolean keepLonger) { }
|
||||
|
||||
@Override
|
||||
public void i(String tag, String message, Throwable t, boolean keepLonger) { }
|
||||
|
||||
@Override
|
||||
public void w(String tag, String message, Throwable t, boolean keepLonger) { }
|
||||
|
||||
@Override
|
||||
public void e(String tag, String message, Throwable t, boolean keepLonger) { }
|
||||
|
||||
@Override
|
||||
public void flush() { }
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.signal.core.util.logging
|
||||
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/** Given a [Matcher], update the [StringBuilder] with the scrubbed output you want for a given match. */
|
||||
private typealias MatchProcessor = (Matcher, StringBuilder) -> Unit
|
||||
|
||||
/**
|
||||
* Scrub data for possibly sensitive information.
|
||||
*/
|
||||
object Scrubber {
|
||||
/**
|
||||
* The middle group will be censored.
|
||||
* Supposedly, the shortest international phone numbers in use contain seven digits.
|
||||
* Handles URL encoded +, %2B
|
||||
*/
|
||||
private val E164_PATTERN = Pattern.compile("(\\+|%2B)(\\d{5,13})(\\d{2})")
|
||||
private const val E164_CENSOR = "*************"
|
||||
|
||||
/** The second group will be censored.*/
|
||||
private val CRUDE_EMAIL_PATTERN = Pattern.compile("\\b([^\\s/])([^\\s/]*@[^\\s]+)")
|
||||
private const val EMAIL_CENSOR = "...@..."
|
||||
|
||||
/** The middle group will be censored. */
|
||||
private val GROUP_ID_V1_PATTERN = Pattern.compile("(__)(textsecure_group__![^\\s]+)([^\\s]{2})")
|
||||
private const val GROUP_ID_V1_CENSOR = "...group..."
|
||||
|
||||
/** The middle group will be censored. */
|
||||
private val GROUP_ID_V2_PATTERN = Pattern.compile("(__)(signal_group__v2__![^\\s]+)([^\\s]{2})")
|
||||
private const val GROUP_ID_V2_CENSOR = "...group_v2..."
|
||||
|
||||
/** The middle group will be censored. */
|
||||
private val UUID_PATTERN = Pattern.compile("(JOB::)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{9})([0-9a-f]{3})", Pattern.CASE_INSENSITIVE)
|
||||
private const val UUID_CENSOR = "********-****-****-****-*********"
|
||||
|
||||
/**
|
||||
* The entire string is censored. Note: left as concatenated strings because kotlin string literals leave trailing newlines, and removing them breaks
|
||||
* syntax highlighting.
|
||||
*/
|
||||
private val IPV4_PATTERN = Pattern.compile(
|
||||
"\\b" +
|
||||
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
|
||||
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
|
||||
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
|
||||
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" +
|
||||
"\\b"
|
||||
)
|
||||
private const val IPV4_CENSOR = "...ipv4..."
|
||||
|
||||
/** The entire string is censored. */
|
||||
private val IPV6_PATTERN = Pattern.compile("([0-9a-fA-F]{0,4}:){3,7}([0-9a-fA-F]){0,4}")
|
||||
private const val IPV6_CENSOR = "...ipv6..."
|
||||
|
||||
/** The domain name except for TLD will be censored. */
|
||||
private val DOMAIN_PATTERN = Pattern.compile("([a-z0-9]+\\.)+([a-z0-9\\-]*[a-z\\-][a-z0-9\\-]*)", Pattern.CASE_INSENSITIVE)
|
||||
private const val DOMAIN_CENSOR = "***."
|
||||
private val TOP_100_TLDS: Set<String> = setOf(
|
||||
"com", "net", "org", "jp", "de", "uk", "fr", "br", "it", "ru", "es", "me", "gov", "pl", "ca", "au", "cn", "co", "in",
|
||||
"nl", "edu", "info", "eu", "ch", "id", "at", "kr", "cz", "mx", "be", "tv", "se", "tr", "tw", "al", "ua", "ir", "vn",
|
||||
"cl", "sk", "ly", "cc", "to", "no", "fi", "us", "pt", "dk", "ar", "hu", "tk", "gr", "il", "news", "ro", "my", "biz",
|
||||
"ie", "za", "nz", "sg", "ee", "th", "io", "xyz", "pe", "bg", "hk", "lt", "link", "ph", "club", "si", "site",
|
||||
"mobi", "by", "cat", "wiki", "la", "ga", "xxx", "cf", "hr", "ng", "jobs", "online", "kz", "ug", "gq", "ae", "is",
|
||||
"lv", "pro", "fm", "tips", "ms", "sa", "app"
|
||||
)
|
||||
|
||||
/** Base16 Call Link Key Pattern */
|
||||
private val CALL_LINK_PATTERN = Pattern.compile("([bBcCdDfFgGhHkKmMnNpPqQrRsStTxXzZ]{4})(-[bBcCdDfFgGhHkKmMnNpPqQrRsStTxXzZ]{4}){7}")
|
||||
private const val CALL_LINK_CENSOR_SUFFIX = "-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX"
|
||||
|
||||
@JvmStatic
|
||||
fun scrub(input: CharSequence): CharSequence {
|
||||
return input
|
||||
.scrubE164()
|
||||
.scrubEmail()
|
||||
.scrubGroupsV1()
|
||||
.scrubGroupsV2()
|
||||
.scrubUuids()
|
||||
.scrubDomains()
|
||||
.scrubIpv4()
|
||||
.scrubIpv6()
|
||||
.scrubCallLinkKeys()
|
||||
}
|
||||
|
||||
private fun CharSequence.scrubE164(): CharSequence {
|
||||
return scrub(this, E164_PATTERN) { matcher, output ->
|
||||
output
|
||||
.append(matcher.group(1))
|
||||
.append(E164_CENSOR, 0, matcher.group(2)!!.length)
|
||||
.append(matcher.group(3))
|
||||
}
|
||||
}
|
||||
|
||||
private fun CharSequence.scrubEmail(): CharSequence {
|
||||
return scrub(this, CRUDE_EMAIL_PATTERN) { matcher, output ->
|
||||
output
|
||||
.append(matcher.group(1))
|
||||
.append(EMAIL_CENSOR)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CharSequence.scrubGroupsV1(): CharSequence {
|
||||
return scrub(this, GROUP_ID_V1_PATTERN) { matcher, output ->
|
||||
output
|
||||
.append(matcher.group(1))
|
||||
.append(GROUP_ID_V1_CENSOR)
|
||||
.append(matcher.group(3))
|
||||
}
|
||||
}
|
||||
|
||||
private fun CharSequence.scrubGroupsV2(): CharSequence {
|
||||
return scrub(this, GROUP_ID_V2_PATTERN) { matcher, output ->
|
||||
output
|
||||
.append(matcher.group(1))
|
||||
.append(GROUP_ID_V2_CENSOR)
|
||||
.append(matcher.group(3))
|
||||
}
|
||||
}
|
||||
|
||||
private fun CharSequence.scrubUuids(): CharSequence {
|
||||
return scrub(this, UUID_PATTERN) { matcher, output ->
|
||||
if (matcher.group(1) != null && matcher.group(1)!!.isNotEmpty()) {
|
||||
output
|
||||
.append(matcher.group(1))
|
||||
.append(matcher.group(2))
|
||||
.append(matcher.group(3))
|
||||
} else {
|
||||
output
|
||||
.append(UUID_CENSOR)
|
||||
.append(matcher.group(3))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CharSequence.scrubDomains(): CharSequence {
|
||||
return scrub(this, DOMAIN_PATTERN) { matcher, output ->
|
||||
val match: String = matcher.group(0)!!
|
||||
if (matcher.groupCount() == 2 && TOP_100_TLDS.contains(matcher.group(2)!!.lowercase()) && !match.endsWith("signal.org")) {
|
||||
output
|
||||
.append(DOMAIN_CENSOR)
|
||||
.append(matcher.group(2))
|
||||
} else {
|
||||
output.append(match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CharSequence.scrubIpv4(): CharSequence {
|
||||
return scrub(this, IPV4_PATTERN) { _, output -> output.append(IPV4_CENSOR) }
|
||||
}
|
||||
|
||||
private fun CharSequence.scrubIpv6(): CharSequence {
|
||||
return scrub(this, IPV6_PATTERN) { _, output -> output.append(IPV6_CENSOR) }
|
||||
}
|
||||
|
||||
private fun CharSequence.scrubCallLinkKeys(): CharSequence {
|
||||
return scrub(this, CALL_LINK_PATTERN) { matcher, output ->
|
||||
val match = matcher.group(1)
|
||||
output
|
||||
.append(match)
|
||||
.append(CALL_LINK_CENSOR_SUFFIX)
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrub(input: CharSequence, pattern: Pattern, processMatch: MatchProcessor): CharSequence {
|
||||
val output = StringBuilder(input.length)
|
||||
val matcher: Matcher = pattern.matcher(input)
|
||||
var lastEndingPos = 0
|
||||
|
||||
while (matcher.find()) {
|
||||
output.append(input, lastEndingPos, matcher.start())
|
||||
processMatch(matcher, output)
|
||||
lastEndingPos = matcher.end()
|
||||
}
|
||||
|
||||
return if (lastEndingPos == 0) {
|
||||
// there were no matches, save copying all the data
|
||||
input
|
||||
} else {
|
||||
output.append(input, lastEndingPos, input.length)
|
||||
output
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user