Update libsignal-service

This commit is contained in:
AsamK 2026-07-29 21:52:16 +02:00
parent 91138452bb
commit 151b42c6e9
12 changed files with 483 additions and 29 deletions

View File

@ -3,7 +3,7 @@ slf4j = "2.0.18"
junit = "6.1.0"
micronaut-json-schema = "2.0.1"
micronaut-core = "5.0.0"
signal-service = "2.15.3_unofficial_149"
signal-service = "2.15.3_unofficial_150"
[libraries]
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"

View File

@ -3,15 +3,15 @@ package org.asamk.signal.manager.config;
import org.signal.libsignal.net.Network.Environment;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.whispersystems.signalservice.api.push.TrustStore;
import org.whispersystems.signalservice.internal.configuration.HttpProxy;
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl;
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl;
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl;
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl;
import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url;
import org.signal.network.config.HttpProxy;
import org.signal.network.config.SignalCdnUrl;
import org.signal.network.config.SignalCdsiUrl;
import org.signal.network.config.SignalProxy;
import org.signal.network.config.SignalServiceConfiguration;
import org.signal.network.config.SignalServiceUrl;
import org.signal.network.config.SignalStorageUrl;
import org.signal.network.config.SignalSvr2Url;
import org.signal.network.config.TrustStore;
import java.util.Base64;
import java.util.List;

View File

@ -3,7 +3,7 @@ package org.asamk.signal.manager.config;
import org.asamk.signal.manager.api.ServiceEnvironment;
import org.signal.libsignal.net.Network;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import org.signal.network.config.SignalServiceConfiguration;
import java.util.List;

View File

@ -3,15 +3,15 @@ package org.asamk.signal.manager.config;
import org.signal.libsignal.net.Network;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.whispersystems.signalservice.api.push.TrustStore;
import org.whispersystems.signalservice.internal.configuration.HttpProxy;
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl;
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl;
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl;
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl;
import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url;
import org.signal.network.config.HttpProxy;
import org.signal.network.config.SignalCdnUrl;
import org.signal.network.config.SignalCdsiUrl;
import org.signal.network.config.SignalProxy;
import org.signal.network.config.SignalServiceConfiguration;
import org.signal.network.config.SignalServiceUrl;
import org.signal.network.config.SignalStorageUrl;
import org.signal.network.config.SignalSvr2Url;
import org.signal.network.config.TrustStore;
import java.util.Base64;
import java.util.List;

View File

@ -1,6 +1,6 @@
package org.asamk.signal.manager.config;
import org.whispersystems.signalservice.api.push.TrustStore;
import org.signal.network.config.TrustStore;
import java.io.InputStream;

View File

