From 40dd9a21b281661a0966e877e4c5386cf30843f5 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 27 Feb 2026 19:24:30 -0500 Subject: [PATCH 1/2] fix: Signal Desktop v8.0.0 binary ACI compatibility Signal Desktop v8.0.0 switched from string ACI fields to binary ACI encoding in protobuf messages. This breaks reactions, mentions, quotes, and other message features when the library cannot parse the new format. Two-part fix applied via patch to signal-cli v0.13.24 source build: 1. Bump signal-service-java from unofficial_137 to unofficial_138, which adds dual-format ACI parsing (string + binary fallback via ServiceId.parseOrNull). 2. Add defensive null guards in MessageEnvelope.java for cases where ServiceId resolution still fails (e.g. ACI.UNKNOWN). Preserves message content with UNKNOWN_UUID fallback rather than dropping entire message components (quotes, reactions, mentions, etc.). The patch is applied during the x86_64 source build. The source-built installDist output replaces the release tarball, so both the JVM and native (GraalVM) paths get the fix. Non-x86_64 architectures continue using the unpatched release tarball until signal-cli cuts a new release with unofficial_138. See: https://github.com/AsamK/signal-cli/pull/1944 Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 41 ++++++++++----- ext/patches/fix-binary-aci.patch | 89 ++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 ext/patches/fix-binary-aci.patch diff --git a/Dockerfile b/Dockerfile index 96e845b..1aa371a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,7 @@ ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION COPY ext/libraries/libsignal-client/v${LIBSIGNAL_CLIENT_VERSION} /tmp/libsignal-client-libraries COPY ext/libraries/libsignal-client/signal-cli-native.patch /tmp/signal-cli-native.patch +COPY ext/patches/fix-binary-aci.patch /tmp/fix-binary-aci.patch # use architecture specific libsignal_jni.so RUN arch="$(uname -m)"; \ @@ -75,6 +76,7 @@ RUN if [ "$(uname -m)" = "x86_64" ]; then \ && git clone https://github.com/AsamK/signal-cli.git signal-cli-${SIGNAL_CLI_VERSION}-source \ && cd signal-cli-${SIGNAL_CLI_VERSION}-source \ && git checkout -q v${SIGNAL_CLI_VERSION} \ + && git apply /tmp/fix-binary-aci.patch \ && cd /tmp && mkdir -p /tmp/graalvm && tar xf gvm.tar.gz -C /tmp/graalvm --strip-components=1 \ && export GRAALVM_HOME=/tmp/graalvm \ && export PATH=/tmp/graalvm/bin:$PATH \ @@ -82,10 +84,14 @@ RUN if [ "$(uname -m)" = "x86_64" ]; then \ && sed -i 's/Signal-Android\/5.22.3/Signal-Android\/5.51.7/g' src/main/java/org/asamk/signal/BaseConfig.java \ && ./gradlew build \ && ./gradlew installDist \ - && ls build/install/signal-cli/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar || (echo "\n\nsignal-client jar file with version ${LIBSIGNAL_CLIENT_VERSION} not found. Maybe the version needs to be bumped in the signal-cli-rest-api Dockerfile?\n\n" && echo "Available version: \n" && ls build/install/signal-cli/lib/libsignal-client-* && echo "\n\n" && exit 1) \ + && BUILT_LIBSIGNAL_JAR=$(ls build/install/signal-cli/lib/libsignal-client-*.jar | head -1) \ + && echo "Built libsignal-client jar: ${BUILT_LIBSIGNAL_JAR}" \ + && rm -rf /tmp/signal-cli-${SIGNAL_CLI_VERSION} \ + && cp -a build/install/signal-cli /tmp/signal-cli-${SIGNAL_CLI_VERSION} \ && cd /tmp \ - && cp signal-cli-${SIGNAL_CLI_VERSION}-source/build/install/signal-cli/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar libsignal-client.jar \ + && cp "${BUILT_LIBSIGNAL_JAR}" libsignal-client.jar \ && zip -qu libsignal-client.jar libsignal_jni.so \ + && cp libsignal-client.jar /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/$(basename "${BUILT_LIBSIGNAL_JAR}") \ && cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source \ && git apply /tmp/signal-cli-native.patch \ && ./gradlew -q nativeCompile; \ @@ -113,20 +119,27 @@ RUN if [ "$(uname -m)" = "x86_64" ]; then \ echo "Unknown architecture"; \ fi; -# replace libsignal-client - -RUN ls /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar || (echo "\n\nsignal-client jar file with version ${LIBSIGNAL_CLIENT_VERSION} not found. Maybe the version needs to be bumped in the signal-cli-rest-api Dockerfile?\n\n" && echo "Available version: \n" && ls /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/libsignal-client-* && echo "\n\n" && exit 1) - -# workaround until upstream is fixed -RUN cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib \ - && unzip signal-cli-${SIGNAL_CLI_VERSION}.jar \ - && sed -i 's/Signal-Android\/5.22.3/Signal-Android\/5.51.7/g' org/asamk/signal/BaseConfig.class \ - && zip -r signal-cli-${SIGNAL_CLI_VERSION}.jar org/ META-INF/ \ - && rm -rf META-INF \ - && rm -rf org +# Post-processing: inject native libsignal_jni.so and apply BaseConfig workaround. +# On x86_64 the source build (above) already produced a patched installDist with +# the native lib injected, so we only need to package it. On other architectures +# the release tarball is still unpatched — apply the BaseConfig sed workaround and +# inject the native lib there. + +RUN if [ "$(uname -m)" != "x86_64" ]; then \ + ls /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar \ + || (echo "\n\nsignal-client jar file with version ${LIBSIGNAL_CLIENT_VERSION} not found.\n\n" \ + && ls /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/libsignal-client-* && exit 1) \ + && cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib \ + && unzip signal-cli-${SIGNAL_CLI_VERSION}.jar \ + && sed -i 's/Signal-Android\/5.22.3/Signal-Android\/5.51.7/g' org/asamk/signal/BaseConfig.class \ + && zip -r signal-cli-${SIGNAL_CLI_VERSION}.jar org/ META-INF/ \ + && rm -rf META-INF \ + && rm -rf org \ + && cd /tmp/ \ + && zip -qu /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar libsignal_jni.so; \ + fi RUN cd /tmp/ \ - && zip -qu /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar libsignal_jni.so \ && zip -qr signal-cli-${SIGNAL_CLI_VERSION}.zip signal-cli-${SIGNAL_CLI_VERSION}/* \ && unzip -q /tmp/signal-cli-${SIGNAL_CLI_VERSION}.zip -d /opt \ && rm -f /tmp/signal-cli-${SIGNAL_CLI_VERSION}.zip diff --git a/ext/patches/fix-binary-aci.patch b/ext/patches/fix-binary-aci.patch new file mode 100644 index 0000000..62eac82 --- /dev/null +++ b/ext/patches/fix-binary-aci.patch @@ -0,0 +1,89 @@ +Fix Signal Desktop v8.0.0 binary ACI encoding compatibility. + +Signal Desktop v8.0.0 switched from string ACI fields to binary ACI encoding +in protobuf messages. This causes null ServiceId values when the library +cannot parse the new format, breaking reactions, mentions, quotes, and other +message features. + +Two-part fix: +1. Bump signal-service-java from unofficial_137 to unofficial_138 which adds + dual-format ACI parsing (string + binary fallback). +2. Add defensive null guards in MessageEnvelope.java for cases where ServiceId + resolution still fails (e.g. ACI.UNKNOWN), preserving message content with + UNKNOWN_UUID fallback rather than dropping entire message components. + +See: https://github.com/AsamK/signal-cli/pull/1944 + +diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml +index 9b1bd5f4..0000001 100644 +--- a/gradle/libs.versions.toml ++++ b/gradle/libs.versions.toml +@@ -11,7 +11,7 @@ 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.25" + +-signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_137" ++signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_138" + sqlite = "org.xerial:sqlite-jdbc:3.51.1.0" + hikari = "com.zaxxer:HikariCP:7.0.2" + junit-jupiter-bom = { module = "org.junit:junit-bom", version.ref = "junit" } +diff --git a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java +index 37946057..57a5a0f4 100644 +--- a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java ++++ b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java +@@ -132,6 +132,7 @@ public record MessageEnvelope( + return new Data(dataMessage.getTimestamp(), + dataMessage.getGroupContext().map(GroupContext::from), + dataMessage.getStoryContext() ++ .filter(s -> s.getAuthorServiceId() != null) + .map((SignalServiceDataMessage.StoryContext storyContext) -> StoryContext.from(storyContext, + recipientResolver, + addressResolver)), +@@ -143,9 +144,10 @@ public record MessageEnvelope( + dataMessage.isEndSession(), + dataMessage.isProfileKeyUpdate(), + dataMessage.getProfileKey().isPresent(), +- dataMessage.getReaction().map(r -> Reaction.from(r, recipientResolver, addressResolver)), ++ dataMessage.getReaction() ++ .filter(r -> r.getTargetAuthor() != null) ++ .map(r -> Reaction.from(r, recipientResolver, addressResolver)), + dataMessage.getQuote() +- .filter(q -> q.getAuthor() != null && q.getAuthor().isValid()) + .map(q -> Quote.from(q, recipientResolver, addressResolver, fileProvider)), + dataMessage.getPayment().map(p -> p.getPaymentNotification().isPresent() ? Payment.from(p) : null), + dataMessage.getAttachments() +@@ -159,10 +161,15 @@ public record MessageEnvelope( + .toList()) + .orElse(List.of()), + dataMessage.getPollCreate().map(PollCreate::from), +- dataMessage.getPollVote().map(p -> PollVote.from(p, recipientResolver, addressResolver)), ++ dataMessage.getPollVote() ++ .filter(p -> p.getTargetAuthor() != null) ++ .map(p -> PollVote.from(p, recipientResolver, addressResolver)), + dataMessage.getPollTerminate().map(PollTerminate::from), + dataMessage.getMentions() +- .map(a -> a.stream().map(m -> Mention.from(m, recipientResolver, addressResolver)).toList()) ++ .map(a -> a.stream() ++ .filter(m -> m.getServiceId() != null) ++ .map(m -> Mention.from(m, recipientResolver, addressResolver)) ++ .toList()) + .orElse(List.of()), + dataMessage.getPreviews() + .map(a -> a.stream().map(preview -> Preview.from(preview, fileProvider)).toList()) +@@ -241,10 +248,13 @@ public record MessageEnvelope( + RecipientAddressResolver addressResolver, + final AttachmentFileProvider fileProvider + ) { ++ final var author = quote.getAuthor() != null && quote.getAuthor().isValid() ++ ? addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor())) ++ .toApiRecipientAddress() ++ : new RecipientAddress(RecipientAddress.UNKNOWN_UUID); + return new Quote(quote.getId(), +- addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor())) +- .toApiRecipientAddress(), +- Optional.of(quote.getText()), ++ author, ++ Optional.ofNullable(quote.getText()), + quote.getMentions() == null + ? List.of() + : quote.getMentions() From 7e1b98f0c2163bceaa159d89816b2b6c2d0cb2d1 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 27 Feb 2026 20:17:50 -0500 Subject: [PATCH 2/2] fix: use absolute path for libsignal-client jar copy The BUILT_LIBSIGNAL_JAR variable captured a relative path that became invalid after cd /tmp. Switch to capturing just the filename and using the already-copied absolute path. Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1aa371a..96928dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -84,14 +84,14 @@ RUN if [ "$(uname -m)" = "x86_64" ]; then \ && sed -i 's/Signal-Android\/5.22.3/Signal-Android\/5.51.7/g' src/main/java/org/asamk/signal/BaseConfig.java \ && ./gradlew build \ && ./gradlew installDist \ - && BUILT_LIBSIGNAL_JAR=$(ls build/install/signal-cli/lib/libsignal-client-*.jar | head -1) \ - && echo "Built libsignal-client jar: ${BUILT_LIBSIGNAL_JAR}" \ + && BUILT_LIBSIGNAL_JAR_NAME=$(ls build/install/signal-cli/lib/ | grep 'libsignal-client-.*\.jar' | head -1) \ + && echo "Built libsignal-client jar: ${BUILT_LIBSIGNAL_JAR_NAME}" \ && rm -rf /tmp/signal-cli-${SIGNAL_CLI_VERSION} \ && cp -a build/install/signal-cli /tmp/signal-cli-${SIGNAL_CLI_VERSION} \ && cd /tmp \ - && cp "${BUILT_LIBSIGNAL_JAR}" libsignal-client.jar \ + && cp /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/${BUILT_LIBSIGNAL_JAR_NAME} libsignal-client.jar \ && zip -qu libsignal-client.jar libsignal_jni.so \ - && cp libsignal-client.jar /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/$(basename "${BUILT_LIBSIGNAL_JAR}") \ + && cp libsignal-client.jar /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/${BUILT_LIBSIGNAL_JAR_NAME} \ && cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source \ && git apply /tmp/signal-cli-native.patch \ && ./gradlew -q nativeCompile; \