mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-05-18 13:14:14 +00:00
Compare commits
19 Commits
67105e14c1
...
2cbff3a4c1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cbff3a4c1 | ||
|
|
540cfbd977 | ||
|
|
97f77b1f69 | ||
|
|
4601e60118 | ||
|
|
fcf82b9318 | ||
|
|
9c8137fafa | ||
|
|
0a1531dcce | ||
|
|
c10f618a3e | ||
|
|
4a3d9d90a6 | ||
|
|
b4275414e1 | ||
|
|
5f94b7b6d1 | ||
|
|
dc43e44020 | ||
|
|
251bd2d87a | ||
|
|
a3fcda7598 | ||
|
|
c9e2504349 | ||
|
|
9b09df5f17 | ||
|
|
5fe94ff44a | ||
|
|
6286a054eb | ||
|
|
e6635d1bb0 |
@ -1,5 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.14.3] - 2026-04-22
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -10,7 +10,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "org.asamk"
|
||||
version = "0.14.3"
|
||||
version = "0.14.4-SNAPSHOT"
|
||||
}
|
||||
|
||||
java {
|
||||
|
||||
@ -7,11 +7,11 @@ plugins {
|
||||
}
|
||||
|
||||
tasks.named<KotlinCompilationTask<KotlinJvmCompilerOptions>>("compileKotlin").configure {
|
||||
compilerOptions.jvmTarget.set(JvmTarget.JVM_24)
|
||||
compilerOptions.jvmTarget.set(JvmTarget.JVM_25)
|
||||
}
|
||||
|
||||
java {
|
||||
targetCompatibility = JavaVersion.VERSION_24
|
||||
targetCompatibility = JavaVersion.VERSION_25
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
4
client/Cargo.lock
generated
4
client/Cargo.lock
generated
@ -912,9 +912,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.12"
|
||||
version = "0.103.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06"
|
||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
[versions]
|
||||
slf4j = "2.0.17"
|
||||
slf4j = "2.0.18"
|
||||
junit = "6.0.3"
|
||||
micronaut-json-schema = "2.0.0-M8"
|
||||
micronaut-core = "4.9.3"
|
||||
micronaut-json-schema = "2.0.0"
|
||||
micronaut-core = "5.0.0"
|
||||
signal-service = "2.15.3_unofficial_146"
|
||||
|
||||
[libraries]
|
||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"
|
||||
@ -18,8 +19,8 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" }
|
||||
logback = "ch.qos.logback:logback-classic:1.5.32"
|
||||
|
||||
signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_144"
|
||||
sqlite = "org.xerial:sqlite-jdbc:3.53.0.0"
|
||||
signalnetwork = { module = "com.github.turasa:signal-network", version.ref = "signal-service" }
|
||||
sqlite = "org.xerial:sqlite-jdbc:3.53.1.0"
|
||||
hikari = "com.zaxxer:HikariCP:7.0.2"
|
||||
junit-jupiter-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
|
||||
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,7 +1,9 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
31
gradlew.bat
vendored
31
gradlew.bat
vendored
@ -23,8 +23,8 @@
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||
setlocal EnableExtensions
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@ -51,7 +51,7 @@ echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
"%COMSPEC%" /c exit 1
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
@ -65,7 +65,7 @@ echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
"%COMSPEC%" /c exit 1
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
@ -73,21 +73,10 @@ goto fail
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||
@rem which allows us to clear the local environment before executing the java command
|
||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
:exitWithErrorLevel
|
||||
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||
|
||||
@ -18,9 +18,9 @@ val libsignalClientPath = project.findProperty("libsignal_client_path")?.toStrin
|
||||
|
||||
dependencies {
|
||||
if (libsignalClientPath == null) {
|
||||
implementation(libs.signalservice)
|
||||
implementation(libs.signalnetwork)
|
||||
} else {
|
||||
implementation(libs.signalservice) {
|
||||
implementation(libs.signalnetwork) {
|
||||
exclude(group = "org.signal", module = "libsignal-client")
|
||||
}
|
||||
implementation(files(libsignalClientPath))
|
||||
|
||||
@ -259,7 +259,7 @@ public interface Manager extends Closeable {
|
||||
RecipientIdentifier.Single recipient
|
||||
) throws IOException;
|
||||
|
||||
SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException;
|
||||
void sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException;
|
||||
|
||||
SendMessageResults sendMessageRequestResponse(
|
||||
MessageEnvelope.Sync.MessageRequestResponse.Type type,
|
||||
|
||||
@ -157,7 +157,7 @@ public record MessageEnvelope(
|
||||
dataMessage.getExpiresInSeconds(),
|
||||
dataMessage.isExpirationUpdate(),
|
||||
dataMessage.isViewOnce(),
|
||||
dataMessage.isEndSession(),
|
||||
false,
|
||||
dataMessage.isProfileKeyUpdate(),
|
||||
dataMessage.getProfileKey().isPresent(),
|
||||
dataMessage.getReaction().map(r -> Reaction.from(r, recipientResolver, addressResolver)),
|
||||
@ -1028,7 +1028,7 @@ public record MessageEnvelope(
|
||||
final AttachmentFileProvider fileProvider,
|
||||
Exception exception
|
||||
) {
|
||||
final var serviceId = envelope.getSourceServiceId().map(ServiceId::parseOrNull).orElse(null);
|
||||
final var serviceId = envelope.getSourceServiceId();
|
||||
final var source = !envelope.isUnidentifiedSender() && serviceId != null
|
||||
? recipientResolver.resolveRecipient(serviceId)
|
||||
: envelope.isUnidentifiedSender() && content != null
|
||||
|
||||
@ -558,16 +558,24 @@ public class GroupHelper {
|
||||
private void storeProfileKeysFromMembers(final DecryptedGroup group) {
|
||||
for (var member : group.members) {
|
||||
final var serviceId = ServiceId.parseOrThrow(member.aciBytes);
|
||||
final var recipientId = account.getRecipientResolver().resolveRecipient(serviceId);
|
||||
final var profileStore = account.getProfileStore();
|
||||
if (profileStore.getProfileKey(recipientId) != null) {
|
||||
// We already have a profile key, not updating it from a non-authoritative source
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
profileStore.storeProfileKey(recipientId, new ProfileKey(member.profileKey.toByteArray()));
|
||||
} catch (InvalidInputException ignored) {
|
||||
}
|
||||
storeProfileKeyIfMissing(serviceId, member.profileKey.toByteArray());
|
||||
}
|
||||
for (var member : group.requestingMembers) {
|
||||
final var serviceId = ServiceId.parseOrThrow(member.aciBytes);
|
||||
storeProfileKeyIfMissing(serviceId, member.profileKey.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
private void storeProfileKeyIfMissing(final ServiceId serviceId, final byte[] profileKeyBytes) {
|
||||
final var recipientId = account.getRecipientResolver().resolveRecipient(serviceId);
|
||||
final var profileStore = account.getProfileStore();
|
||||
if (profileStore.getProfileKey(recipientId) != null) {
|
||||
// We already have a profile key, not updating it from a non-authoritative source
|
||||
return;
|
||||
}
|
||||
try {
|
||||
profileStore.storeProfileKey(recipientId, new ProfileKey(profileKeyBytes));
|
||||
} catch (InvalidInputException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -109,8 +109,8 @@ public final class IncomingMessageHandler {
|
||||
SignalServiceContent content = null;
|
||||
if (!envelope.isReceipt()) {
|
||||
account.getIdentityKeyStore().setRetryingDecryption(true);
|
||||
final var destination = getDestination(envelope).serviceId();
|
||||
try {
|
||||
final var destination = getDestination(envelope).serviceId();
|
||||
final var cipherResult = dependencies.getCipher(destination == null
|
||||
|| destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI)
|
||||
.decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp());
|
||||
@ -140,15 +140,30 @@ public final class IncomingMessageHandler {
|
||||
final Manager.ReceiveMessageHandler handler
|
||||
) {
|
||||
final var actions = new ArrayList<HandleAction>();
|
||||
if (envelope.isPreKeySignalMessage()) {
|
||||
actions.add(RefreshPreKeysAction.create());
|
||||
}
|
||||
SignalServiceContent content = null;
|
||||
Exception exception = null;
|
||||
envelope.getSourceServiceId().map(ServiceId::parseOrNull)
|
||||
// Store uuid if we don't have it already
|
||||
// uuid in envelope is sent by server
|
||||
.ifPresent(serviceId -> account.getRecipientResolver().resolveRecipient(serviceId));
|
||||
if (envelope.getSourceServiceId() != null) {
|
||||
// Store uuid if we don't have it already
|
||||
// uuid in envelope is sent by server
|
||||
account.getRecipientResolver().resolveRecipient(envelope.getSourceServiceId());
|
||||
}
|
||||
if (!envelope.isReceipt()) {
|
||||
final var destination = getDestination(envelope).serviceId();
|
||||
try {
|
||||
final var destination = getDestination(envelope).serviceId();
|
||||
|
||||
if (destination == account.getPni() && envelope.getSourceServiceId() == null) {
|
||||
throw new InvalidMessageException(
|
||||
"Got a sealed sender message to our PNI? Invalid message, ignoring.");
|
||||
}
|
||||
|
||||
if (envelope.getSourceServiceId() instanceof ServiceId.PNI
|
||||
&& envelope.getProto().type != Envelope.Type.SERVER_DELIVERY_RECEIPT) {
|
||||
throw new InvalidMessageException("Got a message from a PNI that was not a SERVER_DELIVERY_RECEIPT.");
|
||||
}
|
||||
|
||||
final var cipherResult = dependencies.getCipher(destination == null
|
||||
|| destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI)
|
||||
.decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp());
|
||||
@ -173,7 +188,13 @@ public final class IncomingMessageHandler {
|
||||
logger.debug("Received invalid message from blocked contact, ignoring.");
|
||||
} else {
|
||||
var serviceId = ServiceId.parseOrNull(e.getSender());
|
||||
if (serviceId != null) {
|
||||
ServiceId destination;
|
||||
try {
|
||||
destination = getDestination(envelope).serviceId();
|
||||
} catch (InvalidMessageException ex) {
|
||||
destination = null;
|
||||
}
|
||||
if (serviceId != null && destination != null) {
|
||||
final var isSelf = sender.equals(account.getSelfRecipientId())
|
||||
&& e.getSenderDevice() == account.getDeviceId();
|
||||
logger.debug("Received invalid message, queuing renew session action.");
|
||||
@ -311,7 +332,12 @@ public final class IncomingMessageHandler {
|
||||
final var sender = senderDeviceAddress.recipientId();
|
||||
final var senderServiceId = senderDeviceAddress.serviceId();
|
||||
final var senderDeviceId = senderDeviceAddress.deviceId();
|
||||
final var destination = getDestination(envelope);
|
||||
final DeviceAddress destination;
|
||||
try {
|
||||
destination = getDestination(envelope);
|
||||
} catch (InvalidMessageException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
if (account.getPni().equals(destination.serviceId)) {
|
||||
account.getRecipientStore().markNeedsPniSignature(destination.recipientId, true);
|
||||
@ -874,11 +900,6 @@ public final class IncomingMessageHandler {
|
||||
|
||||
final var selfAddress = isSync ? source : destination;
|
||||
final var conversationPartnerAddress = isSync ? destination : source;
|
||||
if (conversationPartnerAddress != null && message.isEndSession()) {
|
||||
account.getAccountData(selfAddress.serviceId())
|
||||
.getSessionStore()
|
||||
.deleteAllSessions(conversationPartnerAddress.serviceId());
|
||||
}
|
||||
if (message.isExpirationUpdate() || message.getBody().isPresent()) {
|
||||
if (message.getGroupContext().isPresent()) {
|
||||
final var groupContext = message.getGroupContext().get();
|
||||
@ -1047,7 +1068,7 @@ public final class IncomingMessageHandler {
|
||||
}
|
||||
|
||||
private SignalServiceAddress getSenderAddress(SignalServiceEnvelope envelope, SignalServiceContent content) {
|
||||
final var serviceId = envelope.getSourceServiceId().map(ServiceId::parseOrNull).orElse(null);
|
||||
final var serviceId = envelope.getSourceServiceId();
|
||||
if (!envelope.isUnidentifiedSender() && serviceId != null) {
|
||||
return new SignalServiceAddress(serviceId);
|
||||
} else if (content != null) {
|
||||
@ -1058,7 +1079,7 @@ public final class IncomingMessageHandler {
|
||||
}
|
||||
|
||||
private DeviceAddress getSender(SignalServiceEnvelope envelope, SignalServiceContent content) {
|
||||
final var serviceId = envelope.getSourceServiceId().map(ServiceId::parseOrNull).orElse(null);
|
||||
final var serviceId = envelope.getSourceServiceId();
|
||||
if (!envelope.isUnidentifiedSender() && serviceId != null) {
|
||||
return new DeviceAddress(account.getRecipientResolver().resolveRecipient(serviceId),
|
||||
serviceId,
|
||||
@ -1070,10 +1091,13 @@ public final class IncomingMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceAddress getDestination(SignalServiceEnvelope envelope) {
|
||||
private DeviceAddress getDestination(SignalServiceEnvelope envelope) throws InvalidMessageException {
|
||||
final var destination = envelope.getDestinationServiceId();
|
||||
if (destination == null || destination.isUnknown()) {
|
||||
return new DeviceAddress(account.getSelfRecipientId(), account.getAci(), account.getDeviceId());
|
||||
throw new InvalidMessageException("Missing destination");
|
||||
}
|
||||
if (!account.getAci().equals(destination) && !account.getPni().equals(destination)) {
|
||||
throw new InvalidMessageException("Message not intended for this account");
|
||||
}
|
||||
return new DeviceAddress(account.getRecipientResolver().resolveRecipient(destination),
|
||||
destination,
|
||||
|
||||
@ -9,7 +9,6 @@ import org.asamk.signal.manager.jobs.CleanOldPreKeysJob;
|
||||
import org.asamk.signal.manager.storage.SignalAccount;
|
||||
import org.asamk.signal.manager.storage.messageCache.CachedMessage;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
|
||||
import org.signal.core.models.ServiceId;
|
||||
import org.signal.core.models.ServiceId.ACI;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -150,10 +149,10 @@ public class ReceiveHelper {
|
||||
for (final var it : batch) {
|
||||
SignalServiceEnvelope envelope1 = new SignalServiceEnvelope(it.getEnvelope(),
|
||||
it.getServerDeliveredTimestamp());
|
||||
final var recipientId = envelope1.getSourceServiceId()
|
||||
.map(ServiceId::parseOrNull)
|
||||
.map(s -> account.getRecipientResolver().resolveRecipient(s))
|
||||
.orElse(null);
|
||||
final var sourceServiceId = envelope1.getSourceServiceId();
|
||||
final var recipientId = sourceServiceId == null
|
||||
? null
|
||||
: account.getRecipientResolver().resolveRecipient(sourceServiceId);
|
||||
logger.trace("Storing new message from {}", recipientId);
|
||||
// store message on disk, before acknowledging receipt to the server
|
||||
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
|
||||
@ -238,7 +237,7 @@ public class ReceiveHelper {
|
||||
if (exception instanceof UntrustedIdentityException) {
|
||||
logger.debug("Keeping message with untrusted identity in message cache");
|
||||
final var address = ((UntrustedIdentityException) exception).getSender();
|
||||
if (envelope.getSourceServiceId().isEmpty() && address.aci().isPresent()) {
|
||||
if (envelope.getSourceServiceId() == null && address.aci().isPresent()) {
|
||||
final var recipientId = account.getRecipientResolver()
|
||||
.resolveRecipient(ACI.parseOrThrow(address.aci().get()));
|
||||
try {
|
||||
@ -292,7 +291,7 @@ public class ReceiveHelper {
|
||||
cachedMessage.delete();
|
||||
return null;
|
||||
}
|
||||
if (envelope.getSourceServiceId().isEmpty()) {
|
||||
if (envelope.getSourceServiceId() == null) {
|
||||
final var identifier = ((UntrustedIdentityException) exception).getSender();
|
||||
final var recipientId = account.getRecipientResolver()
|
||||
.resolveRecipient(new RecipientAddress(identifier));
|
||||
|
||||
@ -17,6 +17,9 @@ import org.asamk.signal.manager.util.KeyUtils;
|
||||
import org.signal.core.models.storageservice.StorageKey;
|
||||
import org.signal.core.util.SetUtil;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.network.service.StorageServiceService;
|
||||
import org.signal.network.service.StorageServiceService.ManifestIfDifferentVersionResult;
|
||||
import org.signal.network.service.StorageServiceService.WriteStorageRecordsResult;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||
@ -25,9 +28,6 @@ import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
|
||||
import org.whispersystems.signalservice.api.storage.StorageId;
|
||||
import org.whispersystems.signalservice.api.storage.StorageRecordConvertersKt;
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceRepository;
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceRepository.ManifestIfDifferentVersionResult;
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceRepository.WriteStorageRecordsResult;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.StorageRecord;
|
||||
|
||||
@ -504,7 +504,7 @@ public class StorageHelper {
|
||||
final var result = dependencies.getStorageServiceRepository()
|
||||
.readStorageRecords(storageKey, manifest.recordIkm, storageIds);
|
||||
return switch (result) {
|
||||
case StorageServiceRepository.StorageRecordResult.DecryptionError decryptionError -> {
|
||||
case StorageServiceService.StorageRecordResult.DecryptionError decryptionError -> {
|
||||
if (decryptionError.getException() instanceof InvalidKeyException) {
|
||||
logger.warn("Failed to read storage records, ignoring.");
|
||||
yield List.of();
|
||||
@ -514,11 +514,11 @@ public class StorageHelper {
|
||||
throw new IOException(decryptionError.getException());
|
||||
}
|
||||
}
|
||||
case StorageServiceRepository.StorageRecordResult.NetworkError networkError ->
|
||||
case StorageServiceService.StorageRecordResult.NetworkError networkError ->
|
||||
throw networkError.getException();
|
||||
case StorageServiceRepository.StorageRecordResult.StatusCodeError statusCodeError ->
|
||||
case StorageServiceService.StorageRecordResult.StatusCodeError statusCodeError ->
|
||||
throw statusCodeError.getException();
|
||||
case StorageServiceRepository.StorageRecordResult.Success success -> success.getRecords();
|
||||
case StorageServiceService.StorageRecordResult.Success success -> success.getRecords();
|
||||
default -> throw new IllegalStateException("Unexpected value: " + result);
|
||||
};
|
||||
}
|
||||
|
||||
@ -1091,30 +1091,26 @@ public class ManagerImpl implements Manager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException {
|
||||
var messageBuilder = SignalServiceDataMessage.newBuilder().asEndSessionMessage();
|
||||
|
||||
try {
|
||||
return sendMessage(messageBuilder,
|
||||
recipients.stream().map(RecipientIdentifier.class::cast).collect(Collectors.toSet()),
|
||||
false);
|
||||
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
|
||||
throw new AssertionError(e);
|
||||
} finally {
|
||||
for (var recipient : recipients) {
|
||||
final RecipientId recipientId;
|
||||
try {
|
||||
recipientId = context.getRecipientHelper().resolveRecipient(recipient);
|
||||
} catch (UnregisteredRecipientException e) {
|
||||
continue;
|
||||
}
|
||||
final var serviceId = context.getAccount()
|
||||
.getRecipientAddressResolver()
|
||||
.resolveRecipientAddress(recipientId)
|
||||
.serviceId();
|
||||
if (serviceId.isPresent()) {
|
||||
account.getAccountData(ServiceIdType.ACI).getSessionStore().deleteAllSessions(serviceId.get());
|
||||
}
|
||||
public void sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException {
|
||||
for (var recipient : recipients) {
|
||||
final RecipientId recipientId;
|
||||
try {
|
||||
recipientId = context.getRecipientHelper().resolveRecipient(recipient);
|
||||
} catch (UnregisteredRecipientException e) {
|
||||
continue;
|
||||
}
|
||||
final var recipientAddress = context.getAccount()
|
||||
.getRecipientAddressResolver()
|
||||
.resolveRecipientAddress(recipientId);
|
||||
final var aciSessionStore = account.getAccountData(ServiceIdType.ACI).getSessionStore();
|
||||
final var pniSessionStore = account.getAccountData(ServiceIdType.PNI).getSessionStore();
|
||||
if (recipientAddress.aci().isPresent()) {
|
||||
aciSessionStore.archiveSessions(recipientAddress.aci().get());
|
||||
pniSessionStore.archiveSessions(recipientAddress.aci().get());
|
||||
}
|
||||
if (recipientAddress.pni().isPresent()) {
|
||||
aciSessionStore.archiveSessions(recipientAddress.pni().get());
|
||||
pniSessionStore.archiveSessions(recipientAddress.pni().get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,13 @@ import org.asamk.signal.manager.util.Utils;
|
||||
import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
||||
import org.signal.libsignal.net.Network;
|
||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||
import org.signal.network.api.CallingApi;
|
||||
import org.signal.network.api.CdsApi;
|
||||
import org.signal.network.api.CertificateApi;
|
||||
import org.signal.network.api.LinkDeviceApi;
|
||||
import org.signal.network.api.RateLimitChallengeApi;
|
||||
import org.signal.network.api.UsernameApi;
|
||||
import org.signal.network.service.StorageServiceService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
@ -15,26 +22,19 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||
import org.whispersystems.signalservice.api.account.AccountApi;
|
||||
import org.whispersystems.signalservice.api.attachment.AttachmentApi;
|
||||
import org.whispersystems.signalservice.api.calling.CallingApi;
|
||||
import org.whispersystems.signalservice.api.cds.CdsApi;
|
||||
import org.whispersystems.signalservice.api.certificate.CertificateApi;
|
||||
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
|
||||
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||
import org.whispersystems.signalservice.api.keys.KeysApi;
|
||||
import org.whispersystems.signalservice.api.link.LinkDeviceApi;
|
||||
import org.whispersystems.signalservice.api.message.MessageApi;
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileApi;
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi;
|
||||
import org.whispersystems.signalservice.api.registration.RegistrationApi;
|
||||
import org.whispersystems.signalservice.api.services.ProfileService;
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi;
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceRepository;
|
||||
import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
|
||||
import org.whispersystems.signalservice.api.username.UsernameApi;
|
||||
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
||||
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
||||
import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
|
||||
@ -243,8 +243,8 @@ public class SignalDependencies {
|
||||
getPushServiceSocket()));
|
||||
}
|
||||
|
||||
public StorageServiceRepository getStorageServiceRepository() {
|
||||
return new StorageServiceRepository(getStorageServiceApi());
|
||||
public StorageServiceService getStorageServiceRepository() {
|
||||
return new StorageServiceService(getStorageServiceApi());
|
||||
}
|
||||
|
||||
public CertificateApi getCertificateApi() {
|
||||
@ -368,7 +368,8 @@ public class SignalDependencies {
|
||||
|
||||
public SignalServiceCipher getCipher(ServiceIdType serviceIdType) {
|
||||
final var certificateValidator = new CertificateValidator(serviceEnvironmentConfig.unidentifiedSenderTrustRoots());
|
||||
final var address = new SignalServiceAddress(credentialsProvider.getAci(), credentialsProvider.getE164());
|
||||
final var serviceId = serviceIdType == ServiceIdType.ACI ? credentialsProvider.getAci() : credentialsProvider.getPni();
|
||||
final var address = new SignalServiceAddress(serviceId, credentialsProvider.getE164());
|
||||
final var deviceId = credentialsProvider.getDeviceId();
|
||||
return new SignalServiceCipher(address,
|
||||
deviceId,
|
||||
|
||||
@ -6,7 +6,7 @@ ENV SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_CTYPE=en_US.UTF-8
|
||||
RUN SNAPSHOT="$(date -u -d "@$SOURCE_DATE_EPOCH" +%Y%m%dT%H%M%SZ)" \
|
||||
&& apt install -y make asciidoc-base --update --snapshot "$SNAPSHOT" --no-install-recommends --no-install-suggests
|
||||
&& sed -i 's/^deb /deb [snapshot=yes] /' /etc/apt/sources.list && apt update --snapshot "$SNAPSHOT" && apt install -y make asciidoc-base --snapshot "$SNAPSHOT" --no-install-recommends --no-install-suggests
|
||||
COPY --chmod=0700 reproducible-builds/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
WORKDIR /signal-cli
|
||||
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh", "build" ]
|
||||
|
||||
@ -144,8 +144,7 @@ public class SendCommand implements JsonRpcLocalCommand {
|
||||
}
|
||||
|
||||
try {
|
||||
final var results = m.sendEndSessionMessage(singleRecipients);
|
||||
outputResult(outputWriter, results);
|
||||
m.sendEndSessionMessage(singleRecipients);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
|
||||
|
||||
@ -543,9 +543,8 @@ public class DbusManagerImpl implements Manager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendEndSessionMessage(final Set<RecipientIdentifier.Single> recipients) throws IOException {
|
||||
public void sendEndSessionMessage(final Set<RecipientIdentifier.Single> recipients) throws IOException {
|
||||
signal.sendEndSessionMessage(recipients.stream().map(RecipientIdentifier.Single::getIdentifier).toList());
|
||||
return new SendMessageResults(0, Map.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -432,8 +432,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
||||
@Override
|
||||
public void sendEndSessionMessage(final List<String> recipients) {
|
||||
try {
|
||||
final var results = m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getSelfNumber()));
|
||||
checkSendMessageResults(results);
|
||||
m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getSelfNumber()));
|
||||
} catch (IOException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
}
|
||||
|
||||
@ -169,6 +169,12 @@ public class HttpServerHandler implements AutoCloseable {
|
||||
httpExchange.sendResponseHeaders(200, 0);
|
||||
final var sender = new ServerSentEventSender(httpExchange.getResponseBody());
|
||||
|
||||
// Flush HTTP response headers to the client immediately.
|
||||
// Without this, the JVM HttpServer buffers everything until a later write
|
||||
// in the keep-alive loop (15 s), causing clients with shorter timeouts
|
||||
// (e.g. 10 s) to abort before receiving the initial response.
|
||||
httpExchange.getResponseBody().flush();
|
||||
|
||||
final var shouldStop = new AtomicBoolean(false);
|
||||
final var handlers = subscribeReceiveHandlers(managers, sender, () -> {
|
||||
shouldStop.set(true);
|
||||
|
||||
@ -33,6 +33,7 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SignalJsonRpcDispatcherHandler {
|
||||
|
||||
@ -102,7 +103,9 @@ public class SignalJsonRpcDispatcherHandler {
|
||||
|
||||
private int subscribeCallEvents(final Collection<Manager> managers) {
|
||||
final var subscriptionId = nextSubscriptionId.getAndIncrement();
|
||||
final var listeners = managers.stream().map(m -> createCallEventHandler(m, subscriptionId)).toList();
|
||||
final var listeners = managers.stream()
|
||||
.map(m -> createCallEventHandler(m, subscriptionId))
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
callEventHandlers.put(subscriptionId, listeners);
|
||||
return subscriptionId;
|
||||
}
|
||||
@ -157,7 +160,7 @@ public class SignalJsonRpcDispatcherHandler {
|
||||
final var subscriptionId = nextSubscriptionId.getAndIncrement();
|
||||
final var handlers = managers.stream()
|
||||
.map(m -> createReceiveHandler(m, subscriptionId, internalSubscription))
|
||||
.toList();
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
receiveHandlers.put(subscriptionId, handlers);
|
||||
|
||||
return subscriptionId;
|
||||
|
||||
544
src/test/java/org/asamk/signal/http/SseInitialFlushTest.java
Normal file
544
src/test/java/org/asamk/signal/http/SseInitialFlushTest.java
Normal file
@ -0,0 +1,544 @@
|
||||
package org.asamk.signal.http;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.api.CallInfo;
|
||||
import org.asamk.signal.manager.api.CallOffer;
|
||||
import org.asamk.signal.manager.api.Configuration;
|
||||
import org.asamk.signal.manager.api.Device;
|
||||
import org.asamk.signal.manager.api.DeviceLinkUrl;
|
||||
import org.asamk.signal.manager.api.Group;
|
||||
import org.asamk.signal.manager.api.GroupId;
|
||||
import org.asamk.signal.manager.api.GroupInviteLinkUrl;
|
||||
import org.asamk.signal.manager.api.Identity;
|
||||
import org.asamk.signal.manager.api.IdentityVerificationCode;
|
||||
import org.asamk.signal.manager.api.Message;
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
import org.asamk.signal.manager.api.Pair;
|
||||
import org.asamk.signal.manager.api.ReceiveConfig;
|
||||
import org.asamk.signal.manager.api.Recipient;
|
||||
import org.asamk.signal.manager.api.RecipientIdentifier;
|
||||
import org.asamk.signal.manager.api.SendGroupMessageResults;
|
||||
import org.asamk.signal.manager.api.SendMessageResult;
|
||||
import org.asamk.signal.manager.api.SendMessageResults;
|
||||
import org.asamk.signal.manager.api.StickerPack;
|
||||
import org.asamk.signal.manager.api.StickerPackId;
|
||||
import org.asamk.signal.manager.api.StickerPackUrl;
|
||||
import org.asamk.signal.manager.api.TurnServer;
|
||||
import org.asamk.signal.manager.api.TypingAction;
|
||||
import org.asamk.signal.manager.api.UpdateGroup;
|
||||
import org.asamk.signal.manager.api.UpdateProfile;
|
||||
import org.asamk.signal.manager.api.UserStatus;
|
||||
import org.asamk.signal.manager.api.UsernameLinkUrl;
|
||||
import org.asamk.signal.manager.api.UsernameStatus;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.URL;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Regression test for the SSE initial-flush bug:
|
||||
* HttpServerHandler used to flush the initial SSE response only after a later
|
||||
* write in the 15-second keep-alive loop, meaning the HTTP response headers
|
||||
* were not flushed to the client until then.
|
||||
* Clients with a shorter connection timeout (e.g. 10 s) would time out before
|
||||
* receiving the initial response.
|
||||
*
|
||||
* This test verifies that the endpoint returns HTTP 200 within 2 seconds of
|
||||
* connecting to GET /api/v1/events.
|
||||
*/
|
||||
class SseInitialFlushTest {
|
||||
|
||||
private HttpServerHandler handler;
|
||||
private int port;
|
||||
|
||||
/** Finds a free local port. */
|
||||
private static int freePort() throws Exception {
|
||||
try (var ss = new ServerSocket(0)) {
|
||||
ss.setReuseAddress(true);
|
||||
return ss.getLocalPort();
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
port = freePort();
|
||||
handler = new HttpServerHandler(new InetSocketAddress("127.0.0.1", port), new MinimalStubManager());
|
||||
handler.init();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (handler != null) {
|
||||
handler.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The SSE endpoint MUST flush the initial HTTP response immediately upon
|
||||
* connection, before the 15-second keep-alive loop fires. A read timeout of
|
||||
* 2 000 ms is used — well below the 15-second wait interval but generous
|
||||
* enough to survive any CI scheduling jitter.
|
||||
*/
|
||||
@Test
|
||||
void sseEndpointReturnsHeadersWithinTwoSeconds() {
|
||||
assertDoesNotThrow(() -> {
|
||||
var url = new URL("http://127.0.0.1:" + port + "/api/v1/events");
|
||||
var conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestProperty("Accept", "text/event-stream");
|
||||
conn.setReadTimeout(2_000); // 2 s — fails before fix (15 s flush), passes after
|
||||
conn.setConnectTimeout(2_000);
|
||||
try {
|
||||
conn.connect();
|
||||
assertEquals(200, conn.getResponseCode());
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
}
|
||||
}, "SSE endpoint did not return the initial response within 2 seconds");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Minimal Manager stub — only receive-handler methods need real behaviour;
|
||||
// everything else is a no-op stub.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private static final class MinimalStubManager implements Manager {
|
||||
|
||||
@Override
|
||||
public String getSelfNumber() {
|
||||
return "+10000000000";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addReceiveHandler(ReceiveMessageHandler handler, boolean isWeakListener) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeReceiveHandler(ReceiveMessageHandler handler) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReceiving() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveMessages(Optional<Duration> timeout, Optional<Integer> maxMessages, ReceiveMessageHandler handler) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopReceiveMessages() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReceiveConfig(ReceiveConfig receiveConfig) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, UserStatus> getUserStatus(Set<String> numbers) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, UsernameStatus> getUsernameStatus(Set<String> usernames) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAccountAttributes(String deviceName, Boolean unidentifiedDeliveryIndicators, Boolean discoverableByNumber, Boolean numberSharing) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configuration getConfiguration() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateConfiguration(Configuration configuration) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProfile(UpdateProfile updateProfile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsernameLinkUrl getUsernameLink() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String username) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUsername() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startChangeNumber(String newNumber, boolean voiceVerification, String captcha) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishChangeNumber(String newNumber, String verificationCode, String pin) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAccount() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitRateLimitRecaptchaChallenge(String challenge, String captcha) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Device> getLinkedDevices() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLinkedDevice(int deviceId, String name) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeLinkedDevices(int deviceId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDeviceLink(DeviceLinkUrl deviceLinkUrl) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegistrationLockPin(Optional<String> pin) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> getGroups() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> getGroups(Collection<GroupId> groupIds) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendGroupMessageResults quitGroup(GroupId groupId, Set<RecipientIdentifier.Single> administrators) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteGroup(GroupId groupId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<GroupId, SendGroupMessageResults> createGroup(String name, Set<RecipientIdentifier.Single> members, String avatarFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendGroupMessageResults updateGroup(GroupId groupId, UpdateGroup updateGroup) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<GroupId, SendGroupMessageResults> joinGroup(GroupInviteLinkUrl inviteLinkUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendTypingMessage(TypingAction action, Set<RecipientIdentifier> recipients) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendReadReceipt(RecipientIdentifier.Single sender, List<Long> messageIds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendViewedReceipt(RecipientIdentifier.Single sender, List<Long> messageIds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendMessage(Message message, Set<RecipientIdentifier> recipients, boolean notifySelf) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendEditMessage(Message message, Set<RecipientIdentifier> recipients, long targetSentTimestamp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendRemoteDeleteMessage(long targetSentTimestamp, Set<RecipientIdentifier> recipients) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendMessageReaction(String emoji, boolean remove, RecipientIdentifier.Single targetAuthor, long targetSentTimestamp, Set<RecipientIdentifier> recipients, boolean notifySelf, boolean story) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendAdminDelete(RecipientIdentifier.Single targetAuthor, long targetSentTimestamp, Set<RecipientIdentifier.Group> recipients, boolean notifySelf, boolean story) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendPinMessage(int duration, RecipientIdentifier.Single targetAuthor, long targetSentTimestamp, Set<RecipientIdentifier> recipients, boolean notifySelf, boolean story) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendUnpinMessage(RecipientIdentifier.Single targetAuthor, long targetSentTimestamp, Set<RecipientIdentifier> recipients, boolean notifySelf, boolean story) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendPaymentNotificationMessage(byte[] receipt, String note, RecipientIdentifier.Single recipient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendMessageRequestResponse(MessageEnvelope.Sync.MessageRequestResponse.Type type, Set<RecipientIdentifier> recipients) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendPollCreateMessage(String question, boolean multipleChoice, List<String> options, Set<RecipientIdentifier> recipients, boolean notifySelf) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendPollVoteMessage(RecipientIdentifier.Single author, long timestamp, List<Integer> optionIds, int version, Set<RecipientIdentifier> recipients, boolean notifySelf) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendPollTerminateMessage(long timestamp, Set<RecipientIdentifier> recipients, boolean notifySelf) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideRecipient(RecipientIdentifier.Single recipient) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRecipient(RecipientIdentifier.Single recipient) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteContact(RecipientIdentifier.Single recipient) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContactName(RecipientIdentifier.Single recipient, String givenName, String familyName, String newGivenName, String newFamilyName, String nick) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContactsBlocked(Collection<RecipientIdentifier.Single> recipients, boolean blocked) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroupsBlocked(Collection<GroupId> groupIds, boolean blocked) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpirationTimer(RecipientIdentifier.Single recipient, int messageExpirationTimer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StickerPackUrl uploadStickerPack(File path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installStickerPack(StickerPackUrl url) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StickerPack> getStickerPacks() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestAllSyncData() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContactBlocked(RecipientIdentifier.Single recipient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendContacts() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Recipient> getRecipients(boolean onlyWithProfile, Optional<Boolean> blocked, Collection<RecipientIdentifier.Single> addresses, Optional<String> name) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContactOrProfileName(RecipientIdentifier.Single recipient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group getGroup(GroupId groupId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identity> getIdentities() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identity> getIdentities(RecipientIdentifier.Single recipient) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean trustIdentityVerified(RecipientIdentifier.Single recipient, IdentityVerificationCode verificationCode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAddressChangedListener(Runnable listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addClosedListener(Runnable listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUnidentifiedKeepAlive(String token) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUnidentifiedKeepAlive(String token) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream retrieveAttachment(String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream retrieveContactAvatar(RecipientIdentifier.Single recipient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream retrieveProfileAvatar(RecipientIdentifier.Single recipient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream retrieveGroupAvatar(GroupId groupId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream retrieveSticker(StickerPackId stickerPackId, int stickerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CallInfo startCall(RecipientIdentifier.Single recipient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CallInfo acceptCall(long callId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hangupCall(long callId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResult rejectCall(long callId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CallInfo> listActiveCalls() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCallOffer(RecipientIdentifier.Single recipient, CallOffer callOffer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCallAnswer(RecipientIdentifier.Single recipient, long callId, byte[] answer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendIceUpdate(RecipientIdentifier.Single recipient, long callId, List<byte[]> iceCandidates) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendHangup(RecipientIdentifier.Single recipient, long callId, MessageEnvelope.Call.Hangup.Type type) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendBusy(RecipientIdentifier.Single recipient, long callId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TurnServer> getTurnServerInfo() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCallEventListener(CallEventListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCallEventListener(CallEventListener listener) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -328,8 +328,7 @@ class SubscribeCallEventsTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> r) {
|
||||
return null;
|
||||
public void sendEndSessionMessage(Set<RecipientIdentifier.Single> r) {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user