From 4ffb93129d4db8327c37f9383b55d5cd5b2f3ac9 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 22 Jan 2022 16:55:51 +0100 Subject: [PATCH 01/16] Update libsignal-service-java --- lib/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index c768445b..21dfb19a 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -14,7 +14,7 @@ repositories { } dependencies { - implementation("com.github.turasa", "signal-service-java", "2.15.3_unofficial_38") + implementation("com.github.turasa", "signal-service-java", "2.15.3_unofficial_39") implementation("com.fasterxml.jackson.core", "jackson-databind", "2.13.1") implementation("com.google.protobuf", "protobuf-javalite", "3.11.4") implementation("org.bouncycastle", "bcprov-jdk15on", "1.70") -- 2.51.0 From d51b957adad3014c7c7a48bfc27cf692a0127c10 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 22 Jan 2022 22:58:46 +0100 Subject: [PATCH 02/16] Bump version --- CHANGELOG.md | 5 +++++ build.gradle.kts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84a5d304..9a642ab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +## [0.10.2] - 2022-01-22 +### Fixed +- Archive old sessions/sender keys when a recipient's identity key has changed +- Fix profile fetch with an invalid LANG variable + ## [0.10.1] - 2022-01-16 ### Added - Send group messages with sender keys (more efficient for larger groups) diff --git a/build.gradle.kts b/build.gradle.kts index af537dfa..19ed08d7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.9.9" } -version = "0.10.1" +version = "0.10.2" java { sourceCompatibility = JavaVersion.VERSION_17 -- 2.51.0 From 166bec0f8d2f3291dde4f30964692b550ff8ac40 Mon Sep 17 00:00:00 2001 From: morph027 <600106+morph027@users.noreply.github.com> Date: Sun, 23 Jan 2022 20:48:55 +0100 Subject: [PATCH 03/16] add org.whispersystems.signalservice.internal.push.SignalServiceProtos.storyContext_ to graalvm reflect config (#868) Signed-off-by: morph027 Co-authored-by: morph027 --- graalvm-config-dir/reflect-config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index a9065791..5681cced 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -2347,6 +2347,7 @@ {"name":"reaction_"}, {"name":"requiredProtocolVersion_"}, {"name":"sticker_"}, + {"name":"storyContext_"}, {"name":"timestamp_"} ]} , -- 2.51.0 From 5d23b1ed9d222a3741bebff67e4af9b901a21b0e Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 23 Jan 2022 20:50:56 +0100 Subject: [PATCH 04/16] Improve error handling of getUserStatus command for invalid phonen numbers --- .../org/asamk/signal/manager/helper/RecipientHelper.java | 2 +- .../org/asamk/signal/commands/GetUserStatusCommand.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java index 5c4896e1..38acd37b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java @@ -105,7 +105,7 @@ public class RecipientHelper { .getRegisteredUsers(ServiceConfig.getIasKeyStore(), numbers, serviceEnvironmentConfig.getCdsMrenclave()); - } catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException | InvalidKeyException e) { + } catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException | InvalidKeyException | NumberFormatException e) { throw new IOException(e); } diff --git a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java index 50e9b17f..5ab237f9 100644 --- a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java +++ b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java @@ -42,7 +42,11 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand { try { registered = m.areUsersRegistered(new HashSet<>(ns.getList("recipient"))); } catch (IOException e) { - throw new IOErrorException("Unable to check if users are registered", e); + throw new IOErrorException("Unable to check if users are registered: " + + e.getMessage() + + " (" + + e.getClass().getSimpleName() + + ")", e); } // Output -- 2.51.0 From e5537dc4dbf23ec9ea02e80e102707950c401e08 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 26 Jan 2022 19:22:46 +0100 Subject: [PATCH 05/16] Update graalvm config --- graalvm-config-dir/jni-config.json | 141 +- graalvm-config-dir/reflect-config.json | 2041 +++++++++++------------ graalvm-config-dir/resource-config.json | 6 +- 3 files changed, 1088 insertions(+), 1100 deletions(-) diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json index 51e2b4e1..4865f747 100644 --- a/graalvm-config-dir/jni-config.json +++ b/graalvm-config-dir/jni-config.json @@ -6,41 +6,41 @@ {"name":"groups"}, {"name":"uid"}, {"name":"username"} - ]} -, + ] +}, { "name":"java.lang.Boolean", - "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, { "name":"java.lang.ClassLoader", "methods":[ {"name":"getPlatformClassLoader","parameterTypes":[] }, {"name":"loadClass","parameterTypes":["java.lang.String"] } - ]} -, + ] +}, { "name":"java.lang.IllegalStateException", - "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { - "name":"java.lang.NoSuchMethodError"} -, + "name":"java.lang.NoSuchMethodError" +}, { "name":"java.lang.UnsatisfiedLinkError", - "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"java.util.UUID", "methods":[ {"name":"","parameterTypes":["long","long"] }, {"name":"getLeastSignificantBits","parameterTypes":[] }, {"name":"getMostSignificantBits","parameterTypes":[] } - ]} -, + ] +}, { - "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader"} -, + "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader" +}, { "name":"org.asamk.signal.manager.storage.protocol.SignalProtocolStore", "methods":[ @@ -56,115 +56,100 @@ {"name":"saveIdentity","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","org.whispersystems.libsignal.IdentityKey"] }, {"name":"storeSenderKey","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","java.util.UUID","org.whispersystems.libsignal.groups.state.SenderKeyRecord"] }, {"name":"storeSession","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","org.whispersystems.libsignal.state.SessionRecord"] } - ]} -, + ] +}, { - "name":"org.graalvm.nativebridge.jni.JNIExceptionWrapperEntryPoints", - "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }]} -, + "name":"org.graalvm.jniutils.JNIExceptionWrapperEntryPoints", + "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }] +}, { "name":"org.whispersystems.libsignal.DuplicateMessageException", - "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.whispersystems.libsignal.IdentityKey", "methods":[ {"name":"","parameterTypes":["byte[]"] }, {"name":"serialize","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.whispersystems.libsignal.IdentityKeyPair", - "methods":[{"name":"serialize","parameterTypes":[] }]} -, + "methods":[{"name":"serialize","parameterTypes":[] }] +}, { "name":"org.whispersystems.libsignal.InvalidMessageException", - "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.whispersystems.libsignal.SignalProtocolAddress", - "methods":[{"name":"","parameterTypes":["java.lang.String","int"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String","int"] }] +}, { "name":"org.whispersystems.libsignal.UntrustedIdentityException", - "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.whispersystems.libsignal.groups.state.SenderKeyRecord", "fields":[{"name":"unsafeHandle"}], - "methods":[ - {"name":"","parameterTypes":["long"] }, - {"name":"nativeHandle","parameterTypes":[] } - ]} -, + "methods":[{"name":"","parameterTypes":["long"] }] +}, { - "name":"org.whispersystems.libsignal.groups.state.SenderKeyStore"} -, + "name":"org.whispersystems.libsignal.groups.state.SenderKeyStore" +}, { "name":"org.whispersystems.libsignal.logging.Log", - "methods":[{"name":"log","parameterTypes":["int","java.lang.String","java.lang.String"] }]} -, + "methods":[{"name":"log","parameterTypes":["int","java.lang.String","java.lang.String"] }] +}, { "name":"org.whispersystems.libsignal.protocol.PlaintextContent", - "fields":[{"name":"unsafeHandle"}], - "methods":[{"name":"nativeHandle","parameterTypes":[] }]} -, + "fields":[{"name":"unsafeHandle"}] +}, { "name":"org.whispersystems.libsignal.protocol.PreKeySignalMessage", "fields":[{"name":"unsafeHandle"}], - "methods":[ - {"name":"","parameterTypes":["long"] }, - {"name":"nativeHandle","parameterTypes":[] } - ]} -, + "methods":[{"name":"","parameterTypes":["long"] }] +}, { "name":"org.whispersystems.libsignal.protocol.SenderKeyMessage", "fields":[{"name":"unsafeHandle"}], - "methods":[{"name":"","parameterTypes":["long"] }]} -, + "methods":[{"name":"","parameterTypes":["long"] }] +}, { "name":"org.whispersystems.libsignal.protocol.SignalMessage", "fields":[{"name":"unsafeHandle"}], - "methods":[ - {"name":"","parameterTypes":["long"] }, - {"name":"nativeHandle","parameterTypes":[] } - ]} -, + "methods":[{"name":"","parameterTypes":["long"] }] +}, { - "name":"org.whispersystems.libsignal.state.IdentityKeyStore"} -, + "name":"org.whispersystems.libsignal.state.IdentityKeyStore" +}, { "name":"org.whispersystems.libsignal.state.IdentityKeyStore$Direction", "fields":[ {"name":"RECEIVING"}, {"name":"SENDING"} - ]} -, + ] +}, { "name":"org.whispersystems.libsignal.state.PreKeyRecord", - "fields":[{"name":"unsafeHandle"}], - "methods":[{"name":"nativeHandle","parameterTypes":[] }]} -, + "fields":[{"name":"unsafeHandle"}] +}, { - "name":"org.whispersystems.libsignal.state.PreKeyStore"} -, + "name":"org.whispersystems.libsignal.state.PreKeyStore" +}, { "name":"org.whispersystems.libsignal.state.SessionRecord", "fields":[{"name":"unsafeHandle"}], - "methods":[ - {"name":"","parameterTypes":["byte[]"] }, - {"name":"nativeHandle","parameterTypes":[] } - ]} -, + "methods":[{"name":"","parameterTypes":["byte[]"] }] +}, { - "name":"org.whispersystems.libsignal.state.SessionStore"} -, + "name":"org.whispersystems.libsignal.state.SessionStore" +}, { "name":"org.whispersystems.libsignal.state.SignedPreKeyRecord", - "fields":[{"name":"unsafeHandle"}], - "methods":[{"name":"nativeHandle","parameterTypes":[] }]} -, + "fields":[{"name":"unsafeHandle"}] +}, { - "name":"org.whispersystems.libsignal.state.SignedPreKeyStore"} - + "name":"org.whispersystems.libsignal.state.SignedPreKeyStore" +} ] diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 5681cced..ede0d53f 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -2,437 +2,437 @@ { "name":"[B", "queryAllDeclaredMethods":true, - "queryAllPublicMethods":true} -, + "queryAllPublicMethods":true +}, { - "name":"[C"} -, + "name":"[C" +}, { "name":"[I", "queryAllDeclaredMethods":true, - "queryAllPublicMethods":true} -, + "queryAllPublicMethods":true +}, { - "name":"[J"} -, + "name":"[J" +}, { - "name":"[Ljava.lang.String;"} -, + "name":"[Ljava.lang.String;" +}, { - "name":"[Lorg.whispersystems.signalservice.api.groupsv2.TemporalCredential;"} -, + "name":"[Lorg.whispersystems.signalservice.api.groupsv2.TemporalCredential;" +}, { - "name":"[Lorg.whispersystems.signalservice.internal.push.GroupMismatchedDevices;"} -, + "name":"[Lorg.whispersystems.signalservice.internal.push.GroupMismatchedDevices;" +}, { - "name":"[Lorg.whispersystems.signalservice.internal.push.GroupStaleDevices;"} -, + "name":"[Lorg.whispersystems.signalservice.internal.push.GroupStaleDevices;" +}, { "name":"byte[]", "allDeclaredMethods":true, - "allPublicMethods":true} -, + "allPublicMethods":true +}, { - "name":"char[]"} -, + "name":"char[]" +}, { "name":"com.fasterxml.jackson.databind.ext.Java7HandlersImpl", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.google.protobuf.AbstractProtobufList", "allDeclaredFields":true, - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"com.google.protobuf.GeneratedMessageLite", - "fields":[{"name":"unknownFields"}]} -, + "fields":[{"name":"unknownFields"}] +}, { "name":"com.google.protobuf.Internal$LongList", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"com.google.protobuf.Internal$ProtobufList", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"com.google.protobuf.LongArrayList", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"com.google.protobuf.PrimitiveNonBoxingCollection", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"com.sun.crypto.provider.AESCipher$General", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.sun.crypto.provider.ARCFOURCipher", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.sun.crypto.provider.DESCipher", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.sun.crypto.provider.DESedeCipher", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.sun.crypto.provider.DHParameters", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.sun.crypto.provider.HmacCore$HmacSHA256", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.sun.crypto.provider.HmacCore$HmacSHA384", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.sun.crypto.provider.TlsKeyMaterialGenerator", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.sun.crypto.provider.TlsMasterSecretGenerator", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"com.sun.crypto.provider.TlsPrfGenerator$V12", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"int", "allDeclaredMethods":true, - "allPublicMethods":true} -, + "allPublicMethods":true +}, { "name":"int[]", "allDeclaredMethods":true, - "allPublicMethods":true} -, + "allPublicMethods":true +}, { "name":"java.io.Serializable", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"java.lang.Boolean", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"java.lang.Class", - "methods":[{"name":"getRecordComponents","parameterTypes":[] }]} -, + "methods":[{"name":"getRecordComponents","parameterTypes":[] }] +}, { "name":"java.lang.Comparable", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"java.lang.Double", - "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, { "name":"java.lang.Enum", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"java.lang.Integer", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"java.lang.Iterable", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"java.lang.Long", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"java.lang.Number", "allDeclaredFields":true, - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"java.lang.Record", "allDeclaredFields":true, - "queryAllDeclaredMethods":true} -, + "queryAllDeclaredMethods":true +}, { "name":"java.lang.String", - "allPublicMethods":true} -, + "allPublicMethods":true +}, { "name":"java.lang.Throwable", "queryAllPublicMethods":true, - "methods":[{"name":"addSuppressed","parameterTypes":["java.lang.Throwable"] }]} -, + "methods":[{"name":"addSuppressed","parameterTypes":["java.lang.Throwable"] }] +}, { "name":"java.lang.reflect.Method", - "methods":[{"name":"isDefault","parameterTypes":[] }]} -, + "methods":[{"name":"isDefault","parameterTypes":[] }] +}, { "name":"java.lang.reflect.RecordComponent", "methods":[ {"name":"getName","parameterTypes":[] }, {"name":"getType","parameterTypes":[] } - ]} -, + ] +}, { "name":"java.nio.Buffer", "allDeclaredMethods":true, - "fields":[{"name":"address"}]} -, + "fields":[{"name":"address"}] +}, { "name":"java.nio.ByteBuffer", "allDeclaredMethods":true, - "allPublicMethods":true} -, + "allPublicMethods":true +}, { - "name":"java.security.KeyStoreSpi"} -, + "name":"java.security.KeyStoreSpi" +}, { - "name":"java.security.SecureRandomParameters"} -, + "name":"java.security.SecureRandomParameters" +}, { - "name":"java.security.cert.PKIXRevocationChecker"} -, + "name":"java.security.cert.PKIXRevocationChecker" +}, { - "name":"java.security.interfaces.ECPrivateKey"} -, + "name":"java.security.interfaces.ECPrivateKey" +}, { - "name":"java.security.interfaces.ECPublicKey"} -, + "name":"java.security.interfaces.ECPublicKey" +}, { - "name":"java.security.interfaces.RSAPrivateKey"} -, + "name":"java.security.interfaces.RSAPrivateKey" +}, { - "name":"java.security.interfaces.RSAPublicKey"} -, + "name":"java.security.interfaces.RSAPublicKey" +}, { "name":"java.util.AbstractCollection", "allDeclaredFields":true, - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"java.util.AbstractList", "allDeclaredFields":true, - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"java.util.ArrayList", "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"java.util.Collection", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"java.util.HashSet", "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"java.util.LinkedHashMap", "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"java.util.List", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"java.util.Locale", - "methods":[{"name":"getUnicodeLocaleType","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"getUnicodeLocaleType","parameterTypes":["java.lang.String"] }] +}, { "name":"java.util.Optional", "allDeclaredFields":true, "queryAllDeclaredMethods":true, - "queryAllDeclaredConstructors":true} -, + "queryAllDeclaredConstructors":true +}, { "name":"java.util.RandomAccess", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"java.util.UUID", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"javax.security.auth.x500.X500Principal", - "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }]} -, + "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }] +}, { "name":"long", "allDeclaredMethods":true, - "allPublicMethods":true} -, + "allPublicMethods":true +}, { - "name":"long[]"} -, + "name":"long[]" +}, { "name":"org.asamk.Signal", "allDeclaredMethods":true, - "allDeclaredClasses":true} -, + "allDeclaredClasses":true +}, { "name":"org.asamk.Signal$Configuration", "allDeclaredClasses":true, - "queryAllDeclaredMethods":true} -, + "queryAllDeclaredMethods":true +}, { "name":"org.asamk.Signal$Device", "allDeclaredMethods":true, - "allDeclaredClasses":true} -, + "allDeclaredClasses":true +}, { "name":"org.asamk.Signal$Error$Failure", - "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.asamk.Signal$Error$UntrustedIdentity", - "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.asamk.Signal$Group", "allDeclaredMethods":true, - "allDeclaredClasses":true} -, + "allDeclaredClasses":true +}, { "name":"org.asamk.Signal$MessageReceived", "allDeclaredConstructors":true, - "allPublicConstructors":true} -, + "allPublicConstructors":true +}, { "name":"org.asamk.Signal$MessageReceivedV2", "queryAllDeclaredConstructors":true, "queryAllPublicConstructors":true, - "methods":[{"name":"","parameterTypes":["java.lang.String","long","java.lang.String","byte[]","java.lang.String","java.util.Map"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String","long","java.lang.String","byte[]","java.lang.String","java.util.Map"] }] +}, { "name":"org.asamk.Signal$ReceiptReceived", "allDeclaredConstructors":true, - "allPublicConstructors":true} -, + "allPublicConstructors":true +}, { "name":"org.asamk.Signal$ReceiptReceivedV2", "queryAllDeclaredConstructors":true, "queryAllPublicConstructors":true, - "methods":[{"name":"","parameterTypes":["java.lang.String","long","java.lang.String","java.lang.String","java.util.Map"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String","long","java.lang.String","java.lang.String","java.util.Map"] }] +}, { "name":"org.asamk.Signal$StructDevice", "allDeclaredFields":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":["org.freedesktop.dbus.DBusPath","java.lang.Long","java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["org.freedesktop.dbus.DBusPath","java.lang.Long","java.lang.String"] }] +}, { "name":"org.asamk.Signal$StructGroup", - "allDeclaredFields":true} -, + "allDeclaredFields":true +}, { "name":"org.asamk.Signal$SyncMessageReceived", "allDeclaredConstructors":true, - "allPublicConstructors":true} -, + "allPublicConstructors":true +}, { "name":"org.asamk.Signal$SyncMessageReceivedV2", "queryAllDeclaredConstructors":true, "queryAllPublicConstructors":true, - "methods":[{"name":"","parameterTypes":["java.lang.String","long","java.lang.String","java.lang.String","byte[]","java.lang.String","java.util.Map"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String","long","java.lang.String","java.lang.String","byte[]","java.lang.String","java.util.Map"] }] +}, { "name":"org.asamk.SignalControl", "allDeclaredMethods":true, - "allDeclaredClasses":true} -, + "allDeclaredClasses":true +}, { "name":"org.asamk.SignalControl$Error$Failure", - "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.asamk.signal.commands.FinishLinkCommand$FinishLinkParams", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }] +}, { "name":"org.asamk.signal.commands.FinishLinkCommand$JsonFinishLink", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"number","parameterTypes":[] }]} -, + "methods":[{"name":"number","parameterTypes":[] }] +}, { "name":"org.asamk.signal.commands.GetUserStatusCommand$JsonUserStatus", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.commands.ListAccountsCommand$JsonAccount", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"number","parameterTypes":[] }]} -, + "methods":[{"name":"number","parameterTypes":[] }] +}, { "name":"org.asamk.signal.commands.ListContactsCommand$JsonContact", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.commands.ListDevicesCommand$JsonDevice", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.commands.ListGroupsCommand$JsonGroup", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.commands.ListGroupsCommand$JsonGroupMember", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.commands.ListIdentitiesCommand$JsonIdentity", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.commands.ListStickerPacksCommand$JsonStickerPack", "allDeclaredFields":true, @@ -446,8 +446,8 @@ {"name":"stickers","parameterTypes":[] }, {"name":"title","parameterTypes":[] }, {"name":"url","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.commands.ListStickerPacksCommand$JsonStickerPack$JsonSticker", "allDeclaredFields":true, @@ -457,41 +457,41 @@ {"name":"contentType","parameterTypes":[] }, {"name":"emoji","parameterTypes":[] }, {"name":"id","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.commands.RegisterCommand$RegistrationParams", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.String"] }] +}, { "name":"org.asamk.signal.commands.StartLinkCommand$JsonLink", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"deviceLinkUri","parameterTypes":[] }]} -, + "methods":[{"name":"deviceLinkUri","parameterTypes":[] }] +}, { "name":"org.asamk.signal.commands.VerifyCommand$VerifyParams", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }] +}, { "name":"org.asamk.signal.json.JsonAttachment", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonCallMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonCallMessage$Answer", "allDeclaredFields":true, @@ -501,15 +501,15 @@ {"name":"id","parameterTypes":[] }, {"name":"opaque","parameterTypes":[] }, {"name":"sdp","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.json.JsonCallMessage$Busy", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"id","parameterTypes":[] }]} -, + "methods":[{"name":"id","parameterTypes":[] }] +}, { "name":"org.asamk.signal.json.JsonCallMessage$Hangup", "allDeclaredFields":true, @@ -520,8 +520,8 @@ {"name":"id","parameterTypes":[] }, {"name":"isLegacy","parameterTypes":[] }, {"name":"type","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.json.JsonCallMessage$IceUpdate", "allDeclaredFields":true, @@ -531,8 +531,8 @@ {"name":"id","parameterTypes":[] }, {"name":"opaque","parameterTypes":[] }, {"name":"sdp","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.json.JsonCallMessage$Offer", "allDeclaredFields":true, @@ -543,68 +543,68 @@ {"name":"opaque","parameterTypes":[] }, {"name":"sdp","parameterTypes":[] }, {"name":"type","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.json.JsonContactAddress", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonContactAvatar", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonContactEmail", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonContactName", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonContactPhone", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonDataMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonError", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonGroupInfo", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonMention", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonMessageEnvelope", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonPayment", "allDeclaredFields":true, @@ -613,32 +613,32 @@ "methods":[ {"name":"note","parameterTypes":[] }, {"name":"receipt","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.json.JsonQuote", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonQuotedAttachment", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonReaction", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonReceiptMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonRecipientAddress", "allDeclaredFields":true, @@ -647,14 +647,14 @@ "methods":[ {"name":"number","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.json.JsonRemoteDelete", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonSendMessageResult", "allDeclaredFields":true, @@ -666,90 +666,90 @@ {"name":"retryAfterSeconds","parameterTypes":[] }, {"name":"token","parameterTypes":[] }, {"name":"type","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.json.JsonSendMessageResult$Type", "allDeclaredFields":true, - "queryAllDeclaredMethods":true} -, + "queryAllDeclaredMethods":true +}, { "name":"org.asamk.signal.json.JsonSharedContact", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonSticker", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonSyncDataMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonSyncMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonSyncMessageType", "allDeclaredFields":true, - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"org.asamk.signal.json.JsonSyncReadMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.json.JsonTypingMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.jsonrpc.JsonRpcBatchMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.jsonrpc.JsonRpcException", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.jsonrpc.JsonRpcMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.jsonrpc.JsonRpcRequest", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.jsonrpc.JsonRpcResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.jsonrpc.JsonRpcResponse$Error", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.JsonStickerPack", "allDeclaredFields":true, @@ -761,8 +761,8 @@ {"name":"cover","parameterTypes":[] }, {"name":"stickers","parameterTypes":[] }, {"name":"title","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.manager.JsonStickerPack$JsonSticker", "allDeclaredFields":true, @@ -770,18 +770,17 @@ "queryAllDeclaredConstructors":true, "methods":[ {"name":"","parameterTypes":["java.lang.Integer","java.lang.String","java.lang.String","java.lang.String"] }, - {"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String"] }, {"name":"contentType","parameterTypes":[] }, {"name":"emoji","parameterTypes":[] }, {"name":"file","parameterTypes":[] }, {"name":"id","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.manager.api.PhoneNumberSharingMode", "allDeclaredFields":true, - "queryAllDeclaredMethods":true} -, + "queryAllDeclaredMethods":true +}, { "name":"org.asamk.signal.manager.storage.configuration.ConfigurationStore$Storage", "allDeclaredFields":true, @@ -795,48 +794,48 @@ {"name":"readReceipts","parameterTypes":[] }, {"name":"typingIndicators","parameterTypes":[] }, {"name":"unidentifiedDeliveryIndicators","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.manager.storage.contacts.LegacyContactInfo", "allDeclaredFields":true, "queryAllDeclaredMethods":true, - "queryAllDeclaredConstructors":true} -, + "queryAllDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.contacts.LegacyJsonContactsStore", "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "fields":[{"name":"contacts", "allowWrite":true}]} -, + "fields":[{"name":"contacts", "allowWrite":true}] +}, { "name":"org.asamk.signal.manager.storage.groups.GroupInfo", "allDeclaredFields":true, - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"org.asamk.signal.manager.storage.groups.GroupInfoV1", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.groups.GroupStore$GroupsDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage$GroupV1", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage$GroupV1$JsonRecipientAddress", "allDeclaredFields":true, @@ -846,596 +845,596 @@ {"name":"","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"number","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] } - ]} -, + ] +}, { "name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage$GroupV1$MembersDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage$GroupV1$MembersSerializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage$GroupV2", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.identities.IdentityKeyStore$IdentityStorage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.profiles.LegacyProfileStore", "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "fields":[{"name":"profiles", "allowWrite":true}]} -, + "fields":[{"name":"profiles", "allowWrite":true}] +}, { "name":"org.asamk.signal.manager.storage.profiles.LegacyProfileStore$ProfileStoreDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.asamk.signal.manager.storage.profiles.LegacySignalProfileEntry", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.profiles.ProfileStore", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.profiles.SignalProfile", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.profiles.SignalProfile$Capabilities", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.protocol.LegacyJsonIdentityKeyStore$JsonIdentityKeyStoreDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.asamk.signal.manager.storage.protocol.LegacyJsonPreKeyStore$JsonPreKeyStoreDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.asamk.signal.manager.storage.protocol.LegacyJsonSessionStore$JsonSessionStoreDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.protocol.LegacyJsonSignedPreKeyStore$JsonSignedPreKeyStoreDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.asamk.signal.manager.storage.recipients.LegacyRecipientStore", "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "fields":[{"name":"addresses", "allowWrite":true}]} -, + "fields":[{"name":"addresses", "allowWrite":true}] +}, { "name":"org.asamk.signal.manager.storage.recipients.LegacyRecipientStore$RecipientStoreDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.asamk.signal.manager.storage.recipients.RecipientAddress", "allDeclaredFields":true, "queryAllDeclaredMethods":true, - "queryAllDeclaredConstructors":true} -, + "queryAllDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.recipients.RecipientStore$Storage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.recipients.RecipientStore$Storage$Recipient", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.recipients.RecipientStore$Storage$Recipient$Contact", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.recipients.RecipientStore$Storage$Recipient$Profile", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.senderKeys.SenderKeySharedStore$Storage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.senderKeys.SenderKeySharedStore$Storage$SharedSenderKey", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.stickers.StickerStore", "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "fields":[{"name":"stickers", "allowWrite":true}]} -, + "fields":[{"name":"stickers", "allowWrite":true}] +}, { "name":"org.asamk.signal.manager.storage.stickers.StickerStore$Storage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.stickers.StickerStore$Storage$Sticker", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.util.SecurityProvider$DefaultRandom", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.COMPOSITE$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.DH$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.DSA$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.DSTU4145$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.EC$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.ECGOST$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.EdEC$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.ElGamal$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.GM$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.GOST$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.IES$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.X509$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.edec.SignatureSpi$Ed25519", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.edec.SignatureSpi$Ed448", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.Blake2b$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.Blake2s$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.DSTU7564$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.GOST3411$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.Haraka$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.Keccak$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.MD2$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.MD4$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.MD5$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD128$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD160$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD256$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD320$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.SHA1$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.SHA224$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.SHA256$Digest", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.SHA256$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.SHA3$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.SHA384$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.SHA512$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.SM3$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.Skein$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.Tiger$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.digest.Whirlpool$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.drbg.DRBG$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.keystore.BC$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.keystore.BCFKS$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.keystore.PKCS12$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi$Std", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.AES$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.ARC4$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.ARIA$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Blowfish$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.CAST5$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.CAST6$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Camellia$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.ChaCha$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.DES$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.DESede$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.DSTU7624$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.GOST28147$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.GOST3412_2015$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Grain128$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Grainv1$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.HC128$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.HC256$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.IDEA$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Noekeon$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.OpenSSLPBKDF$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF1$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Poly1305$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.RC2$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.RC5$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.RC6$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Rijndael$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.SCRYPT$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.SEED$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.SM4$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Salsa20$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Serpent$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Shacal2$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.SipHash$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.SipHash128$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Skipjack$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.TEA$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.TLSKDF$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Threefish$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Twofish$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.VMPC$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.VMPCKSA3$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.XSalsa20$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.XTEA$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.symmetric.Zuc$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.pqc.jcajce.provider.LMS$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.pqc.jcajce.provider.McEliece$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.pqc.jcajce.provider.NH$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.pqc.jcajce.provider.QTESLA$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.pqc.jcajce.provider.Rainbow$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.pqc.jcajce.provider.SPHINCS$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.pqc.jcajce.provider.XMSS$Mappings", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.freedesktop.dbus.errors.ServiceUnknown", - "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.freedesktop.dbus.errors.UnknownMethod", - "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.freedesktop.dbus.errors.UnknownObject", - "methods":[{"name":"","parameterTypes":["java.lang.String"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.freedesktop.dbus.interfaces.DBus$NameAcquired", - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.freedesktop.dbus.interfaces.Introspectable", "allDeclaredMethods":true, - "allDeclaredClasses":true} -, + "allDeclaredClasses":true +}, { "name":"org.freedesktop.dbus.interfaces.Peer", "allDeclaredMethods":true, - "allDeclaredClasses":true} -, + "allDeclaredClasses":true +}, { "name":"org.freedesktop.dbus.interfaces.Properties", "allDeclaredMethods":true, - "allDeclaredClasses":true} -, + "allDeclaredClasses":true +}, { "name":"org.freedesktop.dbus.interfaces.Properties$PropertiesChanged", - "allPublicConstructors":true} -, + "allPublicConstructors":true +}, { "name":"org.signal.storageservice.protos.groups.AccessControl", "fields":[ {"name":"addFromInviteLink_"}, {"name":"attributes_"}, {"name":"members_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.AvatarUploadAttributes", "fields":[ @@ -1446,8 +1445,8 @@ {"name":"key_"}, {"name":"policy_"}, {"name":"signature_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.Group", "fields":[ @@ -1463,23 +1462,23 @@ {"name":"requestingMembers_"}, {"name":"revision_"}, {"name":"title_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.GroupAttributeBlob", "fields":[ {"name":"contentCase_"}, {"name":"content_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange", "fields":[ {"name":"actions_"}, {"name":"changeEpoch_"}, {"name":"serverSignature_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions", "fields":[ @@ -1504,107 +1503,107 @@ {"name":"promoteRequestingMembers_"}, {"name":"revision_"}, {"name":"sourceUuid_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddMemberAction", "fields":[ {"name":"added_"}, {"name":"joinFromInviteLink_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddPendingMemberAction", - "fields":[{"name":"added_"}]} -, + "fields":[{"name":"added_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddRequestingMemberAction", - "fields":[{"name":"added_"}]} -, + "fields":[{"name":"added_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeleteMemberAction", - "fields":[{"name":"deletedUserId_"}]} -, + "fields":[{"name":"deletedUserId_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeletePendingMemberAction", - "fields":[{"name":"deletedUserId_"}]} -, + "fields":[{"name":"deletedUserId_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeleteRequestingMemberAction", - "fields":[{"name":"deletedUserId_"}]} -, + "fields":[{"name":"deletedUserId_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAddFromInviteLinkAccessControlAction", - "fields":[{"name":"addFromInviteLinkAccess_"}]} -, + "fields":[{"name":"addFromInviteLinkAccess_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAnnouncementsOnlyAction", - "fields":[{"name":"announcementsOnly_"}]} -, + "fields":[{"name":"announcementsOnly_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAttributesAccessControlAction", - "fields":[{"name":"attributesAccess_"}]} -, + "fields":[{"name":"attributesAccess_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAvatarAction", - "fields":[{"name":"avatar_"}]} -, + "fields":[{"name":"avatar_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyDescriptionAction", - "fields":[{"name":"description_"}]} -, + "fields":[{"name":"description_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyDisappearingMessagesTimerAction", - "fields":[{"name":"timer_"}]} -, + "fields":[{"name":"timer_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyInviteLinkPasswordAction", - "fields":[{"name":"inviteLinkPassword_"}]} -, + "fields":[{"name":"inviteLinkPassword_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMemberProfileKeyAction", - "fields":[{"name":"presentation_"}]} -, + "fields":[{"name":"presentation_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMemberRoleAction", "fields":[ {"name":"role_"}, {"name":"userId_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMembersAccessControlAction", - "fields":[{"name":"membersAccess_"}]} -, + "fields":[{"name":"membersAccess_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyTitleAction", - "fields":[{"name":"title_"}]} -, + "fields":[{"name":"title_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$PromotePendingMemberAction", - "fields":[{"name":"presentation_"}]} -, + "fields":[{"name":"presentation_"}] +}, { "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$PromoteRequestingMemberAction", "fields":[ {"name":"role_"}, {"name":"userId_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.GroupInviteLink", "fields":[ {"name":"contentsCase_"}, {"name":"contents_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.GroupInviteLink$GroupInviteLinkContentsV1", "fields":[ {"name":"groupMasterKey_"}, {"name":"inviteLinkPassword_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.Member", "fields":[ @@ -1613,16 +1612,16 @@ {"name":"profileKey_"}, {"name":"role_"}, {"name":"userId_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.PendingMember", "fields":[ {"name":"addedByUserId_"}, {"name":"member_"}, {"name":"timestamp_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.RequestingMember", "fields":[ @@ -1630,15 +1629,15 @@ {"name":"profileKey_"}, {"name":"timestamp_"}, {"name":"userId_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.local.DecryptedApproveMember", "fields":[ {"name":"role_"}, {"name":"uuid_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.local.DecryptedGroup", "fields":[ @@ -1653,8 +1652,8 @@ {"name":"requestingMembers_"}, {"name":"revision_"}, {"name":"title_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.local.DecryptedGroupChange", "fields":[ @@ -1679,8 +1678,8 @@ {"name":"promotePendingMembers_"}, {"name":"promoteRequestingMembers_"}, {"name":"revision_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.local.DecryptedMember", "fields":[ @@ -1688,15 +1687,15 @@ {"name":"profileKey_"}, {"name":"role_"}, {"name":"uuid_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole", "fields":[ {"name":"role_"}, {"name":"uuid_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.local.DecryptedPendingMember", "fields":[ @@ -1705,244 +1704,244 @@ {"name":"timestamp_"}, {"name":"uuidCipherText_"}, {"name":"uuid_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval", "fields":[ {"name":"uuidCipherText_"}, {"name":"uuid_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.local.DecryptedRequestingMember", "fields":[ {"name":"profileKey_"}, {"name":"timestamp_"}, {"name":"uuid_"} - ]} -, + ] +}, { "name":"org.signal.storageservice.protos.groups.local.DecryptedString", - "fields":[{"name":"value_"}]} -, + "fields":[{"name":"value_"}] +}, { "name":"org.signal.storageservice.protos.groups.local.DecryptedTimer", - "fields":[{"name":"duration_"}]} -, + "fields":[{"name":"duration_"}] +}, { "name":"org.signal.zkgroup.internal.ByteArray", "allDeclaredFields":true, - "queryAllDeclaredMethods":true} -, + "queryAllDeclaredMethods":true +}, { "name":"org.signal.zkgroup.profiles.ProfileKey", "allDeclaredFields":true, "queryAllDeclaredMethods":true, - "queryAllDeclaredConstructors":true} -, + "queryAllDeclaredConstructors":true +}, { "name":"org.signal.zkgroup.profiles.ProfileKeyCredential", "allDeclaredFields":true, "queryAllDeclaredMethods":true, - "queryAllDeclaredConstructors":true} -, + "queryAllDeclaredConstructors":true +}, { "name":"org.whispersystems.libsignal.state.IdentityKeyStore", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"org.whispersystems.libsignal.state.PreKeyStore", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"org.whispersystems.libsignal.state.SessionStore", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"org.whispersystems.libsignal.state.SignalProtocolStore", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"org.whispersystems.libsignal.state.SignedPreKeyStore", - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"org.whispersystems.signalservice.api.account.AccountAttributes", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.account.AccountAttributes$Capabilities", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.groupsv2.CredentialResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { - "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]"} -, + "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]" +}, { "name":"org.whispersystems.signalservice.api.messages.calls.HangupMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.messages.calls.HangupMessage$Type", "allDeclaredFields":true, - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.messages.calls.OfferMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.messages.calls.OfferMessage$Type", "allDeclaredFields":true, - "allDeclaredMethods":true} -, + "allDeclaredMethods":true +}, { "name":"org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfile", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfile$Badge", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfile$Capabilities", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfileWrite", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.push.ACI", "allDeclaredFields":true, "queryAllDeclaredMethods":true, - "queryAllDeclaredConstructors":true} -, + "queryAllDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.push.AccountIdentifier", "allDeclaredFields":true, - "queryAllDeclaredMethods":true} -, + "queryAllDeclaredMethods":true +}, { "name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity$ByteArrayDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity$ByteArraySerializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.api.storage.StorageAuthResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.contacts.crypto.SignatureBodyEntity", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.contacts.entities.DiscoveryRequest", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.contacts.entities.DiscoveryResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.contacts.entities.KeyBackupRequest", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.contacts.entities.MultiRemoteAttestationResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.contacts.entities.QueryEnvelope", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationRequest", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.contacts.entities.TokenResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.devices.DeviceNameProtos$DeviceName", "fields":[ @@ -1950,8 +1949,8 @@ {"name":"ciphertext_"}, {"name":"ephemeralPublic_"}, {"name":"syntheticIv_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.keybackup.protos.BackupRequest", "fields":[ @@ -1963,24 +1962,24 @@ {"name":"token_"}, {"name":"tries_"}, {"name":"validFrom_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.keybackup.protos.BackupResponse", "fields":[ {"name":"bitField0_"}, {"name":"status_"}, {"name":"token_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.keybackup.protos.DeleteRequest", "fields":[ {"name":"backupId_"}, {"name":"bitField0_"}, {"name":"serviceId_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.keybackup.protos.Request", "fields":[ @@ -1988,8 +1987,8 @@ {"name":"bitField0_"}, {"name":"delete_"}, {"name":"restore_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.keybackup.protos.Response", "fields":[ @@ -1997,8 +1996,8 @@ {"name":"bitField0_"}, {"name":"delete_"}, {"name":"restore_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.keybackup.protos.RestoreRequest", "fields":[ @@ -2008,8 +2007,8 @@ {"name":"serviceId_"}, {"name":"token_"}, {"name":"validFrom_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.keybackup.protos.RestoreResponse", "fields":[ @@ -2018,134 +2017,134 @@ {"name":"status_"}, {"name":"token_"}, {"name":"tries_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.AttachmentV2UploadAttributes", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.AuthCredentials", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.ConfirmCodeMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.DeviceCode", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.DeviceId", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.DeviceInfoList", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.GroupMismatchedDevices", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.push.GroupStaleDevices", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.push.MismatchedDevices", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.OutgoingPushMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.OutgoingPushMessageList", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.PreKeyEntity", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.PreKeyEntity$ECPublicKeyDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.push.PreKeyEntity$ECPublicKeySerializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.push.PreKeyResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.PreKeyResponseItem", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.PreKeyState", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.PreKeyStatus", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.ProfileAvatarUploadAttributes", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.ProvisioningMessage", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.ProvisioningProtos$ProvisionEnvelope", "fields":[ {"name":"bitField0_"}, {"name":"body_"}, {"name":"publicKey_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.ProvisioningProtos$ProvisionMessage", "fields":[ @@ -2159,50 +2158,50 @@ {"name":"readReceipts_"}, {"name":"userAgent_"}, {"name":"uuid_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.ProvisioningProtos$ProvisioningUuid", "fields":[ {"name":"bitField0_"}, {"name":"uuid_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.PushServiceSocket$RegistrationLockFailure", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.PushServiceSocket$RegistrationLockV2", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.SendGroupMessageResponse", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.push.SendMessageResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.SenderCertificate", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.push.SenderCertificate$ByteArrayDesieralizer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$AttachmentPointer", "fields":[ @@ -2222,8 +2221,8 @@ {"name":"thumbnail_"}, {"name":"uploadTimestamp_"}, {"name":"width_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage", "fields":[ @@ -2237,8 +2236,8 @@ {"name":"multiRing_"}, {"name":"offer_"}, {"name":"opaque_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Answer", "fields":[ @@ -2246,8 +2245,8 @@ {"name":"id_"}, {"name":"opaque_"}, {"name":"sdp_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Hangup", "fields":[ @@ -2255,8 +2254,8 @@ {"name":"deviceId_"}, {"name":"id_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$IceUpdate", "fields":[ @@ -2266,8 +2265,8 @@ {"name":"mid_"}, {"name":"opaque_"}, {"name":"sdp_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Offer", "fields":[ @@ -2276,16 +2275,16 @@ {"name":"opaque_"}, {"name":"sdp_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Opaque", "fields":[ {"name":"bitField0_"}, {"name":"data_"}, {"name":"urgency_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$ContactDetails", "fields":[ @@ -2301,16 +2300,16 @@ {"name":"profileKey_"}, {"name":"uuid_"}, {"name":"verified_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$ContactDetails$Avatar", "fields":[ {"name":"bitField0_"}, {"name":"contentType_"}, {"name":"length_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$Content", "fields":[ @@ -2323,8 +2322,8 @@ {"name":"senderKeyDistributionMessage_"}, {"name":"syncMessage_"}, {"name":"typingMessage_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage", "fields":[ @@ -2349,8 +2348,8 @@ {"name":"sticker_"}, {"name":"storyContext_"}, {"name":"timestamp_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$BodyRange", "fields":[ @@ -2359,8 +2358,8 @@ {"name":"bitField0_"}, {"name":"length_"}, {"name":"start_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact", "fields":[ @@ -2371,16 +2370,16 @@ {"name":"name_"}, {"name":"number_"}, {"name":"organization_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact$Avatar", "fields":[ {"name":"avatar_"}, {"name":"bitField0_"}, {"name":"isProfile_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact$Email", "fields":[ @@ -2388,8 +2387,8 @@ {"name":"label_"}, {"name":"type_"}, {"name":"value_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact$Name", "fields":[ @@ -2400,8 +2399,8 @@ {"name":"middleName_"}, {"name":"prefix_"}, {"name":"suffix_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact$Phone", "fields":[ @@ -2409,8 +2408,8 @@ {"name":"label_"}, {"name":"type_"}, {"name":"value_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact$PostalAddress", "fields":[ @@ -2424,29 +2423,29 @@ {"name":"region_"}, {"name":"street_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Delete", "fields":[ {"name":"bitField0_"}, {"name":"targetSentTimestamp_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$GroupCallUpdate", "fields":[ {"name":"bitField0_"}, {"name":"eraId_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Payment", "fields":[ {"name":"itemCase_"}, {"name":"item_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Payment$Notification", "fields":[ @@ -2454,15 +2453,15 @@ {"name":"note_"}, {"name":"transactionCase_"}, {"name":"transaction_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Payment$Notification$MobileCoin", "fields":[ {"name":"bitField0_"}, {"name":"receipt_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Preview", "fields":[ @@ -2472,8 +2471,8 @@ {"name":"image_"}, {"name":"title_"}, {"name":"url_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Quote", "fields":[ @@ -2484,8 +2483,8 @@ {"name":"bodyRanges_"}, {"name":"id_"}, {"name":"text_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Quote$QuotedAttachment", "fields":[ @@ -2493,8 +2492,8 @@ {"name":"contentType_"}, {"name":"fileName_"}, {"name":"thumbnail_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Reaction", "fields":[ @@ -2503,8 +2502,8 @@ {"name":"remove_"}, {"name":"targetAuthorUuid_"}, {"name":"targetSentTimestamp_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Sticker", "fields":[ @@ -2514,8 +2513,8 @@ {"name":"packId_"}, {"name":"packKey_"}, {"name":"stickerId_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$Envelope", "fields":[ @@ -2530,8 +2529,8 @@ {"name":"sourceUuid_"}, {"name":"timestamp_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$GroupContext", "fields":[ @@ -2542,15 +2541,15 @@ {"name":"members_"}, {"name":"name_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$GroupContext$Member", "fields":[ {"name":"bitField0_"}, {"name":"e164_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$GroupContextV2", "fields":[ @@ -2558,8 +2557,8 @@ {"name":"groupChange_"}, {"name":"masterKey_"}, {"name":"revision_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$GroupDetails", "fields":[ @@ -2575,38 +2574,38 @@ {"name":"membersE164_"}, {"name":"members_"}, {"name":"name_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$GroupDetails$Avatar", "fields":[ {"name":"bitField0_"}, {"name":"contentType_"}, {"name":"length_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$GroupDetails$Member", "fields":[ {"name":"bitField0_"}, {"name":"e164_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$NullMessage", "fields":[ {"name":"bitField0_"}, {"name":"padding_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$ReceiptMessage", "fields":[ {"name":"bitField0_"}, {"name":"timestamp_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage", "fields":[ @@ -2627,16 +2626,16 @@ {"name":"verified_"}, {"name":"viewOnceOpen_"}, {"name":"viewed_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Blocked", "fields":[ {"name":"groupIds_"}, {"name":"numbers_"}, {"name":"uuids_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Configuration", "fields":[ @@ -2646,37 +2645,37 @@ {"name":"readReceipts_"}, {"name":"typingIndicators_"}, {"name":"unidentifiedDeliveryIndicators_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Contacts", "fields":[ {"name":"bitField0_"}, {"name":"blob_"}, {"name":"complete_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$FetchLatest", "fields":[ {"name":"bitField0_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Groups", "fields":[ {"name":"bitField0_"}, {"name":"blob_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Keys", "fields":[ {"name":"bitField0_"}, {"name":"storageService_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$MessageRequestResponse", "fields":[ @@ -2685,8 +2684,8 @@ {"name":"threadE164_"}, {"name":"threadUuid_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Read", "fields":[ @@ -2694,15 +2693,15 @@ {"name":"senderE164_"}, {"name":"senderUuid_"}, {"name":"timestamp_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Request", "fields":[ {"name":"bitField0_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Sent", "fields":[ @@ -2714,8 +2713,8 @@ {"name":"message_"}, {"name":"timestamp_"}, {"name":"unidentifiedStatus_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Sent$UnidentifiedDeliveryStatus", "fields":[ @@ -2723,8 +2722,8 @@ {"name":"destinationE164_"}, {"name":"destinationUuid_"}, {"name":"unidentified_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$StickerPackOperation", "fields":[ @@ -2732,8 +2731,8 @@ {"name":"packId_"}, {"name":"packKey_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$ViewOnceOpen", "fields":[ @@ -2741,8 +2740,8 @@ {"name":"senderE164_"}, {"name":"senderUuid_"}, {"name":"timestamp_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Viewed", "fields":[ @@ -2750,8 +2749,8 @@ {"name":"senderE164_"}, {"name":"senderUuid_"}, {"name":"timestamp_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$TypingMessage", "fields":[ @@ -2759,8 +2758,8 @@ {"name":"bitField0_"}, {"name":"groupId_"}, {"name":"timestamp_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$Verified", "fields":[ @@ -2770,29 +2769,29 @@ {"name":"identityKey_"}, {"name":"nullMessage_"}, {"name":"state_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.push.StaleDevices", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.push.VerifyAccountResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true} -, + "allDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.internal.serialize.protos.AddressProto", "fields":[ {"name":"bitField0_"}, {"name":"e164_"}, {"name":"uuid_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.serialize.protos.MetadataProto", "fields":[ @@ -2805,8 +2804,8 @@ {"name":"serverGuid_"}, {"name":"serverReceivedTimestamp_"}, {"name":"timestamp_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto", "fields":[ @@ -2815,8 +2814,8 @@ {"name":"data_"}, {"name":"localAddress_"}, {"name":"metadata_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.sticker.StickerProtos$Pack", "fields":[ @@ -2825,8 +2824,8 @@ {"name":"cover_"}, {"name":"stickers_"}, {"name":"title_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.sticker.StickerProtos$Pack$Sticker", "fields":[ @@ -2834,101 +2833,101 @@ {"name":"contentType_"}, {"name":"emoji_"}, {"name":"id_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord", - "allDeclaredFields":true} -, + "allDeclaredFields":true +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$PinnedConversation", "fields":[ {"name":"identifierCase_"}, {"name":"identifier_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$PinnedConversation$Contact", "fields":[ {"name":"e164_"}, {"name":"uuid_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord", - "allDeclaredFields":true} -, + "allDeclaredFields":true +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV1Record", - "allDeclaredFields":true} -, + "allDeclaredFields":true +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record", - "allDeclaredFields":true} -, + "allDeclaredFields":true +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.ManifestRecord", "fields":[ {"name":"identifiers_"}, {"name":"version_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.ManifestRecord$Identifier", "fields":[ {"name":"raw_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.Payments", - "allDeclaredFields":true} -, + "allDeclaredFields":true +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.ReadOperation", - "fields":[{"name":"readKey_"}]} -, + "fields":[{"name":"readKey_"}] +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.StorageItem", "fields":[ {"name":"key_"}, {"name":"value_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.StorageItems", - "fields":[{"name":"items_"}]} -, + "fields":[{"name":"items_"}] +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.StorageManifest", "fields":[ {"name":"value_"}, {"name":"version_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.StorageRecord", "fields":[ {"name":"recordCase_"}, {"name":"record_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.util.JsonUtil$AciDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.util.JsonUtil$IdentityKeyDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.util.JsonUtil$IdentityKeySerializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.util.JsonUtil$UuidDeserializer", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.websocket.WebSocketProtos$WebSocketMessage", "fields":[ @@ -2936,8 +2935,8 @@ {"name":"request_"}, {"name":"response_"}, {"name":"type_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.websocket.WebSocketProtos$WebSocketRequestMessage", "fields":[ @@ -2947,8 +2946,8 @@ {"name":"id_"}, {"name":"path_"}, {"name":"verb_"} - ]} -, + ] +}, { "name":"org.whispersystems.signalservice.internal.websocket.WebSocketProtos$WebSocketResponseMessage", "fields":[ @@ -2958,8 +2957,8 @@ {"name":"id_"}, {"name":"message_"}, {"name":"status_"} - ]} -, + ] +}, { "name":"sun.misc.Unsafe", "allDeclaredFields":true, @@ -2999,110 +2998,110 @@ {"name":"putLong","parameterTypes":["java.lang.Object","long","long"] }, {"name":"putObject","parameterTypes":["java.lang.Object","long","java.lang.Object"] }, {"name":"putShort","parameterTypes":["long","short"] } - ]} -, + ] +}, { "name":"sun.security.provider.DSA$SHA224withDSA", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.provider.JavaKeyStore$DualFormatJKS", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.provider.JavaKeyStore$JKS", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.provider.NativePRNG", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.provider.NativePRNG$NonBlocking", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.provider.SHA", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.provider.SHA2$SHA224", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.provider.SHA2$SHA256", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.provider.SHA5$SHA384", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.provider.SHA5$SHA512", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.provider.SecureRandom", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.provider.certpath.PKIXCertPathValidator", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.rsa.PSSParameters", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.rsa.RSAKeyFactory$Legacy", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.rsa.RSAPSSSignature", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.rsa.RSASignature$SHA224withRSA", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.rsa.RSASignature$SHA256withRSA", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.rsa.RSASignature$SHA512withRSA", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.ssl.SSLContextImpl$TLSContext", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", - "methods":[{"name":"","parameterTypes":[] }]} -, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"sun.security.x509.AuthorityKeyIdentifierExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, { "name":"sun.security.x509.BasicConstraintsExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, { "name":"sun.security.x509.CRLDistributionPointsExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, { "name":"sun.security.x509.KeyUsageExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, { "name":"sun.security.x509.SubjectAlternativeNameExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]} -, + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, { "name":"sun.security.x509.SubjectKeyIdentifierExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]} - + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +} ] diff --git a/graalvm-config-dir/resource-config.json b/graalvm-config-dir/resource-config.json index e2f9db0e..8a486340 100644 --- a/graalvm-config-dir/resource-config.json +++ b/graalvm-config-dir/resource-config.json @@ -192,6 +192,10 @@ } ]}, "bundles":[{ - "name":"net.sourceforge.argparse4j.internal.ArgumentParserImpl" + "name":"net.sourceforge.argparse4j.internal.ArgumentParserImpl", + "locales":[ + "", + "und" + ] }] } -- 2.51.0 From 67146f9cc7a32466efff8c1170d5ac4590987890 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 26 Jan 2022 21:03:04 +0100 Subject: [PATCH 06/16] Create stores in SignalAccount lazily --- .../signal/manager/storage/SignalAccount.java | 215 ++++++++++-------- .../storage/recipients/RecipientStore.java | 5 +- .../senderKeys/SenderKeySharedStore.java | 5 +- .../storage/senderKeys/SenderKeyStore.java | 3 +- 4 files changed, 134 insertions(+), 94 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index eac16e84..f23aea03 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -66,6 +66,7 @@ import java.util.Base64; import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.function.Supplier; public class SignalAccount implements Closeable { @@ -74,13 +75,16 @@ public class SignalAccount implements Closeable { private static final int MINIMUM_STORAGE_VERSION = 1; private static final int CURRENT_STORAGE_VERSION = 3; - private int previousStorageVersion; + private final Object LOCK = new Object(); private final ObjectMapper jsonProcessor = Utils.createStorageObjectMapper(); private final FileChannel fileChannel; private final FileLock lock; + private int previousStorageVersion; + + private File dataPath; private String account; private ACI aci; private String encryptedDeviceName; @@ -94,6 +98,9 @@ public class SignalAccount implements Closeable { private ProfileKey profileKey; private int preKeyIdOffset; private int nextSignedPreKeyId; + private IdentityKeyPair identityKeyPair; + private int localRegistrationId; + private TrustNewIdentity trustNewIdentity; private long lastReceiveTimestamp = 0; private boolean registered = false; @@ -165,9 +172,12 @@ public class SignalAccount implements Closeable { signalAccount.account = account; signalAccount.profileKey = profileKey; - signalAccount.initStores(dataPath, identityKey, registrationId, trustNewIdentity); + signalAccount.dataPath = dataPath; + signalAccount.identityKeyPair = identityKey; + signalAccount.localRegistrationId = registrationId; + signalAccount.trustNewIdentity = trustNewIdentity; signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account), - signalAccount.recipientStore, + signalAccount.getRecipientStore(), signalAccount::saveGroupStore); signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore); signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore); @@ -181,36 +191,6 @@ public class SignalAccount implements Closeable { return signalAccount; } - private void initStores( - final File dataPath, - final IdentityKeyPair identityKey, - final int registrationId, - final TrustNewIdentity trustNewIdentity - ) throws IOException { - recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, account), this::mergeRecipients); - - preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, account)); - signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, account)); - sessionStore = new SessionStore(getSessionsPath(dataPath, account), recipientStore); - identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, account), - recipientStore, - identityKey, - registrationId, - trustNewIdentity); - senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, account), - getSenderKeysPath(dataPath, account), - recipientStore::resolveRecipientAddress, - recipientStore); - signalProtocolStore = new SignalProtocolStore(preKeyStore, - signedPreKeyStore, - sessionStore, - identityKeyStore, - senderKeyStore, - this::isMultiDevice); - - messageCache = new MessageCache(getMessageCachePath(dataPath, account)); - } - public static SignalAccount createOrUpdateLinkedAccount( File dataPath, String account, @@ -240,9 +220,9 @@ public class SignalAccount implements Closeable { final var signalAccount = load(dataPath, account, true, trustNewIdentity); signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey); - signalAccount.recipientStore.resolveRecipientTrusted(signalAccount.getSelfAddress()); - signalAccount.sessionStore.archiveAllSessions(); - signalAccount.senderKeyStore.deleteAll(); + signalAccount.getRecipientStore().resolveRecipientTrusted(signalAccount.getSelfAddress()); + signalAccount.getSessionStore().archiveAllSessions(); + signalAccount.getSenderKeyStore().deleteAll(); signalAccount.clearAllPreKeys(); return signalAccount; } @@ -250,8 +230,8 @@ public class SignalAccount implements Closeable { private void clearAllPreKeys() { this.preKeyIdOffset = new SecureRandom().nextInt(Medium.MAX_VALUE); this.nextSignedPreKeyId = new SecureRandom().nextInt(Medium.MAX_VALUE); - this.preKeyStore.removeAllPreKeys(); - this.signedPreKeyStore.removeAllSignedPreKeys(); + this.getPreKeyStore().removeAllPreKeys(); + this.getSignedPreKeyStore().removeAllSignedPreKeys(); save(); } @@ -275,14 +255,17 @@ public class SignalAccount implements Closeable { signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey); - signalAccount.initStores(dataPath, identityKey, registrationId, trustNewIdentity); + signalAccount.dataPath = dataPath; + signalAccount.identityKeyPair = identityKey; + signalAccount.localRegistrationId = registrationId; + signalAccount.trustNewIdentity = trustNewIdentity; signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account), - signalAccount.recipientStore, + signalAccount.getRecipientStore(), signalAccount::saveGroupStore); signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore); signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore); - signalAccount.recipientStore.resolveRecipientTrusted(signalAccount.getSelfAddress()); + signalAccount.getRecipientStore().resolveRecipientTrusted(signalAccount.getSelfAddress()); signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION; signalAccount.migrateLegacyConfigs(); signalAccount.save(); @@ -335,19 +318,19 @@ public class SignalAccount implements Closeable { } private void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) { - sessionStore.mergeRecipients(recipientId, toBeMergedRecipientId); - identityKeyStore.mergeRecipients(recipientId, toBeMergedRecipientId); - messageCache.mergeRecipients(recipientId, toBeMergedRecipientId); - groupStore.mergeRecipients(recipientId, toBeMergedRecipientId); - senderKeyStore.mergeRecipients(recipientId, toBeMergedRecipientId); + getSessionStore().mergeRecipients(recipientId, toBeMergedRecipientId); + getIdentityKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId); + getMessageCache().mergeRecipients(recipientId, toBeMergedRecipientId); + getGroupStore().mergeRecipients(recipientId, toBeMergedRecipientId); + getSenderKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId); } public void removeRecipient(final RecipientId recipientId) { - sessionStore.deleteAllSessions(recipientId); - identityKeyStore.deleteIdentity(recipientId); - messageCache.deleteMessages(recipientId); - senderKeyStore.deleteAll(recipientId); - recipientStore.deleteRecipientData(recipientId); + getSessionStore().deleteAllSessions(recipientId); + getIdentityKeyStore().deleteIdentity(recipientId); + getMessageCache().deleteMessages(recipientId); + getSenderKeyStore().deleteAll(recipientId); + getRecipientStore().deleteRecipientData(recipientId); } public static File getFileName(File dataPath, String account) { @@ -505,7 +488,10 @@ public class SignalAccount implements Closeable { migratedLegacyConfig = true; } - initStores(dataPath, identityKeyPair, registrationId, trustNewIdentity); + this.dataPath = dataPath; + this.identityKeyPair = identityKeyPair; + this.localRegistrationId = registrationId; + this.trustNewIdentity = trustNewIdentity; migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig; @@ -513,10 +499,12 @@ public class SignalAccount implements Closeable { groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"), GroupStore.Storage.class); groupStore = GroupStore.fromStorage(groupStoreStorage, getGroupCachePath(dataPath, account), - recipientStore, + getRecipientStore(), this::saveGroupStore); } else { - groupStore = new GroupStore(getGroupCachePath(dataPath, account), recipientStore, this::saveGroupStore); + groupStore = new GroupStore(getGroupCachePath(dataPath, account), + getRecipientStore(), + this::saveGroupStore); } if (rootNode.hasNonNull("stickerStore")) { @@ -551,7 +539,7 @@ public class SignalAccount implements Closeable { logger.debug("Migrating legacy recipient store."); var legacyRecipientStore = jsonProcessor.convertValue(legacyRecipientStoreNode, LegacyRecipientStore.class); if (legacyRecipientStore != null) { - recipientStore.resolveRecipientsTrusted(legacyRecipientStore.getAddresses()); + getRecipientStore().resolveRecipientsTrusted(legacyRecipientStore.getAddresses()); } getSelfRecipientId(); migrated = true; @@ -561,7 +549,7 @@ public class SignalAccount implements Closeable { logger.debug("Migrating legacy pre key store."); for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) { try { - preKeyStore.storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue())); + getPreKeyStore().storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue())); } catch (IOException e) { logger.warn("Failed to migrate pre key, ignoring", e); } @@ -573,7 +561,7 @@ public class SignalAccount implements Closeable { logger.debug("Migrating legacy signed pre key store."); for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) { try { - signedPreKeyStore.storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue())); + getSignedPreKeyStore().storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue())); } catch (IOException e) { logger.warn("Failed to migrate signed pre key, ignoring", e); } @@ -585,7 +573,7 @@ public class SignalAccount implements Closeable { logger.debug("Migrating legacy session store."); for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) { try { - sessionStore.storeSession(new SignalProtocolAddress(session.address.getIdentifier(), + getSessionStore().storeSession(new SignalProtocolAddress(session.address.getIdentifier(), session.deviceId), new SessionRecord(session.sessionRecord)); } catch (Exception e) { logger.warn("Failed to migrate session, ignoring", e); @@ -597,9 +585,9 @@ public class SignalAccount implements Closeable { if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) { logger.debug("Migrating legacy identity session store."); for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) { - RecipientId recipientId = recipientStore.resolveRecipientTrusted(identity.getAddress()); - identityKeyStore.saveIdentity(recipientId, identity.getIdentityKey(), identity.getDateAdded()); - identityKeyStore.setIdentityTrustLevel(recipientId, + RecipientId recipientId = getRecipientStore().resolveRecipientTrusted(identity.getAddress()); + getIdentityKeyStore().saveIdentity(recipientId, identity.getIdentityKey(), identity.getDateAdded()); + getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), identity.getTrustLevel()); } @@ -611,8 +599,8 @@ public class SignalAccount implements Closeable { final var contactStoreNode = rootNode.get("contactStore"); final var contactStore = jsonProcessor.convertValue(contactStoreNode, LegacyJsonContactsStore.class); for (var contact : contactStore.getContacts()) { - final var recipientId = recipientStore.resolveRecipientTrusted(contact.getAddress()); - recipientStore.storeContact(recipientId, + final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress()); + getRecipientStore().storeContact(recipientId, new Contact(contact.name, contact.color, contact.messageExpirationTime, @@ -639,9 +627,9 @@ public class SignalAccount implements Closeable { var profileStoreNode = rootNode.get("profileStore"); final var legacyProfileStore = jsonProcessor.convertValue(profileStoreNode, LegacyProfileStore.class); for (var profileEntry : legacyProfileStore.getProfileEntries()) { - var recipientId = recipientStore.resolveRecipient(profileEntry.getAddress()); - recipientStore.storeProfileKeyCredential(recipientId, profileEntry.getProfileKeyCredential()); - recipientStore.storeProfileKey(recipientId, profileEntry.getProfileKey()); + var recipientId = getRecipientStore().resolveRecipient(profileEntry.getAddress()); + getRecipientStore().storeProfileKeyCredential(recipientId, profileEntry.getProfileKeyCredential()); + getRecipientStore().storeProfileKey(recipientId, profileEntry.getProfileKey()); final var profile = profileEntry.getProfile(); if (profile != null) { final var capabilities = new HashSet(); @@ -668,7 +656,7 @@ public class SignalAccount implements Closeable { ? Profile.UnidentifiedAccessMode.ENABLED : Profile.UnidentifiedAccessMode.DISABLED, capabilities); - recipientStore.storeProfile(recipientId, newProfile); + getRecipientStore().storeProfile(recipientId, newProfile); } } } @@ -687,10 +675,10 @@ public class SignalAccount implements Closeable { } try { if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) { - final var recipientId = recipientStore.resolveRecipient(thread.id); - var contact = recipientStore.getContact(recipientId); + final var recipientId = getRecipientStore().resolveRecipient(thread.id); + var contact = getRecipientStore().getContact(recipientId); if (contact != null) { - recipientStore.storeContact(recipientId, + getRecipientStore().storeContact(recipientId, Contact.newBuilder(contact) .withMessageExpirationTime(thread.messageExpirationTime) .build()); @@ -738,13 +726,10 @@ public class SignalAccount implements Closeable { .put("isMultiDevice", isMultiDevice) .put("lastReceiveTimestamp", lastReceiveTimestamp) .put("password", password) - .put("registrationId", identityKeyStore.getLocalRegistrationId()) + .put("registrationId", localRegistrationId) .put("identityPrivateKey", - Base64.getEncoder() - .encodeToString(identityKeyStore.getIdentityKeyPair().getPrivateKey().serialize())) - .put("identityKey", - Base64.getEncoder() - .encodeToString(identityKeyStore.getIdentityKeyPair().getPublicKey().serialize())) + Base64.getEncoder().encodeToString(identityKeyPair.getPrivateKey().serialize())) + .put("identityKey", Base64.getEncoder().encodeToString(identityKeyPair.getPublicKey().serialize())) .put("registrationLockPin", registrationLockPin) .put("pinMasterKey", pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize())) @@ -796,7 +781,7 @@ public class SignalAccount implements Closeable { logger.error("Invalid pre key id {}, expected {}", record.getId(), preKeyIdOffset); throw new AssertionError("Invalid pre key id"); } - preKeyStore.storePreKey(record.getId(), record); + getPreKeyStore().storePreKey(record.getId(), record); preKeyIdOffset = (preKeyIdOffset + 1) % Medium.MAX_VALUE; } save(); @@ -807,21 +792,42 @@ public class SignalAccount implements Closeable { logger.error("Invalid signed pre key id {}, expected {}", record.getId(), nextSignedPreKeyId); throw new AssertionError("Invalid signed pre key id"); } - signalProtocolStore.storeSignedPreKey(record.getId(), record); + getSignedPreKeyStore().storeSignedPreKey(record.getId(), record); nextSignedPreKeyId = (nextSignedPreKeyId + 1) % Medium.MAX_VALUE; save(); } public SignalProtocolStore getSignalProtocolStore() { - return signalProtocolStore; + return getOrCreate(() -> signalProtocolStore, + () -> signalProtocolStore = new SignalProtocolStore(getPreKeyStore(), + getSignedPreKeyStore(), + getSessionStore(), + getIdentityKeyStore(), + getSenderKeyStore(), + this::isMultiDevice)); + } + + private PreKeyStore getPreKeyStore() { + return getOrCreate(() -> preKeyStore, () -> preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, account))); + } + + private SignedPreKeyStore getSignedPreKeyStore() { + return getOrCreate(() -> signedPreKeyStore, + () -> signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, account))); } public SessionStore getSessionStore() { - return sessionStore; + return getOrCreate(() -> sessionStore, + () -> sessionStore = new SessionStore(getSessionsPath(dataPath, account), getRecipientStore())); } public IdentityKeyStore getIdentityKeyStore() { - return identityKeyStore; + return getOrCreate(() -> identityKeyStore, + () -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, account), + getRecipientStore(), + identityKeyPair, + localRegistrationId, + trustNewIdentity)); } public GroupStore getGroupStore() { @@ -829,15 +835,17 @@ public class SignalAccount implements Closeable { } public ContactsStore getContactStore() { - return recipientStore; + return getRecipientStore(); } public RecipientStore getRecipientStore() { - return recipientStore; + return getOrCreate(() -> recipientStore, + () -> recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, account), + this::mergeRecipients)); } public ProfileStore getProfileStore() { - return recipientStore; + return getRecipientStore(); } public StickerStore getStickerStore() { @@ -845,7 +853,11 @@ public class SignalAccount implements Closeable { } public SenderKeyStore getSenderKeyStore() { - return senderKeyStore; + return getOrCreate(() -> senderKeyStore, + () -> senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, account), + getSenderKeysPath(dataPath, account), + getRecipientStore()::resolveRecipientAddress, + getRecipientStore())); } public ConfigurationStore getConfigurationStore() { @@ -853,7 +865,8 @@ public class SignalAccount implements Closeable { } public MessageCache getMessageCache() { - return messageCache; + return getOrCreate(() -> messageCache, + () -> messageCache = new MessageCache(getMessageCachePath(dataPath, account))); } public String getAccount() { @@ -874,7 +887,8 @@ public class SignalAccount implements Closeable { } public RecipientId getSelfRecipientId() { - return recipientStore.resolveRecipientTrusted(new RecipientAddress(aci == null ? null : aci.uuid(), account)); + return getRecipientStore().resolveRecipientTrusted(new RecipientAddress(aci == null ? null : aci.uuid(), + account)); } public String getEncryptedDeviceName() { @@ -895,11 +909,11 @@ public class SignalAccount implements Closeable { } public IdentityKeyPair getIdentityKeyPair() { - return signalProtocolStore.getIdentityKeyPair(); + return identityKeyPair; } public int getLocalRegistrationId() { - return signalProtocolStore.getLocalRegistrationId(); + return localRegistrationId; } public String getPassword() { @@ -1026,7 +1040,7 @@ public class SignalAccount implements Closeable { clearAllPreKeys(); getSessionStore().archiveAllSessions(); - senderKeyStore.deleteAll(); + getSenderKeyStore().deleteAll(); final var recipientId = getRecipientStore().resolveRecipientTrusted(getSelfAddress()); final var publicKey = getIdentityKeyPair().getPublicKey(); getIdentityKeyStore().saveIdentity(recipientId, publicKey, new Date()); @@ -1047,4 +1061,25 @@ public class SignalAccount implements Closeable { } } } + + private T getOrCreate(Supplier supplier, Callable creator) { + var value = supplier.get(); + if (value != null) { + return value; + } + + synchronized (LOCK) { + value = supplier.get(); + if (value != null) { + return value; + } + creator.call(); + return supplier.get(); + } + } + + private interface Callable { + + void call(); + } } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java index 6dd327e8..1fd0ae4c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java @@ -49,7 +49,7 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile private long lastId; private boolean isBulkUpdating; - public static RecipientStore load(File file, RecipientMergeHandler recipientMergeHandler) throws IOException { + public static RecipientStore load(File file, RecipientMergeHandler recipientMergeHandler) { final var objectMapper = Utils.createStorageObjectMapper(); try (var inputStream = new FileInputStream(file)) { final var storage = objectMapper.readValue(inputStream, Storage.class); @@ -114,6 +114,9 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile } catch (FileNotFoundException e) { logger.trace("Creating new recipient store."); return new RecipientStore(objectMapper, file, recipientMergeHandler, new HashMap<>(), 0); + } catch (IOException e) { + logger.warn("Failed to load recipient store", e); + throw new RuntimeException(e); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java index 11e8ed69..9cc6ba42 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java @@ -42,7 +42,7 @@ public class SenderKeySharedStore { public static SenderKeySharedStore load( final File file, final RecipientAddressResolver addressResolver, final RecipientResolver resolver - ) throws IOException { + ) { final var objectMapper = Utils.createStorageObjectMapper(); try (var inputStream = new FileInputStream(file)) { final var storage = objectMapper.readValue(inputStream, Storage.class); @@ -70,6 +70,9 @@ public class SenderKeySharedStore { } catch (FileNotFoundException e) { logger.trace("Creating new shared sender key store."); return new SenderKeySharedStore(new HashMap<>(), objectMapper, file, addressResolver, resolver); + } catch (IOException e) { + logger.warn("Failed to load shared sender key store", e); + throw new RuntimeException(e); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java index 3f08c389..8674945c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java @@ -9,7 +9,6 @@ import org.whispersystems.signalservice.api.SignalServiceSenderKeyStore; import org.whispersystems.signalservice.api.push.DistributionId; import java.io.File; -import java.io.IOException; import java.util.Collection; import java.util.Set; import java.util.UUID; @@ -24,7 +23,7 @@ public class SenderKeyStore implements SignalServiceSenderKeyStore { final File senderKeysPath, final RecipientAddressResolver addressResolver, final RecipientResolver resolver - ) throws IOException { + ) { this.senderKeyRecordStore = new SenderKeyRecordStore(senderKeysPath, resolver); this.senderKeySharedStore = SenderKeySharedStore.load(file, addressResolver, resolver); } -- 2.51.0 From ede0dfeef45cc9fc1dbd952285eb82ad162a7224 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 26 Jan 2022 21:55:23 +0100 Subject: [PATCH 07/16] Fix output for envelope receipts --- .../java/org/asamk/signal/manager/api/MessageEnvelope.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java index aa34a459..dde8e166 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java @@ -816,7 +816,9 @@ public record MessageEnvelope( .orNull()); call = Optional.ofNullable(content.getCallMessage().transform(Call::from).orNull()); } else { - receipt = Optional.empty(); + receipt = envelope.isReceipt() ? Optional.of(new Receipt(envelope.getServerReceivedTimestamp(), + Receipt.Type.DELIVERY, + List.of(envelope.getTimestamp()))) : Optional.empty(); typing = Optional.empty(); data = Optional.empty(); sync = Optional.empty(); -- 2.51.0 From 238455ad6c74add28a4bb73645172c60e3d6129a Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 26 Jan 2022 21:57:01 +0100 Subject: [PATCH 08/16] Mark profile for refresh when receiving a profile key message --- .../signal/manager/storage/recipients/RecipientStore.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java index 1fd0ae4c..36f82481 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java @@ -301,7 +301,13 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile public void storeProfileKey(RecipientId recipientId, final ProfileKey profileKey) { synchronized (recipients) { final var recipient = recipients.get(recipientId); - if (profileKey != null && profileKey.equals(recipient.getProfileKey())) { + if (profileKey != null && profileKey.equals(recipient.getProfileKey()) && ( + recipient.getProfile() == null || ( + recipient.getProfile().getUnidentifiedAccessMode() != Profile.UnidentifiedAccessMode.UNKNOWN + && recipient.getProfile().getUnidentifiedAccessMode() + != Profile.UnidentifiedAccessMode.DISABLED + ) + )) { return; } -- 2.51.0 From d812c249baa5ac227a49425375b670d31c8c3584 Mon Sep 17 00:00:00 2001 From: morph027 <600106+morph027@users.noreply.github.com> Date: Wed, 26 Jan 2022 22:04:36 +0100 Subject: [PATCH 09/16] add GraalVM reflections for GroupJoinInfo (#872) Signed-off-by: morph027 Co-authored-by: morph027 --- graalvm-config-dir/reflect-config.json | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index ede0d53f..c7353969 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -1604,6 +1604,19 @@ {"name":"inviteLinkPassword_"} ] }, +{ + "name":"org.signal.storageservice.protos.groups.GroupJoinInfo", + "fields":[ + {"name":"addFromInviteLink_"}, + {"name":"avatar_"}, + {"name":"description_"}, + {"name":"memberCount_"}, + {"name":"pendingAdminApproval_"}, + {"name":"publicKey_"}, + {"name":"revision_"}, + {"name":"title_"} + ] +}, { "name":"org.signal.storageservice.protos.groups.Member", "fields":[ @@ -1680,6 +1693,20 @@ {"name":"revision_"} ] }, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo", + "fields":[ + {"name":"addFromInviteLink_"}, + {"name":"avatar_"}, + {"name":"description_"}, + {"name":"isAnnouncementGroup_"}, + {"name":"memberCount_"}, + {"name":"pendingAdminApproval_"}, + {"name":"publicKey_"}, + {"name":"revision_"}, + {"name":"title_"} + ] +}, { "name":"org.signal.storageservice.protos.groups.local.DecryptedMember", "fields":[ -- 2.51.0 From ffaa9d2ed311d74cf14f25fdc0cc75b66991cc3b Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 27 Jan 2022 20:03:43 +0100 Subject: [PATCH 10/16] Output sender also for sealed sender messages that fail to decrypt --- .../asamk/signal/manager/api/MessageEnvelope.java | 12 +++++++++--- .../manager/helper/IncomingMessageHandler.java | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java index dde8e166..73e7fe19 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java @@ -5,6 +5,7 @@ import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.manager.helper.RecipientAddressResolver; import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.RecipientResolver; +import org.signal.libsignal.metadata.ProtocolException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId; import org.whispersystems.signalservice.api.messages.SignalServiceContent; @@ -789,16 +790,21 @@ public record MessageEnvelope( SignalServiceContent content, RecipientResolver recipientResolver, RecipientAddressResolver addressResolver, - final AttachmentFileProvider fileProvider + final AttachmentFileProvider fileProvider, + Exception exception ) { final var source = !envelope.isUnidentifiedSender() && envelope.hasSourceUuid() ? recipientResolver.resolveRecipient(envelope.getSourceAddress()) : envelope.isUnidentifiedSender() && content != null ? recipientResolver.resolveRecipient(content.getSender()) - : null; + : exception instanceof ProtocolException e + ? recipientResolver.resolveRecipient(e.getSender()) + : null; final var sourceDevice = envelope.hasSourceDevice() ? envelope.getSourceDevice() - : content != null ? content.getSenderDevice() : 0; + : content != null + ? content.getSenderDevice() + : exception instanceof ProtocolException e ? e.getSenderDevice() : 0; Optional receipt; Optional typing; diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 0ef974e0..d7a50971 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -184,7 +184,8 @@ public final class IncomingMessageHandler { content, account.getRecipientStore(), account.getRecipientStore()::resolveRecipientAddress, - context.getAttachmentHelper()::getAttachmentFile), exception); + context.getAttachmentHelper()::getAttachmentFile, + exception), exception); return actions; } } -- 2.51.0 From 3491782912978f17a13c30b713a2d5f34e6a8a39 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 27 Jan 2022 22:42:49 +0100 Subject: [PATCH 11/16] Fix sender check for requesting message resend --- .../org/asamk/signal/manager/helper/IncomingMessageHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index d7a50971..251dfde3 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -127,7 +127,7 @@ public final class IncomingMessageHandler { final var senderProfile = context.getProfileHelper().getRecipientProfile(sender); final var selfProfile = context.getProfileHelper() .getRecipientProfile(account.getSelfRecipientId()); - if (e.getSenderDevice() != account.getDeviceId() + if ((!sender.equals(account.getSelfRecipientId()) || e.getSenderDevice() != account.getDeviceId()) && senderProfile != null && senderProfile.getCapabilities().contains(Profile.Capability.senderKey) && selfProfile != null -- 2.51.0 From 95cc0ae7fdaf0cc34742bce38bb456e02653db43 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 23 Jan 2022 20:50:23 +0100 Subject: [PATCH 12/16] Implement MessageSendLog for resending after encryption error --- graalvm-config-dir/jni-config.json | 34 ++ graalvm-config-dir/proxy-config.json | 3 + graalvm-config-dir/reflect-config.json | 81 +++- graalvm-config-dir/resource-config.json | 12 + lib/build.gradle.kts | 2 + .../org/asamk/signal/manager/Manager.java | 1 + .../org/asamk/signal/manager/ManagerImpl.java | 11 + .../manager/actions/ResendMessageAction.java | 42 ++ .../helper/IncomingMessageHandler.java | 76 +++- .../signal/manager/helper/SendHelper.java | 220 +++++++--- .../signal/manager/storage/Database.java | 94 +++++ .../signal/manager/storage/SignalAccount.java | 38 ++ .../storage/sendLog/MessageSendLogEntry.java | 11 + .../storage/sendLog/MessageSendLogStore.java | 396 ++++++++++++++++++ .../senderKeys/SenderKeySharedStore.java | 15 + .../storage/senderKeys/SenderKeyStore.java | 4 + 16 files changed, 960 insertions(+), 80 deletions(-) create mode 100644 lib/src/main/java/org/asamk/signal/manager/actions/ResendMessageAction.java create mode 100644 lib/src/main/java/org/asamk/signal/manager/storage/Database.java create mode 100644 lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogEntry.java create mode 100644 lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json index 4865f747..acbf7f3f 100644 --- a/graalvm-config-dir/jni-config.json +++ b/graalvm-config-dir/jni-config.json @@ -62,6 +62,36 @@ "name":"org.graalvm.jniutils.JNIExceptionWrapperEntryPoints", "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }] }, +{ + "name":"org.sqlite.Collation" +}, +{ + "name":"org.sqlite.Function" +}, +{ + "name":"org.sqlite.Function$Aggregate" +}, +{ + "name":"org.sqlite.Function$Window" +}, +{ + "name":"org.sqlite.ProgressHandler" +}, +{ + "name":"org.sqlite.core.DB", + "methods":[{"name":"throwex","parameterTypes":["int"] }] +}, +{ + "name":"org.sqlite.core.DB$ProgressObserver" +}, +{ + "name":"org.sqlite.core.NativeDB", + "fields":[ + {"name":"colldatalist"}, + {"name":"pointer"}, + {"name":"udfdatalist"} + ] +}, { "name":"org.whispersystems.libsignal.DuplicateMessageException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] @@ -77,6 +107,10 @@ "name":"org.whispersystems.libsignal.IdentityKeyPair", "methods":[{"name":"serialize","parameterTypes":[] }] }, +{ + "name":"org.whispersystems.libsignal.InvalidKeyException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.whispersystems.libsignal.InvalidMessageException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] diff --git a/graalvm-config-dir/proxy-config.json b/graalvm-config-dir/proxy-config.json index 42660618..3110286d 100644 --- a/graalvm-config-dir/proxy-config.json +++ b/graalvm-config-dir/proxy-config.json @@ -1,4 +1,7 @@ [ + { + "interfaces":["java.sql.Connection"]} + , { "interfaces":["org.asamk.Signal"]} , diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index c7353969..c5e61cee 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -15,9 +15,15 @@ { "name":"[J" }, +{ + "name":"[Lcom.zaxxer.hikari.util.ConcurrentBag$IConcurrentBagEntry;" +}, { "name":"[Ljava.lang.String;" }, +{ + "name":"[Ljava.sql.Statement;" +}, { "name":"[Lorg.whispersystems.signalservice.api.groupsv2.TemporalCredential;" }, @@ -118,6 +124,48 @@ "name":"com.sun.crypto.provider.TlsPrfGenerator$V12", "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"com.zaxxer.hikari.HikariConfig", + "allDeclaredFields":true, + "queryAllPublicMethods":true, + "methods":[ + {"name":"getCatalog","parameterTypes":[] }, + {"name":"getConnectionInitSql","parameterTypes":[] }, + {"name":"getConnectionTestQuery","parameterTypes":[] }, + {"name":"getConnectionTimeout","parameterTypes":[] }, + {"name":"getDataSource","parameterTypes":[] }, + {"name":"getDataSourceClassName","parameterTypes":[] }, + {"name":"getDataSourceJNDI","parameterTypes":[] }, + {"name":"getDataSourceProperties","parameterTypes":[] }, + {"name":"getDriverClassName","parameterTypes":[] }, + {"name":"getExceptionOverrideClassName","parameterTypes":[] }, + {"name":"getHealthCheckProperties","parameterTypes":[] }, + {"name":"getHealthCheckRegistry","parameterTypes":[] }, + {"name":"getIdleTimeout","parameterTypes":[] }, + {"name":"getInitializationFailTimeout","parameterTypes":[] }, + {"name":"getJdbcUrl","parameterTypes":[] }, + {"name":"getKeepaliveTime","parameterTypes":[] }, + {"name":"getLeakDetectionThreshold","parameterTypes":[] }, + {"name":"getMaxLifetime","parameterTypes":[] }, + {"name":"getMaximumPoolSize","parameterTypes":[] }, + {"name":"getMetricRegistry","parameterTypes":[] }, + {"name":"getMetricsTrackerFactory","parameterTypes":[] }, + {"name":"getMinimumIdle","parameterTypes":[] }, + {"name":"getPassword","parameterTypes":[] }, + {"name":"getPoolName","parameterTypes":[] }, + {"name":"getScheduledExecutor","parameterTypes":[] }, + {"name":"getSchema","parameterTypes":[] }, + {"name":"getThreadFactory","parameterTypes":[] }, + {"name":"getTransactionIsolation","parameterTypes":[] }, + {"name":"getUsername","parameterTypes":[] }, + {"name":"getValidationTimeout","parameterTypes":[] }, + {"name":"isAllowPoolSuspension","parameterTypes":[] }, + {"name":"isAutoCommit","parameterTypes":[] }, + {"name":"isIsolateInternalQueries","parameterTypes":[] }, + {"name":"isReadOnly","parameterTypes":[] }, + {"name":"isRegisterMbeans","parameterTypes":[] } + ] +}, { "name":"int", "allDeclaredMethods":true, @@ -1607,13 +1655,13 @@ { "name":"org.signal.storageservice.protos.groups.GroupJoinInfo", "fields":[ - {"name":"addFromInviteLink_"}, - {"name":"avatar_"}, - {"name":"description_"}, - {"name":"memberCount_"}, - {"name":"pendingAdminApproval_"}, - {"name":"publicKey_"}, - {"name":"revision_"}, + {"name":"addFromInviteLink_"}, + {"name":"avatar_"}, + {"name":"description_"}, + {"name":"memberCount_"}, + {"name":"pendingAdminApproval_"}, + {"name":"publicKey_"}, + {"name":"revision_"}, {"name":"title_"} ] }, @@ -1696,14 +1744,14 @@ { "name":"org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo", "fields":[ - {"name":"addFromInviteLink_"}, - {"name":"avatar_"}, - {"name":"description_"}, - {"name":"isAnnouncementGroup_"}, - {"name":"memberCount_"}, - {"name":"pendingAdminApproval_"}, - {"name":"publicKey_"}, - {"name":"revision_"}, + {"name":"addFromInviteLink_"}, + {"name":"avatar_"}, + {"name":"description_"}, + {"name":"isAnnouncementGroup_"}, + {"name":"memberCount_"}, + {"name":"pendingAdminApproval_"}, + {"name":"publicKey_"}, + {"name":"revision_"}, {"name":"title_"} ] }, @@ -1773,6 +1821,9 @@ "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true }, +{ + "name":"org.sqlite.JDBC" +}, { "name":"org.whispersystems.libsignal.state.IdentityKeyStore", "allDeclaredMethods":true diff --git a/graalvm-config-dir/resource-config.json b/graalvm-config-dir/resource-config.json index 8a486340..a52689ae 100644 --- a/graalvm-config-dir/resource-config.json +++ b/graalvm-config-dir/resource-config.json @@ -1,6 +1,12 @@ { "resources":{ "includes":[ + { + "pattern":"\\QMETA-INF/maven/org.xerial/sqlite-jdbc/pom.properties\\E" + }, + { + "pattern":"\\QMETA-INF/services/java.sql.Driver\\E" + }, { "pattern":"\\QMETA-INF/services/org.freedesktop.dbus.spi.transport.ITransportProvider\\E" }, @@ -187,6 +193,12 @@ { "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" }, + { + "pattern":"\\Qorg/sqlite/native/Linux/x86_64/libsqlitejdbc.so\\E" + }, + { + "pattern":"\\Qsqlite-jdbc.properties\\E" + }, { "pattern":"com/google/i18n/phonenumbers/data/.*" } diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 21dfb19a..5b891b8b 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -19,6 +19,8 @@ dependencies { implementation("com.google.protobuf", "protobuf-javalite", "3.11.4") implementation("org.bouncycastle", "bcprov-jdk15on", "1.70") implementation("org.slf4j", "slf4j-api", "1.7.32") + implementation("org.xerial", "sqlite-jdbc", "3.36.0.3") + implementation("com.zaxxer", "HikariCP", "5.0.1") } configurations { diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 2c677dae..ed70bcee 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -67,6 +67,7 @@ public interface Manager extends Closeable { throw new NotRegisteredException(); } + account.initDatabase(); final var serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent); return new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent); diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java index 0e100d72..7c8cff5d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -571,6 +571,17 @@ public class ManagerImpl implements Manager { ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp); final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete); + for (final var recipient : recipients) { + if (recipient instanceof RecipientIdentifier.Single r) { + try { + final var recipientId = context.getRecipientHelper().resolveRecipient(r); + account.getMessageSendLogStore().deleteEntryForRecipientNonGroup(targetSentTimestamp, recipientId); + } catch (UnregisteredRecipientException ignored) { + } + } else if (recipient instanceof RecipientIdentifier.Group r) { + account.getMessageSendLogStore().deleteEntryForGroup(targetSentTimestamp, r.groupId()); + } + } return sendMessage(messageBuilder, recipients); } diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/ResendMessageAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/ResendMessageAction.java new file mode 100644 index 00000000..9f399dd8 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/ResendMessageAction.java @@ -0,0 +1,42 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; +import org.asamk.signal.manager.storage.recipients.RecipientId; +import org.asamk.signal.manager.storage.sendLog.MessageSendLogEntry; + +import java.util.Objects; + +public class ResendMessageAction implements HandleAction { + + private final RecipientId recipientId; + private final long timestamp; + private final MessageSendLogEntry messageSendLogEntry; + + public ResendMessageAction( + final RecipientId recipientId, final long timestamp, final MessageSendLogEntry messageSendLogEntry + ) { + this.recipientId = recipientId; + this.timestamp = timestamp; + this.messageSendLogEntry = messageSendLogEntry; + } + + @Override + public void execute(Context context) throws Throwable { + context.getSendHelper().resendMessage(recipientId, timestamp, messageSendLogEntry); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final ResendMessageAction that = (ResendMessageAction) o; + return timestamp == that.timestamp + && recipientId.equals(that.recipientId) + && messageSendLogEntry.equals(that.messageSendLogEntry); + } + + @Override + public int hashCode() { + return Objects.hash(recipientId, timestamp, messageSendLogEntry); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 251dfde3..eece81d3 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -7,6 +7,7 @@ import org.asamk.signal.manager.UntrustedIdentityException; import org.asamk.signal.manager.actions.HandleAction; import org.asamk.signal.manager.actions.RefreshPreKeysAction; import org.asamk.signal.manager.actions.RenewSessionAction; +import org.asamk.signal.manager.actions.ResendMessageAction; import org.asamk.signal.manager.actions.RetrieveProfileAction; import org.asamk.signal.manager.actions.RetrieveStorageDataAction; import org.asamk.signal.manager.actions.SendGroupInfoAction; @@ -41,6 +42,7 @@ import org.signal.zkgroup.profiles.ProfileKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.SignalProtocolAddress; +import org.whispersystems.libsignal.protocol.DecryptionErrorMessage; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; @@ -165,6 +167,13 @@ public final class IncomingMessageHandler { // address/uuid is validated by unidentified sender certificate account.getRecipientStore().resolveRecipientTrusted(content.getSender()); } + if (envelope.isReceipt()) { + final var senderPair = getSender(envelope, content); + final var sender = senderPair.first(); + final var senderDeviceId = senderPair.second(); + account.getMessageSendLogStore().deleteEntryForRecipient(envelope.getTimestamp(), sender, senderDeviceId); + } + if (isMessageBlocked(envelope, content)) { logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp()); return List.of(); @@ -198,6 +207,14 @@ public final class IncomingMessageHandler { final var sender = senderPair.first(); final var senderDeviceId = senderPair.second(); + if (content.getReceiptMessage().isPresent()) { + final var message = content.getReceiptMessage().get(); + if (message.isDeliveryReceipt()) { + account.getMessageSendLogStore() + .deleteEntriesForRecipient(message.getTimestamps(), sender, senderDeviceId); + } + } + if (content.getSenderKeyDistributionMessage().isPresent()) { final var message = content.getSenderKeyDistributionMessage().get(); final var protocolAddress = new SignalProtocolAddress(context.getRecipientHelper() @@ -212,15 +229,10 @@ public final class IncomingMessageHandler { if (content.getDecryptionErrorMessage().isPresent()) { var message = content.getDecryptionErrorMessage().get(); logger.debug("Received a decryption error message (resend request for {})", message.getTimestamp()); - if (message.getRatchetKey().isPresent()) { - if (message.getDeviceId() == account.getDeviceId() && account.getSessionStore() - .isCurrentRatchetKey(sender, senderDeviceId, message.getRatchetKey().get())) { - logger.debug("Renewing the session with sender"); - actions.add(new RenewSessionAction(sender)); - } + if (message.getDeviceId() == account.getDeviceId()) { + handleDecryptionErrorMessage(actions, sender, senderDeviceId, message); } else { - logger.debug("Reset shared sender keys with this recipient"); - account.getSenderKeyStore().deleteSharedWith(sender); + logger.debug("Request is for another one of our devices"); } } @@ -246,6 +258,54 @@ public final class IncomingMessageHandler { return actions; } + private void handleDecryptionErrorMessage( + final List actions, + final RecipientId sender, + final int senderDeviceId, + final DecryptionErrorMessage message + ) { + final var logEntries = account.getMessageSendLogStore() + .findMessages(sender, senderDeviceId, message.getTimestamp(), !message.getRatchetKey().isPresent()); + + for (final var logEntry : logEntries) { + actions.add(new ResendMessageAction(sender, message.getTimestamp(), logEntry)); + } + + if (message.getRatchetKey().isPresent()) { + if (account.getSessionStore().isCurrentRatchetKey(sender, senderDeviceId, message.getRatchetKey().get())) { + if (logEntries.isEmpty()) { + logger.debug("Renewing the session with sender"); + actions.add(new RenewSessionAction(sender)); + } else { + logger.trace("Archiving the session with sender, a resend message has already been queued"); + context.getAccount().getSessionStore().archiveSessions(sender); + } + } + return; + } + + var found = false; + for (final var logEntry : logEntries) { + if (logEntry.groupId().isEmpty()) { + continue; + } + final var group = account.getGroupStore().getGroup(logEntry.groupId().get()); + if (group == null) { + continue; + } + found = true; + logger.trace("Deleting shared sender key with {} ({}): {}", + sender, + senderDeviceId, + group.getDistributionId()); + account.getSenderKeyStore().deleteSharedWith(sender, senderDeviceId, group.getDistributionId()); + } + if (!found) { + logger.debug("Reset all shared sender keys with this recipient, no related message found in send log"); + account.getSenderKeyStore().deleteSharedWith(sender); + } + } + private List handleSyncMessage( final SignalServiceSyncMessage syncMessage, final RecipientId sender, final boolean ignoreAttachments ) { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java index 4fa1aaeb..aedd29a2 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java @@ -1,5 +1,7 @@ package org.asamk.signal.manager.helper; +import com.google.protobuf.ByteString; + import org.asamk.signal.manager.SignalDependencies; import org.asamk.signal.manager.api.UnregisteredRecipientException; import org.asamk.signal.manager.groups.GroupId; @@ -11,11 +13,13 @@ import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.groups.GroupInfo; import org.asamk.signal.manager.storage.recipients.Profile; import org.asamk.signal.manager.storage.recipients.RecipientId; +import org.asamk.signal.manager.storage.sendLog.MessageSendLogEntry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.InvalidRegistrationIdException; import org.whispersystems.libsignal.NoSessionException; +import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.protocol.DecryptionErrorMessage; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; @@ -45,6 +49,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; public class SendHelper { @@ -74,9 +79,7 @@ public class SendHelper { messageBuilder.withProfileKey(account.getProfileKey().serialize()); final var message = messageBuilder.build(); - final var result = sendMessage(message, recipientId); - handleSendMessageResult(result); - return result; + return sendMessage(message, recipientId); } /** @@ -90,29 +93,6 @@ public class SendHelper { return sendAsGroupMessage(messageBuilder, g); } - private List sendAsGroupMessage( - final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g - ) throws IOException, GroupSendingNotAllowedException { - GroupUtils.setGroupContext(messageBuilder, g); - messageBuilder.withExpiration(g.getMessageExpirationTimer()); - - final var message = messageBuilder.build(); - final var recipients = g.getMembersWithout(account.getSelfRecipientId()); - - if (g.isAnnouncementGroup() && !g.isAdmin(account.getSelfRecipientId())) { - if (message.getBody().isPresent() - || message.getAttachments().isPresent() - || message.getQuote().isPresent() - || message.getPreviews().isPresent() - || message.getMentions().isPresent() - || message.getSticker().isPresent()) { - throw new GroupSendingNotAllowedException(g.getGroupId(), g.getTitle()); - } - } - - return sendGroupMessage(message, recipients, g.getDistributionId()); - } - /** * Send a complete group message to the given recipients (should be current/old/new members) * This method should only be used for create/update/quit group messages. @@ -122,31 +102,7 @@ public class SendHelper { final Set recipientIds, final DistributionId distributionId ) throws IOException { - final var messageSender = dependencies.getMessageSender(); - final var results = sendGroupMessageInternal((recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendDataMessage( - recipients, - unidentifiedAccess, - isRecipientUpdate, - ContentHint.DEFAULT, - message, - SignalServiceMessageSender.LegacyGroupEvents.EMPTY, - sendResult -> logger.trace("Partial message send result: {}", sendResult.isSuccess()), - () -> false), - (distId, recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendGroupDataMessage(distId, - recipients, - unidentifiedAccess, - isRecipientUpdate, - ContentHint.DEFAULT, - message, - SignalServiceMessageSender.SenderKeyGroupEvents.EMPTY), - recipientIds, - distributionId); - - for (var r : results) { - handleSendMessageResult(r); - } - - return results; + return sendGroupMessage(message, recipientIds, distributionId, ContentHint.IMPLICIT); } public SendMessageResult sendDeliveryReceipt( @@ -162,10 +118,14 @@ public class SendHelper { public SendMessageResult sendReceiptMessage( final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId ) { - return handleSendMessage(recipientId, + final var messageSendLogStore = account.getMessageSendLogStore(); + final var result = handleSendMessage(recipientId, (messageSender, address, unidentifiedAccess) -> messageSender.sendReceipt(address, unidentifiedAccess, receiptMessage)); + messageSendLogStore.insertIfPossible(receiptMessage.getWhen(), result, ContentHint.IMPLICIT); + handleSendMessageResult(result); + return result; } public SendMessageResult sendRetryReceipt( @@ -175,15 +135,19 @@ public class SendHelper { errorMessage.getTimestamp(), recipientId, errorMessage.getDeviceId()); - return handleSendMessage(recipientId, + final var result = handleSendMessage(recipientId, (messageSender, address, unidentifiedAccess) -> messageSender.sendRetryReceipt(address, unidentifiedAccess, groupId.transform(GroupId::serialize), errorMessage)); + handleSendMessageResult(result); + return result; } public SendMessageResult sendNullMessage(RecipientId recipientId) { - return handleSendMessage(recipientId, SignalServiceMessageSender::sendNullMessage); + final var result = handleSendMessage(recipientId, SignalServiceMessageSender::sendNullMessage); + handleSendMessageResult(result); + return result; } public SendMessageResult sendSelfMessage( @@ -225,10 +189,12 @@ public class SendHelper { public SendMessageResult sendTypingMessage( SignalServiceTypingMessage message, RecipientId recipientId ) { - return handleSendMessage(recipientId, + final var result = handleSendMessage(recipientId, (messageSender, address, unidentifiedAccess) -> messageSender.sendTyping(address, unidentifiedAccess, message)); + handleSendMessageResult(result); + return result; } public List sendGroupTypingMessage( @@ -244,6 +210,142 @@ public class SendHelper { return sendGroupTypingMessage(message, recipientIds, distributionId); } + public SendMessageResult resendMessage( + final RecipientId recipientId, final long timestamp, final MessageSendLogEntry messageSendLogEntry + ) { + if (messageSendLogEntry.groupId().isEmpty()) { + return handleSendMessage(recipientId, + (messageSender, address, unidentifiedAccess) -> messageSender.resendContent(address, + unidentifiedAccess, + timestamp, + messageSendLogEntry.content(), + messageSendLogEntry.contentHint(), + Optional.absent())); + } + + final var groupId = messageSendLogEntry.groupId().get(); + final var group = account.getGroupStore().getGroup(groupId); + + if (group == null) { + logger.debug("Could not find a matching group for the groupId {}! Skipping message send.", + groupId.toBase64()); + return null; + } else if (!group.getMembers().contains(recipientId)) { + logger.warn("The target user is no longer in the group {}! Skipping message send.", groupId.toBase64()); + return null; + } + + final var senderKeyDistributionMessage = dependencies.getMessageSender() + .getOrCreateNewGroupSession(group.getDistributionId()); + final var distributionBytes = ByteString.copyFrom(senderKeyDistributionMessage.serialize()); + final var contentToSend = messageSendLogEntry.content() + .toBuilder() + .setSenderKeyDistributionMessage(distributionBytes) + .build(); + + final var result = handleSendMessage(recipientId, + (messageSender, address, unidentifiedAccess) -> messageSender.resendContent(address, + unidentifiedAccess, + timestamp, + contentToSend, + messageSendLogEntry.contentHint(), + Optional.of(group.getGroupId().serialize()))); + + if (result.isSuccess()) { + final var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId); + final var addresses = result.getSuccess() + .getDevices() + .stream() + .map(device -> new SignalProtocolAddress(address.getIdentifier(), device)) + .collect(Collectors.toList()); + + account.getSenderKeyStore().markSenderKeySharedWith(group.getDistributionId(), addresses); + } + + return result; + } + + private List sendAsGroupMessage( + final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g + ) throws IOException, GroupSendingNotAllowedException { + GroupUtils.setGroupContext(messageBuilder, g); + messageBuilder.withExpiration(g.getMessageExpirationTimer()); + + final var message = messageBuilder.build(); + final var recipients = g.getMembersWithout(account.getSelfRecipientId()); + + if (g.isAnnouncementGroup() && !g.isAdmin(account.getSelfRecipientId())) { + if (message.getBody().isPresent() + || message.getAttachments().isPresent() + || message.getQuote().isPresent() + || message.getPreviews().isPresent() + || message.getMentions().isPresent() + || message.getSticker().isPresent()) { + throw new GroupSendingNotAllowedException(g.getGroupId(), g.getTitle()); + } + } + + return sendGroupMessage(message, recipients, g.getDistributionId(), ContentHint.RESENDABLE); + } + + private List sendGroupMessage( + final SignalServiceDataMessage message, + final Set recipientIds, + final DistributionId distributionId, + final ContentHint contentHint + ) throws IOException { + final var messageSender = dependencies.getMessageSender(); + final var messageSendLogStore = account.getMessageSendLogStore(); + final AtomicLong entryId = new AtomicLong(-1); + + final LegacySenderHandler legacySender = (recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendDataMessage( + recipients, + unidentifiedAccess, + isRecipientUpdate, + contentHint, + message, + SignalServiceMessageSender.LegacyGroupEvents.EMPTY, + sendResult -> { + logger.trace("Partial message send result: {}", sendResult.isSuccess()); + synchronized (entryId) { + if (entryId.get() == -1) { + final var newId = messageSendLogStore.insertIfPossible(message.getTimestamp(), + sendResult, + contentHint); + entryId.set(newId); + } else { + messageSendLogStore.addRecipientToExistingEntryIfPossible(entryId.get(), sendResult); + } + } + }, + () -> false); + final SenderKeySenderHandler senderKeySender = (distId, recipients, unidentifiedAccess, isRecipientUpdate) -> { + final var res = messageSender.sendGroupDataMessage(distId, + recipients, + unidentifiedAccess, + isRecipientUpdate, + contentHint, + message, + SignalServiceMessageSender.SenderKeyGroupEvents.EMPTY); + synchronized (entryId) { + if (entryId.get() == -1) { + final var newId = messageSendLogStore.insertIfPossible(message.getTimestamp(), res, contentHint); + entryId.set(newId); + } else { + messageSendLogStore.addRecipientToExistingEntryIfPossible(entryId.get(), res); + } + } + return res; + }; + final var results = sendGroupMessageInternal(legacySender, senderKeySender, recipientIds, distributionId); + + for (var r : results) { + handleSendMessageResult(r); + } + + return results; + } + private List sendGroupTypingMessage( final SignalServiceTypingMessage message, final Set recipientIds, @@ -462,12 +564,16 @@ public class SendHelper { private SendMessageResult sendMessage( SignalServiceDataMessage message, RecipientId recipientId ) { - return handleSendMessage(recipientId, + final var messageSendLogStore = account.getMessageSendLogStore(); + final var result = handleSendMessage(recipientId, (messageSender, address, unidentifiedAccess) -> messageSender.sendDataMessage(address, unidentifiedAccess, - ContentHint.DEFAULT, + ContentHint.RESENDABLE, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY)); + messageSendLogStore.insertIfPossible(message.getTimestamp(), result, ContentHint.RESENDABLE); + handleSendMessageResult(result); + return result; } private SendMessageResult handleSendMessage(RecipientId recipientId, SenderHandler s) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/Database.java b/lib/src/main/java/org/asamk/signal/manager/storage/Database.java new file mode 100644 index 00000000..1d69236b --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/storage/Database.java @@ -0,0 +1,94 @@ +package org.asamk.signal.manager.storage; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import org.asamk.signal.manager.storage.sendLog.MessageSendLogStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sqlite.SQLiteConfig; + +import java.io.File; +import java.sql.Connection; +import java.sql.SQLException; + +public class Database implements AutoCloseable { + + private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class); + private static final long DATABASE_VERSION = 1; + + private final HikariDataSource dataSource; + + private Database(final HikariDataSource dataSource) { + this.dataSource = dataSource; + } + + public static Database init(File databaseFile) throws SQLException { + HikariDataSource dataSource = null; + + try { + dataSource = getHikariDataSource(databaseFile.getAbsolutePath()); + + try (final var connection = dataSource.getConnection()) { + final var userVersion = getUserVersion(connection); + logger.trace("Current database version: {} Program database version: {}", + userVersion, + DATABASE_VERSION); + + if (userVersion > DATABASE_VERSION) { + logger.error("Database has been updated by a newer signal-cli version"); + throw new SQLException("Database has been updated by a newer signal-cli version"); + } else if (userVersion < DATABASE_VERSION) { + if (userVersion < 1) { + logger.debug("Updating database: Creating message send log tables"); + MessageSendLogStore.createSql(connection); + } + setUserVersion(connection, DATABASE_VERSION); + } + + final var result = new Database(dataSource); + dataSource = null; + return result; + } + } finally { + if (dataSource != null) { + dataSource.close(); + } + } + } + + public Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + @Override + public void close() throws SQLException { + dataSource.close(); + } + + private static long getUserVersion(final Connection connection) throws SQLException { + try (final var statement = connection.createStatement()) { + final var resultSet = statement.executeQuery("PRAGMA user_version"); + return resultSet.getLong(1); + } + } + + private static void setUserVersion(final Connection connection, long userVersion) throws SQLException { + try (final var statement = connection.createStatement()) { + statement.executeUpdate("PRAGMA user_version = " + userVersion); + } + } + + private static HikariDataSource getHikariDataSource(final String databaseFile) { + final var sqliteConfig = new SQLiteConfig(); + sqliteConfig.setBusyTimeout(10_000); + sqliteConfig.setTransactionMode(SQLiteConfig.TransactionMode.IMMEDIATE); + + HikariConfig config = new HikariConfig(); + config.setJdbcUrl("jdbc:sqlite:" + databaseFile); + config.setDataSourceProperties(sqliteConfig.toProperties()); + config.setMinimumIdle(1); + config.setConnectionInitSql("PRAGMA foreign_keys=ON"); + return new HikariDataSource(config); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index f23aea03..862971c9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -27,6 +27,7 @@ import org.asamk.signal.manager.storage.recipients.Profile; import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.recipients.RecipientStore; +import org.asamk.signal.manager.storage.sendLog.MessageSendLogStore; import org.asamk.signal.manager.storage.senderKeys.SenderKeyStore; import org.asamk.signal.manager.storage.sessions.SessionStore; import org.asamk.signal.manager.storage.stickers.StickerStore; @@ -62,6 +63,7 @@ import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.security.SecureRandom; +import java.sql.SQLException; import java.util.Base64; import java.util.Date; import java.util.HashSet; @@ -120,6 +122,9 @@ public class SignalAccount implements Closeable { private ConfigurationStore.Storage configurationStoreStorage; private MessageCache messageCache; + private MessageSendLogStore messageSendLogStore; + + private Database database; private SignalAccount(final FileChannel fileChannel, final FileLock lock) { this.fileChannel = fileChannel; @@ -227,6 +232,10 @@ public class SignalAccount implements Closeable { return signalAccount; } + public void initDatabase() { + getDatabase(); + } + private void clearAllPreKeys() { this.preKeyIdOffset = new SecureRandom().nextInt(Medium.MAX_VALUE); this.nextSignedPreKeyId = new SecureRandom().nextInt(Medium.MAX_VALUE); @@ -383,6 +392,10 @@ public class SignalAccount implements Closeable { return new File(getUserPath(dataPath, account), "recipients-store"); } + private static File getDatabaseFile(File dataPath, String account) { + return new File(getUserPath(dataPath, account), "account.db"); + } + public static boolean userExists(File dataPath, String account) { if (account == null) { return false; @@ -869,6 +882,21 @@ public class SignalAccount implements Closeable { () -> messageCache = new MessageCache(getMessageCachePath(dataPath, account))); } + public Database getDatabase() { + return getOrCreate(() -> database, () -> { + try { + database = Database.init(getDatabaseFile(dataPath, account)); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + } + + public MessageSendLogStore getMessageSendLogStore() { + return getOrCreate(() -> messageSendLogStore, + () -> messageSendLogStore = new MessageSendLogStore(getRecipientStore(), getDatabase())); + } + public String getAccount() { return account; } @@ -1050,6 +1078,16 @@ public class SignalAccount implements Closeable { @Override public void close() { synchronized (fileChannel) { + if (database != null) { + try { + database.close(); + } catch (SQLException e) { + logger.warn("Failed to close account database: {}", e.getMessage(), e); + } + } + if (messageSendLogStore != null) { + messageSendLogStore.close(); + } try { try { lock.close(); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogEntry.java b/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogEntry.java new file mode 100644 index 00000000..31a4252a --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogEntry.java @@ -0,0 +1,11 @@ +package org.asamk.signal.manager.storage.sendLog; + +import org.asamk.signal.manager.groups.GroupId; +import org.whispersystems.signalservice.api.crypto.ContentHint; +import org.whispersystems.signalservice.internal.push.SignalServiceProtos; + +import java.util.Optional; + +public record MessageSendLogEntry( + Optional groupId, SignalServiceProtos.Content content, ContentHint contentHint +) {} diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java new file mode 100644 index 00000000..795919f6 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java @@ -0,0 +1,396 @@ +package org.asamk.signal.manager.storage.sendLog; + +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupUtils; +import org.asamk.signal.manager.storage.Database; +import org.asamk.signal.manager.storage.recipients.RecipientId; +import org.asamk.signal.manager.storage.recipients.RecipientResolver; +import org.signal.zkgroup.InvalidInputException; +import org.signal.zkgroup.groups.GroupMasterKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.crypto.ContentHint; +import org.whispersystems.signalservice.api.messages.SendMessageResult; +import org.whispersystems.signalservice.internal.push.SignalServiceProtos; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public class MessageSendLogStore implements AutoCloseable { + + private static final Logger logger = LoggerFactory.getLogger(MessageSendLogStore.class); + + private static final String TABLE_MESSAGE_SEND_LOG = "message_send_log"; + private static final String TABLE_MESSAGE_SEND_LOG_CONTENT = "message_send_log_content"; + + private static final Duration LOG_DURATION = Duration.ofDays(1); + + private final RecipientResolver recipientResolver; + private final Database database; + private final Thread cleanupThread; + + public MessageSendLogStore( + final RecipientResolver recipientResolver, final Database database + ) { + this.recipientResolver = recipientResolver; + this.database = database; + this.cleanupThread = new Thread(() -> { + try { + final var interval = Duration.ofHours(1).toMillis(); + while (true) { + try (final var connection = database.getConnection()) { + deleteOutdatedEntries(connection); + Thread.sleep(interval); + } catch (SQLException e) { + logger.warn("Deleting outdated entries failed"); + break; + } + } + } catch (InterruptedException e) { + logger.debug("Stopping msl cleanup thread"); + } + }); + cleanupThread.setDaemon(true); + cleanupThread.start(); + } + + public static void createSql(Connection connection) throws SQLException { + try (final var statement = connection.createStatement()) { + statement.executeUpdate(""" + CREATE TABLE message_send_log ( + _id INTEGER PRIMARY KEY, + content_id INTEGER NOT NULL REFERENCES message_send_log_content (_id) ON DELETE CASCADE, + recipient_id INTEGER NOT NULL, + device_id INTEGER NOT NULL + ); + CREATE TABLE message_send_log_content ( + _id INTEGER PRIMARY KEY, + group_id BLOB, + timestamp INTEGER NOT NULL, + content BLOB NOT NULL, + content_hint INTEGER NOT NULL + ); + CREATE INDEX mslc_timestamp_index ON message_send_log_content (timestamp); + CREATE INDEX msl_recipient_index ON message_send_log (recipient_id, device_id, content_id); + CREATE INDEX msl_content_index ON message_send_log (content_id); + """); + } + } + + public List findMessages( + final RecipientId recipientId, final int deviceId, final long timestamp, final boolean isSenderKey + ) { + try (final var connection = database.getConnection()) { + deleteOutdatedEntries(connection); + + try (final var statement = connection.prepareStatement( + "SELECT group_id, content, content_hint FROM %s l INNER JOIN %s lc ON l.content_id = lc._id WHERE l.recipient_id = ? AND l.device_id = ? AND lc.timestamp = ?".formatted( + TABLE_MESSAGE_SEND_LOG, + TABLE_MESSAGE_SEND_LOG_CONTENT))) { + statement.setLong(1, recipientId.id()); + statement.setInt(2, deviceId); + statement.setLong(3, timestamp); + try (var result = executeQueryForStream(statement, resultSet -> { + final var groupId = Optional.ofNullable(resultSet.getBytes("group_id")) + .map(GroupId::unknownVersion); + final SignalServiceProtos.Content content; + try { + content = SignalServiceProtos.Content.parseFrom(resultSet.getBinaryStream("content")); + } catch (IOException e) { + logger.warn("Failed to parse content from message send log", e); + return null; + } + final var contentHint = ContentHint.fromType(resultSet.getInt("content_hint")); + return new MessageSendLogEntry(groupId, content, contentHint); + })) { + return result.filter(Objects::nonNull) + .filter(e -> !isSenderKey || e.groupId().isPresent()) + .toList(); + } + } + } catch (SQLException e) { + logger.warn("Failed read from message send log", e); + return List.of(); + } + } + + public long insertIfPossible( + long sentTimestamp, SendMessageResult sendMessageResult, ContentHint contentHint + ) { + final RecipientDevices recipientDevice = getRecipientDevices(sendMessageResult); + if (recipientDevice == null) { + return -1; + } + + return insert(List.of(recipientDevice), + sentTimestamp, + sendMessageResult.getSuccess().getContent().get(), + contentHint); + } + + public long insertIfPossible( + long sentTimestamp, List sendMessageResults, ContentHint contentHint + ) { + final var recipientDevices = sendMessageResults.stream() + .map(this::getRecipientDevices) + .filter(Objects::nonNull) + .toList(); + if (recipientDevices.isEmpty()) { + return -1; + } + + final var content = sendMessageResults.stream() + .filter(r -> r.isSuccess() && r.getSuccess().getContent().isPresent()) + .map(r -> r.getSuccess().getContent().get()) + .findFirst() + .get(); + + return insert(recipientDevices, sentTimestamp, content, contentHint); + } + + public void addRecipientToExistingEntryIfPossible(final long contentId, final SendMessageResult sendMessageResult) { + final RecipientDevices recipientDevice = getRecipientDevices(sendMessageResult); + if (recipientDevice == null) { + return; + } + + insertRecipientsForExistingContent(contentId, List.of(recipientDevice)); + } + + public void addRecipientToExistingEntryIfPossible( + final long contentId, final List sendMessageResults + ) { + final var recipientDevices = sendMessageResults.stream() + .map(this::getRecipientDevices) + .filter(Objects::nonNull) + .toList(); + if (recipientDevices.isEmpty()) { + return; + } + + insertRecipientsForExistingContent(contentId, recipientDevices); + } + + public void deleteEntryForGroup(long sentTimestamp, GroupId groupId) { + try (final var connection = database.getConnection()) { + try (final var statement = connection.prepareStatement( + "DELETE FROM %s AS lc WHERE lc.timestamp = ? AND lc.group_id = ?".formatted( + TABLE_MESSAGE_SEND_LOG_CONTENT))) { + statement.setLong(1, sentTimestamp); + statement.setBytes(2, groupId.serialize()); + statement.executeUpdate(); + } + } catch (SQLException e) { + logger.warn("Failed delete from message send log", e); + } + } + + public void deleteEntryForRecipientNonGroup(long sentTimestamp, RecipientId recipientId) { + try (final var connection = database.getConnection()) { + connection.setAutoCommit(false); + try (final var statement = connection.prepareStatement( + "DELETE FROM %s AS lc WHERE lc.timestamp = ? AND lc.group_id IS NULL AND lc._id IN (SELECT content_id FROM %s l WHERE l.recipient_id = ?)".formatted( + TABLE_MESSAGE_SEND_LOG_CONTENT, + TABLE_MESSAGE_SEND_LOG))) { + statement.setLong(1, sentTimestamp); + statement.setLong(2, recipientId.id()); + statement.executeUpdate(); + } + + deleteOrphanedLogContents(connection); + connection.commit(); + } catch (SQLException e) { + logger.warn("Failed delete from message send log", e); + } + } + + public void deleteEntryForRecipient(long sentTimestamp, RecipientId recipientId, int deviceId) { + deleteEntriesForRecipient(List.of(sentTimestamp), recipientId, deviceId); + } + + public void deleteEntriesForRecipient(List sentTimestamps, RecipientId recipientId, int deviceId) { + try (final var connection = database.getConnection()) { + connection.setAutoCommit(false); + try (final var statement = connection.prepareStatement( + "DELETE FROM %s AS l WHERE l.content_id IN (SELECT _id FROM %s lc WHERE lc.timestamp = ?) AND l.recipient_id = ? AND l.device_id = ?".formatted( + TABLE_MESSAGE_SEND_LOG, + TABLE_MESSAGE_SEND_LOG_CONTENT))) { + for (final var sentTimestamp : sentTimestamps) { + statement.setLong(1, sentTimestamp); + statement.setLong(2, recipientId.id()); + statement.setInt(3, deviceId); + statement.executeUpdate(); + } + } + + deleteOrphanedLogContents(connection); + connection.commit(); + } catch (SQLException e) { + logger.warn("Failed delete from message send log", e); + } + } + + @Override + public void close() { + cleanupThread.interrupt(); + try { + cleanupThread.join(); + } catch (InterruptedException ignored) { + } + } + + private RecipientDevices getRecipientDevices(final SendMessageResult sendMessageResult) { + if (sendMessageResult.isSuccess() && sendMessageResult.getSuccess().getContent().isPresent()) { + final var recipientId = recipientResolver.resolveRecipient(sendMessageResult.getAddress()); + return new RecipientDevices(recipientId, sendMessageResult.getSuccess().getDevices()); + } else { + return null; + } + } + + private long insert( + final List recipientDevices, + final long sentTimestamp, + final SignalServiceProtos.Content content, + final ContentHint contentHint + ) { + byte[] groupId = getGroupId(content); + + try (final var connection = database.getConnection()) { + connection.setAutoCommit(false); + final long contentId; + try (final var statement = connection.prepareStatement( + "INSERT INTO %s (timestamp, group_id, content, content_hint) VALUES (?,?,?,?)".formatted( + TABLE_MESSAGE_SEND_LOG_CONTENT))) { + statement.setLong(1, sentTimestamp); + statement.setBytes(2, groupId); + statement.setBytes(3, content.toByteArray()); + statement.setInt(4, contentHint.getType()); + statement.executeUpdate(); + final var generatedKeys = statement.getGeneratedKeys(); + if (generatedKeys.next()) { + contentId = generatedKeys.getLong(1); + } else { + contentId = -1; + } + } + if (contentId == -1) { + logger.warn("Failed to insert message send log content"); + return -1; + } + insertRecipientsForExistingContent(contentId, recipientDevices, connection); + + connection.commit(); + return contentId; + } catch (SQLException e) { + logger.warn("Failed to insert into message send log", e); + return -1; + } + } + + private byte[] getGroupId(final SignalServiceProtos.Content content) { + try { + return !content.hasDataMessage() + ? null + : content.getDataMessage().hasGroup() + ? content.getDataMessage().getGroup().getId().toByteArray() + : content.getDataMessage().hasGroupV2() + ? GroupUtils.getGroupIdV2(new GroupMasterKey(content.getDataMessage() + .getGroupV2() + .getMasterKey() + .toByteArray())).serialize() + : null; + } catch (InvalidInputException e) { + logger.warn("Failed to parse groupId id from content"); + return null; + } + } + + private void insertRecipientsForExistingContent( + final long contentId, final List recipientDevices + ) { + try (final var connection = database.getConnection()) { + connection.setAutoCommit(false); + insertRecipientsForExistingContent(contentId, recipientDevices, connection); + connection.commit(); + } catch (SQLException e) { + logger.warn("Failed to append recipients to message send log", e); + } + } + + private void insertRecipientsForExistingContent( + final long contentId, final List recipientDevices, final Connection connection + ) throws SQLException { + try (final var statement = connection.prepareStatement( + "INSERT INTO %s (recipient_id, device_id, content_id) VALUES (?,?,?)".formatted(TABLE_MESSAGE_SEND_LOG))) { + for (final var recipientDevice : recipientDevices) { + for (final var deviceId : recipientDevice.deviceIds()) { + statement.setLong(1, recipientDevice.recipientId().id()); + statement.setInt(2, deviceId); + statement.setLong(3, contentId); + statement.executeUpdate(); + } + } + } + } + + private void deleteOutdatedEntries(final Connection connection) throws SQLException { + try (final var statement = connection.prepareStatement("DELETE FROM %s WHERE timestamp < ?".formatted( + TABLE_MESSAGE_SEND_LOG_CONTENT))) { + statement.setLong(1, System.currentTimeMillis() - LOG_DURATION.toMillis()); + final var rowCount = statement.executeUpdate(); + if (rowCount > 0) { + logger.debug("Removed {} outdated entries from the message send log", rowCount); + } + } + } + + private void deleteOrphanedLogContents(final Connection connection) throws SQLException { + try (final var statement = connection.prepareStatement( + "DELETE FROM %s WHERE _id NOT IN (SELECT content_id FROM %s)".formatted(TABLE_MESSAGE_SEND_LOG_CONTENT, + TABLE_MESSAGE_SEND_LOG))) { + statement.executeUpdate(); + } + } + + private Stream executeQueryForStream( + PreparedStatement statement, ResultSetMapper mapper + ) throws SQLException { + final var resultSet = statement.executeQuery(); + + return StreamSupport.stream(new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE, Spliterator.ORDERED) { + @Override + public boolean tryAdvance(final Consumer consumer) { + try { + if (!resultSet.next()) { + return false; + } + consumer.accept(mapper.apply(resultSet)); + return true; + } catch (SQLException e) { + logger.warn("Failed to read from database result", e); + throw new RuntimeException(e); + } + } + }, false); + } + + private interface ResultSetMapper { + + T apply(ResultSet resultSet) throws SQLException; + } + + private record RecipientDevices(RecipientId recipientId, List deviceIds) {} +} diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java index 9cc6ba42..a5947ef2 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java @@ -164,6 +164,21 @@ public class SenderKeySharedStore { } } + public void deleteSharedWith( + final RecipientId recipientId, final int deviceId, final DistributionId distributionId + ) { + synchronized (sharedSenderKeys) { + final var entries = sharedSenderKeys.getOrDefault(distributionId.asUuid(), Set.of()); + + sharedSenderKeys.put(distributionId.asUuid(), new HashSet<>(entries) { + { + remove(new SenderKeySharedEntry(recipientId, deviceId)); + } + }); + saveLocked(); + } + } + public void deleteAllFor(final DistributionId distributionId) { synchronized (sharedSenderKeys) { if (sharedSenderKeys.remove(distributionId.asUuid()) != null) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java index 8674945c..5318b3f2 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java @@ -71,6 +71,10 @@ public class SenderKeyStore implements SignalServiceSenderKeyStore { senderKeySharedStore.deleteAllFor(recipientId); } + public void deleteSharedWith(RecipientId recipientId, int deviceId, DistributionId distributionId) { + senderKeySharedStore.deleteSharedWith(recipientId, deviceId, distributionId); + } + public void deleteOurKey(RecipientId selfRecipientId, DistributionId distributionId) { senderKeySharedStore.deleteAllFor(distributionId); senderKeyRecordStore.deleteSenderKey(selfRecipientId, distributionId.asUuid()); -- 2.51.0 From 2e74acaabe9e24dda980c19ac174be10157f4578 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 29 Jan 2022 12:35:19 +0100 Subject: [PATCH 13/16] Add --log-file parameter to write logs to separate file Use logback for more control over the log output Fixes #845 --- build.gradle.kts | 3 +- graalvm-config-dir/reflect-config.json | 53 ++++++++ graalvm-config-dir/resource-config.json | 3 + man/signal-cli.1.adoc | 4 + src/main/java/org/asamk/signal/App.java | 3 + .../org/asamk/signal/LogConfigurator.java | 114 ++++++++++++++++++ src/main/java/org/asamk/signal/Main.java | 39 +++--- .../ch.qos.logback.classic.spi.Configurator | 1 + 8 files changed, 199 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/asamk/signal/LogConfigurator.java create mode 100644 src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator diff --git a/build.gradle.kts b/build.gradle.kts index 19ed08d7..fa87a606 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,7 +37,8 @@ dependencies { implementation("com.fasterxml.jackson.core", "jackson-databind", "2.13.1") implementation("net.sourceforge.argparse4j", "argparse4j", "0.9.0") implementation("com.github.hypfvieh", "dbus-java-transport-native-unixsocket", "4.0.0") - implementation("org.slf4j", "slf4j-simple", "1.7.32") + implementation("org.slf4j", "slf4j-api", "1.7.32") + implementation("ch.qos.logback", "logback-classic", "1.2.10") implementation("org.slf4j", "jul-to-slf4j", "1.7.32") implementation(project(":lib")) } diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index c5e61cee..46c106eb 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -38,6 +38,30 @@ "allDeclaredMethods":true, "allPublicMethods":true }, +{ + "name":"ch.qos.logback.classic.pattern.DateConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LevelConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LineSeparatorConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LoggerConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.MessageConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.ThreadConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"char[]" }, @@ -176,6 +200,13 @@ "allDeclaredMethods":true, "allPublicMethods":true }, +{ + "name":"java.io.File", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.io.FilePermission" +}, { "name":"java.io.Serializable", "allDeclaredMethods":true @@ -228,6 +259,9 @@ "allDeclaredFields":true, "queryAllDeclaredMethods":true }, +{ + "name":"java.lang.RuntimePermission" +}, { "name":"java.lang.String", "allPublicMethods":true @@ -248,6 +282,16 @@ {"name":"getType","parameterTypes":[] } ] }, +{ + "name":"java.net.NetPermission" +}, +{ + "name":"java.net.SocketPermission" +}, +{ + "name":"java.net.URLPermission", + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }] +}, { "name":"java.nio.Buffer", "allDeclaredMethods":true, @@ -258,12 +302,18 @@ "allDeclaredMethods":true, "allPublicMethods":true }, +{ + "name":"java.security.AllPermission" +}, { "name":"java.security.KeyStoreSpi" }, { "name":"java.security.SecureRandomParameters" }, +{ + "name":"java.security.SecurityPermission" +}, { "name":"java.security.cert.PKIXRevocationChecker" }, @@ -322,6 +372,9 @@ "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true }, +{ + "name":"java.util.PropertyPermission" +}, { "name":"java.util.RandomAccess", "allDeclaredMethods":true diff --git a/graalvm-config-dir/resource-config.json b/graalvm-config-dir/resource-config.json index a52689ae..c0866365 100644 --- a/graalvm-config-dir/resource-config.json +++ b/graalvm-config-dir/resource-config.json @@ -4,6 +4,9 @@ { "pattern":"\\QMETA-INF/maven/org.xerial/sqlite-jdbc/pom.properties\\E" }, + { + "pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E" + }, { "pattern":"\\QMETA-INF/services/java.sql.Driver\\E" }, diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index b71f549c..e7296207 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -37,6 +37,10 @@ Print the version and quit. *--verbose*:: Raise log level and include lib signal logs. +*--log-file* LOG_FILE:: +Write log output to the given file. +If `--verbose` is also given, the detailed logs will only be written to the log file. + *--config* CONFIG:: Set the path, where to store the config. Make sure you have full read/write access to the given directory. diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index 6c4726d5..d06cd798 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -69,6 +69,9 @@ public class App { parser.addArgument("--verbose") .help("Raise log level and include lib signal logs. Specify multiple times for even more logs.") .action(Arguments.count()); + parser.addArgument("--log-file") + .type(File.class) + .help("Write log output to the given file. If --verbose is also given, the detailed logs will only be written to the log file."); parser.addArgument("-c", "--config") .help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli)."); diff --git a/src/main/java/org/asamk/signal/LogConfigurator.java b/src/main/java/org/asamk/signal/LogConfigurator.java new file mode 100644 index 00000000..9271d6b7 --- /dev/null +++ b/src/main/java/org/asamk/signal/LogConfigurator.java @@ -0,0 +1,114 @@ +package org.asamk.signal; + +import java.io.File; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.PatternLayout; +import ch.qos.logback.classic.spi.Configurator; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.ConsoleAppender; +import ch.qos.logback.core.FileAppender; +import ch.qos.logback.core.Layout; +import ch.qos.logback.core.encoder.LayoutWrappingEncoder; +import ch.qos.logback.core.filter.Filter; +import ch.qos.logback.core.spi.ContextAwareBase; +import ch.qos.logback.core.spi.FilterReply; + +public class LogConfigurator extends ContextAwareBase implements Configurator { + + private static int verboseLevel = 0; + private static File logFile = null; + + public static void setVerboseLevel(int verboseLevel) { + LogConfigurator.verboseLevel = verboseLevel; + } + + public static void setLogFile(File logFile) { + LogConfigurator.logFile = logFile; + } + + public void configure(LoggerContext lc) { + final var rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME); + + final var defaultLevel = verboseLevel > 1 ? Level.ALL : verboseLevel > 0 ? Level.DEBUG : Level.INFO; + rootLogger.setLevel(defaultLevel); + + final var consoleLayout = verboseLevel == 0 || logFile != null + ? createSimpleLoggingLayout(lc) + : createDetailedLoggingLayout(lc); + final var consoleAppender = createLoggingConsoleAppender(lc, createLayoutWrappingEncoder(consoleLayout)); + rootLogger.addAppender(consoleAppender); + + lc.getLogger("com.zaxxer.hikari") + .setLevel(verboseLevel > 1 ? Level.ALL : verboseLevel > 0 ? Level.INFO : Level.WARN); + + if (logFile != null) { + consoleAppender.addFilter(new Filter<>() { + @Override + public FilterReply decide(final ILoggingEvent event) { + return event.getLevel().isGreaterOrEqual(Level.INFO) + && !"LibSignal".equals(event.getLoggerName()) + && ( + event.getLevel().isGreaterOrEqual(Level.WARN) || !event.getLoggerName() + .startsWith("com.zaxxer.hikari") + ) + + ? FilterReply.NEUTRAL : FilterReply.DENY; + } + }); + + final var fileLayout = createDetailedLoggingLayout(lc); + final var fileAppender = createLoggingFileAppender(lc, createLayoutWrappingEncoder(fileLayout)); + rootLogger.addAppender(fileAppender); + } + } + + private ConsoleAppender createLoggingConsoleAppender( + final LoggerContext lc, final LayoutWrappingEncoder layoutEncoder + ) { + return new ConsoleAppender<>() {{ + setContext(lc); + setName("console"); + setTarget("System.err"); + setEncoder(layoutEncoder); + start(); + }}; + } + + private FileAppender createLoggingFileAppender( + final LoggerContext lc, final LayoutWrappingEncoder layoutEncoder + ) { + return new FileAppender<>() {{ + setContext(lc); + setName("file"); + setFile(logFile.getAbsolutePath()); + setEncoder(layoutEncoder); + start(); + }}; + } + + private LayoutWrappingEncoder createLayoutWrappingEncoder(final Layout l) { + return new LayoutWrappingEncoder<>() {{ + setContext(l.getContext()); + setLayout(l); + }}; + } + + private PatternLayout createSimpleLoggingLayout(final LoggerContext lc) { + return new PatternLayout() {{ + setPattern("%-5level %logger{0} - %msg%n"); + setContext(lc); + start(); + }}; + } + + private PatternLayout createDetailedLoggingLayout(final LoggerContext lc) { + return new PatternLayout() {{ + setPattern("%d{yyyy-MM-dd'T'HH:mm:ss.SSSXX} [%thread] %-5level %logger{36} - %msg%n"); + setContext(lc); + start(); + }}; + } +} diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index 104a9105..f0b78590 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -17,6 +17,7 @@ package org.asamk.signal; import net.sourceforge.argparse4j.ArgumentParsers; +import net.sourceforge.argparse4j.DefaultSettings; import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.ArgumentParserException; import net.sourceforge.argparse4j.inf.Namespace; @@ -31,6 +32,7 @@ import org.asamk.signal.util.SecurityProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.bridge.SLF4JBridgeHandler; +import java.io.File; import java.security.Security; public class Main { @@ -41,8 +43,11 @@ public class Main { installSecurityProviderWorkaround(); // Configuring the logger needs to happen before any logger is initialized - final var verboseLevel = getVerboseLevel(args); - configureLogging(verboseLevel); + + final var nsLog = parseArgs(args); + final var verboseLevel = nsLog == null ? 0 : nsLog.getInt("verbose"); + final var logFile = nsLog == null ? null : nsLog.get("log-file"); + configureLogging(verboseLevel, logFile); var parser = App.buildArgumentParser(); @@ -70,35 +75,29 @@ public class Main { Security.addProvider(new BouncyCastleProvider()); } - private static int getVerboseLevel(String[] args) { - var parser = ArgumentParsers.newFor("signal-cli").build().defaultHelp(false); + private static Namespace parseArgs(String[] args) { + var parser = ArgumentParsers.newFor("signal-cli", DefaultSettings.VERSION_0_9_0_DEFAULT_SETTINGS) + .includeArgumentNamesAsKeysInResult(true) + .build() + .defaultHelp(false); parser.addArgument("--verbose").action(Arguments.count()); + parser.addArgument("--log-file").type(File.class); - Namespace ns; try { - ns = parser.parseKnownArgs(args, null); + return parser.parseKnownArgs(args, null); } catch (ArgumentParserException e) { - return 0; + return null; } - - return ns.getInt("verbose"); } - private static void configureLogging(final int verboseLevel) { - final var defaultLogLevel = verboseLevel > 1 ? "trace" : verboseLevel > 0 ? "debug" : "info"; - System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", defaultLogLevel); + private static void configureLogging(final int verboseLevel, final File logFile) { + LogConfigurator.setVerboseLevel(verboseLevel); + LogConfigurator.setLogFile(logFile); + if (verboseLevel > 0) { - System.setProperty("org.slf4j.simpleLogger.showThreadName", "true"); - System.setProperty("org.slf4j.simpleLogger.showShortLogName", "false"); - System.setProperty("org.slf4j.simpleLogger.showDateTime", "true"); - System.setProperty("org.slf4j.simpleLogger.dateTimeFormat", "yyyy-MM-dd'T'HH:mm:ss.SSSXX"); java.util.logging.Logger.getLogger("") .setLevel(verboseLevel > 2 ? java.util.logging.Level.FINEST : java.util.logging.Level.INFO); Manager.initLogger(); - } else { - System.setProperty("org.slf4j.simpleLogger.showThreadName", "false"); - System.setProperty("org.slf4j.simpleLogger.showShortLogName", "true"); - System.setProperty("org.slf4j.simpleLogger.showDateTime", "false"); } SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); diff --git a/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator b/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator new file mode 100644 index 00000000..354cf5bd --- /dev/null +++ b/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator @@ -0,0 +1 @@ +org.asamk.signal.LogConfigurator -- 2.51.0 From 380c892e24fe2d60cd8963855511c15ca9858e7b Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 29 Jan 2022 15:01:34 +0100 Subject: [PATCH 14/16] Add more informative thread names --- .../org/asamk/signal/manager/ManagerImpl.java | 4 ++++ .../storage/sendLog/MessageSendLogStore.java | 1 + .../org/asamk/signal/commands/DaemonCommand.java | 16 ++++++++++++---- .../asamk/signal/dbus/DbusSignalControlImpl.java | 6 ++++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java index 7c8cff5d..fd536d10 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -81,6 +81,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.stream.Collectors; @@ -749,6 +750,8 @@ public class ManagerImpl implements Manager { } } + private static final AtomicInteger threadNumber = new AtomicInteger(0); + private void startReceiveThreadIfRequired() { if (receiveThread != null) { return; @@ -784,6 +787,7 @@ public class ManagerImpl implements Manager { } } }); + receiveThread.setName("receive-" + threadNumber.getAndIncrement()); receiveThread.start(); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java index 795919f6..98f4ac69 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java @@ -62,6 +62,7 @@ public class MessageSendLogStore implements AutoCloseable { logger.debug("Stopping msl cleanup thread"); } }); + cleanupThread.setName("msl-cleanup"); cleanupThread.setDaemon(true); cleanupThread.start(); } diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java index 020c91c1..34a80226 100644 --- a/src/main/java/org/asamk/signal/commands/DaemonCommand.java +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -36,6 +36,7 @@ import java.nio.channels.SocketChannel; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; public class DaemonCommand implements MultiLocalCommand, LocalCommand { @@ -234,8 +235,10 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand { }); } + private static final AtomicInteger threadNumber = new AtomicInteger(0); + private void runSocket(final ServerSocketChannel serverChannel, Consumer socketHandler) { - new Thread(() -> { + final var thread = new Thread(() -> { while (true) { final SocketChannel channel; final String clientString; @@ -250,16 +253,20 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand { } break; } - new Thread(() -> { + final var connectionThread = new Thread(() -> { try (final var c = channel) { socketHandler.accept(c); logger.info("Connection closed: " + clientString); } catch (IOException e) { logger.warn("Failed to close channel", e); } - }).start(); + }); + connectionThread.setName("daemon-connection-" + threadNumber.getAndIncrement()); + connectionThread.start(); } - }).start(); + }); + thread.setName("daemon-listener"); + thread.start(); } private SignalJsonRpcDispatcherHandler getSignalJsonRpcDispatcherHandler( @@ -367,6 +374,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand { final var signal = new DbusSignalImpl(m, conn, objectPath, noReceiveOnStart); conn.exportObject(signal); final var initThread = new Thread(signal::initObjects); + initThread.setName("dbus-init"); initThread.start(); logger.debug("Exported dbus object: " + objectPath); diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java index a11d8fa0..2d211444 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java @@ -96,14 +96,16 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { public String link(final String newDeviceName) throws Error.Failure { try { final URI deviceLinkUri = c.getNewProvisioningDeviceLinkUri(); - new Thread(() -> { + final var thread = new Thread(() -> { final ProvisioningManager provisioningManager = c.getProvisioningManagerFor(deviceLinkUri); try { provisioningManager.finishDeviceLink(newDeviceName); } catch (IOException | TimeoutException | UserAlreadyExists e) { e.printStackTrace(); } - }).start(); + }); + thread.setName("dbus-link"); + thread.start(); return deviceLinkUri.toString(); } catch (TimeoutException | IOException e) { throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage()); -- 2.51.0 From e284b990768799354bc16a6ca9387f17a9841968 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 29 Jan 2022 15:02:30 +0100 Subject: [PATCH 15/16] Refactor JsonMessageEnvelope to remove unnecessary number canonicalization --- .../signal/json/JsonMessageEnvelope.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java index 0c1d0738..ab5d73e1 100644 --- a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java +++ b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java @@ -4,9 +4,9 @@ import com.fasterxml.jackson.annotation.JsonInclude; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.UntrustedIdentityException; -import org.asamk.signal.manager.api.InvalidNumberException; import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.manager.api.RecipientIdentifier; +import org.asamk.signal.manager.storage.recipients.RecipientAddress; import java.util.UUID; @@ -27,35 +27,35 @@ public record JsonMessageEnvelope( public static JsonMessageEnvelope from( MessageEnvelope envelope, Throwable exception, Manager m ) { + final RecipientAddress sourceAddress; + final Integer sourceDevice; + if (envelope.sourceAddress().isPresent()) { + sourceAddress = envelope.sourceAddress().get(); + sourceDevice = envelope.sourceDevice(); + } else if (exception instanceof UntrustedIdentityException e) { + sourceAddress = e.getSender(); + sourceDevice = e.getSenderDevice(); + } else { + sourceAddress = null; + sourceDevice = null; + } + final String source; final String sourceNumber; final String sourceUuid; - final Integer sourceDevice; - if (envelope.sourceAddress().isPresent()) { - final var sourceAddress = envelope.sourceAddress().get(); + final String sourceName; + if (sourceAddress != null) { source = sourceAddress.getLegacyIdentifier(); sourceNumber = sourceAddress.number().orElse(null); sourceUuid = sourceAddress.uuid().map(UUID::toString).orElse(null); - sourceDevice = envelope.sourceDevice(); - } else if (exception instanceof UntrustedIdentityException e) { - final var sender = e.getSender(); - source = sender.getLegacyIdentifier(); - sourceNumber = sender.number().orElse(null); - sourceUuid = sender.uuid().map(UUID::toString).orElse(null); - sourceDevice = e.getSenderDevice(); + sourceName = m.getContactOrProfileName(RecipientIdentifier.Single.fromAddress(envelope.sourceAddress() + .get())); } else { source = null; sourceNumber = null; sourceUuid = null; - sourceDevice = null; - } - String name; - try { - name = m.getContactOrProfileName(RecipientIdentifier.Single.fromString(source, m.getSelfNumber())); - } catch (InvalidNumberException | NullPointerException e) { - name = null; + sourceName = null; } - final var sourceName = name; final var timestamp = envelope.timestamp(); final var receiptMessage = envelope.receipt().map(JsonReceiptMessage::from).orElse(null); final var typingMessage = envelope.typing().map(JsonTypingMessage::from).orElse(null); -- 2.51.0 From e5a8cdb056e247eed0486d7e856b8826cb4998a3 Mon Sep 17 00:00:00 2001 From: exquo <62397152+exquo@users.noreply.github.com> Date: Tue, 1 Feb 2022 19:57:32 +0000 Subject: [PATCH 16/16] Repackage signal-client native builds (#879) * Repackage signal-client native builds * Change repo to upstream * Use `listAccounts` to test run signal-cli * Use "macOS" in filename --- .github/workflows/ci.yml | 2 +- .github/workflows/repackage-native-libs.yml | 172 ++++++++++++++++++++ README.md | 2 +- 3 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/repackage-native-libs.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff73584b..17f23ddf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: signal-cli CI -on: [ push, pull_request ] +on: [ push, pull_request, workflow_call ] jobs: build: diff --git a/.github/workflows/repackage-native-libs.yml b/.github/workflows/repackage-native-libs.yml new file mode 100644 index 00000000..9f731bd7 --- /dev/null +++ b/.github/workflows/repackage-native-libs.yml @@ -0,0 +1,172 @@ +name: repackage-native-libs + +on: + push: + tags: + - v* + + +jobs: + + ci_wf: + uses: AsamK/signal-cli/.github/workflows/ci.yml@master + # ${{ github.repository }} not accpeted here + + + lib_to_jar: + needs: ci_wf + runs-on: ubuntu-latest + + outputs: + signal_cli_version: ${{ steps.cli_ver.outputs.signal_cli_version }} + release_id: ${{ steps.create_release.outputs.id }} + + steps: + + - name: Download signal-cli build from CI workflow + uses: actions/download-artifact@v2 + + - name: Get signal-cli version + id: cli_ver + run: | + #echo ${GITHUB_REF#refs/tag/} + tree . + mv ./*/*.tar.gz . + ver=$(ls ./*.tar.gz | xargs basename | sed -E 's/signal-cli-(.*).tar.gz/\1/') + echo $ver + echo "::set-output name=signal_cli_version::${ver}" + tar -xzf ./*.tar.gz + + - name: Get signal-client jar version + id: lib_ver + run: | + JAR_PREFIX=signal-client-java- + jar_file=$(find ./signal-cli-*/lib/ -name "$JAR_PREFIX*.jar") + jar_version=$(echo "$jar_file" | xargs basename | sed "s/$JAR_PREFIX//; s/.jar//") + echo "$jar_version" + echo "::set-output name=signal_client_version::$jar_version" + + - name: Download signal-client builds + env: + RELEASES_URL: https://github.com/signalapp/libsignal-client/releases/download/ + FILE_NAMES: signal_jni.dll libsignal_jni.dylib + SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }} + run: | + for file_name in $FILE_NAMES; do + curl -sOL "${RELEASES_URL}/v${SIGNAL_CLIENT_VER}/${file_name}" # note: added v + done + tree . + + - name: Replace Windows lib + env: + SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.signal_cli_version }} + SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }} + run: | + mv signal_jni.dll libsignal_jni.so + zip -u ./signal-cli-${SIGNAL_CLI_VER}/lib/signal-client-java-${SIGNAL_CLIENT_VER}.jar ./libsignal_jni.so + tar -czf signal-cli-${SIGNAL_CLI_VER}-Windows.tar.gz signal-cli-${SIGNAL_CLI_VER}/ + + - name: Replace macOS lib + env: + SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.signal_cli_version }} + SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }} + run: | + jar_file=./signal-cli-${SIGNAL_CLI_VER}/lib/signal-client-java-${SIGNAL_CLIENT_VER}.jar + zip -d "$jar_file" libsignal_jni.so + zip "$jar_file" libsignal_jni.dylib + tar -czf signal-cli-${SIGNAL_CLI_VER}-macOS.tar.gz signal-cli-${SIGNAL_CLI_VER}/ + + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ steps.cli_ver.outputs.signal_cli_version }} # note: added `v` + release_name: v${{ steps.cli_ver.outputs.signal_cli_version }} # note: added `v` + draft: true + + - name: Upload Linux archive + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}.tar.gz + asset_name: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-Linux.tar.gz + asset_content_type: application/x-compressed-tar # .tar.gz + + - name: Upload windows archive + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-Windows.tar.gz + asset_name: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-Windows.tar.gz + asset_content_type: application/x-compressed-tar # .tar.gz + + - name: Upload macos archive + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-macOS.tar.gz + asset_name: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-macOS.tar.gz + asset_content_type: application/x-compressed-tar # .tar.gz + + + run_repackaged: + + needs: + - lib_to_jar + + strategy: + matrix: + runner: + - windows-latest + - macos-latest + + runs-on: ${{ matrix.runner }} + + defaults: + run: + shell: bash # Explicit for windows + + env: + JAVA_VERSION: 17 + + steps: + + - name: Download the release file + env: + SIGNAL_CLI_VER: ${{ needs.lib_to_jar.outputs.signal_cli_version }} + RELEASE_ID: ${{ needs.lib_to_jar.outputs.release_id }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + file_name=signal-cli-${SIGNAL_CLI_VER}-${RUNNER_OS}.tar.gz + echo "$file_name" + assets_json=$(curl -s \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets") + asset_dl_url=$(echo "$assets_json" | jq -r ".[] | select (.name == \"$file_name\") | .url") + echo "$asset_dl_url" + curl -sLOJ \ + -H 'Accept: application/octet-stream' \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + "$asset_dl_url" + tar -xzf "$file_name" + + - name: Set up JDK for running signal-cli executable + uses: actions/setup-java@v1 + with: + java-version: ${{ env.JAVA_VERSION }} + + - name: Run signal-cli + run: | + cd signal-cli-*/bin + if [[ "$RUNNER_OS" == 'Windows' ]]; then + EXECUTABLE_SUFFIX=".bat" + fi + ./signal-cli${EXECUTABLE_SUFFIX} listAccounts diff --git a/README.md b/README.md index dcfbad66..277b843e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ System requirements: - at least Java Runtime Environment (JRE) 17 - native library: libsignal-client - The native lib is bundled for x86_64 Linux (with recent enough glibc, see #643), for other systems/architectures + The native libs are bundled for x86_64 Linux (with recent enough glibc, see #643), Windows and MacOS. For other systems/architectures see: [Provide native lib for libsignal](https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal) ### Install system-wide on Linux -- 2.51.0