@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
@ -58,6 +59,7 @@ import okio.ByteString;
import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_MAXIMUM_ID;
import static org.asamk.signal.manager.util.Utils.handleResponseException;
import static org.asamk.signal.manager.util.Utils.handleResponseExceptionSuspend;
import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
public class AccountHelper {
@ -508,8 +510,9 @@ public class AccountHelper {
}
}
@SuppressWarnings("unchecked")
public void deleteUsername() throws IOException {
handleResponseException(dependencies.getAccountApi().deleteUsername());
handleResponseException(dependencies.getAccountApi().deleteUsernameHash());
account.setUsernameLink(null);
account.setUsername(null);
logger.debug("[deleteUsername] Successfully deleted the username.");
@ -529,7 +532,8 @@ public class AccountHelper {
}
public void refreshDeviceName() throws IOException {
final var devices = handleResponseException(dependencies.getLinkDeviceApi().getDevices());
final List<DeviceInfo> devices = handleResponseExceptionSuspend(cont -> dependencies.getLinkDeviceApi()
.getDevices(cont));
final var deviceId = account.getDeviceId();
final var device = devices.stream().filter(d -> d.id == deviceId).findFirst();
if (device.isPresent()) {
@ -569,8 +573,9 @@ public class AccountHelper {
}
public void removeLinkedDevices(int deviceId) throws IOException {
handleResponseException(dependencies.getLinkDeviceApi().removeDevice(deviceId));
var devices = handleResponseException(dependencies.getLinkDeviceApi().getDevices());
handleResponseExceptionSuspend(cont -> dependencies.getLinkDeviceApi().removeDevice(deviceId, cont));
final List<DeviceInfo> devices = handleResponseExceptionSuspend(cont -> dependencies.getLinkDeviceApi()
.getDevices(cont));
account.setMultiDevice(devices.size() > 1);
}
@ -605,7 +610,7 @@ public class AccountHelper {
// When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
// If this is the primary device, other users can't send messages to this number anymore.
// If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
handleResponseException(dependencies.getAccountApi().clearFcmToken());
handleResponseExceptionSuspend(cont -> dependencies.getAccountApi().clearFcmToken(cont));
account.setRegistered(false);
unregisteredListener.call();

View File

@ -160,6 +160,7 @@ import okio.Utf8;
import static org.asamk.signal.manager.config.ServiceConfig.MAX_MESSAGE_SIZE_BYTES;
import static org.asamk.signal.manager.util.Utils.handleResponseException;
import static org.asamk.signal.manager.util.Utils.handleResponseExceptionSuspend;
import static org.signal.core.util.StringExtensionsKt.splitByByteLength;
public class ManagerImpl implements Manager {
@ -483,7 +484,8 @@ public class ManagerImpl implements Manager {
@Override
public List<Device> getLinkedDevices() throws IOException {
var devices = handleResponseException(dependencies.getLinkDeviceApi().getDevices());
final List<DeviceInfo> devices = handleResponseExceptionSuspend(cont -> dependencies.getLinkDeviceApi()
.getDevices(cont));
account.setMultiDevice(devices.size() > 1);
var identityKey = account.getAciIdentityKeyPair().getPrivateKey();
return devices.stream().map(d -> {

View File

@ -355,6 +355,7 @@ public class SignalDependencies {
() -> preKeyRepository = new PreKeyRepository(getKeysApi(),
dataStore.aci(),
localProtocolAddress,
getSessionLock(),
Runnable::run));
}

View File

@ -9,6 +9,7 @@ import org.whispersystems.signalservice.api.websocket.HealthMonitor;
import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
import java.time.Duration;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@ -103,6 +104,18 @@ final class SignalWebSocketHealthMonitor implements HealthMonitor {
logger.info("Received alerts: {}", String.join(", ", strings));
}
@Override
public void onServerTimestamp(final long serverTimestamp, final boolean isIdentifiedWebsocket) {
final var skew = skewFrom(serverTimestamp);
if (skew.compareTo(Duration.ofDays(1)) > 0) {
logger.warn("Local clock is off from the server by {}, which exceeds the allowed limit..", skew);
}
}
private Duration skewFrom(long serverTime) {
return Duration.ofMillis(Math.abs(System.currentTimeMillis() - serverTime));
}
/**
* Sends periodic heartbeats/keep-alives over the WebSocket to prevent connection timeouts. If
* the WebSocket fails to get a return heartbeat after [KEEP_ALIVE_TIMEOUT] seconds, it is forced to be recreated.

View File

@ -168,6 +168,11 @@ public final class StorageSyncValidations {
throw new DuplicateCallLinkError();
}
ids = manifest.getStorageIdsByType().get(ManifestRecord.Identifier.Type.STICKER_PACK.getValue());
if (ids.size() != new HashSet<>(ids).size()) {
throw new DuplicateStickerPackError();
}
throw new DuplicateRawIdAcrossTypesError();
}
@ -217,6 +222,8 @@ public final class StorageSyncValidations {
private static final class DuplicateInsertInWriteError extends Error {}
private static final class DuplicateStickerPackError extends Error {}
private static final class InsertNotPresentInFullIdSetError extends Error {}
private static final class DeletePresentInFullIdSetError extends Error {}

View File

@ -17,7 +17,6 @@ import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.Proxy;
import java.net.ProxySelector;
@ -32,6 +31,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
@ -39,6 +40,11 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import kotlin.ResultKt;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;
import kotlin.coroutines.EmptyCoroutineContext;
import kotlin.coroutines.intrinsics.IntrinsicsKt;
import okio.ByteString;
public class Utils {
@ -161,6 +167,67 @@ public class Utils {
return NetworkResultUtil.toBasicLegacy(response);
}
/**
* Bridge Kotlin suspend functions for Java callers.
*/
@SuppressWarnings("unchecked")
public static <T> T runSuspendBlocking(final Function<Continuation<? super T>, Object> call) {
final var value = new AtomicReference<T>();
final var throwable = new AtomicReference<Throwable>();
final var done = new CountDownLatch(1);
final var immediate = call.apply(new Continuation<>() {
@Override
public CoroutineContext getContext() {
return EmptyCoroutineContext.INSTANCE;
}
@Override
public void resumeWith(final Object result) {
try {
ResultKt.throwOnFailure(result);
value.set((T) result);
} catch (Throwable t) {
throwable.set(t);
} finally {
done.countDown();
}
}
});
if (immediate != IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
ResultKt.throwOnFailure(immediate);
return (T) immediate;
}
try {
done.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted while waiting for suspend function", e);
}
final var t = throwable.get();
if (t instanceof RuntimeException e) {
throw e;
}
if (t instanceof Error e) {
throw e;
}
if (t != null) {
throw new RuntimeException(t);
}
return value.get();
}
@SuppressWarnings("unchecked")
public static <T, E extends BadRequestError> T handleResponseExceptionSuspend(
final Function<Continuation<? super RequestResult>, Object> call
) throws IOException {
return handleResponseException((RequestResult<T, E>) runSuspendBlocking((Function) call));
}
@SuppressWarnings("unchecked")
public static <T, E extends BadRequestError> T handleResponseException(final RequestResult<T, E> result) throws IOException {
if (result instanceof RequestResult.Success<?> success) {

View File

@ -268,6 +268,78 @@
}
]
},
{
"type": "com.sun.imageio.plugins.jpeg.JPEGImageReader",
"jniAccessible": true,
"methods": [
{
"name": "acceptPixels",
"parameterTypes": [
"int",
"boolean"
]
},
{
"name": "passComplete",
"parameterTypes": []
},
{
"name": "passStarted",
"parameterTypes": [
"int"
]
},
{
"name": "pushBack",
"parameterTypes": [
"int"
]
},
{
"name": "readInputData",
"parameterTypes": [
"byte[]",
"int",
"int"
]
},
{
"name": "setImageData",
"parameterTypes": [
"int",
"int",
"int",
"int",
"int",
"byte[]"
]
},
{
"name": "skipInputBytes",
"parameterTypes": [
"long"
]
},
{
"name": "skipPastImage",
"parameterTypes": [
"int"
]
},
{
"name": "warningOccurred",
"parameterTypes": [
"int"
]
},
{
"name": "warningWithMessage",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{
"type": "com.sun.security.auth.module.UnixSystem",
"jniAccessible": true,
@ -737,6 +809,183 @@
"allDeclaredMethods": true,
"allPublicMethods": true
},
{
"type": "java.awt.GraphicsEnvironment",
"jniAccessible": true,
"methods": [
{
"name": "isHeadless",
"parameterTypes": []
}
]
},
{
"type": "java.awt.image.BufferedImage",
"jniAccessible": true,
"fields": [
{
"name": "colorModel"
},
{
"name": "imageType"
},
{
"name": "raster"
}
],
"methods": [
{
"name": "getRGB",
"parameterTypes": [
"int",
"int",
"int",
"int",
"int[]",
"int",
"int"
]
},
{
"name": "setRGB",
"parameterTypes": [
"int",
"int",
"int",
"int",
"int[]",
"int",
"int"
]
}
]
},
{
"type": "java.awt.image.ColorModel",
"jniAccessible": true,
"fields": [
{
"name": "colorSpace"
},
{
"name": "colorSpaceType"
},
{
"name": "isAlphaPremultiplied"
},
{
"name": "is_sRGB"
},
{
"name": "nBits"
},
{
"name": "numComponents"
},
{
"name": "supportsAlpha"
},
{
"name": "transparency"
}
],
"methods": [
{
"name": "getRGBdefault",
"parameterTypes": []
}
]
},
{
"type": "java.awt.image.Raster",
"jniAccessible": true,
"fields": [
{
"name": "dataBuffer"
},
{
"name": "height"
},
{
"name": "minX"
},
{
"name": "minY"
},
{
"name": "numBands"
},
{
"name": "numDataElements"
},
{
"name": "sampleModel"
},
{
"name": "sampleModelTranslateX"
},
{
"name": "sampleModelTranslateY"
},
{
"name": "width"
}
]
},
{
"type": "java.awt.image.SampleModel",
"jniAccessible": true,
"fields": [
{
"name": "height"
},
{
"name": "width"
}
],
"methods": [
{
"name": "getPixels",
"parameterTypes": [
"int",
"int",
"int",
"int",
"int[]",
"java.awt.image.DataBuffer"
]
},
{
"name": "setPixels",
"parameterTypes": [
"int",
"int",
"int",
"int",
"int[]",
"java.awt.image.DataBuffer"
]
}
]
},
{
"type": "java.awt.image.SinglePixelPackedSampleModel",
"jniAccessible": true,
"fields": [
{
"name": "bitMasks"
},
{
"name": "bitOffsets"
},
{
"name": "bitSizes"
},
{
"name": "maxBitSize"
}
]
},
{
"type": "java.io.File",
"methods": [
@ -984,6 +1233,18 @@
{
"type": "java.lang.String[]"
},
{
"type": "java.lang.System",
"jniAccessible": true,
"methods": [
{
"name": "load",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{
"type": "java.lang.Thread",
"jniAccessible": true,
@ -1455,6 +1716,33 @@
}
]
},
{
"type": "javax.imageio.plugins.jpeg.JPEGHuffmanTable",
"jniAccessible": true,
"fields": [
{
"name": "lengths"
},
{
"name": "values"
}
]
},
{
"type": "javax.imageio.plugins.jpeg.JPEGQTable",
"jniAccessible": true,
"fields": [
{
"name": "qTable"
}
]
},
{
"type": "javax.imageio.spi.ImageReaderSpi"
},
{
"type": "javax.imageio.spi.ImageWriterSpi"
},
{
"type": "javax.security.auth.x500.X500Principal",
"methods": [
@ -5887,6 +6175,12 @@
{
"name": "receivedQueueEmpty",
"parameterTypes": []
},
{
"name": "receivedServerTimestamp",
"parameterTypes": [
"long"
]
}
]
},
@ -9567,6 +9861,41 @@
}
]
},
{
"type": "sun.awt.image.ByteComponentRaster",
"jniAccessible": true,
"fields": [
{
"name": "data"
},
{
"name": "dataOffsets"
},
{
"name": "pixelStride"
},
{
"name": "scanlineStride"
},
{
"name": "type"
}
]
},
{
"type": "sun.java2d.Disposer",
"jniAccessible": true,
"methods": [
{
"name": "addRecord",
"parameterTypes": [
"java.lang.Object",
"long",
"long"
]
}
]
},
{
"type": "sun.launcher.LauncherHelper",
"jniAccessible": true,
@ -10344,6 +10673,21 @@
{
"glob": "META-INF/services/java.util.spi.ResourceBundleControlProvider"
},
{
"glob": "META-INF/services/javax.imageio.spi.ImageInputStreamSpi"
},
{
"glob": "META-INF/services/javax.imageio.spi.ImageOutputStreamSpi"
},
{
"glob": "META-INF/services/javax.imageio.spi.ImageReaderSpi"
},
{
"glob": "META-INF/services/javax.imageio.spi.ImageTranscoderSpi"
},
{
"glob": "META-INF/services/javax.imageio.spi.ImageWriterSpi"
},
{
"glob": "META-INF/services/kotlin.reflect.jvm.internal.impl.km.internal.extensions.MetadataExtensions"
},
@ -10458,8 +10802,23 @@
"module": "java.base",
"glob": "sun/text/resources/LineBreakIteratorData"
},
{
"module": "java.desktop",
"glob": "sun/awt/resources/awt_fr.properties"
},
{
"module": "java.desktop",
"glob": "sun/awt/resources/awt_fr_FR.properties"
},
{
"module": "java.desktop",
"glob": "sun/java2d/cmm/profiles/sRGB.pf"
},
{
"bundle": "net.sourceforge.argparse4j.internal.ArgumentParserImpl"
},
{
"bundle": "sun.awt.resources.awt"
}
]
}
}