mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-05-19 13:24:16 +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
|
# Changelog
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
## [0.14.3] - 2026-04-22
|
## [0.14.3] - 2026-04-22
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@ -10,7 +10,7 @@ plugins {
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "org.asamk"
|
group = "org.asamk"
|
||||||
version = "0.14.3"
|
version = "0.14.4-SNAPSHOT"
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
|||||||
@ -7,11 +7,11 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.named<KotlinCompilationTask<KotlinJvmCompilerOptions>>("compileKotlin").configure {
|
tasks.named<KotlinCompilationTask<KotlinJvmCompilerOptions>>("compileKotlin").configure {
|
||||||
compilerOptions.jvmTarget.set(JvmTarget.JVM_24)
|
compilerOptions.jvmTarget.set(JvmTarget.JVM_25)
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
targetCompatibility = JavaVersion.VERSION_24
|
targetCompatibility = JavaVersion.VERSION_25
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
4
client/Cargo.lock
generated
4
client/Cargo.lock
generated
@ -912,9 +912,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.12"
|
version = "0.103.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06"
|
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
[versions]
|
[versions]
|
||||||
slf4j = "2.0.17"
|
slf4j = "2.0.18"
|
||||||
junit = "6.0.3"
|
junit = "6.0.3"
|
||||||
micronaut-json-schema = "2.0.0-M8"
|
micronaut-json-schema = "2.0.0"
|
||||||
micronaut-core = "4.9.3"
|
micronaut-core = "5.0.0"
|
||||||
|
signal-service = "2.15.3_unofficial_146"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"
|
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" }
|
slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" }
|
||||||
logback = "ch.qos.logback:logback-classic:1.5.32"
|
logback = "ch.qos.logback:logback-classic:1.5.32"
|
||||||
|
|
||||||
signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_144"
|
signalnetwork = { module = "com.github.turasa:signal-network", version.ref = "signal-service" }
|
||||||
sqlite = "org.xerial:sqlite-jdbc:3.53.0.0"
|
sqlite = "org.xerial:sqlite-jdbc:3.53.1.0"
|
||||||
hikari = "com.zaxxer:HikariCP:7.0.2"
|
hikari = "com.zaxxer:HikariCP:7.0.2"
|
||||||
junit-jupiter-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
|
junit-jupiter-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
|
||||||
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", 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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
|
retries=0
|
||||||
|
retryBackOffMs=500
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
31
gradlew.bat
vendored
31
gradlew.bat
vendored
@ -23,8 +23,8 @@
|
|||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
setlocal EnableExtensions
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
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 Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
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 Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
@ -73,21 +73,10 @@ goto fail
|
|||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@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
|
:exitWithErrorLevel
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
|
|
||||||
: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
|
|
||||||
|
|||||||
@ -18,9 +18,9 @@ val libsignalClientPath = project.findProperty("libsignal_client_path")?.toStrin
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
if (libsignalClientPath == null) {
|
if (libsignalClientPath == null) {
|
||||||
implementation(libs.signalservice)
|
implementation(libs.signalnetwork)
|
||||||
} else {
|
} else {
|
||||||
implementation(libs.signalservice) {
|
implementation(libs.signalnetwork) {
|
||||||
exclude(group = "org.signal", module = "libsignal-client")
|
exclude(group = "org.signal", module = "libsignal-client")
|
||||||
}
|
}
|
||||||
implementation(files(libsignalClientPath))
|
implementation(files(libsignalClientPath))
|
||||||
|
|||||||
@ -259,7 +259,7 @@ public interface Manager extends Closeable {
|
|||||||
RecipientIdentifier.Single recipient
|
RecipientIdentifier.Single recipient
|
||||||
) throws IOException;
|
) throws IOException;
|
||||||
|
|
||||||
SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException;
|
void sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException;
|
||||||
|
|
||||||
SendMessageResults sendMessageRequestResponse(
|
SendMessageResults sendMessageRequestResponse(
|
||||||
MessageEnvelope.Sync.MessageRequestResponse.Type type,
|
MessageEnvelope.Sync.MessageRequestResponse.Type type,
|
||||||
@ -407,6 +407,10 @@ public interface Manager extends Closeable {
|
|||||||
|
|
||||||
void addClosedListener(Runnable listener);
|
void addClosedListener(Runnable listener);
|
||||||
|
|
||||||
|
void addUnidentifiedKeepAlive(String token);
|
||||||
|
|
||||||
|
void removeUnidentifiedKeepAlive(String token);
|
||||||
|
|
||||||
InputStream retrieveAttachment(final String id) throws IOException;
|
InputStream retrieveAttachment(final String id) throws IOException;
|
||||||
|
|
||||||
InputStream retrieveContactAvatar(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException;
|
InputStream retrieveContactAvatar(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException;
|
||||||
|
|||||||
@ -157,7 +157,7 @@ public record MessageEnvelope(
|
|||||||
dataMessage.getExpiresInSeconds(),
|
dataMessage.getExpiresInSeconds(),
|
||||||
dataMessage.isExpirationUpdate(),
|
dataMessage.isExpirationUpdate(),
|
||||||
dataMessage.isViewOnce(),
|
dataMessage.isViewOnce(),
|
||||||
dataMessage.isEndSession(),
|
false,
|
||||||
dataMessage.isProfileKeyUpdate(),
|
dataMessage.isProfileKeyUpdate(),
|
||||||
dataMessage.getProfileKey().isPresent(),
|
dataMessage.getProfileKey().isPresent(),
|
||||||
dataMessage.getReaction().map(r -> Reaction.from(r, recipientResolver, addressResolver)),
|
dataMessage.getReaction().map(r -> Reaction.from(r, recipientResolver, addressResolver)),
|
||||||
@ -1028,7 +1028,7 @@ public record MessageEnvelope(
|
|||||||
final AttachmentFileProvider fileProvider,
|
final AttachmentFileProvider fileProvider,
|
||||||
Exception exception
|
Exception exception
|
||||||
) {
|
) {
|
||||||
final var serviceId = envelope.getSourceServiceId().map(ServiceId::parseOrNull).orElse(null);
|
final var serviceId = envelope.getSourceServiceId();
|
||||||
final var source = !envelope.isUnidentifiedSender() && serviceId != null
|
final var source = !envelope.isUnidentifiedSender() && serviceId != null
|
||||||
? recipientResolver.resolveRecipient(serviceId)
|
? recipientResolver.resolveRecipient(serviceId)
|
||||||
: envelope.isUnidentifiedSender() && content != null
|
: envelope.isUnidentifiedSender() && content != null
|
||||||
|
|||||||
@ -558,18 +558,26 @@ public class GroupHelper {
|
|||||||
private void storeProfileKeysFromMembers(final DecryptedGroup group) {
|
private void storeProfileKeysFromMembers(final DecryptedGroup group) {
|
||||||
for (var member : group.members) {
|
for (var member : group.members) {
|
||||||
final var serviceId = ServiceId.parseOrThrow(member.aciBytes);
|
final var serviceId = ServiceId.parseOrThrow(member.aciBytes);
|
||||||
|
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 recipientId = account.getRecipientResolver().resolveRecipient(serviceId);
|
||||||
final var profileStore = account.getProfileStore();
|
final var profileStore = account.getProfileStore();
|
||||||
if (profileStore.getProfileKey(recipientId) != null) {
|
if (profileStore.getProfileKey(recipientId) != null) {
|
||||||
// We already have a profile key, not updating it from a non-authoritative source
|
// We already have a profile key, not updating it from a non-authoritative source
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
profileStore.storeProfileKey(recipientId, new ProfileKey(member.profileKey.toByteArray()));
|
profileStore.storeProfileKey(recipientId, new ProfileKey(profileKeyBytes));
|
||||||
} catch (InvalidInputException ignored) {
|
} catch (InvalidInputException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void storeProfileKeyFromChange(final DecryptedGroupChange decryptedGroupChange) {
|
private void storeProfileKeyFromChange(final DecryptedGroupChange decryptedGroupChange) {
|
||||||
final var profileKeyFromChange = context.getGroupV2Helper()
|
final var profileKeyFromChange = context.getGroupV2Helper()
|
||||||
|
|||||||
@ -109,8 +109,8 @@ public final class IncomingMessageHandler {
|
|||||||
SignalServiceContent content = null;
|
SignalServiceContent content = null;
|
||||||
if (!envelope.isReceipt()) {
|
if (!envelope.isReceipt()) {
|
||||||
account.getIdentityKeyStore().setRetryingDecryption(true);
|
account.getIdentityKeyStore().setRetryingDecryption(true);
|
||||||
final var destination = getDestination(envelope).serviceId();
|
|
||||||
try {
|
try {
|
||||||
|
final var destination = getDestination(envelope).serviceId();
|
||||||
final var cipherResult = dependencies.getCipher(destination == null
|
final var cipherResult = dependencies.getCipher(destination == null
|
||||||
|| destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI)
|
|| destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI)
|
||||||
.decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp());
|
.decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp());
|
||||||
@ -140,15 +140,30 @@ public final class IncomingMessageHandler {
|
|||||||
final Manager.ReceiveMessageHandler handler
|
final Manager.ReceiveMessageHandler handler
|
||||||
) {
|
) {
|
||||||
final var actions = new ArrayList<HandleAction>();
|
final var actions = new ArrayList<HandleAction>();
|
||||||
|
if (envelope.isPreKeySignalMessage()) {
|
||||||
|
actions.add(RefreshPreKeysAction.create());
|
||||||
|
}
|
||||||
SignalServiceContent content = null;
|
SignalServiceContent content = null;
|
||||||
Exception exception = null;
|
Exception exception = null;
|
||||||
envelope.getSourceServiceId().map(ServiceId::parseOrNull)
|
if (envelope.getSourceServiceId() != null) {
|
||||||
// Store uuid if we don't have it already
|
// Store uuid if we don't have it already
|
||||||
// uuid in envelope is sent by server
|
// uuid in envelope is sent by server
|
||||||
.ifPresent(serviceId -> account.getRecipientResolver().resolveRecipient(serviceId));
|
account.getRecipientResolver().resolveRecipient(envelope.getSourceServiceId());
|
||||||
|
}
|
||||||
if (!envelope.isReceipt()) {
|
if (!envelope.isReceipt()) {
|
||||||
final var destination = getDestination(envelope).serviceId();
|
|
||||||
try {
|
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
|
final var cipherResult = dependencies.getCipher(destination == null
|
||||||
|| destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI)
|
|| destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI)
|
||||||
.decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp());
|
.decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp());
|
||||||
@ -173,7 +188,13 @@ public final class IncomingMessageHandler {
|
|||||||
logger.debug("Received invalid message from blocked contact, ignoring.");
|
logger.debug("Received invalid message from blocked contact, ignoring.");
|
||||||
} else {
|
} else {
|
||||||
var serviceId = ServiceId.parseOrNull(e.getSender());
|
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())
|
final var isSelf = sender.equals(account.getSelfRecipientId())
|
||||||
&& e.getSenderDevice() == account.getDeviceId();
|
&& e.getSenderDevice() == account.getDeviceId();
|
||||||
logger.debug("Received invalid message, queuing renew session action.");
|
logger.debug("Received invalid message, queuing renew session action.");
|
||||||
@ -311,7 +332,12 @@ public final class IncomingMessageHandler {
|
|||||||
final var sender = senderDeviceAddress.recipientId();
|
final var sender = senderDeviceAddress.recipientId();
|
||||||
final var senderServiceId = senderDeviceAddress.serviceId();
|
final var senderServiceId = senderDeviceAddress.serviceId();
|
||||||
final var senderDeviceId = senderDeviceAddress.deviceId();
|
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)) {
|
if (account.getPni().equals(destination.serviceId)) {
|
||||||
account.getRecipientStore().markNeedsPniSignature(destination.recipientId, true);
|
account.getRecipientStore().markNeedsPniSignature(destination.recipientId, true);
|
||||||
@ -874,11 +900,6 @@ public final class IncomingMessageHandler {
|
|||||||
|
|
||||||
final var selfAddress = isSync ? source : destination;
|
final var selfAddress = isSync ? source : destination;
|
||||||
final var conversationPartnerAddress = isSync ? destination : source;
|
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.isExpirationUpdate() || message.getBody().isPresent()) {
|
||||||
if (message.getGroupContext().isPresent()) {
|
if (message.getGroupContext().isPresent()) {
|
||||||
final var groupContext = message.getGroupContext().get();
|
final var groupContext = message.getGroupContext().get();
|
||||||
@ -1047,7 +1068,7 @@ public final class IncomingMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SignalServiceAddress getSenderAddress(SignalServiceEnvelope envelope, SignalServiceContent content) {
|
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) {
|
if (!envelope.isUnidentifiedSender() && serviceId != null) {
|
||||||
return new SignalServiceAddress(serviceId);
|
return new SignalServiceAddress(serviceId);
|
||||||
} else if (content != null) {
|
} else if (content != null) {
|
||||||
@ -1058,7 +1079,7 @@ public final class IncomingMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private DeviceAddress getSender(SignalServiceEnvelope envelope, SignalServiceContent content) {
|
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) {
|
if (!envelope.isUnidentifiedSender() && serviceId != null) {
|
||||||
return new DeviceAddress(account.getRecipientResolver().resolveRecipient(serviceId),
|
return new DeviceAddress(account.getRecipientResolver().resolveRecipient(serviceId),
|
||||||
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();
|
final var destination = envelope.getDestinationServiceId();
|
||||||
if (destination == null || destination.isUnknown()) {
|
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),
|
return new DeviceAddress(account.getRecipientResolver().resolveRecipient(destination),
|
||||||
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.SignalAccount;
|
||||||
import org.asamk.signal.manager.storage.messageCache.CachedMessage;
|
import org.asamk.signal.manager.storage.messageCache.CachedMessage;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
|
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
|
||||||
import org.signal.core.models.ServiceId;
|
|
||||||
import org.signal.core.models.ServiceId.ACI;
|
import org.signal.core.models.ServiceId.ACI;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -150,10 +149,10 @@ public class ReceiveHelper {
|
|||||||
for (final var it : batch) {
|
for (final var it : batch) {
|
||||||
SignalServiceEnvelope envelope1 = new SignalServiceEnvelope(it.getEnvelope(),
|
SignalServiceEnvelope envelope1 = new SignalServiceEnvelope(it.getEnvelope(),
|
||||||
it.getServerDeliveredTimestamp());
|
it.getServerDeliveredTimestamp());
|
||||||
final var recipientId = envelope1.getSourceServiceId()
|
final var sourceServiceId = envelope1.getSourceServiceId();
|
||||||
.map(ServiceId::parseOrNull)
|
final var recipientId = sourceServiceId == null
|
||||||
.map(s -> account.getRecipientResolver().resolveRecipient(s))
|
? null
|
||||||
.orElse(null);
|
: account.getRecipientResolver().resolveRecipient(sourceServiceId);
|
||||||
logger.trace("Storing new message from {}", recipientId);
|
logger.trace("Storing new message from {}", recipientId);
|
||||||
// store message on disk, before acknowledging receipt to the server
|
// store message on disk, before acknowledging receipt to the server
|
||||||
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
|
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
|
||||||
@ -238,7 +237,7 @@ public class ReceiveHelper {
|
|||||||
if (exception instanceof UntrustedIdentityException) {
|
if (exception instanceof UntrustedIdentityException) {
|
||||||
logger.debug("Keeping message with untrusted identity in message cache");
|
logger.debug("Keeping message with untrusted identity in message cache");
|
||||||
final var address = ((UntrustedIdentityException) exception).getSender();
|
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()
|
final var recipientId = account.getRecipientResolver()
|
||||||
.resolveRecipient(ACI.parseOrThrow(address.aci().get()));
|
.resolveRecipient(ACI.parseOrThrow(address.aci().get()));
|
||||||
try {
|
try {
|
||||||
@ -292,7 +291,7 @@ public class ReceiveHelper {
|
|||||||
cachedMessage.delete();
|
cachedMessage.delete();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (envelope.getSourceServiceId().isEmpty()) {
|
if (envelope.getSourceServiceId() == null) {
|
||||||
final var identifier = ((UntrustedIdentityException) exception).getSender();
|
final var identifier = ((UntrustedIdentityException) exception).getSender();
|
||||||
final var recipientId = account.getRecipientResolver()
|
final var recipientId = account.getRecipientResolver()
|
||||||
.resolveRecipient(new RecipientAddress(identifier));
|
.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.models.storageservice.StorageKey;
|
||||||
import org.signal.core.util.SetUtil;
|
import org.signal.core.util.SetUtil;
|
||||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
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.SignalStorageRecord;
|
||||||
import org.whispersystems.signalservice.api.storage.StorageId;
|
import org.whispersystems.signalservice.api.storage.StorageId;
|
||||||
import org.whispersystems.signalservice.api.storage.StorageRecordConvertersKt;
|
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.ManifestRecord;
|
||||||
import org.whispersystems.signalservice.internal.storage.protos.StorageRecord;
|
import org.whispersystems.signalservice.internal.storage.protos.StorageRecord;
|
||||||
|
|
||||||
@ -504,7 +504,7 @@ public class StorageHelper {
|
|||||||
final var result = dependencies.getStorageServiceRepository()
|
final var result = dependencies.getStorageServiceRepository()
|
||||||
.readStorageRecords(storageKey, manifest.recordIkm, storageIds);
|
.readStorageRecords(storageKey, manifest.recordIkm, storageIds);
|
||||||
return switch (result) {
|
return switch (result) {
|
||||||
case StorageServiceRepository.StorageRecordResult.DecryptionError decryptionError -> {
|
case StorageServiceService.StorageRecordResult.DecryptionError decryptionError -> {
|
||||||
if (decryptionError.getException() instanceof InvalidKeyException) {
|
if (decryptionError.getException() instanceof InvalidKeyException) {
|
||||||
logger.warn("Failed to read storage records, ignoring.");
|
logger.warn("Failed to read storage records, ignoring.");
|
||||||
yield List.of();
|
yield List.of();
|
||||||
@ -514,11 +514,11 @@ public class StorageHelper {
|
|||||||
throw new IOException(decryptionError.getException());
|
throw new IOException(decryptionError.getException());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case StorageServiceRepository.StorageRecordResult.NetworkError networkError ->
|
case StorageServiceService.StorageRecordResult.NetworkError networkError ->
|
||||||
throw networkError.getException();
|
throw networkError.getException();
|
||||||
case StorageServiceRepository.StorageRecordResult.StatusCodeError statusCodeError ->
|
case StorageServiceService.StorageRecordResult.StatusCodeError statusCodeError ->
|
||||||
throw statusCodeError.getException();
|
throw statusCodeError.getException();
|
||||||
case StorageServiceRepository.StorageRecordResult.Success success -> success.getRecords();
|
case StorageServiceService.StorageRecordResult.Success success -> success.getRecords();
|
||||||
default -> throw new IllegalStateException("Unexpected value: " + result);
|
default -> throw new IllegalStateException("Unexpected value: " + result);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1091,16 +1091,7 @@ public class ManagerImpl implements Manager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException {
|
public void 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) {
|
for (var recipient : recipients) {
|
||||||
final RecipientId recipientId;
|
final RecipientId recipientId;
|
||||||
try {
|
try {
|
||||||
@ -1108,13 +1099,18 @@ public class ManagerImpl implements Manager {
|
|||||||
} catch (UnregisteredRecipientException e) {
|
} catch (UnregisteredRecipientException e) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final var serviceId = context.getAccount()
|
final var recipientAddress = context.getAccount()
|
||||||
.getRecipientAddressResolver()
|
.getRecipientAddressResolver()
|
||||||
.resolveRecipientAddress(recipientId)
|
.resolveRecipientAddress(recipientId);
|
||||||
.serviceId();
|
final var aciSessionStore = account.getAccountData(ServiceIdType.ACI).getSessionStore();
|
||||||
if (serviceId.isPresent()) {
|
final var pniSessionStore = account.getAccountData(ServiceIdType.PNI).getSessionStore();
|
||||||
account.getAccountData(ServiceIdType.ACI).getSessionStore().deleteAllSessions(serviceId.get());
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1712,6 +1708,16 @@ public class ManagerImpl implements Manager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addUnidentifiedKeepAlive(final String token) {
|
||||||
|
dependencies.getUnauthenticatedSignalWebSocket().registerKeepAliveToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUnidentifiedKeepAlive(final String token) {
|
||||||
|
dependencies.getUnauthenticatedSignalWebSocket().removeKeepAliveToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCallEventListener(final CallEventListener listener) {
|
public void addCallEventListener(final CallEventListener listener) {
|
||||||
context.getCallManager().addCallEventListener(listener);
|
context.getCallManager().addCallEventListener(listener);
|
||||||
|
|||||||
@ -6,6 +6,13 @@ import org.asamk.signal.manager.util.Utils;
|
|||||||
import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
||||||
import org.signal.libsignal.net.Network;
|
import org.signal.libsignal.net.Network;
|
||||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
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.SignalSessionLock;
|
||||||
import org.whispersystems.signalservice.api.account.AccountApi;
|
import org.whispersystems.signalservice.api.account.AccountApi;
|
||||||
import org.whispersystems.signalservice.api.attachment.AttachmentApi;
|
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.crypto.SignalServiceCipher;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||||
import org.whispersystems.signalservice.api.keys.KeysApi;
|
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.message.MessageApi;
|
||||||
import org.whispersystems.signalservice.api.profiles.ProfileApi;
|
import org.whispersystems.signalservice.api.profiles.ProfileApi;
|
||||||
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
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.registration.RegistrationApi;
|
||||||
import org.whispersystems.signalservice.api.services.ProfileService;
|
import org.whispersystems.signalservice.api.services.ProfileService;
|
||||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi;
|
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.svr.SecureValueRecovery;
|
||||||
import org.whispersystems.signalservice.api.username.UsernameApi;
|
|
||||||
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
||||||
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
||||||
import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
|
import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
|
||||||
@ -243,8 +243,8 @@ public class SignalDependencies {
|
|||||||
getPushServiceSocket()));
|
getPushServiceSocket()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public StorageServiceRepository getStorageServiceRepository() {
|
public StorageServiceService getStorageServiceRepository() {
|
||||||
return new StorageServiceRepository(getStorageServiceApi());
|
return new StorageServiceService(getStorageServiceApi());
|
||||||
}
|
}
|
||||||
|
|
||||||
public CertificateApi getCertificateApi() {
|
public CertificateApi getCertificateApi() {
|
||||||
@ -368,7 +368,8 @@ public class SignalDependencies {
|
|||||||
|
|
||||||
public SignalServiceCipher getCipher(ServiceIdType serviceIdType) {
|
public SignalServiceCipher getCipher(ServiceIdType serviceIdType) {
|
||||||
final var certificateValidator = new CertificateValidator(serviceEnvironmentConfig.unidentifiedSenderTrustRoots());
|
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();
|
final var deviceId = credentialsProvider.getDeviceId();
|
||||||
return new SignalServiceCipher(address,
|
return new SignalServiceCipher(address,
|
||||||
deviceId,
|
deviceId,
|
||||||
|
|||||||
@ -6,7 +6,7 @@ ENV SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH
|
|||||||
ENV LANG=C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
ENV LC_CTYPE=en_US.UTF-8
|
ENV LC_CTYPE=en_US.UTF-8
|
||||||
RUN SNAPSHOT="$(date -u -d "@$SOURCE_DATE_EPOCH" +%Y%m%dT%H%M%SZ)" \
|
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
|
COPY --chmod=0700 reproducible-builds/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
WORKDIR /signal-cli
|
WORKDIR /signal-cli
|
||||||
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh", "build" ]
|
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh", "build" ]
|
||||||
|
|||||||
@ -144,8 +144,7 @@ public class SendCommand implements JsonRpcLocalCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final var results = m.sendEndSessionMessage(singleRecipients);
|
m.sendEndSessionMessage(singleRecipients);
|
||||||
outputResult(outputWriter, results);
|
|
||||||
return;
|
return;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
|
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
|
||||||
|
|||||||
@ -543,9 +543,8 @@ public class DbusManagerImpl implements Manager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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());
|
signal.sendEndSessionMessage(recipients.stream().map(RecipientIdentifier.Single::getIdentifier).toList());
|
||||||
return new SendMessageResults(0, Map.of());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -916,6 +915,14 @@ public class DbusManagerImpl implements Manager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addUnidentifiedKeepAlive(final String token) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUnidentifiedKeepAlive(final String token) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCallEventListener(final CallEventListener listener) {
|
public void addCallEventListener(final CallEventListener listener) {
|
||||||
// Not supported over DBus
|
// Not supported over DBus
|
||||||
|
|||||||
@ -100,6 +100,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
|||||||
|
|
||||||
public void initObjects() {
|
public void initObjects() {
|
||||||
exportObjects();
|
exportObjects();
|
||||||
|
m.addUnidentifiedKeepAlive("dbus");
|
||||||
if (!noReceiveOnStart) {
|
if (!noReceiveOnStart) {
|
||||||
subscribeReceive();
|
subscribeReceive();
|
||||||
}
|
}
|
||||||
@ -116,6 +117,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
m.removeUnidentifiedKeepAlive("dbus");
|
||||||
if (dbusMessageHandler != null) {
|
if (dbusMessageHandler != null) {
|
||||||
m.removeReceiveHandler(dbusMessageHandler);
|
m.removeReceiveHandler(dbusMessageHandler);
|
||||||
dbusMessageHandler = null;
|
dbusMessageHandler = null;
|
||||||
@ -430,8 +432,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
|||||||
@Override
|
@Override
|
||||||
public void sendEndSessionMessage(final List<String> recipients) {
|
public void sendEndSessionMessage(final List<String> recipients) {
|
||||||
try {
|
try {
|
||||||
final var results = m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getSelfNumber()));
|
m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getSelfNumber()));
|
||||||
checkSendMessageResults(results);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new Error.Failure(e.getMessage());
|
throw new Error.Failure(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -169,6 +169,12 @@ public class HttpServerHandler implements AutoCloseable {
|
|||||||
httpExchange.sendResponseHeaders(200, 0);
|
httpExchange.sendResponseHeaders(200, 0);
|
||||||
final var sender = new ServerSentEventSender(httpExchange.getResponseBody());
|
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 shouldStop = new AtomicBoolean(false);
|
||||||
final var handlers = subscribeReceiveHandlers(managers, sender, () -> {
|
final var handlers = subscribeReceiveHandlers(managers, sender, () -> {
|
||||||
shouldStop.set(true);
|
shouldStop.set(true);
|
||||||
|
|||||||
@ -25,12 +25,15 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.nio.channels.ClosedChannelException;
|
import java.nio.channels.ClosedChannelException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class SignalJsonRpcDispatcherHandler {
|
public class SignalJsonRpcDispatcherHandler {
|
||||||
|
|
||||||
@ -43,6 +46,9 @@ public class SignalJsonRpcDispatcherHandler {
|
|||||||
|
|
||||||
private final Map<Integer, List<Pair<Manager, Manager.ReceiveMessageHandler>>> receiveHandlers = new HashMap<>();
|
private final Map<Integer, List<Pair<Manager, Manager.ReceiveMessageHandler>>> receiveHandlers = new HashMap<>();
|
||||||
private final Map<Integer, List<Pair<Manager, Manager.CallEventListener>>> callEventHandlers = new HashMap<>();
|
private final Map<Integer, List<Pair<Manager, Manager.CallEventListener>>> callEventHandlers = new HashMap<>();
|
||||||
|
private final String connectionKeepAliveToken = "jsonrpc-" + UUID.randomUUID();
|
||||||
|
private final List<Manager> keepAliveManagers = new ArrayList<>();
|
||||||
|
private boolean connectionActive = true;
|
||||||
private SignalJsonRpcCommandHandler commandHandler;
|
private SignalJsonRpcCommandHandler commandHandler;
|
||||||
|
|
||||||
public SignalJsonRpcDispatcherHandler(
|
public SignalJsonRpcDispatcherHandler(
|
||||||
@ -69,6 +75,10 @@ public class SignalJsonRpcDispatcherHandler {
|
|||||||
c.addOnManagerAddedHandler(m -> callEventHandlers.forEach((subscriptionId, handlers) -> handlers.add(
|
c.addOnManagerAddedHandler(m -> callEventHandlers.forEach((subscriptionId, handlers) -> handlers.add(
|
||||||
createCallEventHandler(m, subscriptionId))));
|
createCallEventHandler(m, subscriptionId))));
|
||||||
|
|
||||||
|
c.getManagers().forEach(this::registerKeepAlive);
|
||||||
|
c.addOnManagerAddedHandler(this::registerKeepAlive);
|
||||||
|
c.addOnManagerRemovedHandler(this::unregisterKeepAlive);
|
||||||
|
|
||||||
handleConnection();
|
handleConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +92,8 @@ public class SignalJsonRpcDispatcherHandler {
|
|||||||
final var currentThread = Thread.currentThread();
|
final var currentThread = Thread.currentThread();
|
||||||
m.addClosedListener(currentThread::interrupt);
|
m.addClosedListener(currentThread::interrupt);
|
||||||
|
|
||||||
|
registerKeepAlive(m);
|
||||||
|
|
||||||
handleConnection();
|
handleConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +103,9 @@ public class SignalJsonRpcDispatcherHandler {
|
|||||||
|
|
||||||
private int subscribeCallEvents(final Collection<Manager> managers) {
|
private int subscribeCallEvents(final Collection<Manager> managers) {
|
||||||
final var subscriptionId = nextSubscriptionId.getAndIncrement();
|
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);
|
callEventHandlers.put(subscriptionId, listeners);
|
||||||
return subscriptionId;
|
return subscriptionId;
|
||||||
}
|
}
|
||||||
@ -146,7 +160,7 @@ public class SignalJsonRpcDispatcherHandler {
|
|||||||
final var subscriptionId = nextSubscriptionId.getAndIncrement();
|
final var subscriptionId = nextSubscriptionId.getAndIncrement();
|
||||||
final var handlers = managers.stream()
|
final var handlers = managers.stream()
|
||||||
.map(m -> createReceiveHandler(m, subscriptionId, internalSubscription))
|
.map(m -> createReceiveHandler(m, subscriptionId, internalSubscription))
|
||||||
.toList();
|
.collect(Collectors.toCollection(ArrayList::new));
|
||||||
receiveHandlers.put(subscriptionId, handlers);
|
receiveHandlers.put(subscriptionId, handlers);
|
||||||
|
|
||||||
return subscriptionId;
|
return subscriptionId;
|
||||||
@ -200,14 +214,29 @@ public class SignalJsonRpcDispatcherHandler {
|
|||||||
subscriptionId.ifPresent(this::unsubscribeReceive);
|
subscriptionId.ifPresent(this::unsubscribeReceive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerKeepAlive(final Manager m) {
|
||||||
|
if (!connectionActive) return;
|
||||||
|
m.addUnidentifiedKeepAlive(connectionKeepAliveToken);
|
||||||
|
keepAliveManagers.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterKeepAlive(final Manager m) {
|
||||||
|
if (!connectionActive) return;
|
||||||
|
m.removeUnidentifiedKeepAlive(connectionKeepAliveToken);
|
||||||
|
keepAliveManagers.remove(m);
|
||||||
|
}
|
||||||
|
|
||||||
private void handleConnection() {
|
private void handleConnection() {
|
||||||
try {
|
try {
|
||||||
jsonRpcReader.readMessages((method, params) -> commandHandler.handleRequest(objectMapper, method, params),
|
jsonRpcReader.readMessages((method, params) -> commandHandler.handleRequest(objectMapper, method, params),
|
||||||
response -> logger.debug("Received unexpected response for id {}", response.getId()));
|
response -> logger.debug("Received unexpected response for id {}", response.getId()));
|
||||||
} finally {
|
} finally {
|
||||||
|
connectionActive = false;
|
||||||
receiveHandlers.forEach((_subscriptionId, handlers) -> handlers.forEach(this::unsubscribeReceiveHandler));
|
receiveHandlers.forEach((_subscriptionId, handlers) -> handlers.forEach(this::unsubscribeReceiveHandler));
|
||||||
receiveHandlers.clear();
|
receiveHandlers.clear();
|
||||||
unsubscribeAllCallEvents();
|
unsubscribeAllCallEvents();
|
||||||
|
keepAliveManagers.forEach(m -> m.removeUnidentifiedKeepAlive(connectionKeepAliveToken));
|
||||||
|
keepAliveManagers.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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
|
@Override
|
||||||
public SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> r) {
|
public void sendEndSessionMessage(Set<RecipientIdentifier.Single> r) {
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -496,6 +495,14 @@ class SubscribeCallEventsTest {
|
|||||||
public void addClosedListener(Runnable l) {
|
public void addClosedListener(Runnable l) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addUnidentifiedKeepAlive(String token) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUnidentifiedKeepAlive(String token) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream retrieveAttachment(String id) {
|
public InputStream retrieveAttachment(String id) {
|
||||||
return null;
|
return null;
|
||||||
@ -741,8 +748,8 @@ class SubscribeCallEventsTest {
|
|||||||
|
|
||||||
assertEquals(1, manager1.addCount.get(), "manager1 should have one listener");
|
assertEquals(1, manager1.addCount.get(), "manager1 should have one listener");
|
||||||
assertEquals(1, manager2.addCount.get(), "manager2 should have one listener");
|
assertEquals(1, manager2.addCount.get(), "manager2 should have one listener");
|
||||||
// Also registers an onManagerAdded handler for receive and one for call events
|
// Registers onManagerAdded handlers for receive, call events, and keep-alive
|
||||||
assertEquals(2, multi.addedHandlers.size(), "should register onManagerAdded handlers");
|
assertEquals(3, multi.addedHandlers.size(), "should register onManagerAdded handlers");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user