mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-05-26 14:34:19 +00:00
Compare commits
15 Commits
8310b16895
...
f27eb524de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f27eb524de | ||
|
|
0006fd0dc0 | ||
|
|
417d2ce971 | ||
|
|
33b2b563b3 | ||
|
|
740cd6f89b | ||
|
|
7887ed408d | ||
|
|
ddfad2c4ce | ||
|
|
7e95ea7403 | ||
|
|
2991cdafe7 | ||
|
|
561dfc373f | ||
|
|
5bfb044245 | ||
|
|
e1b17bf863 | ||
|
|
72332750a8 | ||
|
|
aafb40fd94 | ||
|
|
7dc55eba81 |
57
.github/workflows/build.yml
vendored
Normal file
57
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
pull_request:
|
||||
workflow_call:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
# The "reproducible" entry is used to build the project with the LTS Java version used in reproducible builds script.
|
||||
# More Java versions can be added to test compatibility, eg. "26".
|
||||
java: ["reproducible", "26"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build
|
||||
run: |
|
||||
if [ "${{ matrix.java }}" != "reproducible" ]; then
|
||||
export OVERRIDE_JAVA_VERSION="${{ matrix.java }}"
|
||||
fi
|
||||
./reproducible-builds/build.sh
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: signal-cli-${{ matrix.java }}-${{ github.job }}
|
||||
path: dist/*
|
||||
|
||||
build-client:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu
|
||||
- macos
|
||||
- windows
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./client
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install rust
|
||||
run: rustup default stable
|
||||
- name: Build client
|
||||
run: cargo build --release --verbose
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: signal-cli-client-${{ matrix.os }}
|
||||
path: |
|
||||
client/target/release/signal-cli-client
|
||||
client/target/release/signal-cli-client.exe
|
||||
96
.github/workflows/ci.yml
vendored
96
.github/workflows/ci.yml
vendored
@ -1,96 +0,0 @@
|
||||
name: signal-cli CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: write # to fetch code (actions/checkout) and submit dependency graph (gradle/gradle-build-action)
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [ '25', '26' ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: ${{ matrix.java }}
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
dependency-graph: generate-and-submit
|
||||
- name: Install asciidoc
|
||||
run: sudo apt update && sudo apt --no-install-recommends install -y asciidoc-base
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew --no-daemon build
|
||||
- name: Build man page
|
||||
run: |
|
||||
cd man
|
||||
make install
|
||||
- name: Add man page to archive
|
||||
run: |
|
||||
version=$(tar tf build/distributions/signal-cli-*.tar | head -n1 | sed 's|signal-cli-\([^/]*\)/.*|\1|')
|
||||
echo $version
|
||||
tar --transform="flags=r;s|man|signal-cli-${version}/man|" -rf build/distributions/signal-cli-${version}.tar man/man{1,5}
|
||||
- name: Compress archive
|
||||
run: gzip -n -9 build/distributions/signal-cli-*.tar
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: signal-cli-archive-${{ matrix.java }}
|
||||
path: build/distributions/signal-cli-*.tar.gz
|
||||
|
||||
build-graalvm:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: graalvm/setup-graalvm@v1
|
||||
with:
|
||||
distribution: 'graalvm'
|
||||
java-version: '25'
|
||||
cache: 'gradle'
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew --no-daemon nativeCompile
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: signal-cli-native
|
||||
path: build/native/nativeCompile/signal-cli
|
||||
|
||||
build-client:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu
|
||||
- macos
|
||||
- windows
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./client
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install rust
|
||||
run: rustup default stable
|
||||
- name: Build client
|
||||
run: cargo build --release --verbose
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: signal-cli-client-${{ matrix.os }}
|
||||
path: |
|
||||
client/target/release/signal-cli-client
|
||||
client/target/release/signal-cli-client.exe
|
||||
200
.github/workflows/release.yml
vendored
200
.github/workflows/release.yml
vendored
@ -5,8 +5,7 @@ on:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
permissions:
|
||||
contents: write # to fetch code (actions/checkout) and create release
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
IMAGE_NAME: signal-cli
|
||||
@ -15,96 +14,25 @@ env:
|
||||
REGISTRY_PASSWORD: ${{ github.token }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: AsamK/signal-cli/.github/workflows/build.yml@master
|
||||
|
||||
ci_wf:
|
||||
permissions:
|
||||
contents: write
|
||||
uses: AsamK/signal-cli/.github/workflows/ci.yml@master
|
||||
# ${{ github.repository }} not accepted here
|
||||
|
||||
lib_to_jar:
|
||||
needs: ci_wf
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
outputs:
|
||||
signal_cli_version: ${{ steps.cli_ver.outputs.version }}
|
||||
release_id: ${{ steps.create_release.outputs.id }}
|
||||
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
|
||||
- name: Download signal-cli build from CI workflow
|
||||
uses: actions/download-artifact@v8
|
||||
|
||||
- name: Get signal-cli version
|
||||
id: cli_ver
|
||||
id: version
|
||||
run: |
|
||||
ver="${GITHUB_REF_NAME#v}"
|
||||
echo "version=${ver}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Extract archive
|
||||
run: |
|
||||
tree .
|
||||
ARCHIVE_DIR=$(ls signal-cli-archive-*/ -d | tail -n1)
|
||||
tar -xzf ./"${ARCHIVE_DIR}"/*.tar.gz
|
||||
mv ./"${ARCHIVE_DIR}"/*.tar.gz signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
|
||||
rm -rf signal-cli-archive-*/
|
||||
|
||||
# - name: Get signal-client jar version
|
||||
# id: lib_ver
|
||||
# run: |
|
||||
# JAR_PREFIX=libsignal-client-
|
||||
# jar_file=$(find ./signal-cli-*/lib/ -name "$JAR_PREFIX*.jar")
|
||||
# jar_version=$(echo "$jar_file" | xargs basename | sed "s/$JAR_PREFIX//; s/.jar//")
|
||||
# echo "$jar_version"
|
||||
# echo "signal_client_version=${jar_version}" >> $GITHUB_OUTPUT
|
||||
#
|
||||
# - name: Download signal-client builds
|
||||
# env:
|
||||
# RELEASES_URL: https://github.com/signalapp/libsignal/releases/download/
|
||||
# FILE_NAMES: signal_jni.dll libsignal_jni.dylib
|
||||
# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
|
||||
# run: |
|
||||
# for file_name in $FILE_NAMES; do
|
||||
# curl -sOL "${RELEASES_URL}/v${SIGNAL_CLIENT_VER}/${file_name}" # note: added v
|
||||
# done
|
||||
# tree .
|
||||
|
||||
- name: Compress native app
|
||||
env:
|
||||
SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
|
||||
run: |
|
||||
chmod +x signal-cli-native/signal-cli
|
||||
tar -czf signal-cli-${SIGNAL_CLI_VER}-Linux-native.tar.gz -C signal-cli-native signal-cli
|
||||
rm -rf signal-cli-native/
|
||||
|
||||
- name: Compress client app
|
||||
env:
|
||||
SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
|
||||
run: |
|
||||
chmod +x signal-cli-client-ubuntu/signal-cli-client
|
||||
tar -czf signal-cli-${SIGNAL_CLI_VER}-Linux-client.tar.gz -C signal-cli-client-ubuntu signal-cli-client
|
||||
rm -rf signal-cli-client-ubuntu/
|
||||
|
||||
# - name: Replace Windows lib
|
||||
# env:
|
||||
# SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
|
||||
# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
|
||||
# run: |
|
||||
# mv signal_jni.dll libsignal_jni.so
|
||||
# zip -u ./signal-cli-*/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar ./libsignal_jni.so
|
||||
# tar -czf signal-cli-${SIGNAL_CLI_VER}-Windows.tar.gz signal-cli-*/
|
||||
#
|
||||
# - name: Replace macOS lib
|
||||
# env:
|
||||
# SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
|
||||
# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
|
||||
# run: |
|
||||
# jar_file=./signal-cli-*/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar
|
||||
# zip -d $jar_file libsignal_jni.so
|
||||
# zip $jar_file libsignal_jni.dylib
|
||||
# tar -czf signal-cli-${SIGNAL_CLI_VER}-macOS.tar.gz signal-cli-*/
|
||||
mv ./signal-cli-reproducible-build/* .
|
||||
echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create release
|
||||
id: create_release
|
||||
@ -112,8 +40,8 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: v${{ steps.cli_ver.outputs.version }} # note: added `v`
|
||||
release_name: v${{ steps.cli_ver.outputs.version }} # note: added `v`
|
||||
tag_name: v${{ steps.version.outputs.version }} # note: added `v`
|
||||
release_name: v${{ steps.version.outputs.version }} # note: added `v`
|
||||
draft: true
|
||||
|
||||
- name: Upload archive
|
||||
@ -122,19 +50,9 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
|
||||
asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
|
||||
asset_content_type: application/x-compressed-tar # .tar.gz
|
||||
|
||||
# - name: Upload Linux archive
|
||||
# uses: actions/upload-release-asset@v1
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# with:
|
||||
# upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux.tar.gz
|
||||
# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux.tar.gz
|
||||
# asset_content_type: application/x-compressed-tar # .tar.gz
|
||||
asset_path: signal-cli-${{ steps.version.outputs.version }}.tar.gz
|
||||
asset_name: signal-cli-${{ steps.version.outputs.version }}.tar.gz
|
||||
asset_content_type: application/x-compressed-tar # .tar.gz
|
||||
|
||||
- name: Upload Linux native archive
|
||||
uses: actions/upload-release-asset@v1
|
||||
@ -142,9 +60,9 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-native.tar.gz
|
||||
asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-native.tar.gz
|
||||
asset_content_type: application/x-compressed-tar # .tar.gz
|
||||
asset_path: signal-cli-${{ steps.version.outputs.version }}-Linux-native.tar.gz
|
||||
asset_name: signal-cli-${{ steps.version.outputs.version }}-Linux-native.tar.gz
|
||||
asset_content_type: application/x-compressed-tar # .tar.gz
|
||||
|
||||
- name: Upload Linux client archive
|
||||
uses: actions/upload-release-asset@v1
|
||||
@ -152,35 +70,14 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-client.tar.gz
|
||||
asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-client.tar.gz
|
||||
asset_content_type: application/x-compressed-tar # .tar.gz
|
||||
|
||||
# - name: Upload windows archive
|
||||
# uses: actions/upload-release-asset@v1
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# with:
|
||||
# upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Windows.tar.gz
|
||||
# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Windows.tar.gz
|
||||
# asset_content_type: application/x-compressed-tar # .tar.gz
|
||||
#
|
||||
# - name: Upload macos archive
|
||||
# uses: actions/upload-release-asset@v1
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# with:
|
||||
# upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-macOS.tar.gz
|
||||
# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-macOS.tar.gz
|
||||
# asset_content_type: application/x-compressed-tar # .tar.gz
|
||||
asset_path: signal-cli-${{ steps.version.outputs.version }}-Linux-client.tar.gz
|
||||
asset_name: signal-cli-${{ steps.version.outputs.version }}-Linux-client.tar.gz
|
||||
asset_content_type: application/x-compressed-tar # .tar.gz
|
||||
|
||||
build-container:
|
||||
needs: ci_wf
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
@ -188,28 +85,19 @@ jobs:
|
||||
- name: Download signal-cli build from CI workflow
|
||||
uses: actions/download-artifact@v8
|
||||
|
||||
- name: Get signal-cli version
|
||||
id: cli_ver
|
||||
run: |
|
||||
ver="${GITHUB_REF_NAME#v}"
|
||||
echo "version=${ver}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Move archive file
|
||||
run: |
|
||||
ARCHIVE_DIR=$(ls signal-cli-archive-*/ -d | tail -n1)
|
||||
tar xf ./"${ARCHIVE_DIR}"/*.tar.gz
|
||||
rm -r signal-cli-archive-* signal-cli-native
|
||||
tar xf ./signal-cli-reproducible-build/signal-cli-${{ needs.release.outputs.version }}.tar.gz
|
||||
mkdir -p build/install/
|
||||
mv ./signal-cli-"${GITHUB_REF_NAME#v}"/ build/install/signal-cli
|
||||
mv ./signal-cli-"${{ needs.release.outputs.version }}"/ build/install/signal-cli
|
||||
|
||||
- name: Build Image
|
||||
id: build_image
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
tags: latest ${{ github.sha }} ${{ steps.cli_ver.outputs.version }}
|
||||
containerfiles:
|
||||
./Containerfile
|
||||
tags: latest ${{ github.sha }} ${{ needs.release.outputs.version }}
|
||||
containerfiles: ./Containerfile
|
||||
oci: true
|
||||
|
||||
- name: Push To GHCR
|
||||
@ -227,10 +115,9 @@ jobs:
|
||||
echo "${{ toJSON(steps.push.outputs) }}"
|
||||
|
||||
build-container-native:
|
||||
needs: ci_wf
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
@ -238,26 +125,20 @@ jobs:
|
||||
- name: Download signal-cli build from CI workflow
|
||||
uses: actions/download-artifact@v8
|
||||
|
||||
- name: Get signal-cli version
|
||||
id: cli_ver
|
||||
run: |
|
||||
ver="${GITHUB_REF_NAME#v}"
|
||||
echo "version=${ver}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Move archive file
|
||||
run: |
|
||||
tar xf ./signal-cli-reproducible-build/signal-cli-${{ needs.release.outputs.version }}-Linux-native.tar.gz
|
||||
mkdir -p build/native/nativeCompile/
|
||||
chmod +x ./signal-cli-native/signal-cli
|
||||
mv ./signal-cli-native/signal-cli build/native/nativeCompile/
|
||||
mv signal-cli build/native/nativeCompile/
|
||||
chmod +x build/native/nativeCompile/signal-cli
|
||||
|
||||
- name: Build Image
|
||||
id: build_image
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
tags: latest-native ${{ github.sha }}-native ${{ steps.cli_ver.outputs.version }}-native
|
||||
containerfiles:
|
||||
./native.Containerfile
|
||||
tags: latest-native ${{ github.sha }}-native ${{ needs.release.outputs.version }}-native
|
||||
containerfiles: ./native.Containerfile
|
||||
oci: true
|
||||
|
||||
- name: Push To GHCR
|
||||
@ -275,10 +156,9 @@ jobs:
|
||||
echo "${{ toJSON(steps.push.outputs) }}"
|
||||
|
||||
build-container-client:
|
||||
needs: ci_wf
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
@ -286,26 +166,20 @@ jobs:
|
||||
- name: Download signal-cli build from CI workflow
|
||||
uses: actions/download-artifact@v8
|
||||
|
||||
- name: Get signal-cli version
|
||||
id: cli_ver
|
||||
run: |
|
||||
ver="${GITHUB_REF_NAME#v}"
|
||||
echo "version=${ver}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Move archive file
|
||||
run: |
|
||||
tar xf ./signal-cli-reproducible-build/signal-cli-${{ needs.release.outputs.version }}-Linux-client.tar.gz
|
||||
mkdir -p client/target/release/
|
||||
chmod +x ./signal-cli-client-ubuntu/signal-cli-client
|
||||
mv ./signal-cli-client-ubuntu/signal-cli-client client/target/release/
|
||||
mv signal-cli-client client/target/release/
|
||||
chmod +x client/target/release/signal-cli-client
|
||||
|
||||
- name: Build Image
|
||||
id: build_image
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
tags: latest-client ${{ github.sha }}-client ${{ steps.cli_ver.outputs.version }}-client
|
||||
containerfiles:
|
||||
./client.Containerfile
|
||||
tags: latest-client ${{ github.sha }}-client ${{ needs.release.outputs.version }}-client
|
||||
containerfiles: ./client.Containerfile
|
||||
oci: true
|
||||
|
||||
- name: Push To GHCR
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
.gradle/
|
||||
.kotlin/
|
||||
.idea/*
|
||||
!.idea/codeStyles/
|
||||
build/
|
||||
@ -13,3 +14,9 @@ out/
|
||||
.DS_Store
|
||||
/bin/
|
||||
/test-config/
|
||||
/dist/
|
||||
/github/
|
||||
man/*.1
|
||||
man/*.5
|
||||
man/man1
|
||||
man/man5
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Send message results now surface server-advised retry time for plain rate-limit (HTTP 413) failures, not only for proof-required challenges. The `retryAfterSeconds` field in JSON-RPC `SendMessageResult` is populated whenever the server sends a `Retry-After` header. The canonical way to distinguish proof-required failures remains `token != null`. Text output includes "retry after N seconds" when known.
|
||||
|
||||
## [0.14.2] - 2026-04-04
|
||||
|
||||
### Added
|
||||
|
||||
10
README.md
10
README.md
@ -148,6 +148,16 @@ version installed, you can replace `./gradlew` with `gradle` in the following st
|
||||
./gradlew run --args="--help"
|
||||
```
|
||||
|
||||
### JSON Schemas for the JSON-RPC mode
|
||||
|
||||
1. Generate [JSON Schema](https://json-schema.org/) files for all the JSON-RPC data classes (`src/main/java/org/asamk/signal/json`):
|
||||
|
||||
```sh
|
||||
./gradlew jsonSchemas
|
||||
```
|
||||
|
||||
2. The generated files can be found in the `build/generated/META-INF/schemas` folder.
|
||||
|
||||
### Building a native binary with GraalVM (EXPERIMENTAL)
|
||||
|
||||
It is possible to build a native binary with [GraalVM](https://www.graalvm.org). This is still experimental and will not
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import groovy.json.JsonOutput
|
||||
|
||||
plugins {
|
||||
java
|
||||
application
|
||||
@ -72,6 +74,11 @@ val excludePatterns = mapOf(
|
||||
)
|
||||
)
|
||||
|
||||
val schemaAnnotationProcessor by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
registerTransform(JarFileExcluder::class) {
|
||||
from.attribute(minified, false).attribute(artifactType, "jar")
|
||||
@ -82,6 +89,8 @@ dependencies {
|
||||
}
|
||||
}
|
||||
|
||||
schemaAnnotationProcessor(libs.micronaut.json.schema.processor)
|
||||
schemaAnnotationProcessor(libs.micronaut.inject.java)
|
||||
implementation(libs.bouncycastle)
|
||||
implementation(libs.jackson.databind)
|
||||
implementation(libs.argparse4j)
|
||||
@ -90,6 +99,10 @@ dependencies {
|
||||
implementation(libs.slf4j.jul)
|
||||
implementation(libs.logback)
|
||||
implementation(libs.zxing)
|
||||
implementation(libs.micronaut.json.schema.annotations)
|
||||
if (gradle.startParameter.taskNames.any { it.contains("jsonSchemas") }) {
|
||||
implementation(libs.micronaut.json.schema.generator)
|
||||
}
|
||||
implementation(project(":libsignal-cli"))
|
||||
|
||||
testImplementation(libs.junit.jupiter)
|
||||
@ -160,3 +173,30 @@ tasks.register("writeLibsignalVersion") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<JavaCompile>("jsonSchemas") {
|
||||
dependsOn(tasks.compileJava)
|
||||
val schemaBaseUri = "http://localhost:8080/schemas/"
|
||||
source = sourceSets.main.get().java
|
||||
include("org/asamk/signal/json/**/*.java")
|
||||
classpath = sourceSets.main.get().compileClasspath + files(sourceSets.main.get().java.destinationDirectory)
|
||||
destinationDirectory.set(layout.buildDirectory.dir("generated"))
|
||||
options.annotationProcessorPath = schemaAnnotationProcessor
|
||||
options.compilerArgs.addAll(
|
||||
listOf(
|
||||
"-Amicronaut.processing.group=org.asamk",
|
||||
"-Amicronaut.processing.module=signal-cli",
|
||||
"-Amicronaut.processing.annotations=org.asamk.signal.json.*",
|
||||
"-Amicronaut.jsonschema.baseUri=$schemaBaseUri",
|
||||
)
|
||||
)
|
||||
doLast {
|
||||
fileTree(destinationDirectory.get().dir("META-INF/schemas").asFile) {
|
||||
include("*.schema.json")
|
||||
}.forEach { schemaFile ->
|
||||
val normalized = schemaFile.readText().replace("\"$schemaBaseUri/", "\"")
|
||||
val prettyJson = JsonOutput.prettyPrint(normalized)
|
||||
schemaFile.writeText("$prettyJson\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
323
client/Cargo.lock
generated
323
client/Cargo.lock
generated
@ -4,9 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@ -19,15 +19,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
@ -83,9 +83,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
@ -95,9 +95,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.56"
|
||||
version = "1.2.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@ -117,9 +117,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.60"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@ -127,9 +127,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.60"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@ -140,9 +140,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.55"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@ -152,15 +152,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
@ -326,9 +326,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@ -377,9 +377,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.8.1"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
|
||||
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@ -391,7 +391,6 @@ dependencies = [
|
||||
"httparse",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
@ -399,16 +398,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.7"
|
||||
version = "0.27.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
|
||||
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
|
||||
dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
@ -436,12 +434,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
|
||||
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"utf8_iter",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
@ -449,9 +448,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
|
||||
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
@ -462,9 +461,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
|
||||
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
@ -476,15 +475,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
|
||||
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.1.2"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
|
||||
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
@ -496,15 +495,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.1.2"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
|
||||
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
|
||||
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
@ -538,9 +537,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.13.0"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
@ -554,9 +553,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
@ -567,7 +566,7 @@ dependencies = [
|
||||
"cesu8",
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-sys",
|
||||
"jni-sys 0.3.1",
|
||||
"log",
|
||||
"thiserror 1.0.69",
|
||||
"walkdir",
|
||||
@ -576,9 +575,31 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
|
||||
dependencies = [
|
||||
"jni-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
|
||||
dependencies = [
|
||||
"jni-sys-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys-macros"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee"
|
||||
@ -668,9 +689,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.182"
|
||||
version = "0.2.185"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
@ -680,9 +701,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@ -698,9 +719,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
@ -709,9 +730,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
@ -757,26 +778,20 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
|
||||
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
@ -792,9 +807,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.44"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -815,9 +830,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
@ -834,9 +849,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.37"
|
||||
version = "0.23.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
|
||||
checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
@ -897,9 +912,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.10"
|
||||
version = "0.103.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
|
||||
checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@ -917,9 +932,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.28"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
||||
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
@ -1026,12 +1041,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.2"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
|
||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1082,12 +1097,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
|
||||
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1132,9 +1147,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
|
||||
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
@ -1142,9 +1157,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.49.0"
|
||||
version = "1.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
||||
checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@ -1157,9 +1172,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.0"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1202,18 +1217,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
||||
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.10+spec-1.0.0"
|
||||
version = "0.25.11+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
@ -1223,9 +1238,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.9+spec-1.1.0"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
@ -1360,14 +1375,14 @@ version = "0.26.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e"
|
||||
dependencies = [
|
||||
"webpki-root-certs 1.0.6",
|
||||
"webpki-root-certs 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-root-certs"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
|
||||
checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
@ -1414,15 +1429,6 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
@ -1456,30 +1462,13 @@ dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm 0.53.1",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@ -1492,12 +1481,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
@ -1510,12 +1493,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
@ -1528,24 +1505,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
@ -1558,12 +1523,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
@ -1576,12 +1535,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@ -1594,12 +1547,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
@ -1612,32 +1559,26 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.14"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.2"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
|
||||
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
@ -1646,9 +1587,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
|
||||
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1658,18 +1599,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1685,9 +1626,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
|
||||
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
@ -1696,9 +1637,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.5"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
|
||||
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
@ -1707,9 +1648,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.2"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
|
||||
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@ -1,19 +1,25 @@
|
||||
[versions]
|
||||
slf4j = "2.0.17"
|
||||
junit = "6.0.2"
|
||||
junit = "6.0.3"
|
||||
micronaut-json-schema = "2.0.0-M8"
|
||||
micronaut-core = "4.9.3"
|
||||
|
||||
[libraries]
|
||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.83"
|
||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"
|
||||
jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.20.2"
|
||||
argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0"
|
||||
dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0"
|
||||
zxing = "com.google.zxing:core:3.5.4"
|
||||
micronaut-json-schema-annotations = { module = "io.micronaut.jsonschema:micronaut-json-schema-annotations", version.ref = "micronaut-json-schema" }
|
||||
micronaut-json-schema-processor = { module = "io.micronaut.jsonschema:micronaut-json-schema-processor", version.ref = "micronaut-json-schema" }
|
||||
micronaut-json-schema-generator = { module = "io.micronaut.jsonschema:micronaut-json-schema-generator", version.ref = "micronaut-json-schema" }
|
||||
micronaut-inject-java = { module = "io.micronaut:micronaut-inject-java", version.ref = "micronaut-core" }
|
||||
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_143"
|
||||
sqlite = "org.xerial:sqlite-jdbc:3.51.2.0"
|
||||
signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_144"
|
||||
sqlite = "org.xerial:sqlite-jdbc:3.53.0.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" }
|
||||
|
||||
@ -407,6 +407,10 @@ public interface Manager extends Closeable {
|
||||
|
||||
void addClosedListener(Runnable listener);
|
||||
|
||||
void addUnidentifiedKeepAlive(String token);
|
||||
|
||||
void removeUnidentifiedKeepAlive(String token);
|
||||
|
||||
InputStream retrieveAttachment(final String id) throws IOException;
|
||||
|
||||
InputStream retrieveContactAvatar(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException;
|
||||
|
||||
@ -2,11 +2,11 @@ package org.asamk.signal.manager.api;
|
||||
|
||||
public class CaptchaRequiredException extends Exception {
|
||||
|
||||
private long nextAttemptTimestamp;
|
||||
private long nextVerificationAttemptMilliseconds;
|
||||
|
||||
public CaptchaRequiredException(final long nextAttemptTimestamp) {
|
||||
public CaptchaRequiredException(final long nextVerificationAttemptMilliseconds) {
|
||||
super("Captcha required");
|
||||
this.nextAttemptTimestamp = nextAttemptTimestamp;
|
||||
this.nextVerificationAttemptMilliseconds = nextVerificationAttemptMilliseconds;
|
||||
}
|
||||
|
||||
public CaptchaRequiredException(final String message) {
|
||||
@ -17,7 +17,7 @@ public class CaptchaRequiredException extends Exception {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public long getNextAttemptTimestamp() {
|
||||
return nextAttemptTimestamp;
|
||||
public long getNextVerificationAttemptMilliseconds() {
|
||||
return nextVerificationAttemptMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,12 +10,19 @@ public class ProofRequiredException extends Exception {
|
||||
|
||||
private final String token;
|
||||
private final Set<Option> options;
|
||||
private final long retryAfterSeconds;
|
||||
private final long retryAfterMilliseconds;
|
||||
|
||||
public ProofRequiredException(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException e) {
|
||||
this.token = e.getToken();
|
||||
this.options = e.getOptions().stream().map(Option::from).collect(Collectors.toSet());
|
||||
this.retryAfterSeconds = e.getRetryAfterSeconds();
|
||||
public ProofRequiredException(final String token, final Set<Option> options, final long retryAfterMilliseconds) {
|
||||
super("Rate limit");
|
||||
this.token = token;
|
||||
this.options = options;
|
||||
this.retryAfterMilliseconds = retryAfterMilliseconds;
|
||||
}
|
||||
|
||||
public static ProofRequiredException from(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException e) {
|
||||
return new ProofRequiredException(e.getToken(),
|
||||
e.getOptions().stream().map(Option::from).collect(Collectors.toSet()),
|
||||
e.getRetryAfterSeconds() * 1000L);
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
@ -26,8 +33,8 @@ public class ProofRequiredException extends Exception {
|
||||
return options;
|
||||
}
|
||||
|
||||
public long getRetryAfterSeconds() {
|
||||
return retryAfterSeconds;
|
||||
public long getRetryAfterMilliseconds() {
|
||||
return retryAfterMilliseconds;
|
||||
}
|
||||
|
||||
public enum Option {
|
||||
|
||||
@ -2,14 +2,18 @@ package org.asamk.signal.manager.api;
|
||||
|
||||
public class RateLimitException extends Exception {
|
||||
|
||||
private final long nextAttemptTimestamp;
|
||||
private final Long retryAfterMilliseconds;
|
||||
|
||||
public RateLimitException(final long nextAttemptTimestamp) {
|
||||
public RateLimitException(final Long retryAfterMilliseconds) {
|
||||
super("Rate limit");
|
||||
this.nextAttemptTimestamp = nextAttemptTimestamp;
|
||||
this.retryAfterMilliseconds = retryAfterMilliseconds;
|
||||
}
|
||||
|
||||
public long getNextAttemptTimestamp() {
|
||||
return nextAttemptTimestamp;
|
||||
public static RateLimitException from(org.whispersystems.signalservice.api.push.exceptions.RateLimitException e) {
|
||||
return new RateLimitException(e.getRetryAfterMilliseconds().orElse(null));
|
||||
}
|
||||
|
||||
public Long getRetryAfterMilliseconds() {
|
||||
return retryAfterMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,13 +9,13 @@ public record SendMessageResult(
|
||||
boolean isNetworkFailure,
|
||||
boolean isUnregisteredFailure,
|
||||
boolean isIdentityFailure,
|
||||
boolean isRateLimitFailure,
|
||||
RateLimitException rateLimitException,
|
||||
ProofRequiredException proofRequiredFailure,
|
||||
boolean isInvalidPreKeyFailure
|
||||
) {
|
||||
|
||||
public static SendMessageResult unregisteredFailure(RecipientAddress address) {
|
||||
return new SendMessageResult(address, false, false, true, false, false, null, false);
|
||||
return new SendMessageResult(address, false, false, true, false, null, null, false);
|
||||
}
|
||||
|
||||
public static SendMessageResult from(
|
||||
@ -23,16 +23,30 @@ public record SendMessageResult(
|
||||
RecipientResolver recipientResolver,
|
||||
RecipientAddressResolver addressResolver
|
||||
) {
|
||||
final var rateLimitFailure = sendMessageResult.getRateLimitFailure();
|
||||
final var proofRequiredFailure = sendMessageResult.getProofRequiredFailure();
|
||||
return new SendMessageResult(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
|
||||
sendMessageResult.getAddress())).toApiRecipientAddress(),
|
||||
sendMessageResult.isSuccess(),
|
||||
sendMessageResult.isNetworkFailure(),
|
||||
sendMessageResult.isUnregisteredFailure(),
|
||||
sendMessageResult.getIdentityFailure() != null,
|
||||
sendMessageResult.getRateLimitFailure() != null || sendMessageResult.getProofRequiredFailure() != null,
|
||||
sendMessageResult.getProofRequiredFailure() == null
|
||||
? null
|
||||
: new ProofRequiredException(sendMessageResult.getProofRequiredFailure()),
|
||||
rateLimitFailure == null ? null : RateLimitException.from(rateLimitFailure),
|
||||
proofRequiredFailure == null ? null : ProofRequiredException.from(proofRequiredFailure),
|
||||
sendMessageResult.isInvalidPreKeyFailure());
|
||||
}
|
||||
|
||||
public boolean isRateLimitFailure() {
|
||||
return this.rateLimitException != null || this.proofRequiredFailure != null;
|
||||
}
|
||||
|
||||
public Long rateLimitRetryAfterMilliseconds() {
|
||||
if (proofRequiredFailure != null) {
|
||||
return proofRequiredFailure.getRetryAfterMilliseconds();
|
||||
} else if (rateLimitException != null) {
|
||||
return rateLimitException.getRetryAfterMilliseconds();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package org.asamk.signal.manager.api;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public record SendMessageResults(long timestamp, Map<RecipientIdentifier, List<SendMessageResult>> results) {
|
||||
|
||||
@ -26,4 +27,18 @@ public record SendMessageResults(long timestamp, Map<RecipientIdentifier, List<S
|
||||
.flatMap(res -> res.stream().map(SendMessageResult::isRateLimitFailure))
|
||||
.allMatch(r -> r) && results.values().stream().mapToInt(List::size).sum() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Longest rate-limit retry-after window across all rate-limited recipients, in milliseconds.
|
||||
* Null when no recipient reported one (server omitted Retry-After, or no rate-limit failures).
|
||||
*/
|
||||
public Long maxRateLimitRetryAfterMilliseconds() {
|
||||
return results.values()
|
||||
.stream()
|
||||
.flatMap(List::stream)
|
||||
.map(SendMessageResult::rateLimitRetryAfterMilliseconds)
|
||||
.filter(Objects::nonNull)
|
||||
.max(Long::compareTo)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +105,8 @@ public class CallManager implements AutoCloseable {
|
||||
recipientAddress,
|
||||
recipientId);
|
||||
activeCalls.put(callId, state);
|
||||
dependencies.getAuthenticatedSignalWebSocket().registerKeepAliveToken("call" + callId);
|
||||
dependencies.getUnauthenticatedSignalWebSocket().registerKeepAliveToken("call" + callId);
|
||||
fireCallEvent(state, null);
|
||||
|
||||
// Spawn call tunnel binary and connect control channel
|
||||
@ -197,11 +199,6 @@ public class CallManager implements AutoCloseable {
|
||||
if (callEventListeners.isEmpty()) {
|
||||
logger.debug("Ignoring incoming offer for call {}: no call event listeners registered",
|
||||
callIdUnsigned(callId));
|
||||
|
||||
final var result = sendBusyMessage(callId, recipientId, deviceId);
|
||||
if (!result.isSuccess()) {
|
||||
logger.warn("Failed to send busy for unhandled call {}", callIdUnsigned(callId));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -701,6 +698,8 @@ public class CallManager implements AutoCloseable {
|
||||
|
||||
private void endCall(final long callId, final String reason) {
|
||||
var state = activeCalls.remove(callId);
|
||||
dependencies.getAuthenticatedSignalWebSocket().removeKeepAliveToken("call" + callId);
|
||||
dependencies.getUnauthenticatedSignalWebSocket().removeKeepAliveToken("call" + callId);
|
||||
if (state == null) return;
|
||||
|
||||
state.state = CallInfo.State.ENDED;
|
||||
|
||||
@ -134,7 +134,9 @@ public final class ProfileHelper {
|
||||
SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL,
|
||||
false));
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage());
|
||||
logger.warn("Failed to retrieve profile key credential for {}, ignoring: {}",
|
||||
context.getRecipientHelper().resolveSignalServiceAddress(recipientId).getIdentifier(),
|
||||
e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -263,7 +265,9 @@ public final class ProfileHelper {
|
||||
try {
|
||||
blockingGetProfile(retrieveProfile(recipientId, SignalServiceProfile.RequestType.PROFILE, false));
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
|
||||
logger.warn("Failed to retrieve profile for {}, ignoring: {}",
|
||||
context.getRecipientHelper().resolveSignalServiceAddress(recipientId).getIdentifier(),
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
return account.getProfileStore().getProfile(recipientId);
|
||||
@ -381,7 +385,9 @@ public final class ProfileHelper {
|
||||
|
||||
logger.trace("Done handling retrieved profile");
|
||||
}).doOnError(e -> {
|
||||
logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
|
||||
logger.warn("Failed to retrieve profile for {}, ignoring: {}",
|
||||
context.getRecipientHelper().resolveSignalServiceAddress(recipientId).getIdentifier(),
|
||||
e.getMessage());
|
||||
final var profile = account.getProfileStore().getProfile(recipientId);
|
||||
final var newProfile = (
|
||||
profile == null ? Profile.newBuilder() : Profile.newBuilder(profile)
|
||||
|
||||
@ -102,9 +102,6 @@ public class ReceiveHelper {
|
||||
signalWebSocket.connect();
|
||||
signalWebSocket.registerKeepAliveToken("receive");
|
||||
|
||||
final var unauthenticatedSignalWebSocket = dependencies.getUnauthenticatedSignalWebSocket();
|
||||
unauthenticatedSignalWebSocket.registerKeepAliveToken("receive");
|
||||
|
||||
try {
|
||||
receiveMessagesInternal(signalWebSocket, timeout, maxMessages, handler, queuedActions);
|
||||
} finally {
|
||||
@ -113,7 +110,6 @@ public class ReceiveHelper {
|
||||
queuedActions.clear();
|
||||
signalWebSocket.removeKeepAliveToken("receive");
|
||||
signalWebSocket.disconnect();
|
||||
unauthenticatedSignalWebSocket.removeKeepAliveToken("receive");
|
||||
webSocketStateDisposable.dispose();
|
||||
shouldStop = false;
|
||||
}
|
||||
|
||||
@ -278,7 +278,7 @@ public class ManagerImpl implements Manager {
|
||||
registeredUsers = context.getRecipientHelper().getRegisteredUsers(canonicalizedNumbersSet);
|
||||
} catch (CdsiResourceExhaustedException e) {
|
||||
logger.debug("CDSI resource exhausted: {}", e.getMessage());
|
||||
throw new RateLimitException(System.currentTimeMillis() + e.getRetryAfterSeconds() * 1000L);
|
||||
throw new RateLimitException(e.getRetryAfterSeconds() * 1000L);
|
||||
}
|
||||
|
||||
return numbers.stream().collect(Collectors.toMap(n -> n, n -> {
|
||||
@ -1712,6 +1712,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
|
||||
public void addCallEventListener(final CallEventListener listener) {
|
||||
context.getCallManager().addCallEventListener(listener);
|
||||
|
||||
@ -301,7 +301,7 @@ public class SignalDependencies {
|
||||
getLibSignalNetwork(),
|
||||
credentialsProvider,
|
||||
allowStories,
|
||||
healthMonitor), () -> true, timer, TimeUnit.SECONDS.toMillis(10));
|
||||
healthMonitor), () -> true, timer, TimeUnit.SECONDS.toMillis(30));
|
||||
healthMonitor.monitor(authenticatedSignalWebSocket);
|
||||
});
|
||||
}
|
||||
@ -316,7 +316,7 @@ public class SignalDependencies {
|
||||
getLibSignalNetwork(),
|
||||
null,
|
||||
allowStories,
|
||||
healthMonitor), () -> true, timer, TimeUnit.SECONDS.toMillis(10));
|
||||
healthMonitor), () -> true, timer, TimeUnit.SECONDS.toMillis(30));
|
||||
healthMonitor.monitor(unauthenticatedSignalWebSocket);
|
||||
});
|
||||
}
|
||||
|
||||
@ -204,7 +204,7 @@ public class SenderKeySharedStore {
|
||||
).formatted(TABLE_SENDER_KEY_SHARED);
|
||||
try (final var statement = connection.prepareStatement(sql)) {
|
||||
for (final var entry : newEntries) {
|
||||
statement.setString(1, entry.toString());
|
||||
statement.setString(1, entry.address());
|
||||
statement.setInt(2, entry.deviceId());
|
||||
statement.setBytes(3, UuidUtil.toByteArray(distributionId.asUuid()));
|
||||
statement.setLong(4, System.currentTimeMillis());
|
||||
|
||||
@ -65,14 +65,12 @@ public class NumberVerificationUtils {
|
||||
if (nextAttempt == null) {
|
||||
throw new VerificationMethodNotAvailableException();
|
||||
} else if (nextAttempt > 0) {
|
||||
final var timestamp = sessionResponse.getClientReceivedAtMilliseconds() + nextAttempt * 1000;
|
||||
throw new RateLimitException(timestamp);
|
||||
throw new RateLimitException(nextAttempt * 1000L);
|
||||
}
|
||||
|
||||
final var nextVerificationAttempt = sessionResponse.getMetadata().getNextVerificationAttempt();
|
||||
if (nextVerificationAttempt != null && nextVerificationAttempt > 0) {
|
||||
final var timestamp = sessionResponse.getClientReceivedAtMilliseconds() + nextVerificationAttempt * 1000;
|
||||
throw new CaptchaRequiredException(timestamp);
|
||||
throw new CaptchaRequiredException(nextVerificationAttempt * 1000L);
|
||||
}
|
||||
|
||||
if (sessionResponse.getMetadata().getRequestedInformation().contains("captcha")) {
|
||||
|
||||
@ -14,8 +14,8 @@ all: $(MANPAGESRC)
|
||||
.PHONY: install
|
||||
install: all
|
||||
$(MKDIR) -p man1 man5
|
||||
for f in *.1; do $(GZIP) < "$$f" > man1/"$$f".gz ; done
|
||||
for f in *.5; do $(GZIP) < "$$f" > man5/"$$f".gz ; done
|
||||
for f in *.1; do $(GZIP) -n < "$$f" > man1/"$$f".gz ; done
|
||||
for f in *.5; do $(GZIP) -n < "$$f" > man5/"$$f".gz ; done
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
||||
@ -1168,6 +1168,7 @@ signal-cli -a ACCOUNT trust -a RECIPIENT
|
||||
* *3*: Server or IO error
|
||||
* *4*: Sending failed due to untrusted key
|
||||
* *5*: Server rate limiting error
|
||||
* *6*: CAPTCHA was rejected
|
||||
|
||||
== Files
|
||||
|
||||
|
||||
34
reproducible-builds/README.md
Normal file
34
reproducible-builds/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Reproducible builds
|
||||
|
||||
This process lets you verify that the version of signal-cli that was downloaded from the Github Releases matches the source code in the public repository.
|
||||
|
||||
This is achieved by replicating the build environment as Docker images.
|
||||
|
||||
Currently, only the following binaries are reproducible:
|
||||
|
||||
- [x] JAR package (`signal-cli-XXX.tar.gz`)
|
||||
- [ ] Native binary (`signal-cli-XXX-Linux-native.tar.gz`)
|
||||
- [x] Rust client binary (`signal-cli-XXX-Linux-client.tar.gz`)
|
||||
|
||||
In the following section, we will use signal-cli version 0.14.2 as the reference example. Simply replace all occurrences of 0.14.2 with the version number you are about to verify.
|
||||
|
||||
## Step-by-step instructions
|
||||
|
||||
### 0. Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following installed:
|
||||
|
||||
- git
|
||||
- docker (or podman)
|
||||
|
||||
### 1. Verifying reproducibility
|
||||
|
||||
```bash
|
||||
git clone --depth 1 --branch v0.14.2 https://github.com/AsamK/signal-cli
|
||||
cd ./signal-cli
|
||||
./reproducible-builds/verify.sh
|
||||
```
|
||||
|
||||
If each one ends with `... matches!` for every binary (except the native one for now), you're good to go! You've successfully verified that the Github Release binaries were built from exactly the same code as is in the signal-cli git repository.
|
||||
|
||||
If you get `... doesn't match!`, it means something went wrong (except for the native one for now). Please [open an issue](https://github.com/AsamK/signal-cli/issues/new/choose).
|
||||
13
reproducible-builds/build.Containerfile
Normal file
13
reproducible-builds/build.Containerfile
Normal file
@ -0,0 +1,13 @@
|
||||
ARG ZULU_TAG="25.0.2-jdk@sha256:9582df6c4415d9c770eb5ff8fce426ebba53631149c9eb083ee126568d32fab3"
|
||||
|
||||
FROM docker.io/azul/zulu-openjdk:$ZULU_TAG
|
||||
ENV SOURCE_DATE_EPOCH=1767225600
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_CTYPE=en_US.UTF-8
|
||||
ARG SNAPSHOT=20260101T000000Z
|
||||
RUN echo "deb http://snapshot.ubuntu.com/ubuntu/${SNAPSHOT}/ jammy main" > /etc/apt/sources.list \
|
||||
&& echo "deb http://snapshot.ubuntu.com/ubuntu/${SNAPSHOT}/ jammy universe" >> /etc/apt/sources.list
|
||||
RUN apt update && apt install -y make asciidoc-base
|
||||
COPY --chmod=0700 reproducible-builds/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
WORKDIR /signal-cli
|
||||
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh", "build" ]
|
||||
50
reproducible-builds/build.sh
Executable file
50
reproducible-builds/build.sh
Executable file
@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../"
|
||||
cd "$ROOT_DIR"
|
||||
rm -rf "$ROOT_DIR/dist"
|
||||
mkdir -p "$ROOT_DIR/dist"
|
||||
|
||||
if command -v podman >/dev/null; then
|
||||
ENGINE=podman
|
||||
USER=
|
||||
else
|
||||
ENGINE=docker
|
||||
USER="--user $(id -u):$(id -g)"
|
||||
fi
|
||||
|
||||
VERSION=$(sed -n 's/\s*version\s*=\s*"\(.*\)".*/\1/p' build.gradle.kts | tail -n1)
|
||||
echo "$VERSION" >dist/VERSION
|
||||
|
||||
$ENGINE build -t signal-cli:build ${OVERRIDE_JAVA_VERSION:+--build-arg ZULU_TAG=$OVERRIDE_JAVA_VERSION} -f reproducible-builds/build.Containerfile .
|
||||
$ENGINE build -t signal-cli:native -f reproducible-builds/native.Containerfile .
|
||||
$ENGINE build -t signal-cli:client -f reproducible-builds/client.Containerfile .
|
||||
|
||||
# Build jar
|
||||
git clean -Xfd -e '!/dist/' -e '!/dist/**' -e '!/github/' -e '!/github/**'
|
||||
# shellcheck disable=SC2086
|
||||
$ENGINE run --pull=never --rm -v "$(pwd)":/signal-cli:Z -e VERSION="$VERSION" $USER signal-cli:build
|
||||
mv build/distributions/signal-cli-*.tar.gz dist/
|
||||
|
||||
if [ -n "${OVERRIDE_JAVA_VERSION:-}" ]; then
|
||||
echo -e "\e[33mBuild was performed with overridden Java version $OVERRIDE_JAVA_VERSION, native-image and client will not be built.\e[0m"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build native-image
|
||||
git clean -Xfd -e '!/dist/' -e '!/dist/**' -e '!/github/' -e '!/github/**'
|
||||
# shellcheck disable=SC2086
|
||||
$ENGINE run --pull=never --rm -v "$(pwd)":/signal-cli:Z -e VERSION="$VERSION" $USER signal-cli:native
|
||||
mv build/signal-cli-*-Linux-native.tar.gz dist/
|
||||
|
||||
# Build rust client
|
||||
git clean -Xfd -e '!/dist/' -e '!/dist/**' -e '!/github/' -e '!/github/**'
|
||||
# shellcheck disable=SC2086
|
||||
$ENGINE run --pull=never --rm -v "$(pwd)":/signal-cli:Z -e VERSION="$VERSION" $USER signal-cli:client
|
||||
mv build/signal-cli-*-Linux-client.tar.gz dist/
|
||||
|
||||
ls -lsh dist/
|
||||
|
||||
echo -e "\e[32mBuild successful!\e[0m"
|
||||
7
reproducible-builds/client.Containerfile
Normal file
7
reproducible-builds/client.Containerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM docker.io/rust:1.94.1-slim-trixie@sha256:c6a474d7164ea2455e09b60a759b1edca38db7373c5689c1dae31780de4e71ac
|
||||
ENV SOURCE_DATE_EPOCH=1767225600
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_CTYPE=en_US.UTF-8
|
||||
COPY --chmod=0700 reproducible-builds/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
WORKDIR /signal-cli
|
||||
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh", "client" ]
|
||||
78
reproducible-builds/entrypoint.sh
Normal file
78
reproducible-builds/entrypoint.sh
Normal file
@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
echo "Build '$1' variant $VERSION ..."
|
||||
|
||||
function reset_file_dates() {
|
||||
find . -exec touch -m -d "@$SOURCE_DATE_EPOCH" {} \;
|
||||
}
|
||||
|
||||
reset_file_dates
|
||||
|
||||
if [ "$1" == "build" ]; then
|
||||
|
||||
./gradlew build \
|
||||
--no-daemon \
|
||||
--max-workers=1 \
|
||||
-Dkotlin.compiler.execution.strategy=in-process \
|
||||
--no-build-cache \
|
||||
-Dorg.gradle.caching=false \
|
||||
-Porg.gradle.java.installations.auto-download=false \
|
||||
-Porg.gradle.java.installations.auto-detect=false
|
||||
cd man
|
||||
make install
|
||||
cd ..
|
||||
tar_archive="build/distributions/signal-cli-${VERSION}.tar"
|
||||
tar --transform="flags=r;s|man|signal-cli-${VERSION}/man|" -rf "$tar_archive" man/man{1,5}
|
||||
|
||||
# Remake the tarball to ensure reproducible file order and timestamps
|
||||
mkdir -p build/extracted
|
||||
tar -xf "$tar_archive" -C build/extracted/
|
||||
reset_file_dates
|
||||
rm -f "$tar_archive"
|
||||
tar --sort=name --mtime="@$SOURCE_DATE_EPOCH" --transform='s|^\./||' --owner=0 --group=0 --numeric-owner -cf "$tar_archive" -C build/extracted .
|
||||
|
||||
gzip -n -9 "$tar_archive"
|
||||
|
||||
elif [ "$1" == "native" ]; then
|
||||
|
||||
./gradlew nativeCompile \
|
||||
--no-daemon \
|
||||
--max-workers=1 \
|
||||
-Dkotlin.compiler.execution.strategy=in-process \
|
||||
--no-build-cache \
|
||||
-Dorg.gradle.caching=false \
|
||||
-Dgraalvm.native-image.build-time=2026-01-01T00:00:00Z \
|
||||
-Porg.gradle.java.installations.auto-download=false \
|
||||
-Porg.gradle.java.installations.auto-detect=false
|
||||
|
||||
strip --strip-all \
|
||||
--remove-section=.note.gnu.build-id \
|
||||
--remove-section=.comment \
|
||||
--remove-section=.gnu_debuglink \
|
||||
--remove-section=.annobin.notes \
|
||||
--remove-section=.gnu.build.attributes \
|
||||
--remove-section=.note.ABI-tag \
|
||||
build/native/nativeCompile/signal-cli
|
||||
|
||||
chmod +x build/native/nativeCompile/signal-cli
|
||||
reset_file_dates
|
||||
tar --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --numeric-owner -cf "build/signal-cli-${VERSION}-Linux-native.tar" -C build/native/nativeCompile signal-cli
|
||||
gzip -n -9 "build/signal-cli-${VERSION}-Linux-native.tar"
|
||||
|
||||
elif [ "$1" == "client" ]; then
|
||||
|
||||
cd client
|
||||
cargo build --release --locked
|
||||
cd ..
|
||||
chmod +x client/target/release/signal-cli-client
|
||||
mkdir -p build
|
||||
reset_file_dates
|
||||
tar --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --numeric-owner -cf "build/signal-cli-${VERSION}-Linux-client.tar" -C client/target/release signal-cli-client
|
||||
gzip -n -9 "build/signal-cli-${VERSION}-Linux-client.tar"
|
||||
|
||||
else
|
||||
echo "Unknown build variant '$1'"
|
||||
exit 1
|
||||
fi
|
||||
7
reproducible-builds/native.Containerfile
Normal file
7
reproducible-builds/native.Containerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM container-registry.oracle.com/graalvm/native-image:25.0.2@sha256:4c0d5919f6840d89721274eb8cf81962faa2f870b816967e6732e2a151b150d8
|
||||
ENV SOURCE_DATE_EPOCH=1767225600
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_CTYPE=en_US.UTF-8
|
||||
COPY --chmod=0700 reproducible-builds/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
WORKDIR /signal-cli
|
||||
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh", "native" ]
|
||||
44
reproducible-builds/verify.sh
Executable file
44
reproducible-builds/verify.sh
Executable file
@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../"
|
||||
cd "$ROOT_DIR"
|
||||
rm -rf "$ROOT_DIR/github"
|
||||
mkdir -p "$ROOT_DIR/github"
|
||||
|
||||
VERSION=$(sed -n 's/\s*version\s*=\s*"\(.*\)".*/\1/p' build.gradle.kts | tail -n1)
|
||||
|
||||
echo "Download latest release from GitHub..."
|
||||
|
||||
curl -L --fail "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}.tar.gz" -o "github/signal-cli-${VERSION}.tar.gz"
|
||||
curl -L --fail "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}-Linux-native.tar.gz" -o "github/signal-cli-${VERSION}-Linux-native.tar.gz"
|
||||
curl -L --fail "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}-Linux-client.tar.gz" -o "github/signal-cli-${VERSION}-Linux-client.tar.gz"
|
||||
|
||||
./reproducible-builds/build.sh
|
||||
|
||||
rm -f {github,dist}/VERSION
|
||||
|
||||
echo "commit: $(git rev-parse HEAD)"
|
||||
|
||||
echo "sha256 hashes of GitHub release:"
|
||||
sha256sum github/*
|
||||
echo "sha256 hashes of locally built files:"
|
||||
sha256sum dist/*
|
||||
|
||||
reproducible=true
|
||||
for file in $(cd github && find . -type f); do
|
||||
if diff "github/$file" "dist/$file" >/dev/null 2>&1; then
|
||||
echo -e "\e[32m[+] '$(basename "$file")' matches!\e[0m"
|
||||
elif [[ "$file" =~ "native" ]]; then
|
||||
echo -e "\e[33m[-] '$(basename "$file")' doesn't match! (not supported yet)\e[0m"
|
||||
reproducible=false
|
||||
else
|
||||
echo -e "\e[31m[-] '$(basename "$file")' doesn't match!\e[0m"
|
||||
reproducible=false
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$reproducible" = false ]; then
|
||||
exit 1
|
||||
fi
|
||||
@ -8,7 +8,7 @@ public class BaseConfig {
|
||||
public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion();
|
||||
|
||||
static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT"))
|
||||
.orElse("Signal-Android/8.6.1");
|
||||
.orElse("Signal-Android/8.8.0");
|
||||
static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null
|
||||
? "signal-cli"
|
||||
: PROJECT_NAME + "/" + PROJECT_VERSION;
|
||||
|
||||
@ -22,6 +22,7 @@ import net.sourceforge.argparse4j.impl.Arguments;
|
||||
import net.sourceforge.argparse4j.inf.ArgumentParserException;
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
|
||||
import org.asamk.signal.commands.exceptions.CaptchaRejectedErrorException;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||
import org.asamk.signal.commands.exceptions.RateLimitErrorException;
|
||||
@ -128,6 +129,7 @@ public class Main {
|
||||
case IOErrorException ioErrorException -> 3;
|
||||
case UntrustedKeyErrorException untrustedKeyErrorException -> 4;
|
||||
case RateLimitErrorException rateLimitErrorException -> 5;
|
||||
case CaptchaRejectedErrorException captchaRejectedErrorException -> 6;
|
||||
case null -> 2;
|
||||
};
|
||||
}
|
||||
|
||||
@ -3,9 +3,9 @@ package org.asamk.signal.commands;
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.commands.exceptions.CaptchaRejectedErrorException;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.api.CaptchaRejectedException;
|
||||
import org.asamk.signal.output.OutputWriter;
|
||||
@ -41,8 +41,8 @@ public class SubmitRateLimitChallengeCommand implements JsonRpcLocalCommand {
|
||||
} catch (IOException e) {
|
||||
throw new IOErrorException("Submit challenge error: " + e.getMessage(), e);
|
||||
} catch (CaptchaRejectedException e) {
|
||||
throw new UserErrorException(
|
||||
"Captcha rejected, it may be outdated, already used or solved from a different IP address.");
|
||||
throw new CaptchaRejectedErrorException(
|
||||
"Captcha rejected, it may be outdated, already used or solved from a different IP address.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package org.asamk.signal.commands.exceptions;
|
||||
|
||||
import org.asamk.signal.manager.api.CaptchaRejectedException;
|
||||
|
||||
public final class CaptchaRejectedErrorException extends CommandException {
|
||||
|
||||
public CaptchaRejectedErrorException(final String message, final CaptchaRejectedException cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package org.asamk.signal.commands.exceptions;
|
||||
|
||||
public sealed abstract class CommandException extends Exception permits IOErrorException, RateLimitErrorException, UnexpectedErrorException, UntrustedKeyErrorException, UserErrorException {
|
||||
public sealed abstract class CommandException extends Exception permits CaptchaRejectedErrorException, IOErrorException, RateLimitErrorException, UnexpectedErrorException, UntrustedKeyErrorException, UserErrorException {
|
||||
|
||||
public CommandException(final String message) {
|
||||
super(message);
|
||||
|
||||
@ -916,6 +916,14 @@ public class DbusManagerImpl implements Manager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUnidentifiedKeepAlive(final String token) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUnidentifiedKeepAlive(final String token) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCallEventListener(final CallEventListener listener) {
|
||||
// Not supported over DBus
|
||||
|
||||
@ -100,6 +100,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
||||
|
||||
public void initObjects() {
|
||||
exportObjects();
|
||||
m.addUnidentifiedKeepAlive("dbus");
|
||||
if (!noReceiveOnStart) {
|
||||
subscribeReceive();
|
||||
}
|
||||
@ -116,6 +117,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
m.removeUnidentifiedKeepAlive("dbus");
|
||||
if (dbusMessageHandler != null) {
|
||||
m.removeReceiveHandler(dbusMessageHandler);
|
||||
dbusMessageHandler = null;
|
||||
@ -700,9 +702,13 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
||||
} catch (IOException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
} catch (RateLimitException e) {
|
||||
throw new Error.Failure(e.getMessage()
|
||||
+ ", retry at "
|
||||
+ DateUtils.formatTimestamp(e.getNextAttemptTimestamp()));
|
||||
final var retryAfterMilliseconds = e.getRetryAfterMilliseconds();
|
||||
throw new Error.Failure(e.getMessage() + (
|
||||
retryAfterMilliseconds == null
|
||||
? ""
|
||||
: ", retry at " + DateUtils.formatTimestamp(System.currentTimeMillis()
|
||||
+ retryAfterMilliseconds)
|
||||
));
|
||||
}
|
||||
|
||||
return numbers.stream().map(number -> registered.get(number).uuid() != null).toList();
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "AdminDelete")
|
||||
public record JsonAdminDelete(
|
||||
@Deprecated String targetAuthor, String targetAuthorNumber, String targetAuthorUuid, long targetSentTimestamp
|
||||
) {
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
@JsonSchema(title = "Attachment")
|
||||
record JsonAttachment(
|
||||
String contentType,
|
||||
String filename,
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
@JsonSchema(title = "AttachmentData")
|
||||
public record JsonAttachmentData(
|
||||
String data
|
||||
) {}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
@ -10,6 +11,7 @@ import java.util.List;
|
||||
|
||||
import static org.asamk.signal.manager.util.Utils.callIdUnsigned;
|
||||
|
||||
@JsonSchema(title = "CallMessage")
|
||||
record JsonCallMessage(
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL) Offer offerMessage,
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL) Answer answerMessage,
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonSchema(title = "Contact")
|
||||
public record JsonContact(
|
||||
String number,
|
||||
String uuid,
|
||||
@ -26,6 +28,7 @@ public record JsonContact(
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL) JsonInternal internal
|
||||
) {
|
||||
|
||||
@JsonSchema(title = "Profile")
|
||||
public record JsonProfile(
|
||||
long lastUpdateTimestamp,
|
||||
String givenName,
|
||||
@ -36,6 +39,7 @@ public record JsonContact(
|
||||
String mobileCoinAddress
|
||||
) {}
|
||||
|
||||
@JsonSchema(title = "Internal")
|
||||
public record JsonInternal(
|
||||
List<String> capabilities,
|
||||
String unidentifiedAccessMode,
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
import org.asamk.signal.util.Util;
|
||||
|
||||
@JsonSchema(title = "ContactAddress")
|
||||
public record JsonContactAddress(
|
||||
String type,
|
||||
String label,
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
@JsonSchema(title = "ContactAvatar")
|
||||
public record JsonContactAvatar(JsonAttachment attachment, boolean isProfile) {
|
||||
|
||||
static JsonContactAvatar from(MessageEnvelope.Data.SharedContact.Avatar avatar) {
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
import org.asamk.signal.util.Util;
|
||||
|
||||
@JsonSchema(title = "ContactEmail")
|
||||
public record JsonContactEmail(String value, String type, String label) {
|
||||
|
||||
static JsonContactEmail from(MessageEnvelope.Data.SharedContact.Email email) {
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
import org.asamk.signal.util.Util;
|
||||
|
||||
@JsonSchema(title = "ContactName")
|
||||
public record JsonContactName(
|
||||
String nickname, String given, String family, String prefix, String suffix, String middle
|
||||
) {
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
import org.asamk.signal.util.Util;
|
||||
|
||||
@JsonSchema(title = "ContactPhone")
|
||||
public record JsonContactPhone(String value, String type, String label) {
|
||||
|
||||
static JsonContactPhone from(MessageEnvelope.Data.SharedContact.Phone phone) {
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonSchema(title = "DataMessage")
|
||||
record JsonDataMessage(
|
||||
long timestamp,
|
||||
String message,
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
@JsonSchema(title = "EditMessage")
|
||||
record JsonEditMessage(long targetSentTimestamp, JsonDataMessage dataMessage) {
|
||||
|
||||
static JsonEditMessage from(MessageEnvelope.Edit editMessage, Manager m) {
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
@JsonSchema(title = "Error")
|
||||
public record JsonError(String message, String type) {
|
||||
|
||||
public static JsonError from(Throwable exception) {
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
@JsonSchema(title = "GroupInfo")
|
||||
record JsonGroupInfo(String groupId, String groupName, int revision, String type) {
|
||||
|
||||
static JsonGroupInfo from(MessageEnvelope.Data.GroupContext groupContext, Manager m) {
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "Mention")
|
||||
public record JsonMention(@Deprecated String name, String number, String uuid, int start, int length) {
|
||||
|
||||
static JsonMention from(MessageEnvelope.Data.Mention mention) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
@ -10,6 +11,7 @@ import org.asamk.signal.manager.api.UntrustedIdentityException;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "MessageEnvelope")
|
||||
public record JsonMessageEnvelope(
|
||||
@Deprecated String source,
|
||||
String sourceNumber,
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
@JsonSchema(title = "Payment")
|
||||
public record JsonPayment(String note, byte[] receipt) {
|
||||
|
||||
static JsonPayment from(MessageEnvelope.Data.Payment payment) {
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "PinMessage")
|
||||
public record JsonPinMessage(
|
||||
@Deprecated String targetAuthor,
|
||||
String targetAuthorNumber,
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonSchema(title = "PollCreate")
|
||||
public record JsonPollCreate(
|
||||
String question, boolean allowMultiple, List<String> options
|
||||
) {
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
@JsonSchema(title = "PollTerminate")
|
||||
public record JsonPollTerminate(long targetSentTimestamp) {
|
||||
|
||||
static JsonPollTerminate from(MessageEnvelope.Data.PollTerminate pollTerminate) {
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "PollVote")
|
||||
public record JsonPollVote(
|
||||
@Deprecated String author,
|
||||
String authorNumber,
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
@JsonSchema(title = "Preview")
|
||||
public record JsonPreview(String url, String title, String description, JsonAttachment image) {
|
||||
|
||||
static JsonPreview from(MessageEnvelope.Data.Preview preview) {
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "Quote")
|
||||
public record JsonQuote(
|
||||
long id,
|
||||
@Deprecated String author,
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
@JsonSchema(title = "QuotedAttachment")
|
||||
public record JsonQuotedAttachment(
|
||||
String contentType, String filename, @JsonInclude(JsonInclude.Include.NON_NULL) JsonAttachment thumbnail
|
||||
) {
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "Reaction")
|
||||
public record JsonReaction(
|
||||
String emoji,
|
||||
@Deprecated String targetAuthor,
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonSchema(title = "ReceiptMessage")
|
||||
record JsonReceiptMessage(long when, boolean isDelivery, boolean isRead, boolean isViewed, List<Long> timestamps) {
|
||||
|
||||
static JsonReceiptMessage from(MessageEnvelope.Receipt receiptMessage) {
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.RecipientAddress;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "RecipientAddress")
|
||||
public record JsonRecipientAddress(String uuid, String number, String username) {
|
||||
|
||||
public static JsonRecipientAddress from(RecipientAddress address) {
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
@JsonSchema(title = "RemoteDelete")
|
||||
record JsonRemoteDelete(long timestamp) {}
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.GroupId;
|
||||
import org.asamk.signal.manager.api.SendMessageResult;
|
||||
|
||||
@JsonSchema(title = "SendMessageResult")
|
||||
public record JsonSendMessageResult(
|
||||
JsonRecipientAddress recipientAddress,
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL) String groupId,
|
||||
@ -18,6 +20,7 @@ public record JsonSendMessageResult(
|
||||
}
|
||||
|
||||
public static JsonSendMessageResult from(SendMessageResult result, GroupId groupId) {
|
||||
final var rateLimitRetryAfterMilliseconds = result.rateLimitRetryAfterMilliseconds();
|
||||
return new JsonSendMessageResult(JsonRecipientAddress.from(result.address()),
|
||||
groupId != null ? groupId.toBase64() : null,
|
||||
result.isSuccess()
|
||||
@ -32,7 +35,7 @@ public record JsonSendMessageResult(
|
||||
? Type.INVALID_PRE_KEY_FAILURE
|
||||
: Type.IDENTITY_FAILURE,
|
||||
result.proofRequiredFailure() != null ? result.proofRequiredFailure().getToken() : null,
|
||||
result.proofRequiredFailure() != null ? result.proofRequiredFailure().getRetryAfterSeconds() : null);
|
||||
rateLimitRetryAfterMilliseconds == null ? null : Math.ceilDiv(rateLimitRetryAfterMilliseconds, 1000L));
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonSchema(title = "SharedContact")
|
||||
public record JsonSharedContact(
|
||||
JsonContactName name,
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL) JsonContactAvatar avatar,
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
import org.asamk.signal.util.Hex;
|
||||
|
||||
@JsonSchema(title = "Sticker")
|
||||
public record JsonSticker(String packId, int stickerId) {
|
||||
|
||||
static JsonSticker from(MessageEnvelope.Data.Sticker sticker) {
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "StoryContext")
|
||||
record JsonStoryContext(
|
||||
String authorNumber, String authorUuid, long sentTimestamp
|
||||
) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.Color;
|
||||
import org.asamk.signal.manager.api.GroupId;
|
||||
@ -8,6 +9,7 @@ import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@JsonSchema(title = "StoryMessage")
|
||||
record JsonStoryMessage(
|
||||
boolean allowsReplies,
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL) String groupId,
|
||||
|
||||
@ -2,6 +2,7 @@ package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
@ -9,6 +10,7 @@ import org.asamk.signal.manager.api.RecipientAddress;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "SyncDataMessage")
|
||||
record JsonSyncDataMessage(
|
||||
@Deprecated String destination,
|
||||
String destinationNumber,
|
||||
|
||||
@ -9,12 +9,15 @@ import org.asamk.signal.manager.api.RecipientAddress;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
enum JsonSyncMessageType {
|
||||
CONTACTS_SYNC,
|
||||
GROUPS_SYNC,
|
||||
REQUEST_SYNC
|
||||
}
|
||||
|
||||
@JsonSchema(title = "SyncMessage")
|
||||
record JsonSyncMessage(
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncDataMessage sentMessage,
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncStoryMessage sentStoryMessage,
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "SyncReadMessage")
|
||||
record JsonSyncReadMessage(
|
||||
@Deprecated String sender, String senderNumber, String senderUuid, long timestamp
|
||||
) {
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "SyncStoryMessage")
|
||||
record JsonSyncStoryMessage(
|
||||
String destinationNumber, String destinationUuid, @JsonUnwrapped JsonStoryMessage dataMessage
|
||||
) {
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.TextStyle;
|
||||
|
||||
@JsonSchema(title = "TextStyle")
|
||||
public record JsonTextStyle(String style, int start, int length) {
|
||||
|
||||
static JsonTextStyle from(TextStyle textStyle) {
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.GroupId;
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
@JsonSchema(title = "TypingMessage")
|
||||
record JsonTypingMessage(
|
||||
String action, long timestamp, @JsonInclude(JsonInclude.Include.NON_NULL) String groupId
|
||||
) {
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import io.micronaut.jsonschema.JsonSchema;
|
||||
|
||||
import org.asamk.signal.manager.api.MessageEnvelope;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonSchema(title = "UnpinMessage")
|
||||
public record JsonUnpinMessage(
|
||||
@Deprecated String targetAuthor, String targetAuthorNumber, String targetAuthorUuid, long targetSentTimestamp
|
||||
) {
|
||||
|
||||
@ -12,6 +12,7 @@ import org.asamk.signal.commands.Command;
|
||||
import org.asamk.signal.commands.JsonRpcMultiCommand;
|
||||
import org.asamk.signal.commands.JsonRpcRegistrationCommand;
|
||||
import org.asamk.signal.commands.JsonRpcSingleCommand;
|
||||
import org.asamk.signal.commands.exceptions.CaptchaRejectedErrorException;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||
import org.asamk.signal.commands.exceptions.RateLimitErrorException;
|
||||
@ -39,6 +40,7 @@ public class SignalJsonRpcCommandHandler {
|
||||
private static final int IO_ERROR = -3;
|
||||
private static final int UNTRUSTED_KEY_ERROR = -4;
|
||||
private static final int RATELIMIT_ERROR = -5;
|
||||
private static final int CAPTCHA_REJECTED_ERROR = -6;
|
||||
|
||||
private final Manager m;
|
||||
private final MultiAccountManager c;
|
||||
@ -258,6 +260,10 @@ public class SignalJsonRpcCommandHandler {
|
||||
case RateLimitErrorException e -> throw new JsonRpcException(new JsonRpcResponse.Error(RATELIMIT_ERROR,
|
||||
e.getMessage(),
|
||||
getErrorDataNode(objectMapper, result)));
|
||||
case CaptchaRejectedErrorException e -> throw new JsonRpcException(new JsonRpcResponse.Error(
|
||||
CAPTCHA_REJECTED_ERROR,
|
||||
e.getMessage(),
|
||||
getErrorDataNode(objectMapper, result)));
|
||||
case UnexpectedErrorException e -> {
|
||||
logger.error("Command execution failed with unexpected error", e);
|
||||
throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INTERNAL_ERROR,
|
||||
|
||||
@ -25,10 +25,12 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@ -43,6 +45,9 @@ public class SignalJsonRpcDispatcherHandler {
|
||||
|
||||
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 String connectionKeepAliveToken = "jsonrpc-" + UUID.randomUUID();
|
||||
private final List<Manager> keepAliveManagers = new ArrayList<>();
|
||||
private boolean connectionActive = true;
|
||||
private SignalJsonRpcCommandHandler commandHandler;
|
||||
|
||||
public SignalJsonRpcDispatcherHandler(
|
||||
@ -69,6 +74,10 @@ public class SignalJsonRpcDispatcherHandler {
|
||||
c.addOnManagerAddedHandler(m -> callEventHandlers.forEach((subscriptionId, handlers) -> handlers.add(
|
||||
createCallEventHandler(m, subscriptionId))));
|
||||
|
||||
c.getManagers().forEach(this::registerKeepAlive);
|
||||
c.addOnManagerAddedHandler(this::registerKeepAlive);
|
||||
c.addOnManagerRemovedHandler(this::unregisterKeepAlive);
|
||||
|
||||
handleConnection();
|
||||
}
|
||||
|
||||
@ -82,6 +91,8 @@ public class SignalJsonRpcDispatcherHandler {
|
||||
final var currentThread = Thread.currentThread();
|
||||
m.addClosedListener(currentThread::interrupt);
|
||||
|
||||
registerKeepAlive(m);
|
||||
|
||||
handleConnection();
|
||||
}
|
||||
|
||||
@ -200,14 +211,29 @@ public class SignalJsonRpcDispatcherHandler {
|
||||
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() {
|
||||
try {
|
||||
jsonRpcReader.readMessages((method, params) -> commandHandler.handleRequest(objectMapper, method, params),
|
||||
response -> logger.debug("Received unexpected response for id {}", response.getId()));
|
||||
} finally {
|
||||
connectionActive = false;
|
||||
receiveHandlers.forEach((_subscriptionId, handlers) -> handlers.forEach(this::unsubscribeReceiveHandler));
|
||||
receiveHandlers.clear();
|
||||
unsubscribeAllCallEvents();
|
||||
keepAliveManagers.forEach(m -> m.removeUnidentifiedKeepAlive(connectionKeepAliveToken));
|
||||
keepAliveManagers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -129,16 +129,19 @@ public class CommandUtil {
|
||||
} else {
|
||||
message = "Invalid captcha given.";
|
||||
}
|
||||
if (e.getNextAttemptTimestamp() > 0) {
|
||||
message += "\nNext Captcha may be provided at " + DateUtils.formatTimestamp(e.getNextAttemptTimestamp());
|
||||
if (e.getNextVerificationAttemptMilliseconds() > 0) {
|
||||
message += "\nNext Captcha may be provided at " + DateUtils.formatTimestamp(System.currentTimeMillis()
|
||||
+ e.getNextVerificationAttemptMilliseconds());
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
public static String getRateLimitMessage(final RateLimitException e) {
|
||||
String message = "Rate limit reached";
|
||||
if (e.getNextAttemptTimestamp() > 0) {
|
||||
message += "\nNext attempt may be tried at " + DateUtils.formatTimestamp(e.getNextAttemptTimestamp());
|
||||
final var retryAfterMilliseconds = e.getRetryAfterMilliseconds();
|
||||
if (retryAfterMilliseconds != null) {
|
||||
message += "\nNext attempt may be tried at " + DateUtils.formatTimestamp(System.currentTimeMillis()
|
||||
+ retryAfterMilliseconds);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
@ -59,8 +59,10 @@ public class SendMessageResultUtils {
|
||||
if (sendMessageResults.hasOnlyUntrustedIdentity()) {
|
||||
throw new UntrustedKeyErrorException("Failed to send message due to untrusted identities");
|
||||
} else if (sendMessageResults.hasOnlyRateLimitFailure()) {
|
||||
final var retryAfter = sendMessageResults.maxRateLimitRetryAfterMilliseconds();
|
||||
final var nextAttempt = retryAfter == null ? 0L : System.currentTimeMillis() + retryAfter;
|
||||
throw new RateLimitErrorException("Failed to send message due to rate limiting",
|
||||
new RateLimitException(0));
|
||||
new RateLimitException(nextAttempt));
|
||||
} else {
|
||||
throw new UserErrorException("Failed to send message");
|
||||
}
|
||||
@ -106,11 +108,14 @@ public class SendMessageResultUtils {
|
||||
.map(ProofRequiredException.Option::toString)
|
||||
.collect(Collectors.joining(", ")),
|
||||
failure.getToken(),
|
||||
failure.getRetryAfterSeconds());
|
||||
Math.ceilDiv(failure.getRetryAfterMilliseconds(), 1000L));
|
||||
} else if (result.isNetworkFailure()) {
|
||||
return String.format("Network failure for \"%s\"", identifier);
|
||||
} else if (result.isRateLimitFailure()) {
|
||||
return String.format("Rate limit failure for \"%s\"", identifier);
|
||||
final var retryAfter = result.rateLimitRetryAfterMilliseconds();
|
||||
return retryAfter != null ? String.format("Rate limit failure for \"%s\", retry after %d seconds",
|
||||
identifier,
|
||||
Math.ceilDiv(retryAfter, 1000L)) : String.format("Rate limit failure for \"%s\"", identifier);
|
||||
} else if (result.isUnregisteredFailure()) {
|
||||
return String.format("Unregistered user \"%s\"", identifier);
|
||||
} else if (result.isIdentityFailure()) {
|
||||
|
||||
@ -1564,6 +1564,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "kotlin.reflect.jvm.internal.impl.km.jvm.internal.JvmMetadataExtensions"
|
||||
},
|
||||
{
|
||||
"type": "kotlin.reflect.jvm.internal.impl.load.java.ErasedOverridabilityCondition"
|
||||
},
|
||||
@ -1626,6 +1629,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "kotlinx.coroutines.CancelledContinuation"
|
||||
},
|
||||
{
|
||||
"type": "kotlinx.coroutines.CompletedExceptionally",
|
||||
"fields": [
|
||||
@ -1681,6 +1687,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "kotlinx.coroutines.internal.LimitedDispatcher"
|
||||
},
|
||||
{
|
||||
"type": "kotlinx.coroutines.internal.LockFreeLinkedListNode",
|
||||
"fields": [
|
||||
@ -1714,6 +1723,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "kotlinx.coroutines.internal.ThreadSafeHeap"
|
||||
},
|
||||
{
|
||||
"type": "kotlinx.coroutines.scheduling.CoroutineScheduler",
|
||||
"fields": [
|
||||
@ -1728,6 +1740,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "kotlinx.coroutines.scheduling.CoroutineScheduler$Worker"
|
||||
},
|
||||
{
|
||||
"type": "kotlinx.coroutines.scheduling.WorkQueue"
|
||||
},
|
||||
{
|
||||
"type": "libcore.io.Memory"
|
||||
},
|
||||
@ -4898,6 +4916,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.bouncycastle.jcajce.provider.kdf.PBKDF2$Mappings",
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.bouncycastle.jcajce.provider.kdf.SCRYPT$Mappings",
|
||||
"methods": [
|
||||
@ -10115,6 +10142,9 @@
|
||||
{
|
||||
"glob": "META-INF/services/java.util.spi.ResourceBundleControlProvider"
|
||||
},
|
||||
{
|
||||
"glob": "META-INF/services/kotlin.reflect.jvm.internal.impl.km.internal.extensions.MetadataExtensions"
|
||||
},
|
||||
{
|
||||
"glob": "META-INF/services/kotlin.reflect.jvm.internal.impl.resolve.ExternalOverridabilityCondition"
|
||||
},
|
||||
|
||||
@ -0,0 +1,114 @@
|
||||
package org.asamk.signal.json;
|
||||
|
||||
import org.asamk.signal.manager.api.RateLimitException;
|
||||
import org.asamk.signal.manager.api.RecipientAddress;
|
||||
import org.asamk.signal.manager.api.RecipientIdentifier;
|
||||
import org.asamk.signal.manager.api.SendMessageResult;
|
||||
import org.asamk.signal.manager.api.SendMessageResults;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
class JsonSendMessageResultTest {
|
||||
|
||||
private static final RecipientAddress ADDRESS = new RecipientAddress(null, null, "+15551234567", null);
|
||||
|
||||
@Test
|
||||
void rateLimitFailureSurfacesRetryAfterSeconds() {
|
||||
var result = new SendMessageResult(ADDRESS,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
new RateLimitException(3600_000L),
|
||||
null,
|
||||
false);
|
||||
|
||||
var json = JsonSendMessageResult.from(result);
|
||||
|
||||
assertEquals(JsonSendMessageResult.Type.RATE_LIMIT_FAILURE, json.type());
|
||||
assertEquals(3600L, json.retryAfterSeconds());
|
||||
assertNull(json.token());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rateLimitFailureWithoutRetryAfterLeavesFieldNull() {
|
||||
var result = new SendMessageResult(ADDRESS,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
new RateLimitException(null),
|
||||
null,
|
||||
false);
|
||||
|
||||
var json = JsonSendMessageResult.from(result);
|
||||
|
||||
assertEquals(JsonSendMessageResult.Type.RATE_LIMIT_FAILURE, json.type());
|
||||
assertNull(json.retryAfterSeconds());
|
||||
}
|
||||
|
||||
@Test
|
||||
void successLeavesRetryAfterNull() {
|
||||
var result = new SendMessageResult(ADDRESS, true, false, false, false, null, null, false);
|
||||
|
||||
var json = JsonSendMessageResult.from(result);
|
||||
|
||||
assertEquals(JsonSendMessageResult.Type.SUCCESS, json.type());
|
||||
assertNull(json.retryAfterSeconds());
|
||||
}
|
||||
|
||||
@Test
|
||||
void aggregateReturnsLongestRetryAfter() {
|
||||
var small = rateLimited("+15551234567", 60L);
|
||||
var big = rateLimited("+15559876543", 3600L);
|
||||
var unknown = rateLimited("+15550000000", null);
|
||||
|
||||
var aggregate = new SendMessageResults(1L,
|
||||
Map.of(new RecipientIdentifier.Uuid(UUID.randomUUID()), List.of(small, big, unknown)));
|
||||
|
||||
assertEquals(3600L, aggregate.maxRateLimitRetryAfterMilliseconds());
|
||||
}
|
||||
|
||||
@Test
|
||||
void aggregateReturnsNullWhenNoRetryAfter() {
|
||||
var aggregate = new SendMessageResults(1L,
|
||||
Map.of(new RecipientIdentifier.Uuid(UUID.randomUUID()), List.of(rateLimited("+15551234567", null))));
|
||||
|
||||
assertNull(aggregate.maxRateLimitRetryAfterMilliseconds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression for a bug where the aggregate helper could overlook the longest
|
||||
* wait if only some recipients reported a value. Ensures the max is picked
|
||||
* across any mix — which is what downstream captcha/rate-limit clients rely on.
|
||||
*/
|
||||
@Test
|
||||
void aggregatePicksMaxEvenWhenSomeValuesAreNull() {
|
||||
var withValue = rateLimited("+15551111111", 7200L);
|
||||
var withoutValue = rateLimited("+15552222222", null);
|
||||
var alsoWithValue = rateLimited("+15553333333", 120L);
|
||||
|
||||
var aggregate = new SendMessageResults(1L,
|
||||
Map.of(new RecipientIdentifier.Uuid(UUID.randomUUID()),
|
||||
List.of(withoutValue, withValue, alsoWithValue)));
|
||||
|
||||
assertEquals(7200L, aggregate.maxRateLimitRetryAfterMilliseconds());
|
||||
}
|
||||
|
||||
private static SendMessageResult rateLimited(String number, Long retryAfterSeconds) {
|
||||
return new SendMessageResult(new RecipientAddress(null, null, number, null),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
new RateLimitException(retryAfterSeconds),
|
||||
null,
|
||||
false);
|
||||
}
|
||||
}
|
||||
@ -496,6 +496,14 @@ class SubscribeCallEventsTest {
|
||||
public void addClosedListener(Runnable l) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUnidentifiedKeepAlive(String token) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUnidentifiedKeepAlive(String token) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream retrieveAttachment(String id) {
|
||||
return null;
|
||||
@ -741,8 +749,8 @@ class SubscribeCallEventsTest {
|
||||
|
||||
assertEquals(1, manager1.addCount.get(), "manager1 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
|
||||
assertEquals(2, multi.addedHandlers.size(), "should register onManagerAdded handlers");
|
||||
// Registers onManagerAdded handlers for receive, call events, and keep-alive
|
||||
assertEquals(3, multi.addedHandlers.size(), "should register onManagerAdded handlers");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user