diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1924d640..23e19225 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,14 @@ name: signal-cli CI -on: [push, pull_request] +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: @@ -8,13 +16,81 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '11', '14' ] + java: [ '21', '24' ] steps: - - uses: actions/checkout@v1 - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: ${{ matrix.java }} - - name: Build with Gradle - run: ./gradlew build + - uses: actions/checkout@v4 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: ${{ matrix.java }} + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + 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@v4 + with: + name: signal-cli-archive-${{ matrix.java }} + path: build/distributions/signal-cli-*.tar.gz + + build-graalvm: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + version: 'latest' + java-version: '21' + cache: 'gradle' + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Build with Gradle + run: ./gradlew --no-daemon nativeCompile + - name: Archive production artifacts + uses: actions/upload-artifact@v4 + 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@v4 + - name: Install rust + run: rustup default stable + - name: Build client + run: cargo build --release --verbose + - name: Archive production artifacts + uses: actions/upload-artifact@v4 + with: + name: signal-cli-client-${{ matrix.os }} + path: | + client/target/release/signal-cli-client + client/target/release/signal-cli-client.exe diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index edf8a5a2..f778268a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,13 +2,17 @@ name: "CodeQL" on: push: - branches: [master, ] + branches: [ master ] pull_request: # The branches below must be a subset of the branches above - branches: [master] + branches: [ master ] schedule: - cron: '0 7 * * 4' +permissions: + contents: read # to fetch code (actions/checkout) + security-events: write + jobs: analyse: name: Analyse @@ -16,45 +20,41 @@ jobs: steps: - - name: Setup Java JDK - uses: actions/setup-java@v1.3.0 - with: - java-version: 11 + - name: Setup Java JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: 21 - - name: Checkout repository - uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 + - name: Checkout repository + uses: actions/checkout@v4 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - # Override language selection by uncommenting this and choosing your languages - # with: - # languages: go, javascript, csharp, python, cpp, java + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + #- run: | + # make bootstrap + # make release - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..b7ce03f1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,257 @@ +name: release + +on: + push: + tags: + - v* + +permissions: + contents: write # to fetch code (actions/checkout) and create release + +env: + IMAGE_NAME: signal-cli + IMAGE_REGISTRY: ghcr.io/asamk + REGISTRY_USER: ${{ github.actor }} + REGISTRY_PASSWORD: ${{ github.token }} + +jobs: + + 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 + runs-on: ubuntu-latest + permissions: + contents: write + + outputs: + signal_cli_version: ${{ steps.cli_ver.outputs.version }} + release_id: ${{ steps.create_release.outputs.id }} + + steps: + + - name: Download signal-cli build from CI workflow + uses: actions/download-artifact@v4 + + - name: Get signal-cli version + id: cli_ver + 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: 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-*/ + + - name: Create release + id: create_release + uses: actions/create-release@v1 + 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` + draft: true + + - name: Upload 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 }}.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 + + - name: Upload Linux native 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-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 + +# - 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 + + build-container: + needs: ci_wf + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + - name: Download signal-cli build from CI workflow + uses: actions/download-artifact@v4 + + - 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 + mkdir -p build/install/ + mv ./signal-cli-"${GITHUB_REF_NAME#v}"/ 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 + oci: true + + - name: Push To GHCR + uses: redhat-actions/push-to-registry@v2 + id: push + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.IMAGE_REGISTRY }} + username: ${{ env.REGISTRY_USER }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Echo outputs + run: | + echo "${{ toJSON(steps.push.outputs) }}" + + build-container-native: + needs: ci_wf + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + - name: Download signal-cli build from CI workflow + uses: actions/download-artifact@v4 + + - name: Get signal-cli version + id: cli_ver + run: | + ver="${GITHUB_REF_NAME#v}" + echo "version=${ver}" >> $GITHUB_OUTPUT + + - name: Move archive file + run: | + mkdir -p build/native/nativeCompile/ + chmod +x ./signal-cli-native/signal-cli + mv ./signal-cli-native/signal-cli build/native/nativeCompile/ + + - 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 + oci: true + + - name: Push To GHCR + uses: redhat-actions/push-to-registry@v2 + id: push + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.IMAGE_REGISTRY }} + username: ${{ env.REGISTRY_USER }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Echo outputs + run: | + echo "${{ toJSON(steps.push.outputs) }}" diff --git a/.gitignore b/.gitignore index 3dc9875b..2b6774c1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ local.properties .project .settings/ out/ +.DS_Store +/bin/ +/test-config/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 4953eaca..aa627792 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -4,8 +4,9 @@ + - - + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..74d0bdc0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1192 @@ +# Changelog + +## [Unreleased] + +## [0.13.18] - 2025-07-16 + +Requires libsignal-client version 0.76.3. + +### Added + +- Added `--view-once` parameter to send command to send view once images + +### Fixed + +- Handle rate limit exception correctly when querying usernames + +### Improved + +- Shut down when dbus daemon connection goes away unexpectedly +- In daemon mode, exit immediately if account check fails at startup +- Improve behavior when sending to devices that have no available prekeys + +## [0.13.17] - 2025-06-28 + +Requires libsignal-client version 0.76.0. + +### Fixed + +- Fix issue when loading an older inactive group +- Close attachment input streams after upload +- Fix storage sync behavior with unhandled fields + +### Changed + +- Improve behavior when pin data doesn't exist on the server + +## [0.13.16] - 2025-06-07 + +Requires libsignal-client version 0.73.2. + +### Changed + +- Ensure every sent message gets a unique timestamp + +## [0.13.15] - 2025-05-08 + +Requires libsignal-client version 0.70.0. + +### Fixed + +- Fix native access warning with Java 24 +- Fix storage sync loop due to old removed e164 field + +### Changed + +- Increased compatibility of native build with older/virtual CPUs + +## [0.13.14] - 2025-04-06 + +Requires libsignal-client version 0.68.1. + +### Fixed + +- Fix pre key import from old data files + +### Changed + +- Use websocket connection instead of HTTP for more requests +- Improve handling of messages with decryption error + +## [0.13.13] - 2025-02-28 + +Requires libsignal-client version 0.66.2. + +### Added +- Allow setting nickname and note with `updateContact` command + +### Fixed +- Fix syncing nickname, note and expiration timer +- Fix check for registered users with a proxy +- Improve handling of storage records not yet supported by signal-cli +- Fix contact sync for networks requiring proxy + +## [0.13.12] - 2025-01-18 + +Requires libsignal-client version 0.65.2. + +### Fixed + +- Fix sync of contact nick name +- Fix incorrectly marking recipients as unregistered after sync +- Fix cause of database deadlock (Thanks @dukhaSlayer) +- Fix parsing of account query param in events http endpoint + +### Changed + +- Enable sqlite WAL journal\_mode for improved performance + +## [0.13.11] - 2024-12-26 + +Requires libsignal-client version 0.64.0. + +### Fixed +- Fix issue with receiving messages that have an invalid destination + +## [0.13.10] - 2024-11-30 + +Requires libsignal-client version 0.62.0. + +### Fixed + +- Fix receiving some unusual contact sync messages +- Fix receiving expiration timer updates + +### Improved +- Add support for new storage encryption scheme + +## [0.13.9] - 2024-10-28 + +### Fixed + +- Fix verify command + +## [0.13.8] - 2024-10-26 + +Requires libsignal-client version 0.58.2 + +### Fixed + +- Fix sending large text messages +- Fix setting message expiration timer with recent Signal apps + +### Improved + +- Add group name and timestamps on json message (Thanks @jailson-dias) + +## [0.13.7] - 2024-09-28 + +Requires libsignal-client version 0.58.0 + +### Fixed + +- Fix unnecessary log output +- Fix issue with CDSI sync with invalid token + +## [0.13.6] - 2024-09-08 + +Requires libsignal-client version 0.56.0 + +### Improved + +- Send sync message to linked devices when sending read/viewed receipts + +### Fixed + +- Fix issue with sending to some groups +- Fix CDSI sync if no token is stored +- Fix possible db dead lock during storage sync + +## [0.13.5] - 2024-07-25 + +Requires libsignal-client version 0.52.2 + +### Fixed + +- Fixed device linking, due to new feature flag + +## [0.13.4] - 2024-06-06 + +**Attention**: Now requires libsignal-client version 0.47.0 + +### Improved + +- Improve username update error message +- Update groups when using listGroups command + +### Fixed + +- Update libsignal to fix graalvm native startup +- Fix issue with saving username link +- Fix sendMessageRequestResponse type parameter parsing in JSON RPC mode +- Fix getUserStatus command with only username parameter + +## [0.13.3] - 2024-04-19 + +**Attention**: Now requires libsignal-client version 0.44.0 + +### Added + +- Support for reading contact nickname and notes +- Add `--internal` and `--detailed` parameters to `listContacts` command + +### Fixed + +- Fix issue with sending messages when a new session is created + +## [0.13.2] - 2024-03-23 + +**Attention**: Now requires libsignal-client version 0.40.1 + +### Added + +- Add `--username` parameter to `getUserStatus` command + +### Fixed + +- Fixed setting and retrieving PIN after server changes + +## [0.13.1] - 2024-02-27 + +### Added + +- Add `--reregister` parameter to force registration of an already registered account + +### Fixed + +- Fixed rare issue with duplicate PNIs during migration + +### Improved + +- Show information when requesting voice verification without prior SMS verification +- Username can now be set with an explicit discriminator (e.g. testname.000) +- Improve behavior when PNI prekeys upload fails +- Improve `submitRateLimitChallenge` error message if captcha is rejected by server +- Only retry messages after an identity was trusted + +### Changed + +- Default number sharing to NOBODY, to match the official apps behavior. + +## [0.13.0] - 2024-02-18 + +**Attention**: Now requires Java 21 and libsignal-client version 0.39.2 + +### Breaking changes + +- Sending to the self number (+XXXX) now behaves the same as the `--note-to-self` parameter. To get the previous + behavior with notification, the `--notify-self` parameter can be added. + +### Added + +- New `--hidden` parameter for `removeContact` command +- New `--notify-self` parameter for `send` command, for sending a non-sync message when self is part of the recipients + or groups. +- New `--unrestricted-unidentified-sender`, `--discoverable-by-number`, `--number-sharing`, `--username` + and `--delete-username` parameter for `updateAccount` command +- New `--bus-name` parameter for `daemon` command to use another D-Bus bus name +- New `getAvatar` and `getSticker` commands to get avatar and sticker images +- New `sendMessageRequestResponse` command to accept/delete message requests + +### Fixed + +- Improve issue with stale prekeys and receiving messages to PNI address + +### Improved + +- Better shutdown handling after Ctrl+C and SIGTERM +- Implemented full remote storage sync. + Provides better contact and settings sync for linked devices. +- `listContacts` doesn't list unregistered users anymore + +## [0.12.8] - 2024-02-06 + +### Fixes + +- Update user agent + +## [0.12.7] - 2023-12-15 + +**Attention**: Now requires native libsignal-client version 0.36.1 + +### Fixes + +- Fix linking to an existing account + +## [0.12.6] - 2023-12-11 + +### Fixes + +- Fix linking to an existing account +- Fix migration from old account data + +## [0.12.5] - 2023-11-21 + +### Fixes + +- Fix issue with joining groups by group link for new accounts +- Fix receiving address of shared contact +- Fix receiving sync edit messages in groups + +### Changed + +- Create safety numbers based on ACI instead of phone number + +## [0.12.4] - 2023-10-22 + +### Fixes + +- Prevent ConcurrentModificationException +- Update captcha help text + +## [0.12.3] - 2023-10-17 + +### Added + +- Added `startChangeNumber` and `finishChangeNumber` commands to switch to another phone number +- Added `--quote-attachment` parameter to `send` command +- Added support for scannable safety numbers based on serviceId +- Added `EditMessageReceived` signal for D-Bus interface +- Added new exit code `5` for rate limit failures +- Added full CDSI refresh to get current ACI/PNIs for known numbers regularly + +### Fixed + +- Correctly respond with delivery receipts for edit messages + +### Changed + +- JSON-RPC requests are now executed in parallel. + Clients should make sure to use the `id` field to get the correct response for a request. + +## [0.12.2] - 2023-09-30 + +**Attention**: Now requires native libsignal-client version 0.32.1 + +### Added + +- Added `--receive-mode` parameter for `jsonRpc` command +- Add `libsignal_client_path` build property to override libsignal-client jar file + +### Changed + +- `jsonRpc` command now supports multi-account mode including registering and linking + +## [0.12.1] - 2023-08-26 + +### Added + +- New `addStickerPack` command + +### Fixed + +- Fixed some issues with upgrading from older accounts + +### Changed + +- Reverted receive notification in JSON-RPC to old format, only explicit subscriptions should use the new format + +## [0.12.0] - 2023-08-11 + +**Attention**: Now requires native libsignal-client version 0.30.0 + +### Breaking changes + +- Adapt receive subscription notification in JSON-RPC to have payload in result field + - Before: `{"jsonrpc":"2.0","method":"receive","params":{"envelope":{ ... },"account":"+XXX","subscription":0}}` + - After: + `{"jsonrpc":"2.0","method":"receive","params":{"subscription":0,"result":{"envelope":{ ... },"account":"+XXX"}}}` + +### Added + +- Manage identities via DBus (Thanks @bublath) +- Added support for SVR2 PINs + +### Fixed + +- Fixed finishLink/receive/register/verify commands for JSON-RPC +- Update to the latest libsignal to fix various issues + +## [0.11.11] - 2023-05-24 + +**Attention**: Now requires native libsignal-client version 0.25.0 + +### Added + +- New `--text-style` and `--quote-text-style` flags for `send` command + +### Fixed + +- Fixed migration of older account files +- Fix deleting old unregistered recipient + +## [0.11.10] - 2023-05-11 + +**Attention**: Now requires native libsignal-client version 0.23.1 + +### Added + +- Support for receiving and sending edit messages with `--edit-timestamp` + +## [0.11.9.1] - 2023-04-23 + +### Fixed + +- Fix build with Java 20 + +## [0.11.9] - 2023-04-22 + +### Fixed + +- Workaround issue with linking to newer app versions + +## [0.11.8] - 2023-04-05 + +### Added + +- Added file attachment attributes to JSON output (Thanks @signals-from-outer-space) + +### Fixed + +- Scrub E164 number from dbus paths +- Fix sending large text messages to multiple recipient +- Fix deleting old group in dbus mode +- Fix issue with unknown identity serviceId + +### Improved + +- Relaxed Content-Type check in http daemon mode (Thanks @cedb) + +## [0.11.7] - 2023-02-19 + +**Attention**: Now requires native libsignal-client version 0.22.0 + +### Fixed + +- Fix issue with missing pni identity key +- Fix graalvm sqlite issue (Thanks @Marvin A. Ruder) +- Fix issue with forgetting recipient + +### Changed + +- Allow JSON-RPC commands without account param if only one account exists + +## [0.11.6] - 2022-12-18 + +### Added + +- Allow using data URIs for updateGroup/updateProfile +- New alive check endpoint for http daemon (Thanks @ced-b) + +### Fixed + +- Registration with voice verification now works if no system locale is set +- Fixed retrieving attachments in JSON RPC mode (Thanks @ced-b) + +## [0.11.5.1] - 2022-11-09 + +### Fixed + +- Fix updating from older signal-cli version + +## [0.11.5] - 2022-11-07 + +**Attention**: Now requires native libsignal-client version 0.21.1 + +### Added + +- Add `--http` flag to `daemon` command to provide a JSON-RPC http endpoint (`/api/v1/rpc`). (Thanks @ced-b) +- The `receive` method is now also available in JSON-RPC daemon mode, for polling new messages. +- Add `getAttachment` command to get attachment file base64 encoded. (Thanks @ced-b) +- Add `--disable-send-log` to disable the message send log. +- Add `--story` to `sendReaction` command, to react to stories. +- Add `--story-timestamp` and `--story-author` to `send` command, to reply to stories. +- Add `--max-messages` to `receive` command, to only receive a certain number of messages. + +### Changed + +- Send long text messages as attachment instead. This matches the behavior of the official clients. +- Store attachments with a file extension, for common file types. + +## [0.11.4] - 2022-10-19 + +### Added + +- Approve/Refuse group join requests, using same interface as adding/removing members +- Add --ignore-stories flag to prevent receiving story messages + +### Fixed + +- Fixed issue with receiving messages that can't be decrypted +- Do not discard incoming group join messages + +### Improved + +- Add code to receive new PNI after change number + +## [0.11.3] - 2022-10-07 + +### Fixed + +- Fix sending messages to groups (in non-daemon mode) +- Fix updating from older signal-cli version +- Fix issue with handling decryption error message +- Fix graalvm native build (Thanks @bentolor) + +## [0.11.2] - 2022-10-06 + +### Fixed + +- Update user agent version to work with new Signal-Server check + +## [0.11.1] - 2022-10-05 + +### Fixed + +- Fix sending group messages +- Fix store migration issue on Windows +- Fix building fat jars + +## [0.11.0] - 2022-10-02 + +**Attention**: Now requires native libsignal-client version 0.20.0 + +### Breaking changes + +- Changed meaning of `-v` flag from `--version` to `--verbose`. + So now extended logging can be achieved with `-vv`. +- Remove deprecated fallback to reading from stdin if no message body is given. + To read a message from stdin, use the `--message-from-stdin` flag. + +### Added + +- Migrate PIN to new KBS enclave when Signal updates it +- Add `--scrub-log` flag to remove possibly sensitive information from the log +- Add `sendPaymentNotification` dbus method + +### Fixed + +- Fix an issue where messages were sent without sender phone number + +### Changed + +- Store data except base account data in sqlite database +- Use new CDSI for contact discovery in compat mode + +## [0.10.11] - 2022-08-17 + +**Attention**: Now requires native libsignal-client version 0.19.3 + +### Added + +- Output content of received story messages + +## [0.10.10] - 2022-07-30 + +**Attention**: Now requires native libsignal-client version 0.18.1 + +### Fixed + +- Fix setPin/removePin commands which broke due to server side changes +- Workaround GraalVM 22.2.0 issue with daemon connection + +## [0.10.9] - 2022-07-16 + +### Changed + +- updateAccount command checks self number and PNI after updating account attributes + +### Fixed + +- Fixed small issue with syncing contacts from storage +- Fixed issue with opening older account files + +## [0.10.8] - 2022-06-13 + +### Added + +- Attachments can now be given as data: URIs with base64 data instead of just file paths (Thanks @KevinRoebert) +- `version` command can now be used on the commandline, in addition to the `--version` flag. + In the next version the current short form `-v` will change its meaning to `--verbose`! + +### Improved + +- An account can now be registered on both LIVE and STAGING environment in the same config directory. +- Logging output for registering has been extended. + +## [0.10.7] - 2022-05-29 + +### Added + +- Added profile information to `listContacts` command output +- Added filter flags for `listContacts` command +- New `sendPaymentNotification` command to send payment receipt blobs +- New `--given-name` and `--family-name` parameters for `updateContact` command +- Implement sending link previews with `--preview-url`, `--preview-title` parameters for the `send` command +- New `--send-read-receipts` parameter for `receive` and `daemon` commands for automatically marking received messages + as read + +### Fixed + +- Issue with endless growing `pre-keys-pni` data directory + +## [0.10.6] - 2022-05-19 + +**Attention**: Now requires native libsignal-client version 0.17 + +### Added + +- Check if account is used on the environment it was registered (live or staging) +- New command `deleteLocalAccountData` to delete all local data of an unregistered account +- New parameter `-g` for `listGroups` command to filter for specific groups + +### Fixed + +- Fix deleting a recipient which has no uuid + +### Changed + +- Show warning when sending a message and no profile name has been set. + (A profile name may become mandatory in the future) +- After blocking a contact/group the profile key is now rotated +- Only update profile keys from authoritative group changes + +## [0.10.5] - 2022-04-11 + +**Attention**: Now requires native libsignal-client version 0.15 + +### Added + +- New `--ban`, `--unban` flags for `updateGroup` command to ban users from joining by group link + +### Fixed + +- Fix plain text output of blocked group ids +- Fix error output in case of rate limiting +- Fix error when creating a group with no members +- Fix adding recent Signal-Desktop versions as linked devices + +## [0.10.4.2] - 2022-03-17 + +### Fixed + +- Crash in json output when receiving message from untrusted identity +- Fix multi account commands for newly created accounts + +## [0.10.4.1] - 2022-03-02 + +### Fixed + +- Linking to current apps (which currently don't include a PNI identity yet) +- Show better error message when --target-timestamp is missing for sendReceipt + +## [0.10.4] - 2022-02-20 + +### Added + +- Implement support for change number as linked device +- Add `--message-from-stdin` flag for send command. The current behavior of + reading from stdin if the `-m` flag is not given, will be removed in a future + version. + +### Changed + +- Align receive timeout behavior for dbus client with cli and JSON-RPC. + Timeout is reset by every incoming message +- Renamed `error` field in json receive response to `exception` + +### Fixed + +- Prevent a stale jsonrpc connection from interfering with message receiving + +## [0.10.3] - 2022-02-01 + +### Added + +- MessageSendLog to cache sent message for 24h. + For resending messages in case the recipient fails to decrypt the message. +- New global `--log-file` parameter to write logs to a separate file. + (`--verbose` can be used to increase the log level) + +### Improved + +- Better subscription handling for JSON-RPC `subscribeReceive` command + +### Fixed + +- Output receipt data for unsealed sender receipts again +- Fix sending message resend requests to devices that happen to have the same deviceId + +## [0.10.2] - 2022-01-22 + +### Fixed + +- Archive old sessions/sender keys when a recipient's identity key has changed +- Fix profile fetch with an invalid LANG variable + +## [0.10.1] - 2022-01-16 + +### Added + +- Send group messages with sender keys (more efficient for larger groups) +- New command `listStickerPacks` to display all known sticker packs +- New flag `--sticker` for `send` command to send stickers + +### Changed + +- Improve exit code for message sending. + Exit with 0 status code if the message was sent successfully to at least + one recipient, otherwise exit with status code 2 or 4 (for untrusted). +- Download profiles in parallel for improved performance +- `--verbose` flag can be specified multiple times for additional log output +- Enable more security options for systemd service file +- Rename sandbox to staging environment, to match the upstream name. + +### Fixed + +- The first incoming message after registration can now always be decrypted successfully +- Ignore decryption failures from blocked contacts and don't send a resend request. + +## [0.10.0] - 2021-12-11 + +**Attention**: Now requires Java 17 and libsignal-client version 0.11 + +### Added + +- The daemon command now provides a JSON-RPC based socket interface (`--socket` and `--tcp`) +- New daemon command flag `--receive-mode` to configure when messages are received +- New daemon command flag `--no-receive-stdout` to prevent outputting messages on stdout +- New command `listAccounts` that lists all registered local accounts +- New command `removeContact` +- Extend `send` command to allow sending mentions (`--mention`) and + quotes (`--quote-timestamp`, `--quote-author`, `--quote-message`, `--quote-mention`) +- New dbus methods sendGroupTying, unregister, deleteAccount +- New dbus events MessageReceivedV2, ReceiptReceivedV2, SyncMessageReceivedV2 that provide an extras parameter with + additional message info as a key/value map +- New dbus method sendViewedReceipt (Thanks @John Freed) +- New dbus object Configuration to read and update configuration values (Thanks @John Freed) +- Payment info in json receive output (Thanks @technillogue) +- `-c` alias for `--config` (Thanks @technillogue) + +### Changed + +- libzkgroup dependency is no longer required +- Renamed `-u` and `--username` flags to `-a` and `--account` to prevent confusion with upcoming Signal usernames. The + old flags are also still supported for now. +- Respect phone number sharing mode and unlisted state set by primary device +- Adapt register command to reactivate account if possible. +- dbus-java now uses Java 16 native unix sockets, which should provide better cross-platform compatibility +- If sending to a recipient fails (e.g. unregistered) signal-cli now exits with a success exit code and prints + additional information about the failure. + +### Fixed + +- Registering an existing unregistered account now works reliably in daemon mode +- Fixed an issue with loading some old config files without UUID +- More reliable send behavior if some recipients are unregistered + +## [0.9.2] - 2021-10-24 + +### Fixed + +- dbus `listNumbers` method works again + +### Changed + +- Improved provisioning error handling if the last steps fail +- Adapt behavior of receive command as dbus client to match normal mode +- Update captcha url for proof required handling + +## [0.9.1] - 2021-10-16 + +**Attention**: Now requires native libzkgroup version 0.8 + +### Added + +- New command `updateConfiguration` which allows setting configurations for linked devices +- Improved dbus daemon for group handling, groups are now exported as separate dbus objects +- Linked devices can be managed via dbus +- New dbus methods sendTyping and sendReadReceipt (Thanks @JtheSaw) +- New dbus methods submitRateLimitChallenge, isRegistered, listDevices, setExpirationTimer, sendContacts, + sendSyncRequest, uploadStickerPack, setPin and removePin (Thanks @John Freed) +- New dbus method getSelfNumber + +### Fixed + +- Do not send message resend request to own device +- Allow message from pending member to accept group invitations +- Fix issue which could cause signal-cli to repeatedly send the same delivery receipts +- Reconnect websocket after connection loss + +### Changed + +- Use new provisioning URL `sgnl://linkdevice` instead of `tsdevice:/` +- The gradle command to build a graalvm native image is now `./gradlew nativeCompile` + +## [0.9.0] - 2021-09-12 + +**Attention**: Now requires native libsignal-client version 0.9 + +### Breaking changes + +- Removed deprecated `--json` parameter, use global parameter `--output=json` instead +- Json output format of `listGroups` command changed: + Members are now arrays of `{"number":"...","uuid":"..."}` objects instead of arrays of strings. +- Removed deprecated fallback data paths, only `$XDG_DATA_HOME/signal-cli` is used now + For those still using the old paths (`$HOME/.config/signal`, `$HOME/.config/textsecure`) you need to move those to the + new location. + +### Added + +- New global parameter `--trust-new-identities=always` to allow trusting any new identity key without verification +- New parameter `--device-name` for `updateAccount` command to change the device name (also works for the primary + device) +- New SignalControl DBus interface, to register/verify/link new accounts +- New `jsonRpc` command that provides a JSON-RPC based API on stdout/stdin +- Support for announcement groups +- New parameter `--set-permission-send-messages` for `updateGroup` to create an announcement group +- New `sendReceipt` command to send read and viewed receipts +- Support for receiving sender key messages, mobile apps can now send messages more efficiently with server-side fan-out + to groups with signal-cli members. +- Support for reading data from remote Signal storage. Now v2 groups will be shown after linking a new device. +- New `submitRateLimitChallenge` command that can be used to lift some rate-limits by solving a captcha + +### Fixed + +- Store identity key correctly when sending a message after a recipient has changed keys + +## [0.8.5] - 2021-08-07 + +### Added + +- Source name is included in JSON receive output (Thanks @technillogue) + +### Fixed + +- Allow updateContact command to only set expiration timer without requiring a name parameter + +## [0.8.4.1] - 2021-06-20 + +### Fixed + +- Incorrect error handling in register command + +## [0.8.4] - 2021-06-13 + +**Attention**: Now requires native libsignal-client version 0.8.1 + +### Added + +- New parameters for `updateGroup` command for group v2 features: + `--description`, `--remove-member`, `--admin`, `--remove-admin`, `--reset-link`, `--link`, + `--set-permission-add-member`, `--set-permission-edit-details`, `--expiration` +- New `--admin` parameter for `quitGroup` to set an admin before leaving the group +- New `--delete` parameter for `quitGroup`, to delete the local group data +- New 'sendTyping' command to send typing indicators + +### Fixed + +- Fixed issue that prevented registration with invalid locales +- Prevent last admin of a group from leaving the group +- All commands now show a short description with `--help` +- Now a hint is shown if messages aren't received regularly +- Group edit conflicts are now resolved automatically + +## [0.8.3] - 2021-05-13 + +### Fixed + +- Upgrading from account files with older profiles +- Building native image with graalvm + +## [0.8.2] - 2021-05-11 + +### Added + +- A manual page for the DBus interface (Thanks @bublath, @exquo) +- Remote message delete command (Thanks @adaptivegarage) +- sendSyncRequest command to request complete contact/group list from primary device +- New `--delete-account` argument for unregister (Dangerous) +- New `--family-name` argument for updateProfile + +### Fixed + +- Sending reaction to group (Thanks @adaptivegarage) +- Displaying of address for messages from untrusted identities +- Handling of recipient number or uuid changes (e.g. after account deletions) +- Only respond to sync requests from primary device +- Display of quit group messages + +### Changed + +- Unlimited strength crypto is now enabled automatically for JREs that require it (Thanks @i-infra) +- Only one identity key is stored per recipient and updated from profile (to match app behavior) +- updateContact, block and unblock are now disabled for linked devices +- After registering an empty profile is created so new groups can be joined immediately +- If message decryption fails due to a broken session, the session is automatically renewed +- Rework account storage for better reliability +- Improved device linking flow + - Allow relinking existing account + - Encrypt/Decrypt device names + +## [0.8.1] - 2021-03-02 + +### Added + +- New dbus commands: updateProfile, listNumbers, getContactNumber, quitGroup, isContactBlocked, isGroupBlocked, + isMember, joinGroup (Thanks @bublath) +- Additional output for json format: shared contacts (Thanks @Atomic-Bean) +- Improved plain text output to be more consistent and synced messages are now indented + +### Fixed + +- Issue with broken sessions with linked devices + +### Changed + +- Behavior of `trust` command improved, when trusting a new identity key all other known keys for + the same number are removed. + +## [0.8.0] - 2021-02-14 + +**Attention**: For all signal protocol functionality an additional native library is now +required: [libsignal-client](https://github.com/signalapp/libsignal-client/). +See https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal for more information. + +### Added + +- Experimental support for building a GraalVM native image +- Support for setting profile about text and emoji + +### Fixed + +- Incorrect error message when removing a non-existent profile avatar + +## [0.7.4] - 2021-01-19 + +### Changed + +- Notify linked devices after profile has been updated + +### Fixed + +- After registering a new account, receiving messages didn't work + You may have to register and verify again to fix the issue. +- Creating v1 groups works again + +## [0.7.3] - 2021-01-17 + +### Added + +- `getUserStatus` command to check if a user is registered on Signal (Thanks @Atomic-Bean) +- Global `--verbose` flag to increase log level +- Global `--output=json` flag, currently supported by `receive`, `daemon`, `getUserStatus`, `listGroups` +- `--note-to-self` flag for `send` command to send a note to linked devices +- More info for received messages in json output: stickers, viewOnce, typing, remoteDelete + +### Changed + +- signal-cli can now be used without the username `-u` flag + For daemon command all local users will be exposed as dbus objects. + If only one local user exists, all other commands will use that user, + otherwise a user has to be specified. +- Messages sent to self number will be sent as normal Signal messages again, to + send a sync message, use the new `--note-to-self` flag +- Ignore messages with group context sent by non group member +- Profile key is sent along with all direct messages +- In json output unnecessary fields that are null are now omitted + +### Fixed + +- Disable registration lock before removing the PIN +- Fix PIN hash version to match the official clients. + If you had previously set a PIN you need to set it again to be able to unlock the registration lock later. +- Issue with saving account file after linking + +## [0.7.2] - 2020-12-31 + +### Added + +- Implement new registration lock PIN with `setPin` and `removePin` (with KBS) +- Include quotes, mentions and reactions in json output (Thanks @Atomic-Bean) + +### Fixed + +- Retrieve avatars for v2 groups +- Download attachment thumbnail for quoted attachments + +## [0.7.1] - 2020-12-21 + +### Added + +- Accept group invitation with `updateGroup -g GROUP_ID` +- Decline group invitation with `quitGroup -g GROUP_ID` +- Join group via invitation link `joinGroup --uri https://signal.group/#...` + +### Fixed + +- Include group ids for v2 groups in json output + +## [0.7.0] - 2020-12-15 + +### Added + +Support for groups of new type/v2 + +- Sending and receiving +- Updating name, avatar and adding members with `updateGroup` +- Quit group and decline invitation with `quitGroup` +- In the `listGroups` output v2 groups can be recognized by the longer groupId + +**Attention**: For the new group support to work the native libzkgroup library is required. +See https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal for more information. + +### Fixed + +- Rare NullPointerException when receiving messages + +## [0.6.12] - 2020-11-22 + +### Added + +- Show additional message content (view once, remote delete, mention, â€Ļ) for received messages +- `--captcha` parameter for `register` command, required for some IP ranges + +### Changed + +- Profile keys are now stored separately from contact list +- Receipts from normal and unidentified messages now have the same format in json output + +### Fixed + +- Issue where some messages were sent with an old counter index + +## [0.6.11] - 2020-10-14 + +- Fix issue with receiving message reactions + +## [0.6.10] - 2020-09-11 + +- Fix issue when retrieving profiles +- Workaround issue with libzkgroup on platforms other than linux x86_64 + +## [0.6.9] - 2020-09-10 + +- Minor bug fixes and improvements +- dbus functionality now works on FreeBSD +- signal-cli now requires Java 11 + +**Warning: this version only works on Linux x86_64, will be fixed in 0.6.10** + +## [0.6.8] - 2020-05-22 + +- Switch to hypfvieh dbus-java, which doesn't require a native library anymore (drops requirement of + libmatthew-unix-java) +- Bugfixes for messages with uuids +- Add `--expiration` parameter to `updateContact` command to set expiration timer + +## [0.6.7] - 2020-04-03 + +- Send command now returns the timestamp of the sent message +- DBus daemon: Publish received sync message to SyncMessageReceived signal +- Fix issue with resolving e164/uuid addresses for sessions +- Fix pack key length for sticker upload + +## [0.6.6] - 2020-03-29 + +- Added listContacts command +- Added block/unblock commands to block contacts and groups +- Added uploadStickerPack command to upload sticker packs (see man page for more details) +- Full support for sending and receiving unidentified sender messages +- Support for message reactions with emojis +- Internal: support recipients with uuids + +## [0.6.5] - 2019-11-11 + +Supports receiving messages sent with unidentified sender + +## [0.6.4] - 2019-11-02 + +- Fix rounding error for attachment ids in json output +- Add additional info to json output +- Add commands to update profile name and avatar +- Add command to update contact names + +## [0.6.3] - 2019-09-05 + +Bug fixes and small improvements + +## [0.6.2] - 2018-12-16 + +- Fixes sending of group messages + +## [0.6.1] - 2018-12-09 + +- Added getGroupIds dbus command +- Use "NativePRNG" pseudo random number generator, if available +- Switch default data path: + `$XDG_DATA_HOME/signal-cli` (`$HOME/.local/share/signal-cli`) + Existing data paths will continue to work (used as fallback) + +## [0.6.0] - 2018-05-03 + +- Simple json output +- dbus signal for receiving messages +- Registration lock PIN +- Output quoted message + +## [0.5.6] - 2017-06-16 + +* new listGroups command +* Support for attachments with file names +* Support for complete contacts sync +* Support for contact verification sync +* DBus interface: +* Get/Set group info +* Get/Set contact info + +## [0.5.5] - 2017-02-18 + +- fix receiving messages on linked devices +- add unregister command + +## [0.5.4] - 2017-02-17 + +- Fix linking of new devices + +## [0.5.3] - 2017-01-29 + +* New commandline parameter for receive: --ignore-attachments +* Updated dependencies + +## [0.5.2] - 2016-12-16 + +- Add support for group info requests +- Improve closing of file streams + +## [0.5.1] - 2016-11-18 + +- Support new safety numbers (https://whispersystems.org/blog/safety-number-updates/) +- Add a man page +- Support sending disappearing messages, if the recipient has activated it + +## [0.5.0] - 2016-08-29 + +- Check if a number is registered on Signal, before adding it to a group +- Prevent sending to groups that the user has quit +- Commands to trust new identity keys (see README) +- Messages from untrusted identities are stored on disk and decrypted when the user trusts the identity +- Timestamps shown in ISO 8601 format + +## [0.4.1] - 2016-07-18 + +- Fix issue with creating groups +- Lock config file to prevent parallel access by multiple instances of signal-cli +- Improve return codes, always return non-zero code, when sending failed + +## [0.4.0] - 2016-06-19 + +- Linking to Signal-Desktop and Signal-Android is now possible (Provisioning) +- Added a contact store, mainly for syncing contacts with linked devices (editing not yet possible via cli) +- Avatars for groups and contacts are now stored (new folder "avatars" in the config path) + +## [0.3.1] - 2016-04-03 + +- Fix running with Oracle JRE 8 +- Fix registering +- Fix unicode warning when compiling with non utf8 locale + +## [0.3.0] - 2016-04-02 + +- Renamed textsecure-cli to signal-cli, following the rename of libtextsecure-java to libsignal-service-java +- The experimental dbus interface was also renamed to org.asamk.Signal +- Upload new prekeys to the server, when there are less than 20 left, prekeys are needed to create new sessions + +## [0.2.1] - 2016-02-10 + +- Improve dbus service +- New command line argument --config to specify config directory + +## [0.2.0] - 2015-12-30 + +Added an experimental dbus interface, for sending and receiving messages (The interface is unstable and may change with +future releases). + +This release works with Java 7 and 8. + +## [0.1.0] - 2015-11-28 + +Add support for creating/updating groups and sending to them + +## [0.0.5] - 2015-11-21 + +- Add receive timeout commandline parameter +- Show message group info + +## [0.0.4] - 2015-09-22 + +## [0.0.3] - 2015-08-07 + +## [0.0.2] - 2015-07-08 + +First release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..c18cd8a8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +# Question + +If you have a question you can ask it in the [GitHub discussions page](https://github.com/AsamK/signal-cli/discussions) + +# Report a bug + +- Search [existing issues](https://github.com/AsamK/signal-cli/issues?q=is%3Aissue) if it has been reported already +- If you're unable to find an open issue addressing the + problem, [open a new one](https://github.com/AsamK/signal-cli/issues/new). + - Be sure to include a **title and clear description**, as much relevant information as possible. + - Specify the versions of signal-cli, libsignal-client (if self-compiled), JDK and OS you're using + - Specify if it's the normal java or the graalvm native version. + - Run the failing command with `--verbose` flag to get a more detailed log output and include that in the bug report + +# Pull request + +- Code style should match the existing code, IntelliJ users can use the auto formatter +- Separate PRs should be opened for each implemented feature or bug fix diff --git a/Containerfile b/Containerfile new file mode 100644 index 00000000..361c2667 --- /dev/null +++ b/Containerfile @@ -0,0 +1,11 @@ +FROM docker.io/azul/zulu-openjdk:21-jre-headless + +LABEL org.opencontainers.image.source=https://github.com/AsamK/signal-cli +LABEL org.opencontainers.image.description="signal-cli provides an unofficial commandline, dbus and JSON-RPC interface for the Signal messenger." +LABEL org.opencontainers.image.licenses=GPL-3.0-only + +RUN useradd signal-cli --system --create-home --home-dir /var/lib/signal-cli +ADD build/install/signal-cli /opt/signal-cli + +USER signal-cli +ENTRYPOINT ["/opt/signal-cli/bin/signal-cli", "--config=/var/lib/signal-cli"] diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 00000000..c2420609 --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1,4 @@ +github: AsamK +liberapay: asamk +ko_fi: asamk +#bitcoin: bc1qykae53fry8a8ycgdzgv0rlxfc959hmmllvz698 diff --git a/README.md b/README.md index fb0d9d88..c7228c09 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,159 @@ # signal-cli -signal-cli is a commandline interface for [libsignal-service-java](https://github.com/WhisperSystems/libsignal-service-java). It supports registering, verifying, sending and receiving messages. -To be able to link to an existing Signal-Android/signal-cli instance, signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because libsignal-service-java does not yet support [provisioning as a slave device](https://github.com/WhisperSystems/libsignal-service-java/pull/21). +signal-cli is a commandline interface for the [Signal messenger](https://signal.org/). +It supports registering, verifying, sending and receiving messages. +signal-cli uses a [patched libsignal-service-java](https://github.com/Turasa/libsignal-service-java), +extracted from the [Signal-Android source code](https://github.com/signalapp/Signal-Android/tree/main/libsignal-service). For registering you need a phone number where you can receive SMS or incoming calls. -signal-cli is primarily intended to be used on servers to notify admins of important events. For this use-case, it has a dbus interface, that can be used to send messages from any programming language that has dbus bindings. + +signal-cli is primarily intended to be used on servers to notify admins of important events. +For this use-case, it has a daemon mode with JSON-RPC interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-jsonrpc.5.adoc)) +and D-BUS interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)) . +For the JSON-RPC interface there's also a simple [example client](https://github.com/AsamK/signal-cli/tree/master/client), written in Rust. + +signal-cli needs to be kept up-to-date to keep up with Signal-Server changes. +The official Signal clients expire after three months and then the Signal-Server can make incompatible changes. +So signal-cli releases older than three months may not work correctly. ## Installation -You can [build signal-cli](#building) yourself, or use the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and Windows. For Arch Linux there is also a [package in AUR](https://aur.archlinux.org/packages/signal-cli/) and there is a [FreeBSD port](https://www.freshports.org/net-im/signal-cli) available as well. You need to have at least JRE 8 installed, to run signal-cli. +You can [build signal-cli](#building) yourself or use +the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and +Windows. There's also a [docker image and some Linux packages](https://github.com/AsamK/signal-cli/wiki/Binary-distributions) provided by the community. + +System requirements: + +- at least Java Runtime Environment (JRE) 21 +- native library: libsignal-client + + The native libs are bundled for x86_64 Linux (with recent enough glibc), Windows and MacOS. For other + systems/architectures + see: [Provide native lib for libsignal](https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal) ### Install system-wide on Linux + See [latest version](https://github.com/AsamK/signal-cli/releases). + ```sh export VERSION= wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz sudo tar xf signal-cli-"${VERSION}".tar.gz -C /opt sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/ ``` + You can find further instructions on the Wiki: + - [Quickstart](https://github.com/AsamK/signal-cli/wiki/Quickstart) -- [DBus Service](https://github.com/AsamK/signal-cli/wiki/DBus-service) ## Usage -Important: The USERNAME (your phone number) must include the country calling code, i.e. the number must start with a "+" sign. (See [Wikipedia](https://en.wikipedia.org/wiki/List_of_country_calling_codes) for a list of all country codes.) +For a complete usage overview please read +the [man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc) and +the [wiki](https://github.com/AsamK/signal-cli/wiki). + +Important: The ACCOUNT is your phone number in international format and must include the country calling code. Hence it +should start with a "+" sign. (See [Wikipedia](https://en.wikipedia.org/wiki/List_of_country_calling_codes) for a list +of all country codes.) * Register a number (with SMS verification) - signal-cli -u USERNAME register - - You can register Signal using a land line number. In this case you can skip SMS verification process and jump directly to the voice call verification by adding the --voice switch at the end of above register command. + signal-cli -a ACCOUNT register -* Verify the number using the code received via SMS or voice, optionally add `--pin PIN_CODE` if you've added a pin code to your account + You can register Signal using a landline number. In this case, you need to follow the procedure below: + * Attempt a SMS verification process first (`signal-cli -a ACCOUNT register`) + * You will get an error `400 (InvalidTransportModeException)`, this is normal + * Wait 60 seconds + * Attempt a voice call verification by adding the `--voice` switch and wait for the call: - signal-cli -u USERNAME verify CODE + ```sh + signal-cli -a ACCOUNT register --voice + ``` + + Registering may require solving a CAPTCHA + challenge: [Registration with captcha](https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha) + +* Verify the number using the code received via SMS or voice, optionally add `--pin PIN_CODE` if you've added a pin code + to your account + + signal-cli -a ACCOUNT verify CODE * Send a message - signal-cli -u USERNAME send -m "This is a message" RECIPIENT + ```sh + signal-cli -a ACCOUNT send -m "This is a message" RECIPIENT + ``` + +* Send a message to a username, usernames need to be prefixed with `u:` + + ```sh + signal-cli -a ACCOUNT send -m "This is a message" u:USERNAME.000 + ``` * Pipe the message content from another process. - uname -a | signal-cli -u USERNAME send RECIPIENT - + uname -a | signal-cli -a ACCOUNT send --message-from-stdin RECIPIENT + * Receive messages - signal-cli -u USERNAME receive + signal-cli -a ACCOUNT receive -For more information read the [man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc) and the [wiki](https://github.com/AsamK/signal-cli/wiki). +**Hint**: The Signal protocol expects that incoming messages are regularly received (using `daemon` or `receive` +command). This is required for the encryption to work efficiently and for getting updates to groups, expiration timer +and other features. ## Storage The password and cryptographic keys are created when registering and stored in the current users home directory: -`$XDG_DATA_HOME/signal-cli/data/` (`$HOME/.local/share/signal-cli/data/`) - -For legacy users, the old config directories are used as a fallback: - - $HOME/.config/signal/data/ - - $HOME/.config/textsecure/data/ + $XDG_DATA_HOME/signal-cli/data/ + $HOME/.local/share/signal-cli/data/ ## Building -This project uses [Gradle](http://gradle.org) for building and maintaining -dependencies. If you have a recent gradle version installed, you can replace `./gradlew` with `gradle` in the following steps. +This project uses [Gradle](http://gradle.org) for building and maintaining dependencies. If you have a recent gradle +version installed, you can replace `./gradlew` with `gradle` in the following steps. 1. Checkout the source somewhere on your filesystem with - git clone https://github.com/AsamK/signal-cli.git + git clone https://github.com/AsamK/signal-cli.git 2. Execute Gradle: - ./gradlew build + ./gradlew build -3. Create shell wrapper in *build/install/signal-cli/bin*: + 2a. Create shell wrapper in *build/install/signal-cli/bin*: - ./gradlew installDist + ./gradlew installDist -4. Create tar file in *build/distributions*: + 2b. Create tar file in *build/distributions*: - ./gradlew distTar + ./gradlew distTar -## Troubleshooting -If you use a version of the Oracle JRE and get an InvalidKeyException you need to enable unlimited strength crypto. See https://stackoverflow.com/questions/6481627/java-security-illegal-key-size-or-default-parameters for instructions. + 2c. Create a fat tar file in *build/libs/signal-cli-fat*: + + ./gradlew fatJar + + 2d. Compile and run signal-cli: + + ```sh + ./gradlew run --args="--help" + ``` + +### 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 +work in all situations. + +1. [Install GraalVM and setup the environment](https://www.graalvm.org/docs/getting-started/#install-graalvm) +2. Execute Gradle: + + ./gradlew nativeCompile + + The binary is available at *build/native/nativeCompile/signal-cli* + +## FAQ and Troubleshooting + +For frequently asked questions and issues have a look at the [wiki](https://github.com/AsamK/signal-cli/wiki/FAQ). ## License diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 0607c12b..00000000 --- a/build.gradle +++ /dev/null @@ -1,76 +0,0 @@ -apply plugin: 'java' -apply plugin: 'application' -apply plugin: 'eclipse' - -sourceCompatibility = JavaVersion.VERSION_11 -targetCompatibility = JavaVersion.VERSION_11 - -mainClassName = 'org.asamk.signal.Main' - -version = '0.6.10' - -compileJava.options.encoding = 'UTF-8' - -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - implementation 'com.github.turasa:signal-service-java:2.15.3_unofficial_14' - implementation 'org.bouncycastle:bcprov-jdk15on:1.66' - implementation 'net.sourceforge.argparse4j:argparse4j:0.8.1' - implementation 'com.github.hypfvieh:dbus-java:3.2.3' - implementation 'org.slf4j:slf4j-nop:1.7.30' -} - -jar { - manifest { - attributes( - 'Implementation-Title': project.name, - 'Implementation-Version': project.version, - 'Main-Class': project.mainClassName, - ) - } -} - -run { - if (project.hasProperty("appArgs")) { - // allow passing command-line arguments to the main application e.g.: - // $ gradle run -PappArgs="['-u', '+...', 'daemon', '--json']" - args Eval.me(appArgs) - } -} - -// Find any 3rd party libraries which have released new versions -// to the central Maven repo since we last upgraded. -task checkLibVersions { - doLast { - def checked = [:] - allprojects { - configurations.each { configuration -> - configuration.allDependencies.each { dependency -> - def version = dependency.version - if (!checked[dependency]) { - def group = dependency.group - def path = group.replace('.', '/') - def name = dependency.name - def url = "https://repo1.maven.org/maven2/$path/$name/maven-metadata.xml" - try { - def metadata = new XmlSlurper().parseText(url.toURL().text) - def newest = metadata.versioning.latest; - if ("$version" != "$newest") { - println "UPGRADE {\"group\": \"$group\", \"name\": \"$name\", \"current\": \"$version\", \"latest\": \"$newest\"}" - } - } catch (FileNotFoundException e) { - logger.debug "Unable to download $url: $e.message" - } catch (org.xml.sax.SAXParseException e) { - logger.debug "Unable to parse $url: $e.message" - } - checked[dependency] = true - } - } - } - } - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..ea69d97e --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,139 @@ +plugins { + java + application + eclipse + `check-lib-versions` + id("org.graalvm.buildtools.native") version "0.10.6" +} + +allprojects { + group = "org.asamk" + version = "0.13.19-SNAPSHOT" +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + + if (!JavaVersion.current().isCompatibleWith(targetCompatibility)) { + toolchain { + languageVersion.set(JavaLanguageVersion.of(targetCompatibility.majorVersion)) + } + } +} + +application { + mainClass.set("org.asamk.signal.Main") + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED") +} + +graalvmNative { + binaries { + this["main"].run { + buildArgs.add("--install-exit-handlers") + buildArgs.add("-Dfile.encoding=UTF-8") + buildArgs.add("-J-Dfile.encoding=UTF-8") + buildArgs.add("-march=compatibility") + resources.autodetect() + configurationFileDirectories.from(file("graalvm-config-dir")) + if (System.getenv("GRAALVM_HOME") == null) { + toolchainDetection.set(true) + javaLauncher.set(javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(21)) + }) + } else { + toolchainDetection.set(false) + } + } + } +} + +val artifactType = Attribute.of("artifactType", String::class.java) +val minified = Attribute.of("minified", Boolean::class.javaObjectType) +dependencies { + attributesSchema { + attribute(minified) + } + artifactTypes.getByName("jar") { + attributes.attribute(minified, false) + } +} + +configurations.runtimeClasspath.configure { + attributes { + attribute(minified, true) + } +} +val excludePatterns = mapOf( + "libsignal-client" to setOf( + "libsignal_jni_testing_amd64.so", + "signal_jni_testing_amd64.dll", + "libsignal_jni_testing_amd64.dylib", + "libsignal_jni_testing_aarch64.dylib", + ) +) + +dependencies { + registerTransform(JarFileExcluder::class) { + from.attribute(minified, false).attribute(artifactType, "jar") + to.attribute(minified, true).attribute(artifactType, "jar") + + parameters { + excludeFilesByArtifact = excludePatterns + } + } + + implementation(libs.bouncycastle) + implementation(libs.jackson.databind) + implementation(libs.argparse4j) + implementation(libs.dbusjava) + implementation(libs.slf4j.api) + implementation(libs.slf4j.jul) + implementation(libs.logback) + implementation(project(":libsignal-cli")) +} + +configurations { + implementation { + resolutionStrategy.failOnVersionConflict() + } +} + + +tasks.withType().configureEach { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true +} + +tasks.withType { + options.encoding = "UTF-8" +} + +tasks.withType { + manifest { + attributes( + "Implementation-Title" to project.name, + "Implementation-Version" to project.version, + "Main-Class" to application.mainClass.get(), + "Enable-Native-Access" to "ALL-UNNAMED", + ) + } +} + +tasks.register("fatJar", type = Jar::class) { + archiveBaseName.set("${project.name}-fat") + exclude( + "META-INF/*.SF", + "META-INF/*.DSA", + "META-INF/*.RSA", + "META-INF/NOTICE*", + "META-INF/LICENSE*", + "META-INF/INDEX.LIST", + "**/module-info.class", + ) + duplicatesStrategy = DuplicatesStrategy.WARN + doFirst { + from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) + } + with(tasks.jar.get()) +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 00000000..3e8942b9 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,28 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask + +plugins { + `kotlin-dsl` +} + +tasks.named>("compileKotlin").configure { + compilerOptions.jvmTarget.set(JvmTarget.JVM_17) +} + +java { + targetCompatibility = JavaVersion.VERSION_17 +} + +repositories { + mavenCentral() +} + +gradlePlugin { + plugins { + register("check-lib-versions") { + id = "check-lib-versions" + implementationClass = "CheckLibVersionsPlugin" + } + } +} diff --git a/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt b/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt new file mode 100644 index 00000000..ae8ae4de --- /dev/null +++ b/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt @@ -0,0 +1,40 @@ +@file:Suppress("DEPRECATION") + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.Dependency +import javax.xml.parsers.DocumentBuilderFactory + +class CheckLibVersionsPlugin : Plugin { + override fun apply(project: Project) { + project.task("checkLibVersions") { + description = + "Find any 3rd party libraries which have released new versions to the central Maven repo since we last upgraded." + doLast { + project.configurations.flatMap { it.allDependencies } + .toSet() + .forEach { checkDependency(it) } + } + } + } + + private fun Task.checkDependency(dependency: Dependency) { + val version = dependency.version + val group = dependency.group + val path = group?.replace(".", "/") ?: "" + val name = dependency.name + val metaDataUrl = "https://repo1.maven.org/maven2/$path/$name/maven-metadata.xml" + try { + val dbf = DocumentBuilderFactory.newInstance() + val db = dbf.newDocumentBuilder() + val doc = db.parse(metaDataUrl); + val newest = doc.getElementsByTagName("latest").item(0).textContent + if (version != newest.toString()) { + println("UPGRADE {\"group\": \"$group\", \"name\": \"$name\", \"current\": \"$version\", \"latest\": \"$newest\"}") + } + } catch (e: Throwable) { + logger.debug("Unable to download or parse {}: {}", metaDataUrl, e.message) + } + } +} diff --git a/buildSrc/src/main/kotlin/ExcludeFileFromJar.kt b/buildSrc/src/main/kotlin/ExcludeFileFromJar.kt new file mode 100644 index 00000000..25862cc9 --- /dev/null +++ b/buildSrc/src/main/kotlin/ExcludeFileFromJar.kt @@ -0,0 +1,53 @@ +import org.gradle.api.artifacts.transform.* +import org.gradle.api.file.FileSystemLocation +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream + +@CacheableTransform +abstract class JarFileExcluder : TransformAction { + interface Parameters : TransformParameters { + @get:Input + var excludeFilesByArtifact: Map> + } + + @get:PathSensitive(PathSensitivity.NAME_ONLY) + @get:InputArtifact + abstract val inputArtifact: Provider + + override + fun transform(outputs: TransformOutputs) { + val fileName = inputArtifact.get().asFile.name + for (entry in parameters.excludeFilesByArtifact) { + if (fileName.startsWith(entry.key)) { + val nameWithoutExtension = fileName.substring(0, fileName.lastIndexOf(".")) + excludeFiles(inputArtifact.get().asFile, entry.value, outputs.file("${nameWithoutExtension}.jar")) + return + } + } + outputs.file(inputArtifact) + } + + private fun excludeFiles(artifact: File, excludeFiles: Set, jarFile: File) { + ZipInputStream(FileInputStream(artifact)).use { input -> + ZipOutputStream(FileOutputStream(jarFile)).use { output -> + var entry = input.nextEntry + while (entry != null) { + if (!excludeFiles.contains(entry.name)) { + output.putNextEntry(entry) + input.copyTo(output) + output.closeEntry() + } + + entry = input.nextEntry + } + } + } + } +} diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 00000000..b83d2226 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/client/Cargo.lock b/client/Cargo.lock new file mode 100644 index 00000000..01f28e0c --- /dev/null +++ b/client/Cargo.lock @@ -0,0 +1,1756 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "clap" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jsonrpsee" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fba77a59c4c644fd48732367624d1bcf6f409f9c9a286fbc71d2f1fc0b2ea16" +dependencies = [ + "jsonrpsee-core", + "jsonrpsee-http-client", + "jsonrpsee-proc-macros", + "jsonrpsee-types", + "tracing", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693c93cbb7db25f4108ed121304b671a36002c2db67dff2ee4391a688c738547" +dependencies = [ + "async-trait", + "bytes", + "futures-timer", + "futures-util", + "http", + "http-body", + "http-body-util", + "jsonrpsee-types", + "pin-project", + "rustc-hash", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tower", + "tracing", +] + +[[package]] +name = "jsonrpsee-http-client" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6962d2bd295f75e97dd328891e58fce166894b974c1f7ce2e7597f02eeceb791" +dependencies = [ + "base64", + "http-body", + "hyper", + "hyper-rustls", + "hyper-util", + "jsonrpsee-core", + "jsonrpsee-types", + "rustls", + "rustls-platform-verifier", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tower", + "url", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fa4f5daed39f982a1bb9d15449a28347490ad42b212f8eaa2a2a344a0dce9e9" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66df7256371c45621b3b7d2fb23aea923d577616b9c0e9c0b950a6ea5c2be0ca" +dependencies = [ + "http", + "serde", + "serde_json", + "thiserror 2.0.12", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs 0.26.11", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-cli-client" +version = "0.0.1" +dependencies = [ + "anyhow", + "bytes", + "clap", + "futures-util", + "jsonrpsee", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tokio-util", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "terminal_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "webpki-root-certs" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" +dependencies = [ + "webpki-root-certs 1.0.1", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86138b15b2b7d561bc4469e77027b8dd005a43dc502e9031d1f5afc8ce1f280e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +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.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +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_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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/client/Cargo.toml b/client/Cargo.toml new file mode 100644 index 00000000..aff9ede4 --- /dev/null +++ b/client/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "signal-cli-client" +version = "0.0.1" +edition = "2024" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1" +clap = { version = "4", features = ["cargo", "derive", "wrap_help"] } +serde = "1" +serde_json = "1" +tokio = { version = "1", features = ["rt", "macros", "net", "rt-multi-thread"] } +jsonrpsee = { version = "0.25", features = [ + "macros", + "async-client", + "http-client", +] } +bytes = "1" +tokio-util = "0.7" +futures-util = "0.3" +thiserror = "2" diff --git a/client/src/cli.rs b/client/src/cli.rs new file mode 100644 index 00000000..7fa5d1c5 --- /dev/null +++ b/client/src/cli.rs @@ -0,0 +1,470 @@ +use std::{ffi::OsString, net::SocketAddr}; + +use clap::{crate_version, Parser, Subcommand, ValueEnum}; + +/// JSON-RPC client for signal-cli +#[derive(Parser, Debug)] +#[command(rename_all = "kebab-case", version = crate_version!())] +pub struct Cli { + /// Account to use (for daemon in multi-account mode) + #[arg(short = 'a', long)] + pub account: Option, + + /// TCP host and port of signal-cli daemon + #[arg(long, conflicts_with = "json_rpc_http")] + pub json_rpc_tcp: Option>, + + /// UNIX socket address and port of signal-cli daemon + #[cfg(unix)] + #[arg(long, conflicts_with = "json_rpc_tcp")] + pub json_rpc_socket: Option>, + + /// HTTP URL of signal-cli daemon + #[arg(long, conflicts_with = "json_rpc_socket")] + pub json_rpc_http: Option>, + + #[arg(long)] + pub verbose: bool, + + #[command(subcommand)] + pub command: CliCommands, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Subcommand, Debug)] +#[command(rename_all = "camelCase", version = crate_version!())] +pub enum CliCommands { + AddDevice { + #[arg(long)] + uri: String, + }, + AddStickerPack { + #[arg(long)] + uri: String, + }, + #[command(rename_all = "kebab-case")] + Block { + recipient: Vec, + + #[arg(short = 'g', long)] + group_id: Vec, + }, + DeleteLocalAccountData { + #[arg(long = "ignore-registered")] + ignore_registered: Option, + }, + FinishChangeNumber { + number: String, + #[arg(short = 'v', long = "verification-code")] + verification_code: String, + + #[arg(short = 'p', long)] + pin: Option, + }, + GetAttachment { + #[arg(long)] + id: String, + #[arg(long)] + recipient: Option, + #[arg(short = 'g', long = "group-id")] + group_id: Option, + }, + GetAvatar { + #[arg(long)] + contact: Option, + #[arg(long)] + profile: Option, + #[arg(short = 'g', long = "group-id")] + group_id: Option, + }, + GetSticker { + #[arg(long = "pack-id")] + pack_id: String, + #[arg(long = "sticker-id")] + sticker_id: u32, + }, + GetUserStatus { + recipient: Vec, + #[arg(long)] + username: Vec, + }, + JoinGroup { + #[arg(long)] + uri: String, + }, + Link { + #[arg(short = 'n', long)] + name: String, + }, + ListAccounts, + ListContacts { + recipient: Vec, + #[arg(short = 'a', long = "all-recipients")] + all_recipients: bool, + #[arg(long)] + blocked: Option, + #[arg(long)] + name: Option, + }, + ListDevices, + ListGroups { + #[arg(short = 'd', long)] + detailed: bool, + #[arg(short = 'g', long = "group-id")] + group_id: Vec, + }, + ListIdentities { + #[arg(short = 'n', long)] + number: Option, + }, + ListStickerPacks, + QuitGroup { + #[arg(short = 'g', long = "group-id")] + group_id: String, + #[arg(long)] + delete: bool, + #[arg(long)] + admin: Vec, + }, + Receive { + #[arg(short = 't', long, default_value_t = 3.0)] + timeout: f64, + }, + Register { + #[arg(short = 'v', long)] + voice: bool, + #[arg(long)] + captcha: Option, + }, + RemoveContact { + recipient: String, + #[arg(long)] + forget: bool, + #[arg(long)] + hide: bool, + }, + RemoveDevice { + #[arg(short = 'd', long = "device-id")] + device_id: u32, + }, + RemovePin, + RemoteDelete { + #[arg(short = 't', long = "target-timestamp")] + target_timestamp: u64, + + recipient: Vec, + + #[arg(short = 'g', long = "group-id")] + group_id: Vec, + + #[arg(long = "note-to-self")] + note_to_self: bool, + }, + #[command(rename_all = "kebab-case")] + Send { + recipient: Vec, + + #[arg(short = 'g', long)] + group_id: Vec, + + #[arg(long)] + note_to_self: bool, + + #[arg(short = 'e', long)] + end_session: bool, + + #[arg(short = 'm', long)] + message: Option, + + #[arg(short = 'a', long)] + attachment: Vec, + + #[arg(long)] + view_once: bool, + + #[arg(long)] + mention: Vec, + + #[arg(long)] + text_style: Vec, + + #[arg(long)] + quote_timestamp: Option, + + #[arg(long)] + quote_author: Option, + + #[arg(long)] + quote_message: Option, + + #[arg(long)] + quote_mention: Vec, + + #[arg(long)] + quote_text_style: Vec, + + #[arg(long)] + quote_attachment: Vec, + + #[arg(long)] + preview_url: Option, + + #[arg(long)] + preview_title: Option, + + #[arg(long)] + preview_description: Option, + + #[arg(long)] + preview_image: Option, + + #[arg(long)] + sticker: Option, + + #[arg(long)] + story_timestamp: Option, + + #[arg(long)] + story_author: Option, + + #[arg(long)] + edit_timestamp: Option, + }, + SendContacts, + SendPaymentNotification { + recipient: String, + + #[arg(long)] + receipt: String, + + #[arg(long)] + note: String, + }, + SendReaction { + recipient: Vec, + + #[arg(short = 'g', long = "group-id")] + group_id: Vec, + + #[arg(long = "note-to-self")] + note_to_self: bool, + + #[arg(short = 'e', long)] + emoji: String, + + #[arg(short = 'a', long = "target-author")] + target_author: String, + + #[arg(short = 't', long = "target-timestamp")] + target_timestamp: u64, + + #[arg(short = 'r', long)] + remove: bool, + + #[arg(long)] + story: bool, + }, + SendReceipt { + recipient: String, + + #[arg(short = 't', long = "target-timestamp")] + target_timestamp: Vec, + + #[arg(value_enum, long)] + r#type: ReceiptType, + }, + SendSyncRequest, + SendTyping { + recipient: Vec, + + #[arg(short = 'g', long = "group-id")] + group_id: Vec, + + #[arg(short = 's', long)] + stop: bool, + }, + SendMessageRequestResponse { + recipient: Vec, + + #[arg(short = 'g', long = "group-id")] + group_id: Vec, + + r#type: MessageRequestResponseType, + }, + SetPin { + pin: String, + }, + StartChangeNumber { + number: String, + #[arg(short = 'v', long)] + voice: bool, + #[arg(long)] + captcha: Option, + }, + SubmitRateLimitChallenge { + challenge: String, + captcha: String, + }, + Trust { + recipient: String, + + #[arg(short = 'a', long = "trust-all-known-keys")] + trust_all_known_keys: bool, + + #[arg(short = 'v', long = "verified-safety-number")] + verified_safety_number: Option, + }, + #[command(rename_all = "kebab-case")] + Unblock { + recipient: Vec, + + #[arg(short = 'g', long)] + group_id: Vec, + }, + Unregister { + #[arg(long = "delete-account")] + delete_account: bool, + }, + UpdateAccount { + #[arg(short = 'n', long = "device-name")] + device_name: Option, + #[arg(long = "unrestricted-unidentified-sender")] + unrestricted_unidentified_sender: Option, + #[arg(long = "discoverable-by-number")] + discoverable_by_number: Option, + #[arg(long = "number-sharing")] + number_sharing: Option, + }, + UpdateConfiguration { + #[arg(long = "read-receipts")] + read_receipts: Option, + + #[arg(long = "unidentified-delivery-indicators")] + unidentified_delivery_indicators: Option, + + #[arg(long = "typing-indicators")] + typing_indicators: Option, + + #[arg(long = "link-previews")] + link_previews: Option, + }, + UpdateContact { + recipient: String, + + #[arg(short = 'e', long)] + expiration: Option, + + #[arg(short = 'n', long)] + name: Option, + }, + UpdateGroup { + #[arg(short = 'g', long = "group-id")] + group_id: Option, + + #[arg(short = 'n', long)] + name: Option, + + #[arg(short = 'd', long)] + description: Option, + + #[arg(short = 'a', long)] + avatar: Option, + + #[arg(short = 'm', long)] + member: Vec, + + #[arg(short = 'r', long = "remove-member")] + remove_member: Vec, + + #[arg(long)] + admin: Vec, + + #[arg(long = "remove-admin")] + remove_admin: Vec, + + #[arg(long)] + ban: Vec, + + #[arg(long)] + unban: Vec, + + #[arg(long = "reset-link")] + reset_link: bool, + + #[arg(value_enum, long)] + link: Option, + + #[arg(value_enum, long = "set-permission-add-member")] + set_permission_add_member: Option, + + #[arg(value_enum, long = "set-permission-edit-details")] + set_permission_edit_details: Option, + + #[arg(value_enum, long = "set-permission-send-messages")] + set_permission_send_messages: Option, + + #[arg(short = 'e', long)] + expiration: Option, + }, + UpdateProfile { + #[arg(long = "given-name")] + given_name: Option, + + #[arg(long = "family-name")] + family_name: Option, + + #[arg(long)] + about: Option, + + #[arg(long = "about-emoji")] + about_emoji: Option, + + #[arg(long = "mobile-coin-address", visible_alias = "mobilecoin-address")] + mobile_coin_address: Option, + + #[arg(long)] + avatar: Option, + + #[arg(long = "remove-avatar")] + remove_avatar: bool, + }, + UploadStickerPack { + path: String, + }, + Verify { + verification_code: String, + + #[arg(short = 'p', long)] + pin: Option, + }, + Version, +} + +#[derive(ValueEnum, Clone, Debug)] +#[value(rename_all = "kebab-case")] +pub enum ReceiptType { + Read, + Viewed, +} + +#[derive(ValueEnum, Clone, Debug)] +#[value(rename_all = "kebab-case")] +pub enum LinkState { + Enabled, + EnabledWithApproval, + Disabled, +} + +#[derive(ValueEnum, Clone, Debug)] +#[value(rename_all = "kebab-case")] +pub enum GroupPermission { + EveryMember, + OnlyAdmins, +} + +#[derive(ValueEnum, Clone, Debug)] +#[value(rename_all = "kebab-case")] +pub enum MessageRequestResponseType { + Accept, + Delete, +} diff --git a/client/src/jsonrpc.rs b/client/src/jsonrpc.rs new file mode 100644 index 00000000..66ef9d9d --- /dev/null +++ b/client/src/jsonrpc.rs @@ -0,0 +1,425 @@ +use std::path::Path; + +use jsonrpsee::async_client::ClientBuilder; +use jsonrpsee::core::client::{Error, SubscriptionClientT}; +use jsonrpsee::http_client::HttpClientBuilder; +use jsonrpsee::proc_macros::rpc; +use serde::Deserialize; +use serde_json::Value; +use tokio::net::ToSocketAddrs; + +#[rpc(client)] +pub trait Rpc { + #[method(name = "addDevice", param_kind = map)] + async fn add_device( + &self, + account: Option, + uri: String, + ) -> Result; + + #[method(name = "addStickerPack", param_kind = map)] + async fn add_sticker_pack( + &self, + account: Option, + uri: String, + ) -> Result; + + #[method(name = "block", param_kind = map)] + fn block( + &self, + account: Option, + recipients: Vec, + #[allow(non_snake_case)] groupIds: Vec, + ) -> Result; + + #[method(name = "deleteLocalAccountData", param_kind = map)] + fn delete_local_account_data( + &self, + account: Option, + #[allow(non_snake_case)] ignoreRegistered: Option, + ) -> Result; + + #[method(name = "getAttachment", param_kind = map)] + fn get_attachment( + &self, + account: Option, + id: String, + recipient: Option, + #[allow(non_snake_case)] groupId: Option, + ) -> Result; + + #[method(name = "getAvatar", param_kind = map)] + fn get_avatar( + &self, + account: Option, + contact: Option, + profile: Option, + #[allow(non_snake_case)] groupId: Option, + ) -> Result; + + #[method(name = "getSticker", param_kind = map)] + fn get_sticker( + &self, + account: Option, + #[allow(non_snake_case)] packId: String, + #[allow(non_snake_case)] stickerId: u32, + ) -> Result; + + #[method(name = "getUserStatus", param_kind = map)] + fn get_user_status( + &self, + account: Option, + recipients: Vec, + usernames: Vec, + ) -> Result; + + #[method(name = "joinGroup", param_kind = map)] + fn join_group(&self, account: Option, uri: String) -> Result; + + #[allow(non_snake_case)] + #[method(name = "finishChangeNumber", param_kind = map)] + fn finish_change_number( + &self, + account: Option, + number: String, + verificationCode: String, + pin: Option, + ) -> Result; + + #[method(name = "finishLink", param_kind = map)] + fn finish_link( + &self, + #[allow(non_snake_case)] deviceLinkUri: String, + #[allow(non_snake_case)] deviceName: String, + ) -> Result; + + #[method(name = "listAccounts", param_kind = map)] + fn list_accounts(&self) -> Result; + + #[method(name = "listContacts", param_kind = map)] + fn list_contacts( + &self, + account: Option, + recipients: Vec, + #[allow(non_snake_case)] allRecipients: bool, + blocked: Option, + name: Option, + ) -> Result; + + #[method(name = "listDevices", param_kind = map)] + fn list_devices(&self, account: Option) -> Result; + + #[method(name = "listGroups", param_kind = map)] + fn list_groups( + &self, + account: Option, + #[allow(non_snake_case)] groupIds: Vec, + ) -> Result; + + #[method(name = "listIdentities", param_kind = map)] + fn list_identities( + &self, + account: Option, + number: Option, + ) -> Result; + + #[method(name = "listStickerPacks", param_kind = map)] + fn list_sticker_packs(&self, account: Option) -> Result; + + #[method(name = "quitGroup", param_kind = map)] + fn quit_group( + &self, + account: Option, + #[allow(non_snake_case)] groupId: String, + delete: bool, + admins: Vec, + ) -> Result; + + #[method(name = "register", param_kind = map)] + fn register( + &self, + account: Option, + voice: bool, + captcha: Option, + ) -> Result; + + #[method(name = "removeContact", param_kind = map)] + fn remove_contact( + &self, + account: Option, + recipient: String, + forget: bool, + hide: bool, + ) -> Result; + + #[method(name = "removeDevice", param_kind = map)] + fn remove_device( + &self, + account: Option, + #[allow(non_snake_case)] deviceId: u32, + ) -> Result; + + #[method(name = "removePin", param_kind = map)] + fn remove_pin(&self, account: Option) -> Result; + + #[method(name = "remoteDelete", param_kind = map)] + fn remote_delete( + &self, + account: Option, + #[allow(non_snake_case)] targetTimestamp: u64, + recipients: Vec, + #[allow(non_snake_case)] groupIds: Vec, + #[allow(non_snake_case)] noteToSelf: bool, + ) -> Result; + + #[allow(non_snake_case)] + #[method(name = "send", param_kind = map)] + fn send( + &self, + account: Option, + recipients: Vec, + groupIds: Vec, + noteToSelf: bool, + endSession: bool, + message: String, + attachments: Vec, + viewOnce: bool, + mentions: Vec, + textStyle: Vec, + quoteTimestamp: Option, + quoteAuthor: Option, + quoteMessage: Option, + quoteMention: Vec, + quoteTextStyle: Vec, + quoteAttachment: Vec, + previewUrl: Option, + previewTitle: Option, + previewDescription: Option, + previewImage: Option, + sticker: Option, + storyTimestamp: Option, + storyAuthor: Option, + editTimestamp: Option, + ) -> Result; + + #[method(name = "sendContacts", param_kind = map)] + fn send_contacts(&self, account: Option) -> Result; + + #[method(name = "sendPaymentNotification", param_kind = map)] + fn send_payment_notification( + &self, + account: Option, + recipient: String, + receipt: String, + note: String, + ) -> Result; + + #[method(name = "sendReaction", param_kind = map)] + fn send_reaction( + &self, + account: Option, + recipients: Vec, + #[allow(non_snake_case)] groupIds: Vec, + #[allow(non_snake_case)] noteToSelf: bool, + emoji: String, + #[allow(non_snake_case)] targetAuthor: String, + #[allow(non_snake_case)] targetTimestamp: u64, + remove: bool, + story: bool, + ) -> Result; + + #[method(name = "sendReceipt", param_kind = map)] + fn send_receipt( + &self, + account: Option, + recipient: String, + #[allow(non_snake_case)] targetTimestamps: Vec, + r#type: String, + ) -> Result; + + #[method(name = "sendSyncRequest", param_kind = map)] + fn send_sync_request(&self, account: Option) -> Result; + + #[method(name = "sendTyping", param_kind = map)] + fn send_typing( + &self, + account: Option, + recipients: Vec, + #[allow(non_snake_case)] groupIds: Vec, + stop: bool, + ) -> Result; + + #[method(name = "sendMessageRequestResponse", param_kind = map)] + fn send_message_request_response( + &self, + account: Option, + recipients: Vec, + #[allow(non_snake_case)] groupIds: Vec, + r#type: String, + ) -> Result; + + #[method(name = "setPin", param_kind = map)] + fn set_pin(&self, account: Option, pin: String) -> Result; + + #[method(name = "submitRateLimitChallenge", param_kind = map)] + fn submit_rate_limit_challenge( + &self, + account: Option, + challenge: String, + captcha: String, + ) -> Result; + + #[method(name = "startChangeNumber", param_kind = map)] + fn start_change_number( + &self, + account: Option, + number: String, + voice: bool, + captcha: Option, + ) -> Result; + + #[method(name = "startLink", param_kind = map)] + fn start_link(&self, account: Option) -> Result; + + #[method(name = "trust", param_kind = map)] + fn trust( + &self, + account: Option, + recipient: String, + #[allow(non_snake_case)] trustAllKnownKeys: bool, + #[allow(non_snake_case)] verifiedSafetyNumber: Option, + ) -> Result; + + #[method(name = "unblock", param_kind = map)] + fn unblock( + &self, + account: Option, + recipients: Vec, + #[allow(non_snake_case)] groupIds: Vec, + ) -> Result; + + #[method(name = "unregister", param_kind = map)] + fn unregister( + &self, + account: Option, + #[allow(non_snake_case)] deleteAccount: bool, + ) -> Result; + + #[allow(non_snake_case)] + #[method(name = "updateAccount", param_kind = map)] + fn update_account( + &self, + account: Option, + deviceName: Option, + unrestrictedUnidentifiedSender: Option, + discoverableByNumber: Option, + numberSharing: Option, + ) -> Result; + + #[method(name = "updateConfiguration", param_kind = map)] + fn update_configuration( + &self, + account: Option, + #[allow(non_snake_case)] readReceipts: Option, + #[allow(non_snake_case)] unidentifiedDeliveryIndicators: Option, + #[allow(non_snake_case)] typingIndicators: Option, + #[allow(non_snake_case)] linkPreviews: Option, + ) -> Result; + + #[method(name = "updateContact", param_kind = map)] + fn update_contact( + &self, + account: Option, + recipient: String, + name: Option, + expiration: Option, + ) -> Result; + + #[method(name = "updateGroup", param_kind = map)] + fn update_group( + &self, + account: Option, + #[allow(non_snake_case)] groupId: Option, + name: Option, + description: Option, + avatar: Option, + member: Vec, + #[allow(non_snake_case)] removeMember: Vec, + admin: Vec, + #[allow(non_snake_case)] removeAdmin: Vec, + ban: Vec, + unban: Vec, + #[allow(non_snake_case)] resetLink: bool, + #[allow(non_snake_case)] link: Option, + #[allow(non_snake_case)] setPermissionAddMember: Option, + #[allow(non_snake_case)] setPermissionEditDetails: Option, + #[allow(non_snake_case)] setPermissionSendMessages: Option, + expiration: Option, + ) -> Result; + + #[method(name = "updateProfile", param_kind = map)] + fn update_profile( + &self, + account: Option, + #[allow(non_snake_case)] givenName: Option, + #[allow(non_snake_case)] familyName: Option, + about: Option, + #[allow(non_snake_case)] aboutEmoji: Option, + #[allow(non_snake_case)] mobileCoinAddress: Option, + avatar: Option, + #[allow(non_snake_case)] removeAvatar: bool, + ) -> Result; + + #[method(name = "uploadStickerPack", param_kind = map)] + fn upload_sticker_pack( + &self, + account: Option, + path: String, + ) -> Result; + + #[method(name = "verify", param_kind = map)] + fn verify( + &self, + account: Option, + #[allow(non_snake_case)] verificationCode: String, + pin: Option, + ) -> Result; + + #[subscription( + name = "subscribeReceive" => "receive", + unsubscribe = "unsubscribeReceive", + item = Value, + param_kind = map + )] + async fn subscribe_receive(&self, account: Option) -> SubscriptionResult; + + #[method(name = "version")] + fn version(&self) -> Result; +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JsonLink { + pub device_link_uri: String, +} + +pub async fn connect_tcp( + tcp: impl ToSocketAddrs, +) -> Result { + let (sender, receiver) = super::transports::tcp::connect(tcp).await?; + + Ok(ClientBuilder::default().build_with_tokio(sender, receiver)) +} + +#[cfg(unix)] +pub async fn connect_unix( + socket_path: impl AsRef, +) -> Result { + let (sender, receiver) = super::transports::ipc::connect(socket_path).await?; + + Ok(ClientBuilder::default().build_with_tokio(sender, receiver)) +} + +pub async fn connect_http(uri: &str) -> Result, Error> { + HttpClientBuilder::default().build(uri) +} diff --git a/client/src/main.rs b/client/src/main.rs new file mode 100644 index 00000000..ac12331d --- /dev/null +++ b/client/src/main.rs @@ -0,0 +1,526 @@ +use std::{path::PathBuf, time::Duration}; + +use clap::Parser; +use jsonrpsee::core::client::{Error as RpcError, Subscription, SubscriptionClientT}; +use serde_json::{Error, Value}; +use tokio::{select, time::sleep}; + +use cli::Cli; + +use crate::cli::{CliCommands, GroupPermission, LinkState}; +use crate::jsonrpc::RpcClient; + +mod cli; +#[allow(non_snake_case, clippy::too_many_arguments)] +mod jsonrpc; +mod transports; + +const DEFAULT_TCP: &str = "127.0.0.1:7583"; +const DEFAULT_SOCKET_SUFFIX: &str = "signal-cli/socket"; +const DEFAULT_HTTP: &str = "http://localhost:8080/api/v1/rpc"; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let cli = cli::Cli::parse(); + + let result = connect(cli).await; + + match result { + Ok(Value::Null) => {} + Ok(v) => println!("{v}"), + Err(e) => return Err(anyhow::anyhow!("JSON-RPC command failed: {e:?}")), + } + Ok(()) +} + +async fn handle_command( + cli: Cli, + client: impl SubscriptionClientT + Sync, +) -> Result { + match cli.command { + CliCommands::Receive { timeout } => { + let mut stream = client.subscribe_receive(cli.account).await?; + + { + while let Some(v) = stream_next(timeout, &mut stream).await { + let v = v?; + println!("{v}"); + } + } + stream.unsubscribe().await?; + Ok(Value::Null) + } + CliCommands::AddDevice { uri } => client.add_device(cli.account, uri).await, + CliCommands::Block { + recipient, + group_id, + } => client.block(cli.account, recipient, group_id).await, + CliCommands::DeleteLocalAccountData { ignore_registered } => { + client + .delete_local_account_data(cli.account, ignore_registered) + .await + } + CliCommands::GetUserStatus { + recipient, + username, + } => { + client + .get_user_status(cli.account, recipient, username) + .await + } + CliCommands::JoinGroup { uri } => client.join_group(cli.account, uri).await, + CliCommands::Link { name } => { + let url = client + .start_link(cli.account) + .await + .map_err(|e| RpcError::Custom(format!("JSON-RPC command startLink failed: {e:?}")))? + .device_link_uri; + println!("{url}"); + client.finish_link(url, name).await + } + CliCommands::ListAccounts => client.list_accounts().await, + CliCommands::ListContacts { + recipient, + all_recipients, + blocked, + name, + } => { + client + .list_contacts(cli.account, recipient, all_recipients, blocked, name) + .await + } + CliCommands::ListDevices => client.list_devices(cli.account).await, + CliCommands::ListGroups { + detailed: _, + group_id, + } => client.list_groups(cli.account, group_id).await, + CliCommands::ListIdentities { number } => client.list_identities(cli.account, number).await, + CliCommands::ListStickerPacks => client.list_sticker_packs(cli.account).await, + CliCommands::QuitGroup { + group_id, + delete, + admin, + } => { + client + .quit_group(cli.account, group_id, delete, admin) + .await + } + CliCommands::Register { voice, captcha } => { + client.register(cli.account, voice, captcha).await + } + CliCommands::RemoveContact { + recipient, + forget, + hide, + } => { + client + .remove_contact(cli.account, recipient, forget, hide) + .await + } + CliCommands::RemoveDevice { device_id } => { + client.remove_device(cli.account, device_id).await + } + CliCommands::RemovePin => client.remove_pin(cli.account).await, + CliCommands::RemoteDelete { + target_timestamp, + recipient, + group_id, + note_to_self, + } => { + client + .remote_delete( + cli.account, + target_timestamp, + recipient, + group_id, + note_to_self, + ) + .await + } + CliCommands::Send { + recipient, + group_id, + note_to_self, + end_session, + message, + attachment, + view_once, + mention, + text_style, + quote_timestamp, + quote_author, + quote_message, + quote_mention, + quote_text_style, + quote_attachment, + preview_url, + preview_title, + preview_description, + preview_image, + sticker, + story_timestamp, + story_author, + edit_timestamp, + } => { + client + .send( + cli.account, + recipient, + group_id, + note_to_self, + end_session, + message.unwrap_or_default(), + attachment, + view_once, + mention, + text_style, + quote_timestamp, + quote_author, + quote_message, + quote_mention, + quote_text_style, + quote_attachment, + preview_url, + preview_title, + preview_description, + preview_image, + sticker, + story_timestamp, + story_author, + edit_timestamp, + ) + .await + } + CliCommands::SendContacts => client.send_contacts(cli.account).await, + CliCommands::SendPaymentNotification { + recipient, + receipt, + note, + } => { + client + .send_payment_notification(cli.account, recipient, receipt, note) + .await + } + CliCommands::SendReaction { + recipient, + group_id, + note_to_self, + emoji, + target_author, + target_timestamp, + remove, + story, + } => { + client + .send_reaction( + cli.account, + recipient, + group_id, + note_to_self, + emoji, + target_author, + target_timestamp, + remove, + story, + ) + .await + } + CliCommands::SendReceipt { + recipient, + target_timestamp, + r#type, + } => { + client + .send_receipt( + cli.account, + recipient, + target_timestamp, + match r#type { + cli::ReceiptType::Read => "read".to_owned(), + cli::ReceiptType::Viewed => "viewed".to_owned(), + }, + ) + .await + } + CliCommands::SendSyncRequest => client.send_sync_request(cli.account).await, + CliCommands::SendTyping { + recipient, + group_id, + stop, + } => { + client + .send_typing(cli.account, recipient, group_id, stop) + .await + } + CliCommands::SetPin { pin } => client.set_pin(cli.account, pin).await, + CliCommands::SubmitRateLimitChallenge { challenge, captcha } => { + client + .submit_rate_limit_challenge(cli.account, challenge, captcha) + .await + } + CliCommands::Trust { + recipient, + trust_all_known_keys, + verified_safety_number, + } => { + client + .trust( + cli.account, + recipient, + trust_all_known_keys, + verified_safety_number, + ) + .await + } + CliCommands::Unblock { + recipient, + group_id, + } => client.unblock(cli.account, recipient, group_id).await, + CliCommands::Unregister { delete_account } => { + client.unregister(cli.account, delete_account).await + } + CliCommands::UpdateAccount { + device_name, + unrestricted_unidentified_sender, + discoverable_by_number, + number_sharing, + } => { + client + .update_account( + cli.account, + device_name, + unrestricted_unidentified_sender, + discoverable_by_number, + number_sharing, + ) + .await + } + CliCommands::UpdateConfiguration { + read_receipts, + unidentified_delivery_indicators, + typing_indicators, + link_previews, + } => { + client + .update_configuration( + cli.account, + read_receipts, + unidentified_delivery_indicators, + typing_indicators, + link_previews, + ) + .await + } + CliCommands::UpdateContact { + recipient, + expiration, + name, + } => { + client + .update_contact(cli.account, recipient, name, expiration) + .await + } + CliCommands::UpdateGroup { + group_id, + name, + description, + avatar, + member, + remove_member, + admin, + remove_admin, + ban, + unban, + reset_link, + link, + set_permission_add_member, + set_permission_edit_details, + set_permission_send_messages, + expiration, + } => { + client + .update_group( + cli.account, + group_id, + name, + description, + avatar, + member, + remove_member, + admin, + remove_admin, + ban, + unban, + reset_link, + link.map(|link| match link { + LinkState::Enabled => "enabled".to_owned(), + LinkState::EnabledWithApproval => "enabledWithApproval".to_owned(), + LinkState::Disabled => "disabled".to_owned(), + }), + set_permission_add_member.map(|p| match p { + GroupPermission::EveryMember => "everyMember".to_owned(), + GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(), + }), + set_permission_edit_details.map(|p| match p { + GroupPermission::EveryMember => "everyMember".to_owned(), + GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(), + }), + set_permission_send_messages.map(|p| match p { + GroupPermission::EveryMember => "everyMember".to_owned(), + GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(), + }), + expiration, + ) + .await + } + CliCommands::UpdateProfile { + given_name, + family_name, + about, + about_emoji, + mobile_coin_address, + avatar, + remove_avatar, + } => { + client + .update_profile( + cli.account, + given_name, + family_name, + about, + about_emoji, + mobile_coin_address, + avatar, + remove_avatar, + ) + .await + } + CliCommands::UploadStickerPack { path } => { + client.upload_sticker_pack(cli.account, path).await + } + CliCommands::Verify { + verification_code, + pin, + } => client.verify(cli.account, verification_code, pin).await, + CliCommands::Version => client.version().await, + CliCommands::AddStickerPack { uri } => client.add_sticker_pack(cli.account, uri).await, + CliCommands::FinishChangeNumber { + number, + verification_code, + pin, + } => { + client + .finish_change_number(cli.account, number, verification_code, pin) + .await + } + CliCommands::GetAttachment { + id, + recipient, + group_id, + } => { + client + .get_attachment(cli.account, id, recipient, group_id) + .await + } + CliCommands::GetAvatar { + contact, + profile, + group_id, + } => { + client + .get_avatar(cli.account, contact, profile, group_id) + .await + } + CliCommands::GetSticker { + pack_id, + sticker_id, + } => client.get_sticker(cli.account, pack_id, sticker_id).await, + CliCommands::StartChangeNumber { + number, + voice, + captcha, + } => { + client + .start_change_number(cli.account, number, voice, captcha) + .await + } + CliCommands::SendMessageRequestResponse { + recipient, + group_id, + r#type, + } => { + client + .send_message_request_response( + cli.account, + recipient, + group_id, + match r#type { + cli::MessageRequestResponseType::Accept => "accept".to_owned(), + cli::MessageRequestResponseType::Delete => "delete".to_owned(), + }, + ) + .await + } + } +} + +async fn connect(cli: Cli) -> Result { + if let Some(http) = &cli.json_rpc_http { + let uri = if let Some(uri) = http { + uri + } else { + DEFAULT_HTTP + }; + let client = jsonrpc::connect_http(uri) + .await + .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?; + + handle_command(cli, client).await + } else if let Some(tcp) = cli.json_rpc_tcp { + let socket_addr = tcp.unwrap_or_else(|| DEFAULT_TCP.parse().unwrap()); + let client = jsonrpc::connect_tcp(socket_addr) + .await + .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?; + + handle_command(cli, client).await + } else { + #[cfg(windows)] + { + Err(RpcError::Custom("Invalid socket".into())) + } + #[cfg(unix)] + { + let socket_path = cli + .json_rpc_socket + .clone() + .unwrap_or(None) + .or_else(|| { + std::env::var_os("XDG_RUNTIME_DIR").map(|runtime_dir| { + PathBuf::from(runtime_dir) + .join(DEFAULT_SOCKET_SUFFIX) + .into() + }) + }) + .unwrap_or_else(|| ("/run".to_owned() + DEFAULT_SOCKET_SUFFIX).into()); + let client = jsonrpc::connect_unix(socket_path) + .await + .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?; + + handle_command(cli, client).await + } + } +} + +async fn stream_next( + timeout: f64, + stream: &mut Subscription, +) -> Option> { + if timeout < 0.0 { + stream.next().await + } else { + select! { + v = stream.next() => v, + _= sleep(Duration::from_millis((timeout * 1000.0) as u64)) => None, + } + } +} diff --git a/client/src/transports/ipc.rs b/client/src/transports/ipc.rs new file mode 100644 index 00000000..b14063aa --- /dev/null +++ b/client/src/transports/ipc.rs @@ -0,0 +1,23 @@ +use std::io::Error; +use std::path::Path; + +use futures_util::stream::StreamExt; +use jsonrpsee::core::client::{TransportReceiverT, TransportSenderT}; +use tokio::net::UnixStream; +use tokio_util::codec::Decoder; + +use super::stream_codec::StreamCodec; +use super::{Receiver, Sender}; + +/// Connect to a JSON-RPC Unix Socket server. +pub async fn connect( + socket: impl AsRef, +) -> Result<(impl TransportSenderT + Send, impl TransportReceiverT + Send), Error> { + let connection = UnixStream::connect(socket).await?; + let (sink, stream) = StreamCodec::stream_incoming().framed(connection).split(); + + let sender = Sender { inner: sink }; + let receiver = Receiver { inner: stream }; + + Ok((sender, receiver)) +} diff --git a/client/src/transports/mod.rs b/client/src/transports/mod.rs new file mode 100644 index 00000000..ed1963a0 --- /dev/null +++ b/client/src/transports/mod.rs @@ -0,0 +1,60 @@ +use futures_util::{stream::StreamExt, Sink, SinkExt, Stream}; +use jsonrpsee::core::client::{ReceivedMessage, TransportReceiverT, TransportSenderT}; +use thiserror::Error; + +#[cfg(unix)] +pub mod ipc; +mod stream_codec; +pub mod tcp; + +#[derive(Debug, Error)] +enum Errors { + #[error("Other: {0}")] + Other(String), + #[error("Closed")] + Closed, +} + +struct Sender> { + inner: T, +} + +impl + Unpin + 'static> TransportSenderT + for Sender +{ + type Error = Errors; + + async fn send(&mut self, body: String) -> Result<(), Self::Error> { + self.inner + .send(body) + .await + .map_err(|e| Errors::Other(format!("{e:?}")))?; + Ok(()) + } + + async fn close(&mut self) -> Result<(), Self::Error> { + self.inner + .close() + .await + .map_err(|e| Errors::Other(format!("{e:?}")))?; + Ok(()) + } +} + +struct Receiver { + inner: T, +} + +impl> + Unpin + 'static> TransportReceiverT + for Receiver +{ + type Error = Errors; + + async fn receive(&mut self) -> Result { + match self.inner.next().await { + None => Err(Errors::Closed), + Some(Ok(msg)) => Ok(ReceivedMessage::Text(msg)), + Some(Err(e)) => Err(Errors::Other(format!("{e:?}"))), + } + } +} diff --git a/client/src/transports/stream_codec.rs b/client/src/transports/stream_codec.rs new file mode 100644 index 00000000..e46233cb --- /dev/null +++ b/client/src/transports/stream_codec.rs @@ -0,0 +1,61 @@ +use bytes::BytesMut; +use std::{io, str}; +use tokio_util::codec::{Decoder, Encoder}; + +type Separator = u8; + +/// Stream codec for streaming protocols (ipc, tcp) +#[derive(Debug, Default)] +pub struct StreamCodec { + incoming_separator: Separator, + outgoing_separator: Separator, +} + +impl StreamCodec { + /// Default codec with streaming input data. Input can be both enveloped and not. + pub fn stream_incoming() -> Self { + StreamCodec::new(b'\n', b'\n') + } + + /// New custom stream codec + pub fn new(incoming_separator: Separator, outgoing_separator: Separator) -> Self { + StreamCodec { + incoming_separator, + outgoing_separator, + } + } +} + +impl Decoder for StreamCodec { + type Item = String; + type Error = io::Error; + + fn decode(&mut self, buf: &mut BytesMut) -> io::Result> { + if let Some(i) = buf + .as_ref() + .iter() + .position(|&b| b == self.incoming_separator) + { + let line = buf.split_to(i); + let _ = buf.split_to(1); + + match str::from_utf8(line.as_ref()) { + Ok(s) => Ok(Some(s.to_string())), + Err(_) => Err(io::Error::other("invalid UTF-8")), + } + } else { + Ok(None) + } + } +} + +impl Encoder for StreamCodec { + type Error = io::Error; + + fn encode(&mut self, msg: String, buf: &mut BytesMut) -> io::Result<()> { + let mut payload = msg.into_bytes(); + payload.push(self.outgoing_separator); + buf.extend_from_slice(&payload); + Ok(()) + } +} diff --git a/client/src/transports/tcp.rs b/client/src/transports/tcp.rs new file mode 100644 index 00000000..ae80759f --- /dev/null +++ b/client/src/transports/tcp.rs @@ -0,0 +1,22 @@ +use std::io::Error; + +use futures_util::stream::StreamExt; +use jsonrpsee::core::client::{TransportReceiverT, TransportSenderT}; +use tokio::net::{TcpStream, ToSocketAddrs}; +use tokio_util::codec::Decoder; + +use super::stream_codec::StreamCodec; +use super::{Receiver, Sender}; + +/// Connect to a JSON-RPC TCP server. +pub async fn connect( + socket: impl ToSocketAddrs, +) -> Result<(impl TransportSenderT + Send, impl TransportReceiverT + Send), Error> { + let connection = TcpStream::connect(socket).await?; + let (sink, stream) = StreamCodec::stream_incoming().framed(connection).split(); + + let sender = Sender { inner: sink }; + let receiver = Receiver { inner: stream }; + + Ok((sender, receiver)) +} diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml new file mode 100644 index 00000000..c6c07411 --- /dev/null +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -0,0 +1,112 @@ + + + org.asamk.SignalCli + + signal-cli + Use Signal messenger in terminal + + AsamK + + org.asamk.SignalCli + + signal + signal-cli + messenger + messaging + + + https://github.com/AsamK/signal-cli/issues + https://github.com/AsamK/signal-cli + https://github.com/sponsors/AsamK + https://github.com/AsamK/signal-cli/discussions + https://github.com/AsamK/signal-cli + + CC0-1.0 + GPL-3.0-only + + +

+ signal-cli is an unofficial commandline interface for the Signal Messenger. + It supports many Signal functions, including registering, verifying, sending and receiving messages. + For registering you need a phone number where you can receive SMS or incoming calls. + Alternatively signal-cli can be linked to an existing App account. +

+
+ + + Utility + Java + + + + signal-cli + + + intense + + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.18 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.17 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.16 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.15 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.14 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.13 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.12 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.11 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.10 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.9 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.8 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.7 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.6 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.5 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.4 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.3 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.2 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.1 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.0 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.12.8 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.12.7 + + +
diff --git a/data/org.asamk.SignalCli.svg b/data/org.asamk.SignalCli.svg new file mode 100644 index 00000000..69ca862b --- /dev/null +++ b/data/org.asamk.SignalCli.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/data/signal-cli-socket.service b/data/signal-cli-socket.service new file mode 100644 index 00000000..11d56ce3 --- /dev/null +++ b/data/signal-cli-socket.service @@ -0,0 +1,46 @@ +[Unit] +Description=Send secure messages to Signal clients +Wants=network-online.target +After=network-online.target +Requires=signal-cli-socket.socket + +[Service] +CapabilityBoundingSet= +Environment="SIGNAL_CLI_OPTS=-Xms2m" +# Update 'ReadWritePaths' if you change the config path here +ExecStart=%dir%/bin/signal-cli --config /var/lib/signal-cli daemon +LockPersonality=true +NoNewPrivileges=true +PrivateDevices=true +PrivateIPC=true +PrivateTmp=true +PrivateUsers=true +ProcSubset=pid +ProtectClock=true +ProtectControlGroups=true +ProtectHome=true +ProtectHostname=true +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectProc=invisible +ProtectSystem=strict +# Profile pictures and attachments to upload must be located here for the service to access them +ReadWritePaths=/var/lib/signal-cli +RemoveIPC=true +RestrictAddressFamilies=AF_INET AF_INET6 +RestrictNamespaces=true +RestrictRealtime=true +RestrictSUIDSGID=true +StandardInput=socket +StandardOutput=journal +StandardError=journal +SystemCallArchitectures=native +SystemCallFilter=~@debug @mount @obsolete @privileged @resources +UMask=0077 +# Create the user and home directory with 'useradd -r -U -s /usr/sbin/nologin -m -b /var/lib signal-cli' +User=signal-cli + +[Install] +Also=signal-cli-socket.socket +WantedBy=default.target diff --git a/data/signal-cli-socket.socket b/data/signal-cli-socket.socket new file mode 100644 index 00000000..0207b059 --- /dev/null +++ b/data/signal-cli-socket.socket @@ -0,0 +1,13 @@ +[Unit] +Description=Send secure messages to Signal clients + +[Socket] +ListenStream=%t/signal-cli/socket +SocketUser=root +# Add yourself to the signal-cli group to talk with the service +# Run 'usermod -aG signal-cli yourusername' +SocketGroup=signal-cli +SocketMode=0660 + +[Install] +WantedBy=sockets.target diff --git a/data/signal.service b/data/signal-cli.service similarity index 77% rename from data/signal.service rename to data/signal-cli.service index 089b0428..a0bba768 100644 --- a/data/signal.service +++ b/data/signal-cli.service @@ -8,7 +8,7 @@ After=network-online.target [Service] Type=dbus Environment="SIGNAL_CLI_OPTS=-Xms2m" -ExecStart=%dir%/bin/signal-cli -u %number% --config /var/lib/signal-cli daemon --system +ExecStart=%dir%/bin/signal-cli --config /var/lib/signal-cli daemon --dbus-system User=signal-cli BusName=org.asamk.Signal diff --git a/data/signal-cli.sysusers.conf b/data/signal-cli.sysusers.conf new file mode 100644 index 00000000..fcab31ef --- /dev/null +++ b/data/signal-cli.sysusers.conf @@ -0,0 +1 @@ +u signal-cli - "Signal messaging service" /var/lib/signal-cli diff --git a/data/signal-cli.tmpfiles.conf b/data/signal-cli.tmpfiles.conf new file mode 100644 index 00000000..8e1d00b1 --- /dev/null +++ b/data/signal-cli.tmpfiles.conf @@ -0,0 +1,5 @@ +d /var/lib/signal-cli 0755 signal-cli signal-cli - +d /var/lib/signal-cli/data 0700 signal-cli signal-cli - +d /var/lib/signal-cli/attachments 0750 signal-cli signal-cli - +d /var/lib/signal-cli/avatars 0750 signal-cli signal-cli - +d /var/lib/signal-cli/stickers 0750 signal-cli signal-cli - diff --git a/data/signal-cli@.service b/data/signal-cli@.service index 4cc6e2cf..c5698ca7 100644 --- a/data/signal-cli@.service +++ b/data/signal-cli@.service @@ -8,9 +8,9 @@ After=network-online.target [Service] Type=dbus Environment="SIGNAL_CLI_OPTS=-Xms2m" -ExecStart=%dir%/bin/signal-cli -u %I --config /var/lib/signal-cli daemon --system +ExecStart=%dir%/bin/signal-cli -a %I --config /var/lib/signal-cli daemon --dbus-system User=signal-cli BusName=org.asamk.Signal [Install] -WantedBy=multi-user.target +Alias=dbus-org.asamk.Signal.service diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json new file mode 100644 index 00000000..523a6c03 --- /dev/null +++ b/graalvm-config-dir/jni-config.json @@ -0,0 +1,312 @@ +[ +{ + "name":"[B" +}, +{ + "name":"[Z" +}, +{ + "name":"[[B" +}, +{ + "name":"com.sun.security.auth.module.UnixSystem", + "fields":[{"name":"gid"}, {"name":"groups"}, {"name":"uid"}, {"name":"username"}] +}, +{ + "name":"java.lang.Boolean", + "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"getCanonicalName","parameterTypes":[] }, {"name":"getClassLoader","parameterTypes":[] }] +}, +{ + "name":"java.lang.ClassLoader", + "methods":[{"name":"getPlatformClassLoader","parameterTypes":[] }, {"name":"loadClass","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.ClassNotFoundException" +}, +{ + "name":"java.lang.Enum", + "methods":[{"name":"ordinal","parameterTypes":[] }] +}, +{ + "name":"java.lang.IllegalArgumentException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.IllegalStateException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Long", + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"java.lang.NoClassDefFoundError" +}, +{ + "name":"java.lang.NoSuchMethodError" +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.lang.Thread", + "methods":[{"name":"currentThread","parameterTypes":[] }, {"name":"getStackTrace","parameterTypes":[] }] +}, +{ + "name":"java.lang.Throwable", + "methods":[{"name":"getMessage","parameterTypes":[] }, {"name":"setStackTrace","parameterTypes":["java.lang.StackTraceElement[]"] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.UnsatisfiedLinkError", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.util.HashMap", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"java.util.Map", + "methods":[{"name":"get","parameterTypes":["java.lang.Object"] }, {"name":"put","parameterTypes":["java.lang.Object","java.lang.Object"] }, {"name":"remove","parameterTypes":["java.lang.Object"] }] +}, +{ + "name":"java.util.UUID", + "methods":[{"name":"","parameterTypes":["long","long"] }, {"name":"getLeastSignificantBits","parameterTypes":[] }, {"name":"getMostSignificantBits","parameterTypes":[] }] +}, +{ + "name":"jdk.internal.loader.ClassLoaders$AppClassLoader" +}, +{ + "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader" +}, +{ + "name":"org.asamk.signal.manager.storage.protocol.SignalProtocolStore", + "methods":[{"name":"getIdentity","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress"] }, {"name":"getIdentityKeyPair","parameterTypes":[] }, {"name":"getLocalRegistrationId","parameterTypes":[] }, {"name":"isTrustedIdentity","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","org.signal.libsignal.protocol.IdentityKey","org.signal.libsignal.protocol.state.IdentityKeyStore$Direction"] }, {"name":"loadKyberPreKey","parameterTypes":["int"] }, {"name":"loadPreKey","parameterTypes":["int"] }, {"name":"loadSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID"] }, {"name":"loadSession","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress"] }, {"name":"loadSignedPreKey","parameterTypes":["int"] }, {"name":"markKyberPreKeyUsed","parameterTypes":["int"] }, {"name":"removePreKey","parameterTypes":["int"] }, {"name":"saveIdentity","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","org.signal.libsignal.protocol.IdentityKey"] }, {"name":"storeSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID","org.signal.libsignal.protocol.groups.state.SenderKeyRecord"] }, {"name":"storeSession","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","org.signal.libsignal.protocol.state.SessionRecord"] }] +}, +{ + "name":"org.asamk.signal.manager.storage.senderKeys.SenderKeyStore", + "methods":[{"name":"loadSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID"] }, {"name":"storeSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID","org.signal.libsignal.protocol.groups.state.SenderKeyRecord"] }] +}, +{ + "name":"org.graalvm.jniutils.JNIExceptionWrapperEntryPoints", + "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }] +}, +{ + "name":"org.signal.libsignal.internal.CompletableFuture", + "methods":[{"name":"","parameterTypes":[] }, {"name":"complete","parameterTypes":["java.lang.Object"] }, {"name":"completeExceptionally","parameterTypes":["java.lang.Throwable"] }, {"name":"setCancellationId","parameterTypes":["long"] }] +}, +{ + "name":"org.signal.libsignal.internal.NativeHandleGuard$SimpleOwner", + "methods":[{"name":"unsafeNativeHandleWithoutGuard","parameterTypes":[] }] +}, +{ + "name":"org.signal.libsignal.net.CdsiLookupResponse", + "methods":[{"name":"","parameterTypes":["java.util.Map","int"] }] +}, +{ + "name":"org.signal.libsignal.net.CdsiLookupResponse$Entry", + "methods":[{"name":"","parameterTypes":["byte[]","byte[]"] }] +}, +{ + "name":"org.signal.libsignal.net.ChatService" +}, +{ + "name":"org.signal.libsignal.net.ChatService$DebugInfo" +}, +{ + "name":"org.signal.libsignal.net.ChatService$Response" +}, +{ + "name":"org.signal.libsignal.net.ChatService$ResponseAndDebugInfo" +}, +{ + "name":"org.signal.libsignal.net.NetworkException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.net.RetryLaterException", + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"org.signal.libsignal.protocol.DuplicateMessageException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.protocol.IdentityKey", + "methods":[{"name":"","parameterTypes":["long"] }, {"name":"","parameterTypes":["byte[]"] }, {"name":"serialize","parameterTypes":[] }] +}, +{ + "name":"org.signal.libsignal.protocol.IdentityKeyPair", + "methods":[{"name":"serialize","parameterTypes":[] }] +}, +{ + "name":"org.signal.libsignal.protocol.InvalidKeyException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.protocol.InvalidKeyIdException" +}, +{ + "name":"org.signal.libsignal.protocol.InvalidMessageException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.protocol.NoSessionException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.protocol.SignalProtocolAddress", + "methods":[{"name":"","parameterTypes":["long"] }, {"name":"","parameterTypes":["java.lang.String","int"] }] +}, +{ + "name":"org.signal.libsignal.protocol.UntrustedIdentityException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.protocol.fingerprint.FingerprintParsingException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.protocol.groups.state.SenderKeyRecord", + "fields":[{"name":"unsafeHandle"}], + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"org.signal.libsignal.protocol.groups.state.SenderKeyStore" +}, +{ + "name":"org.signal.libsignal.protocol.logging.Log", + "methods":[{"name":"log","parameterTypes":["int","java.lang.String","java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.protocol.message.PlaintextContent", + "fields":[{"name":"unsafeHandle"}] +}, +{ + "name":"org.signal.libsignal.protocol.message.PreKeySignalMessage", + "fields":[{"name":"unsafeHandle"}], + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"org.signal.libsignal.protocol.message.SenderKeyMessage", + "fields":[{"name":"unsafeHandle"}], + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"org.signal.libsignal.protocol.message.SignalMessage", + "fields":[{"name":"unsafeHandle"}], + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"org.signal.libsignal.protocol.state.IdentityKeyStore" +}, +{ + "name":"org.signal.libsignal.protocol.state.IdentityKeyStore$Direction", + "fields":[{"name":"RECEIVING"}, {"name":"SENDING"}] +}, +{ + "name":"org.signal.libsignal.protocol.state.IdentityKeyStore$IdentityChange" +}, +{ + "name":"org.signal.libsignal.protocol.state.KyberPreKeyRecord", + "fields":[{"name":"unsafeHandle"}] +}, +{ + "name":"org.signal.libsignal.protocol.state.KyberPreKeyStore" +}, +{ + "name":"org.signal.libsignal.protocol.state.PreKeyRecord", + "fields":[{"name":"unsafeHandle"}] +}, +{ + "name":"org.signal.libsignal.protocol.state.PreKeyStore" +}, +{ + "name":"org.signal.libsignal.protocol.state.SessionRecord", + "fields":[{"name":"unsafeHandle"}], + "methods":[{"name":"","parameterTypes":["long"] }, {"name":"","parameterTypes":["byte[]"] }] +}, +{ + "name":"org.signal.libsignal.protocol.state.SessionStore" +}, +{ + "name":"org.signal.libsignal.protocol.state.SignedPreKeyRecord", + "fields":[{"name":"unsafeHandle"}] +}, +{ + "name":"org.signal.libsignal.protocol.state.SignedPreKeyStore" +}, +{ + "name":"org.signal.libsignal.usernames.BadDiscriminatorCharacterException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.usernames.BadNicknameCharacterException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.usernames.CannotBeEmptyException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.usernames.DiscriminatorCannotBeZeroException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.usernames.MissingSeparatorException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.usernames.NicknameTooLongException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.usernames.NicknameTooShortException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.signal.libsignal.zkgroup.InvalidInputException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.sqlite.BusyHandler", + "methods":[{"name":"callback","parameterTypes":["int"] }] +}, +{ + "name":"org.sqlite.Collation", + "methods":[{"name":"xCompare","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"org.sqlite.Function", + "fields":[{"name":"args"}, {"name":"context"}, {"name":"value"}], + "methods":[{"name":"xFunc","parameterTypes":[] }] +}, +{ + "name":"org.sqlite.Function$Aggregate", + "methods":[{"name":"clone","parameterTypes":[] }, {"name":"xFinal","parameterTypes":[] }, {"name":"xStep","parameterTypes":[] }] +}, +{ + "name":"org.sqlite.Function$Window", + "methods":[{"name":"xInverse","parameterTypes":[] }, {"name":"xValue","parameterTypes":[] }] +}, +{ + "name":"org.sqlite.ProgressHandler", + "methods":[{"name":"progress","parameterTypes":[] }] +}, +{ + "name":"org.sqlite.core.DB", + "methods":[{"name":"onCommit","parameterTypes":["boolean"] }, {"name":"onUpdate","parameterTypes":["int","java.lang.String","java.lang.String","long"] }, {"name":"throwex","parameterTypes":[] }, {"name":"throwex","parameterTypes":["int"] }] +}, +{ + "name":"org.sqlite.core.DB$ProgressObserver", + "methods":[{"name":"progress","parameterTypes":["int","int"] }] +}, +{ + "name":"org.sqlite.core.NativeDB", + "fields":[{"name":"busyHandler"}, {"name":"commitListener"}, {"name":"pointer"}, {"name":"progressHandler"}, {"name":"updateListener"}], + "methods":[{"name":"stringToUtf8ByteArray","parameterTypes":["java.lang.String"] }, {"name":"throwex","parameterTypes":["java.lang.String"] }] +} +] diff --git a/graalvm-config-dir/predefined-classes-config.json b/graalvm-config-dir/predefined-classes-config.json new file mode 100644 index 00000000..0e79b2c5 --- /dev/null +++ b/graalvm-config-dir/predefined-classes-config.json @@ -0,0 +1,8 @@ +[ + { + "type":"agent-extracted", + "classes":[ + ] + } +] + diff --git a/graalvm-config-dir/proxy-config.json b/graalvm-config-dir/proxy-config.json new file mode 100644 index 00000000..1a97b30a --- /dev/null +++ b/graalvm-config-dir/proxy-config.json @@ -0,0 +1,26 @@ +[ + { + "interfaces":["java.sql.Connection"] + }, + { + "interfaces":["org.asamk.Signal"] + }, + { + "interfaces":["org.asamk.Signal$Configuration"] + }, + { + "interfaces":["org.asamk.Signal$Device"] + }, + { + "interfaces":["org.asamk.Signal$Group"] + }, + { + "interfaces":["org.asamk.Signal$Identity"] + }, + { + "interfaces":["org.asamk.SignalControl"] + }, + { + "interfaces":["org.freedesktop.dbus.interfaces.DBus"] + } +] diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json new file mode 100644 index 00000000..0bfd1cdf --- /dev/null +++ b/graalvm-config-dir/reflect-config.json @@ -0,0 +1,3277 @@ +[ +{ + "name":"[B", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"[C" +}, +{ + "name":"[I", + "queryAllDeclaredMethods":true, + "queryAllPublicMethods":true +}, +{ + "name":"[J" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.deser.Deserializers;" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.deser.KeyDeserializers;" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.deser.ValueInstantiators;" +}, +{ + "name":"[Lcom.fasterxml.jackson.databind.ser.Serializers;" +}, +{ + "name":"[Lcom.zaxxer.hikari.util.ConcurrentBag$IConcurrentBagEntry;" +}, +{ + "name":"[Ljava.lang.Object;" +}, +{ + "name":"[Ljava.lang.String;" +}, +{ + "name":"[Ljava.sql.Statement;" +}, +{ + "name":"[Lorg.asamk.signal.commands.ListStickerPacksCommand$JsonStickerPack$JsonSticker;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonAttachment;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonCallMessage$IceUpdate;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonContactAddress;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonContactEmail;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonContactPhone;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonMention;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonPreview;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonQuotedAttachment;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonSharedContact;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonSyncReadMessage;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonTextStyle;" +}, +{ + "name":"[Lorg.asamk.signal.manager.storage.accounts.AccountsStorage$Account;" +}, +{ + "name":"[Lorg.asamk.signal.manager.storage.stickerPacks.JsonStickerPack$JsonSticker;" +}, +{ + "name":"[Lorg.whispersystems.signalservice.api.groupsv2.TemporalCredential;" +}, +{ + "name":"[Lorg.whispersystems.signalservice.internal.push.GroupMismatchedDevices;" +}, +{ + "name":"[Lorg.whispersystems.signalservice.internal.push.GroupStaleDevices;" +}, +{ + "name":"android.os.Build$VERSION" +}, +{ + "name":"byte[]", + "allDeclaredMethods":true, + "allPublicMethods":true +}, +{ + "name":"ch.qos.logback.classic.pattern.DateConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LevelConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LineSeparatorConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.LoggerConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.MessageConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.pattern.ThreadConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"char[]" +}, +{ + "name":"com.fasterxml.jackson.annotation.JsonIgnore" +}, +{ + "name":"com.fasterxml.jackson.annotation.JsonProperty" +}, +{ + "name":"com.fasterxml.jackson.databind.annotation.JsonDeserialize" +}, +{ + "name":"com.fasterxml.jackson.databind.annotation.JsonSerialize" +}, +{ + "name":"com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.squareup.wire.Message", + "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] +}, +{ + "name":"com.squareup.wire.ProtoAdapter" +}, +{ + "name":"com.squareup.wire.internal.ImmutableList", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ARCFOURCipher", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESCipher", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESedeCipher", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DHParameters", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacCore$HmacSHA384", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.TlsKeyMaterialGenerator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.TlsMasterSecretGenerator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.TlsPrfGenerator$V12", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.zaxxer.hikari.HikariConfig", + "allDeclaredFields":true, + "queryAllPublicMethods":true, + "methods":[{"name":"getAllowPoolSuspension","parameterTypes":[] }, {"name":"getAutoCommit","parameterTypes":[] }, {"name":"getCatalog","parameterTypes":[] }, {"name":"getConnectionInitSql","parameterTypes":[] }, {"name":"getConnectionTestQuery","parameterTypes":[] }, {"name":"getConnectionTimeout","parameterTypes":[] }, {"name":"getCredentials","parameterTypes":[] }, {"name":"getDataSource","parameterTypes":[] }, {"name":"getDataSourceClassName","parameterTypes":[] }, {"name":"getDataSourceJNDI","parameterTypes":[] }, {"name":"getDataSourceProperties","parameterTypes":[] }, {"name":"getDriverClassName","parameterTypes":[] }, {"name":"getExceptionOverride","parameterTypes":[] }, {"name":"getExceptionOverrideClassName","parameterTypes":[] }, {"name":"getHealthCheckProperties","parameterTypes":[] }, {"name":"getHealthCheckRegistry","parameterTypes":[] }, {"name":"getIdleTimeout","parameterTypes":[] }, {"name":"getInitializationFailTimeout","parameterTypes":[] }, {"name":"getIsolateInternalQueries","parameterTypes":[] }, {"name":"getJdbcUrl","parameterTypes":[] }, {"name":"getKeepaliveTime","parameterTypes":[] }, {"name":"getLeakDetectionThreshold","parameterTypes":[] }, {"name":"getMaxLifetime","parameterTypes":[] }, {"name":"getMaximumPoolSize","parameterTypes":[] }, {"name":"getMetricRegistry","parameterTypes":[] }, {"name":"getMetricsTrackerFactory","parameterTypes":[] }, {"name":"getMinimumIdle","parameterTypes":[] }, {"name":"getPassword","parameterTypes":[] }, {"name":"getPoolName","parameterTypes":[] }, {"name":"getReadOnly","parameterTypes":[] }, {"name":"getRegisterMbeans","parameterTypes":[] }, {"name":"getScheduledExecutor","parameterTypes":[] }, {"name":"getSchema","parameterTypes":[] }, {"name":"getThreadFactory","parameterTypes":[] }, {"name":"getTransactionIsolation","parameterTypes":[] }, {"name":"getUsername","parameterTypes":[] }, {"name":"getValidationTimeout","parameterTypes":[] }, {"name":"isAllowPoolSuspension","parameterTypes":[] }, {"name":"isAutoCommit","parameterTypes":[] }, {"name":"isIsolateInternalQueries","parameterTypes":[] }, {"name":"isReadOnly","parameterTypes":[] }, {"name":"isRegisterMbeans","parameterTypes":[] }, {"name":"setAllowPoolSuspension","parameterTypes":["boolean"] }, {"name":"setAutoCommit","parameterTypes":["boolean"] }, {"name":"setCatalog","parameterTypes":["java.lang.String"] }, {"name":"setClass","parameterTypes":["java.lang.Class"] }, {"name":"setConnectionInitSql","parameterTypes":["java.lang.String"] }, {"name":"setConnectionTestQuery","parameterTypes":["java.lang.String"] }, {"name":"setConnectionTimeout","parameterTypes":["long"] }, {"name":"setCredentials","parameterTypes":["com.zaxxer.hikari.util.Credentials"] }, {"name":"setDataSource","parameterTypes":["javax.sql.DataSource"] }, {"name":"setDataSourceClassName","parameterTypes":["java.lang.String"] }, {"name":"setDataSourceJNDI","parameterTypes":["java.lang.String"] }, {"name":"setDataSourceProperties","parameterTypes":["java.util.Properties"] }, {"name":"setDriverClassName","parameterTypes":["java.lang.String"] }, {"name":"setExceptionOverride","parameterTypes":["com.zaxxer.hikari.SQLExceptionOverride"] }, {"name":"setExceptionOverrideClassName","parameterTypes":["java.lang.String"] }, {"name":"setHealthCheckProperties","parameterTypes":["java.util.Properties"] }, {"name":"setHealthCheckRegistry","parameterTypes":["java.lang.Object"] }, {"name":"setIdleTimeout","parameterTypes":["long"] }, {"name":"setInitializationFailTimeout","parameterTypes":["long"] }, {"name":"setIsolateInternalQueries","parameterTypes":["boolean"] }, {"name":"setJdbcUrl","parameterTypes":["java.lang.String"] }, {"name":"setKeepaliveTime","parameterTypes":["long"] }, {"name":"setLeakDetectionThreshold","parameterTypes":["long"] }, {"name":"setMaxLifetime","parameterTypes":["long"] }, {"name":"setMaximumPoolSize","parameterTypes":["int"] }, {"name":"setMetricRegistry","parameterTypes":["java.lang.Object"] }, {"name":"setMetricsTrackerFactory","parameterTypes":["com.zaxxer.hikari.metrics.MetricsTrackerFactory"] }, {"name":"setMinimumIdle","parameterTypes":["int"] }, {"name":"setPassword","parameterTypes":["java.lang.String"] }, {"name":"setPoolName","parameterTypes":["java.lang.String"] }, {"name":"setReadOnly","parameterTypes":["boolean"] }, {"name":"setRegisterMbeans","parameterTypes":["boolean"] }, {"name":"setScheduledExecutor","parameterTypes":["java.util.concurrent.ScheduledExecutorService"] }, {"name":"setSchema","parameterTypes":["java.lang.String"] }, {"name":"setThreadFactory","parameterTypes":["java.util.concurrent.ThreadFactory"] }, {"name":"setTransactionIsolation","parameterTypes":["java.lang.String"] }, {"name":"setUsername","parameterTypes":["java.lang.String"] }, {"name":"setValidationTimeout","parameterTypes":["long"] }] +}, +{ + "name":"com.zaxxer.hikari.pool.PoolBase", + "fields":[{"name":"catalog"}] +}, +{ + "name":"com.zaxxer.hikari.pool.PoolEntry", + "fields":[{"name":"state"}] +}, +{ + "name":"int", + "allDeclaredMethods":true, + "allPublicMethods":true +}, +{ + "name":"int[]", + "allDeclaredMethods":true, + "allPublicMethods":true +}, +{ + "name":"java.io.File", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }, {"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.io.FilePermission" +}, +{ + "name":"java.io.OutputStream" +}, +{ + "name":"java.io.Serializable", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredClasses":true +}, +{ + "name":"java.lang.Boolean", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Class", + "methods":[{"name":"getPermittedSubclasses","parameterTypes":[] }, {"name":"getRecordComponents","parameterTypes":[] }, {"name":"isRecord","parameterTypes":[] }, {"name":"isSealed","parameterTypes":[] }] +}, +{ + "name":"java.lang.ClassValue" +}, +{ + "name":"java.lang.Comparable", + "allDeclaredMethods":true +}, +{ + "name":"java.lang.Double", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Enum", + "allDeclaredMethods":true +}, +{ + "name":"java.lang.Float", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.lang.Integer", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Iterable", + "allDeclaredMethods":true +}, +{ + "name":"java.lang.Long", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.Number", + "allDeclaredFields":true, + "allDeclaredMethods":true +}, +{ + "name":"java.lang.Object", + "methods":[{"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"hashCode","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"java.lang.Record", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.RuntimePermission" +}, +{ + "name":"java.lang.String", + "allPublicMethods":true +}, +{ + "name":"java.lang.Thread", + "fields":[{"name":"threadLocalRandomProbe"}] +}, +{ + "name":"java.lang.Throwable", + "queryAllPublicMethods":true, + "methods":[{"name":"addSuppressed","parameterTypes":["java.lang.Throwable"] }, {"name":"getSuppressed","parameterTypes":[] }] +}, +{ + "name":"java.lang.annotation.Retention", + "queryAllDeclaredMethods":true, + "methods":[{"name":"value","parameterTypes":[] }] +}, +{ + "name":"java.lang.annotation.Target", + "queryAllDeclaredMethods":true, + "methods":[{"name":"value","parameterTypes":[] }] +}, +{ + "name":"java.lang.constant.Constable", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.constant.ConstantDesc", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.reflect.Executable", + "methods":[{"name":"getParameters","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Method", + "methods":[{"name":"isDefault","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.Parameter", + "methods":[{"name":"getName","parameterTypes":[] }] +}, +{ + "name":"java.lang.reflect.RecordComponent", + "methods":[{"name":"getAccessor","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getType","parameterTypes":[] }] +}, +{ + "name":"java.net.NetPermission" +}, +{ + "name":"java.net.SocketPermission" +}, +{ + "name":"java.net.URLPermission", + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"java.nio.Buffer", + "allDeclaredMethods":true, + "fields":[{"name":"address"}] +}, +{ + "name":"java.nio.ByteBuffer", + "allDeclaredMethods":true, + "allPublicMethods":true +}, +{ + "name":"java.security.AllPermission" +}, +{ + "name":"java.security.KeyStoreSpi" +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.security.SecurityPermission" +}, +{ + "name":"java.security.cert.CertStoreParameters" +}, +{ + "name":"java.security.cert.PKIXRevocationChecker" +}, +{ + "name":"java.security.interfaces.ECPrivateKey" +}, +{ + "name":"java.security.interfaces.ECPublicKey" +}, +{ + "name":"java.security.interfaces.RSAPrivateKey" +}, +{ + "name":"java.security.interfaces.RSAPublicKey" +}, +{ + "name":"java.sql.SQLException", + "fields":[{"name":"next"}] +}, +{ + "name":"java.util.AbstractCollection", + "allDeclaredFields":true, + "allDeclaredMethods":true +}, +{ + "name":"java.util.AbstractList", + "allDeclaredFields":true, + "allDeclaredMethods":true +}, +{ + "name":"java.util.AbstractMap", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.ArrayList", + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"java.util.Collection", + "allDeclaredMethods":true +}, +{ + "name":"java.util.HashSet", + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"java.util.ImmutableCollections$AbstractImmutableCollection", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.ImmutableCollections$AbstractImmutableList", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.ImmutableCollections$AbstractImmutableMap", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.ImmutableCollections$List12", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.ImmutableCollections$ListN", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.ImmutableCollections$Map1", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.ImmutableCollections$MapN", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.LinkedHashMap", + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"java.util.List", + "allDeclaredMethods":true +}, +{ + "name":"java.util.Locale", + "methods":[{"name":"getUnicodeLocaleType","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.util.Map", + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.Optional", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.PropertyPermission" +}, +{ + "name":"java.util.RandomAccess", + "allDeclaredMethods":true +}, +{ + "name":"java.util.UUID", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicMarkableReference", + "fields":[{"name":"pair"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.Striped64", + "fields":[{"name":"base"}, {"name":"cellsBusy"}] +}, +{ + "name":"java.util.concurrent.atomic.Striped64$Cell", + "fields":[{"name":"value"}] +}, +{ + "name":"javax.security.auth.x500.X500Principal", + "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }] +}, +{ + "name":"javax.smartcardio.CardPermission" +}, +{ + "name":"jdk.internal.misc.Unsafe" +}, +{ + "name":"kotlin.Any" +}, +{ + "name":"kotlin.Boolean" +}, +{ + "name":"kotlin.BooleanArray" +}, +{ + "name":"kotlin.Byte" +}, +{ + "name":"kotlin.ByteArray" +}, +{ + "name":"kotlin.Char" +}, +{ + "name":"kotlin.CharArray" +}, +{ + "name":"kotlin.Double" +}, +{ + "name":"kotlin.DoubleArray" +}, +{ + "name":"kotlin.Float" +}, +{ + "name":"kotlin.FloatArray" +}, +{ + "name":"kotlin.Int" +}, +{ + "name":"kotlin.IntArray" +}, +{ + "name":"kotlin.Long" +}, +{ + "name":"kotlin.LongArray" +}, +{ + "name":"kotlin.Metadata", + "queryAllDeclaredMethods":true, + "methods":[{"name":"bv","parameterTypes":[] }, {"name":"d1","parameterTypes":[] }, {"name":"d2","parameterTypes":[] }, {"name":"k","parameterTypes":[] }, {"name":"mv","parameterTypes":[] }, {"name":"pn","parameterTypes":[] }, {"name":"xi","parameterTypes":[] }, {"name":"xs","parameterTypes":[] }] +}, +{ + "name":"kotlin.SafePublicationLazyImpl", + "fields":[{"name":"_value"}] +}, +{ + "name":"kotlin.Short" +}, +{ + "name":"kotlin.ShortArray" +}, +{ + "name":"kotlin.String" +}, +{ + "name":"kotlin.Unit" +}, +{ + "name":"kotlin.collections.AbstractCollection", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"kotlin.collections.AbstractList", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"kotlin.collections.List" +}, +{ + "name":"kotlin.collections.Map" +}, +{ + "name":"kotlin.collections.MutableList" +}, +{ + "name":"kotlin.collections.MutableMap" +}, +{ + "name":"kotlin.jvm.JvmStatic", + "queryAllDeclaredMethods":true +}, +{ + "name":"kotlin.jvm.internal.DefaultConstructorMarker" +}, +{ + "name":"kotlin.jvm.internal.markers.KMappedMarker", + "queryAllDeclaredMethods":true +}, +{ + "name":"kotlin.reflect.jvm.internal.ReflectionFactoryImpl", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"kotlin.reflect.jvm.internal.impl.resolve.scopes.DescriptorKindFilter", + "allPublicFields":true +}, +{ + "name":"libcore.io.Memory" +}, +{ + "name":"long", + "allDeclaredMethods":true, + "allPublicMethods":true +}, +{ + "name":"long[]" +}, +{ + "name":"okhttp3.internal.connection.RealConnectionPool", + "fields":[{"name":"addressStates"}] +}, +{ + "name":"okio.BufferedSink" +}, +{ + "name":"okio.ByteString" +}, +{ + "name":"org.asamk.Signal", + "allDeclaredMethods":true, + "allDeclaredClasses":true, + "methods":[{"name":"getContactName","parameterTypes":["java.lang.String"] }, {"name":"getDevice","parameterTypes":["long"] }, {"name":"getGroup","parameterTypes":["byte[]"] }, {"name":"getSelfNumber","parameterTypes":[] }, {"name":"getThisDevice","parameterTypes":[] }, {"name":"listDevices","parameterTypes":[] }, {"name":"listIdentities","parameterTypes":[] }, {"name":"sendGroupMessageReaction","parameterTypes":["java.lang.String","boolean","java.lang.String","long","byte[]"] }, {"name":"sendMessage","parameterTypes":["java.lang.String","java.util.List","java.lang.String"] }, {"name":"sendMessage","parameterTypes":["java.lang.String","java.util.List","java.util.List"] }, {"name":"sendMessageReaction","parameterTypes":["java.lang.String","boolean","java.lang.String","long","java.util.List"] }, {"name":"subscribeReceive","parameterTypes":[] }, {"name":"unsubscribeReceive","parameterTypes":[] }, {"name":"version","parameterTypes":[] }] +}, +{ + "name":"org.asamk.Signal$Configuration", + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"org.asamk.Signal$Device", + "allDeclaredMethods":true, + "allDeclaredClasses":true +}, +{ + "name":"org.asamk.Signal$EditMessageReceived", + "queryAllDeclaredConstructors":true, + "queryAllPublicConstructors":true +}, +{ + "name":"org.asamk.Signal$Error$AttachmentInvalid", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.asamk.Signal$Error$Failure", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.asamk.Signal$Error$LastGroupAdmin", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.asamk.Signal$Error$UntrustedIdentity", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.asamk.Signal$Group", + "allDeclaredMethods":true, + "allDeclaredClasses":true +}, +{ + "name":"org.asamk.Signal$Identity", + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"org.asamk.Signal$MessageReceived", + "allDeclaredConstructors":true, + "allPublicConstructors":true +}, +{ + "name":"org.asamk.Signal$MessageReceivedV2", + "queryAllDeclaredConstructors":true, + "queryAllPublicConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","long","java.lang.String","byte[]","java.lang.String","java.util.Map"] }] +}, +{ + "name":"org.asamk.Signal$ReceiptReceived", + "allDeclaredConstructors":true, + "allPublicConstructors":true +}, +{ + "name":"org.asamk.Signal$ReceiptReceivedV2", + "queryAllDeclaredConstructors":true, + "queryAllPublicConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","long","java.lang.String","java.lang.String","java.util.Map"] }] +}, +{ + "name":"org.asamk.Signal$StructDevice", + "allDeclaredFields":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["org.freedesktop.dbus.DBusPath","java.lang.Long","java.lang.String"] }] +}, +{ + "name":"org.asamk.Signal$StructGroup", + "allDeclaredFields":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["org.freedesktop.dbus.DBusPath","byte[]","java.lang.String"] }] +}, +{ + "name":"org.asamk.Signal$StructIdentity", + "allDeclaredFields":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["org.freedesktop.dbus.DBusPath","java.lang.String","java.lang.String"] }] +}, +{ + "name":"org.asamk.Signal$SyncMessageReceived", + "allDeclaredConstructors":true, + "allPublicConstructors":true +}, +{ + "name":"org.asamk.Signal$SyncMessageReceivedV2", + "queryAllDeclaredConstructors":true, + "queryAllPublicConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","long","java.lang.String","java.lang.String","byte[]","java.lang.String","java.util.Map"] }] +}, +{ + "name":"org.asamk.SignalControl", + "allDeclaredMethods":true, + "allDeclaredClasses":true, + "methods":[{"name":"listAccounts","parameterTypes":[] }] +}, +{ + "name":"org.asamk.SignalControl$Error$Failure", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.asamk.signal.commands.FinishLinkCommand$FinishLinkParams", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"deviceLinkUri","parameterTypes":[] }, {"name":"deviceName","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.FinishLinkCommand$JsonFinishLink", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"number","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.GetUserStatusCommand$JsonUserStatus", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"isRegistered","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"recipient","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.ListAccountsCommand$JsonAccount", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"number","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.ListContactsCommand$JsonContact", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"color","parameterTypes":[] }, {"name":"isBlocked","parameterTypes":[] }, {"name":"messageExpirationTime","parameterTypes":[] }, {"name":"name","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"profile","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.ListContactsCommand$JsonContact$JsonProfile", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"about","parameterTypes":[] }, {"name":"aboutEmoji","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"lastUpdateTimestamp","parameterTypes":[] }, {"name":"mobileCoinAddress","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.ListDevicesCommand$JsonDevice", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"createdTimestamp","parameterTypes":[] }, {"name":"id","parameterTypes":[] }, {"name":"lastSeenTimestamp","parameterTypes":[] }, {"name":"name","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.ListGroupsCommand$JsonGroup", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"admins","parameterTypes":[] }, {"name":"banned","parameterTypes":[] }, {"name":"description","parameterTypes":[] }, {"name":"groupInviteLink","parameterTypes":[] }, {"name":"id","parameterTypes":[] }, {"name":"isBlocked","parameterTypes":[] }, {"name":"isMember","parameterTypes":[] }, {"name":"members","parameterTypes":[] }, {"name":"messageExpirationTime","parameterTypes":[] }, {"name":"name","parameterTypes":[] }, {"name":"pendingMembers","parameterTypes":[] }, {"name":"permissionAddMember","parameterTypes":[] }, {"name":"permissionEditDetails","parameterTypes":[] }, {"name":"permissionSendMessage","parameterTypes":[] }, {"name":"requestingMembers","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.ListGroupsCommand$JsonGroupMember", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"number","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.ListIdentitiesCommand$JsonIdentity", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"addedTimestamp","parameterTypes":[] }, {"name":"fingerprint","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"safetyNumber","parameterTypes":[] }, {"name":"scannableSafetyNumber","parameterTypes":[] }, {"name":"trustLevel","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.ListStickerPacksCommand$JsonStickerPack", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"author","parameterTypes":[] }, {"name":"cover","parameterTypes":[] }, {"name":"installed","parameterTypes":[] }, {"name":"packId","parameterTypes":[] }, {"name":"stickers","parameterTypes":[] }, {"name":"title","parameterTypes":[] }, {"name":"url","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.ListStickerPacksCommand$JsonStickerPack$JsonSticker", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"contentType","parameterTypes":[] }, {"name":"emoji","parameterTypes":[] }, {"name":"id","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.ReceiveCommand$ReceiveParams", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.Double","java.lang.Integer"] }, {"name":"maxMessages","parameterTypes":[] }, {"name":"timeout","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.RegisterCommand$RegistrationParams", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.String"] }, {"name":"captcha","parameterTypes":[] }, {"name":"voice","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.StartLinkCommand$JsonLink", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"deviceLinkUri","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.UpdateAccountCommand$JsonAccountResponse", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"username","parameterTypes":[] }, {"name":"usernameLink","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.commands.VerifyCommand$VerifyParams", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"pin","parameterTypes":[] }, {"name":"verificationCode","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.dbus.DbusProperties", + "queryAllDeclaredMethods":true +}, +{ + "name":"org.asamk.signal.dbus.DbusSignalControlImpl", + "queryAllDeclaredMethods":true +}, +{ + "name":"org.asamk.signal.dbus.DbusSignalImpl", + "queryAllDeclaredMethods":true +}, +{ + "name":"org.asamk.signal.dbus.DbusSignalImpl$DbusSignalConfigurationImpl", + "queryAllDeclaredMethods":true +}, +{ + "name":"org.asamk.signal.dbus.DbusSignalImpl$DbusSignalDeviceImpl", + "queryAllDeclaredMethods":true +}, +{ + "name":"org.asamk.signal.dbus.DbusSignalImpl$DbusSignalGroupImpl", + "queryAllDeclaredMethods":true +}, +{ + "name":"org.asamk.signal.dbus.DbusSignalImpl$DbusSignalIdentityImpl", + "queryAllDeclaredMethods":true +}, +{ + "name":"org.asamk.signal.json.JsonAttachment", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"caption","parameterTypes":[] }, {"name":"contentType","parameterTypes":[] }, {"name":"filename","parameterTypes":[] }, {"name":"height","parameterTypes":[] }, {"name":"id","parameterTypes":[] }, {"name":"size","parameterTypes":[] }, {"name":"uploadTimestamp","parameterTypes":[] }, {"name":"width","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonAttachmentData", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"data","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonCallMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"answerMessage","parameterTypes":[] }, {"name":"busyMessage","parameterTypes":[] }, {"name":"hangupMessage","parameterTypes":[] }, {"name":"iceUpdateMessages","parameterTypes":[] }, {"name":"offerMessage","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonCallMessage$Answer", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"id","parameterTypes":[] }, {"name":"opaque","parameterTypes":[] }, {"name":"sdp","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonCallMessage$Busy", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"id","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonCallMessage$Hangup", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"deviceId","parameterTypes":[] }, {"name":"id","parameterTypes":[] }, {"name":"isLegacy","parameterTypes":[] }, {"name":"type","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonCallMessage$IceUpdate", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"id","parameterTypes":[] }, {"name":"opaque","parameterTypes":[] }, {"name":"sdp","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonCallMessage$Offer", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"id","parameterTypes":[] }, {"name":"opaque","parameterTypes":[] }, {"name":"sdp","parameterTypes":[] }, {"name":"type","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonContact", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"color","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"internal","parameterTypes":[] }, {"name":"isBlocked","parameterTypes":[] }, {"name":"isHidden","parameterTypes":[] }, {"name":"messageExpirationTime","parameterTypes":[] }, {"name":"name","parameterTypes":[] }, {"name":"nickFamilyName","parameterTypes":[] }, {"name":"nickGivenName","parameterTypes":[] }, {"name":"nickName","parameterTypes":[] }, {"name":"note","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"profile","parameterTypes":[] }, {"name":"profileSharing","parameterTypes":[] }, {"name":"unregistered","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonContact$JsonInternal", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"capabilities","parameterTypes":[] }, {"name":"discoverableByPhonenumber","parameterTypes":[] }, {"name":"sharesPhoneNumber","parameterTypes":[] }, {"name":"unidentifiedAccessMode","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonContact$JsonProfile", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"about","parameterTypes":[] }, {"name":"aboutEmoji","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"hasAvatar","parameterTypes":[] }, {"name":"lastUpdateTimestamp","parameterTypes":[] }, {"name":"mobileCoinAddress","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonContactAddress", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"city","parameterTypes":[] }, {"name":"country","parameterTypes":[] }, {"name":"label","parameterTypes":[] }, {"name":"neighborhood","parameterTypes":[] }, {"name":"pobox","parameterTypes":[] }, {"name":"postcode","parameterTypes":[] }, {"name":"region","parameterTypes":[] }, {"name":"street","parameterTypes":[] }, {"name":"type","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonContactAvatar", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"attachment","parameterTypes":[] }, {"name":"isProfile","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonContactEmail", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"label","parameterTypes":[] }, {"name":"type","parameterTypes":[] }, {"name":"value","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonContactName", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"display","parameterTypes":[] }, {"name":"family","parameterTypes":[] }, {"name":"given","parameterTypes":[] }, {"name":"middle","parameterTypes":[] }, {"name":"nickname","parameterTypes":[] }, {"name":"prefix","parameterTypes":[] }, {"name":"suffix","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonContactPhone", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"label","parameterTypes":[] }, {"name":"type","parameterTypes":[] }, {"name":"value","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonDataMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"attachments","parameterTypes":[] }, {"name":"contacts","parameterTypes":[] }, {"name":"expiresInSeconds","parameterTypes":[] }, {"name":"groupInfo","parameterTypes":[] }, {"name":"mentions","parameterTypes":[] }, {"name":"message","parameterTypes":[] }, {"name":"payment","parameterTypes":[] }, {"name":"previews","parameterTypes":[] }, {"name":"quote","parameterTypes":[] }, {"name":"reaction","parameterTypes":[] }, {"name":"remoteDelete","parameterTypes":[] }, {"name":"sticker","parameterTypes":[] }, {"name":"storyContext","parameterTypes":[] }, {"name":"textStyles","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }, {"name":"viewOnce","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonEditMessage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"dataMessage","parameterTypes":[] }, {"name":"targetSentTimestamp","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonError", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"message","parameterTypes":[] }, {"name":"type","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonGroupInfo", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"groupId","parameterTypes":[] }, {"name":"groupName","parameterTypes":[] }, {"name":"revision","parameterTypes":[] }, {"name":"type","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonMention", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"length","parameterTypes":[] }, {"name":"name","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"start","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonMessageEnvelope", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"callMessage","parameterTypes":[] }, {"name":"dataMessage","parameterTypes":[] }, {"name":"editMessage","parameterTypes":[] }, {"name":"receiptMessage","parameterTypes":[] }, {"name":"serverDeliveredTimestamp","parameterTypes":[] }, {"name":"serverReceivedTimestamp","parameterTypes":[] }, {"name":"source","parameterTypes":[] }, {"name":"sourceDevice","parameterTypes":[] }, {"name":"sourceName","parameterTypes":[] }, {"name":"sourceNumber","parameterTypes":[] }, {"name":"sourceUuid","parameterTypes":[] }, {"name":"storyMessage","parameterTypes":[] }, {"name":"syncMessage","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }, {"name":"typingMessage","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonPayment", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"note","parameterTypes":[] }, {"name":"receipt","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonPreview", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"description","parameterTypes":[] }, {"name":"image","parameterTypes":[] }, {"name":"title","parameterTypes":[] }, {"name":"url","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonQuote", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"attachments","parameterTypes":[] }, {"name":"author","parameterTypes":[] }, {"name":"authorNumber","parameterTypes":[] }, {"name":"authorUuid","parameterTypes":[] }, {"name":"id","parameterTypes":[] }, {"name":"mentions","parameterTypes":[] }, {"name":"text","parameterTypes":[] }, {"name":"textStyles","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonQuotedAttachment", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"contentType","parameterTypes":[] }, {"name":"filename","parameterTypes":[] }, {"name":"thumbnail","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonReaction", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"emoji","parameterTypes":[] }, {"name":"isRemove","parameterTypes":[] }, {"name":"targetAuthor","parameterTypes":[] }, {"name":"targetAuthorNumber","parameterTypes":[] }, {"name":"targetAuthorUuid","parameterTypes":[] }, {"name":"targetSentTimestamp","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonReceiptMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"isDelivery","parameterTypes":[] }, {"name":"isRead","parameterTypes":[] }, {"name":"isViewed","parameterTypes":[] }, {"name":"timestamps","parameterTypes":[] }, {"name":"when","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonRecipientAddress", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"number","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonRemoteDelete", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"timestamp","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonSendMessageResult", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"groupId","parameterTypes":[] }, {"name":"recipientAddress","parameterTypes":[] }, {"name":"retryAfterSeconds","parameterTypes":[] }, {"name":"token","parameterTypes":[] }, {"name":"type","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonSendMessageResult$Type", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"org.asamk.signal.json.JsonSharedContact", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"address","parameterTypes":[] }, {"name":"avatar","parameterTypes":[] }, {"name":"email","parameterTypes":[] }, {"name":"name","parameterTypes":[] }, {"name":"organization","parameterTypes":[] }, {"name":"phone","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonSticker", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"packId","parameterTypes":[] }, {"name":"stickerId","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonStoryContext", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"authorNumber","parameterTypes":[] }, {"name":"authorUuid","parameterTypes":[] }, {"name":"sentTimestamp","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonStoryMessage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"allowsReplies","parameterTypes":[] }, {"name":"fileAttachment","parameterTypes":[] }, {"name":"groupId","parameterTypes":[] }, {"name":"textAttachment","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonStoryMessage$TextAttachment", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"backgroundColor","parameterTypes":[] }, {"name":"backgroundGradient","parameterTypes":[] }, {"name":"preview","parameterTypes":[] }, {"name":"style","parameterTypes":[] }, {"name":"text","parameterTypes":[] }, {"name":"textBackgroundColor","parameterTypes":[] }, {"name":"textForegroundColor","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonStoryMessage$TextAttachment$Gradient", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"angle","parameterTypes":[] }, {"name":"colors","parameterTypes":[] }, {"name":"endColor","parameterTypes":[] }, {"name":"positions","parameterTypes":[] }, {"name":"startColor","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonSyncDataMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"dataMessage","parameterTypes":[] }, {"name":"destination","parameterTypes":[] }, {"name":"destinationNumber","parameterTypes":[] }, {"name":"destinationUuid","parameterTypes":[] }, {"name":"editMessage","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonSyncMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"blockedGroupIds","parameterTypes":[] }, {"name":"blockedNumbers","parameterTypes":[] }, {"name":"readMessages","parameterTypes":[] }, {"name":"sentMessage","parameterTypes":[] }, {"name":"sentStoryMessage","parameterTypes":[] }, {"name":"type","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonSyncMessageType", + "allDeclaredFields":true, + "allDeclaredMethods":true +}, +{ + "name":"org.asamk.signal.json.JsonSyncReadMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"sender","parameterTypes":[] }, {"name":"senderNumber","parameterTypes":[] }, {"name":"senderUuid","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonSyncStoryMessage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"dataMessage","parameterTypes":[] }, {"name":"destinationNumber","parameterTypes":[] }, {"name":"destinationUuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonTextStyle", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"length","parameterTypes":[] }, {"name":"start","parameterTypes":[] }, {"name":"style","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonTypingMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"action","parameterTypes":[] }, {"name":"groupId","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.jsonrpc.JsonRpcBatchMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.asamk.signal.jsonrpc.JsonRpcException", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.asamk.signal.jsonrpc.JsonRpcMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.asamk.signal.jsonrpc.JsonRpcRequest", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getJsonrpc","parameterTypes":[] }, {"name":"getMethod","parameterTypes":[] }, {"name":"getParams","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.jsonrpc.JsonRpcResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"getError","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getJsonrpc","parameterTypes":[] }, {"name":"getResult","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.jsonrpc.JsonRpcResponse$Error", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"getCode","parameterTypes":[] }, {"name":"getData","parameterTypes":[] }, {"name":"getMessage","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.api.PhoneNumberSharingMode", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"org.asamk.signal.manager.storage.SignalAccount$Storage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["int","long","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["int","long","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"accountEntropyPool","parameterTypes":[] }, {"name":"aciAccountData","parameterTypes":[] }, {"name":"deviceId","parameterTypes":[] }, {"name":"encryptedDeviceName","parameterTypes":[] }, {"name":"isMultiDevice","parameterTypes":[] }, {"name":"mediaRootBackupKey","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"password","parameterTypes":[] }, {"name":"pinMasterKey","parameterTypes":[] }, {"name":"pniAccountData","parameterTypes":[] }, {"name":"profileKey","parameterTypes":[] }, {"name":"registered","parameterTypes":[] }, {"name":"registrationLockPin","parameterTypes":[] }, {"name":"serviceEnvironment","parameterTypes":[] }, {"name":"storageKey","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"usernameLinkEntropy","parameterTypes":[] }, {"name":"usernameLinkServerId","parameterTypes":[] }, {"name":"version","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","int","java.lang.String","java.lang.String","int","int","int","int","int"] }, {"name":"activeLastResortKyberPreKeyId","parameterTypes":[] }, {"name":"activeSignedPreKeyId","parameterTypes":[] }, {"name":"identityPrivateKey","parameterTypes":[] }, {"name":"identityPublicKey","parameterTypes":[] }, {"name":"nextKyberPreKeyId","parameterTypes":[] }, {"name":"nextPreKeyId","parameterTypes":[] }, {"name":"nextSignedPreKeyId","parameterTypes":[] }, {"name":"registrationId","parameterTypes":[] }, {"name":"serviceId","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.accounts.AccountsStorage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.util.List","java.lang.Integer"] }, {"name":"accounts","parameterTypes":[] }, {"name":"version","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.accounts.AccountsStorage$Account", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"environment","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"path","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.configuration.ConfigurationStore$Storage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Boolean","java.lang.Boolean","java.lang.Boolean","java.lang.Boolean","org.asamk.signal.manager.api.PhoneNumberSharingMode"] }, {"name":"linkPreviews","parameterTypes":[] }, {"name":"phoneNumberSharingMode","parameterTypes":[] }, {"name":"phoneNumberUnlisted","parameterTypes":[] }, {"name":"readReceipts","parameterTypes":[] }, {"name":"typingIndicators","parameterTypes":[] }, {"name":"unidentifiedDeliveryIndicators","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.configuration.LegacyConfigurationStore$Storage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Boolean","java.lang.Boolean","java.lang.Boolean","java.lang.Boolean","org.asamk.signal.manager.api.PhoneNumberSharingMode"] }] +}, +{ + "name":"org.asamk.signal.manager.storage.contacts.LegacyContactInfo", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.contacts.LegacyJsonContactsStore", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "fields":[{"name":"contacts", "allowWrite":true}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.groups.GroupInfo", + "allDeclaredFields":true, + "allDeclaredMethods":true +}, +{ + "name":"org.asamk.signal.manager.storage.groups.GroupInfoV1", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.asamk.signal.manager.storage.groups.LegacyGroupStore$GroupsDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.groups.LegacyGroupStore$Storage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.util.List"] }, {"name":"groups","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.groups.LegacyGroupStore$Storage$GroupV1", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String","int","boolean","boolean","java.util.List"] }, {"name":"archived","parameterTypes":[] }, {"name":"blocked","parameterTypes":[] }, {"name":"color","parameterTypes":[] }, {"name":"expectedV2Id","parameterTypes":[] }, {"name":"groupId","parameterTypes":[] }, {"name":"members","parameterTypes":[] }, {"name":"messageExpirationTime","parameterTypes":[] }, {"name":"name","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.groups.LegacyGroupStore$Storage$GroupV1$JsonRecipientAddress", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"number","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.groups.LegacyGroupStore$Storage$GroupV1$MembersDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.groups.LegacyGroupStore$Storage$GroupV2", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","boolean","boolean"] }, {"name":"blocked","parameterTypes":[] }, {"name":"distributionId","parameterTypes":[] }, {"name":"groupId","parameterTypes":[] }, {"name":"masterKey","parameterTypes":[] }, {"name":"permissionDenied","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.identities.LegacyIdentityKeyStore$IdentityStorage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","int","long"] }, {"name":"addedTimestamp","parameterTypes":[] }, {"name":"identityKey","parameterTypes":[] }, {"name":"trustLevel","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.profiles.LegacyProfileStore", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "fields":[{"name":"profiles", "allowWrite":true}] +}, +{ + "name":"org.asamk.signal.manager.storage.profiles.LegacyProfileStore$ProfileStoreDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.profiles.LegacySignalProfile", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.asamk.signal.manager.storage.profiles.LegacySignalProfileEntry", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.asamk.signal.manager.storage.profiles.ProfileStore", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.asamk.signal.manager.storage.protocol.LegacyJsonIdentityKeyStore$JsonIdentityKeyStoreDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.protocol.LegacyJsonPreKeyStore$JsonPreKeyStoreDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.protocol.LegacyJsonSessionStore$JsonSessionStoreDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.protocol.LegacyJsonSignedPreKeyStore$JsonSignedPreKeyStoreDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.recipients.LegacyRecipientStore", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "fields":[{"name":"addresses", "allowWrite":true}] +}, +{ + "name":"org.asamk.signal.manager.storage.recipients.LegacyRecipientStore$RecipientStoreDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.recipients.LegacyRecipientStore2$Storage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.util.List","long"] }, {"name":"lastId","parameterTypes":[] }, {"name":"recipients","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.recipients.LegacyRecipientStore2$Storage$Recipient", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["long","java.lang.String","java.lang.String","java.lang.String","java.lang.String","org.asamk.signal.manager.storage.recipients.LegacyRecipientStore2$Storage$Recipient$Contact","org.asamk.signal.manager.storage.recipients.LegacyRecipientStore2$Storage$Recipient$Profile"] }, {"name":"contact","parameterTypes":[] }, {"name":"expiringProfileKeyCredential","parameterTypes":[] }, {"name":"id","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"profile","parameterTypes":[] }, {"name":"profileKey","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.recipients.LegacyRecipientStore2$Storage$Recipient$Contact", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","int","boolean","boolean","boolean"] }, {"name":"archived","parameterTypes":[] }, {"name":"blocked","parameterTypes":[] }, {"name":"color","parameterTypes":[] }, {"name":"messageExpirationTime","parameterTypes":[] }, {"name":"name","parameterTypes":[] }, {"name":"profileSharingEnabled","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.recipients.LegacyRecipientStore2$Storage$Recipient$Profile", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["long","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.util.Set"] }, {"name":"about","parameterTypes":[] }, {"name":"aboutEmoji","parameterTypes":[] }, {"name":"avatarUrlPath","parameterTypes":[] }, {"name":"capabilities","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"lastUpdateTimestamp","parameterTypes":[] }, {"name":"mobileCoinAddress","parameterTypes":[] }, {"name":"unidentifiedAccessMode","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.senderKeys.LegacySenderKeySharedStore$Storage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.util.List"] }, {"name":"sharedSenderKeys","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.senderKeys.LegacySenderKeySharedStore$Storage$SharedSenderKey", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["long","int","java.lang.String"] }, {"name":"deviceId","parameterTypes":[] }, {"name":"distributionId","parameterTypes":[] }, {"name":"recipientId","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.stickerPacks.JsonStickerPack", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","org.asamk.signal.manager.storage.stickerPacks.JsonStickerPack$JsonSticker","java.util.List"] }, {"name":"author","parameterTypes":[] }, {"name":"cover","parameterTypes":[] }, {"name":"stickers","parameterTypes":[] }, {"name":"title","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.stickerPacks.JsonStickerPack$JsonSticker", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.Integer","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"contentType","parameterTypes":[] }, {"name":"emoji","parameterTypes":[] }, {"name":"file","parameterTypes":[] }, {"name":"id","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.stickers.LegacyStickerStore$Storage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.util.List"] }, {"name":"stickers","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.stickers.LegacyStickerStore$Storage$Sticker", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","boolean"] }, {"name":"installed","parameterTypes":[] }, {"name":"packId","parameterTypes":[] }, {"name":"packKey","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore$ThreadsDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.util.SecurityProvider$DefaultRandom", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.COMPOSITE$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.CONTEXT$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.CompositeSignatures$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.DH$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.DSA$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.DSTU4145$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.Dilithium$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.EC$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.ECGOST$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.EXTERNAL$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.EdEC$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.ElGamal$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.Falcon$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.GM$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.GOST$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.IES$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.LMS$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.MLDSA$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.MLKEM$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.NTRU$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.NoSig$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.SLHDSA$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.SPHINCSPlus$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.X509$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.edec.SignatureSpi$Ed25519", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.edec.SignatureSpi$Ed448", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.Blake2b$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.Blake2s$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.Blake3$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.DSTU7564$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.GOST3411$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.Haraka$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.Keccak$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.MD2$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.MD4$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.MD5$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD128$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD160$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD256$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD320$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.SHA1$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.SHA224$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.SHA256$Digest", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.SHA256$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.SHA3$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.SHA384$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.SHA512$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.SM3$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.Skein$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.Tiger$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.digest.Whirlpool$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.drbg.DRBG$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.keystore.BC$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.keystore.BCFKS$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.keystore.PKCS12$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi$Std", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi.Std" +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.AES$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.ARC4$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.ARIA$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Blowfish$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.CAST5$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.CAST6$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Camellia$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.ChaCha$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.DES$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.DESede$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.DSTU7624$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.GOST28147$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.GOST3412_2015$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Grain128$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Grainv1$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.HC128$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.HC256$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.IDEA$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Noekeon$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.OpenSSLPBKDF$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF1$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Poly1305$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.RC2$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.RC5$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.RC6$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Rijndael$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.SCRYPT$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.SEED$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.SM4$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Salsa20$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Serpent$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Shacal2$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.SipHash$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.SipHash128$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Skipjack$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.TEA$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.TLSKDF$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Threefish$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Twofish$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.VMPC$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.VMPCKSA3$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.XSalsa20$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.XTEA$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.symmetric.Zuc$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.pqc.jcajce.provider.LMS$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.pqc.jcajce.provider.McEliece$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.pqc.jcajce.provider.NH$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.pqc.jcajce.provider.QTESLA$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.pqc.jcajce.provider.Rainbow$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.pqc.jcajce.provider.SPHINCS$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.pqc.jcajce.provider.XMSS$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.freedesktop.dbus.connections.base.GlobalHandler", + "queryAllDeclaredMethods":true +}, +{ + "name":"org.freedesktop.dbus.errors.ServiceUnknown", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.freedesktop.dbus.errors.UnknownMethod", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.freedesktop.dbus.errors.UnknownObject", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"org.freedesktop.dbus.interfaces.DBus$NameAcquired", + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"org.freedesktop.dbus.interfaces.Introspectable", + "allDeclaredMethods":true, + "allDeclaredClasses":true, + "methods":[{"name":"Introspect","parameterTypes":[] }] +}, +{ + "name":"org.freedesktop.dbus.interfaces.Peer", + "allDeclaredMethods":true, + "allDeclaredClasses":true +}, +{ + "name":"org.freedesktop.dbus.interfaces.Properties", + "allDeclaredMethods":true, + "allDeclaredClasses":true, + "methods":[{"name":"Get","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"GetAll","parameterTypes":["java.lang.String"] }, {"name":"Set","parameterTypes":["java.lang.String","java.lang.String","java.lang.Object"] }] +}, +{ + "name":"org.freedesktop.dbus.interfaces.Properties$PropertiesChanged", + "allPublicConstructors":true +}, +{ + "name":"org.robolectric.Robolectric" +}, +{ + "name":"org.signal.cdsi.proto.ClientRequest", + "fields":[{"name":"aciUakPairs_"}, {"name":"discardE164S_"}, {"name":"newE164S_"}, {"name":"prevE164S_"}, {"name":"returnAcisWithoutUaks_"}, {"name":"tokenAck_"}, {"name":"token_"}] +}, +{ + "name":"org.signal.cdsi.proto.ClientResponse", + "fields":[{"name":"debugPermitsUsed_"}, {"name":"e164PniAciTriples_"}, {"name":"retryAfterSecs_"}, {"name":"token_"}] +}, +{ + "name":"org.signal.libsignal.protocol.IdentityKey" +}, +{ + "name":"org.signal.libsignal.protocol.ServiceId", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"org.signal.libsignal.protocol.SignalProtocolAddress" +}, +{ + "name":"org.signal.libsignal.protocol.ecc.ECPublicKey" +}, +{ + "name":"org.signal.libsignal.protocol.kem.KEMPublicKey" +}, +{ + "name":"org.signal.libsignal.protocol.state.IdentityKeyStore", + "allDeclaredMethods":true +}, +{ + "name":"org.signal.libsignal.protocol.state.PreKeyStore", + "allDeclaredMethods":true +}, +{ + "name":"org.signal.libsignal.protocol.state.SessionStore", + "allDeclaredMethods":true +}, +{ + "name":"org.signal.libsignal.protocol.state.SignalProtocolStore", + "allDeclaredMethods":true +}, +{ + "name":"org.signal.libsignal.protocol.state.SignedPreKeyStore", + "allDeclaredMethods":true +}, +{ + "name":"org.signal.libsignal.zkgroup.internal.ByteArray", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"org.signal.libsignal.zkgroup.profiles.ProfileKey", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"org.signal.storageservice.protos.groups.AccessControl", + "fields":[{"name":"addFromInviteLink_"}, {"name":"attributes_"}, {"name":"members_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.AvatarUploadAttributes", + "fields":[{"name":"acl_"}, {"name":"algorithm_"}, {"name":"credential_"}, {"name":"date_"}, {"name":"key_"}, {"name":"policy_"}, {"name":"signature_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.BannedMember", + "fields":[{"name":"timestamp_"}, {"name":"userId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.Group", + "fields":[{"name":"accessControl_"}, {"name":"announcementsOnly_"}, {"name":"avatar_"}, {"name":"bannedMembers_"}, {"name":"description_"}, {"name":"disappearingMessagesTimer_"}, {"name":"inviteLinkPassword_"}, {"name":"members_"}, {"name":"pendingMembers_"}, {"name":"publicKey_"}, {"name":"requestingMembers_"}, {"name":"revision_"}, {"name":"title_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupAttributeBlob", + "fields":[{"name":"contentCase_"}, {"name":"content_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange", + "fields":[{"name":"actions_"}, {"name":"changeEpoch_"}, {"name":"serverSignature_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions", + "fields":[{"name":"addBannedMembers_"}, {"name":"addMembers_"}, {"name":"addPendingMembers_"}, {"name":"addRequestingMembers_"}, {"name":"deleteBannedMembers_"}, {"name":"deleteMembers_"}, {"name":"deletePendingMembers_"}, {"name":"deleteRequestingMembers_"}, {"name":"modifyAddFromInviteLinkAccess_"}, {"name":"modifyAnnouncementsOnly_"}, {"name":"modifyAttributesAccess_"}, {"name":"modifyAvatar_"}, {"name":"modifyDescription_"}, {"name":"modifyDisappearingMessagesTimer_"}, {"name":"modifyInviteLinkPassword_"}, {"name":"modifyMemberAccess_"}, {"name":"modifyMemberProfileKeys_"}, {"name":"modifyMemberRoles_"}, {"name":"modifyTitle_"}, {"name":"promotePendingMembers_"}, {"name":"promotePendingPniAciMembers_"}, {"name":"promoteRequestingMembers_"}, {"name":"revision_"}, {"name":"sourceServiceId_"}, {"name":"sourceUuid_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddBannedMemberAction", + "fields":[{"name":"added_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddMemberAction", + "fields":[{"name":"added_"}, {"name":"joinFromInviteLink_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddPendingMemberAction", + "fields":[{"name":"added_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddRequestingMemberAction", + "fields":[{"name":"added_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeleteBannedMemberAction", + "fields":[{"name":"deletedUserId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeleteMemberAction", + "fields":[{"name":"deletedUserId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeletePendingMemberAction", + "fields":[{"name":"deletedUserId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeleteRequestingMemberAction", + "fields":[{"name":"deletedUserId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAddFromInviteLinkAccessControlAction", + "fields":[{"name":"addFromInviteLinkAccess_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAnnouncementsOnlyAction", + "fields":[{"name":"announcementsOnly_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAttributesAccessControlAction", + "fields":[{"name":"attributesAccess_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAvatarAction", + "fields":[{"name":"avatar_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyDescriptionAction", + "fields":[{"name":"description_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyDisappearingMessagesTimerAction", + "fields":[{"name":"timer_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyInviteLinkPasswordAction", + "fields":[{"name":"inviteLinkPassword_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMemberProfileKeyAction", + "fields":[{"name":"presentation_"}, {"name":"profileKey_"}, {"name":"userId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMemberRoleAction", + "fields":[{"name":"role_"}, {"name":"userId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMembersAccessControlAction", + "fields":[{"name":"membersAccess_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyTitleAction", + "fields":[{"name":"title_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$PromotePendingMemberAction", + "fields":[{"name":"presentation_"}, {"name":"profileKey_"}, {"name":"userId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$PromotePendingPniAciMemberProfileKeyAction", + "fields":[{"name":"pni_"}, {"name":"presentation_"}, {"name":"profileKey_"}, {"name":"userId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChange$Actions$PromoteRequestingMemberAction", + "fields":[{"name":"role_"}, {"name":"userId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChanges", + "fields":[{"name":"groupChanges_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupChanges$GroupChangeState", + "fields":[{"name":"groupChange_"}, {"name":"groupState_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupInviteLink", + "fields":[{"name":"contentsCase_"}, {"name":"contents_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupInviteLink$GroupInviteLinkContentsV1", + "fields":[{"name":"groupMasterKey_"}, {"name":"inviteLinkPassword_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.GroupJoinInfo", + "fields":[{"name":"addFromInviteLink_"}, {"name":"avatar_"}, {"name":"description_"}, {"name":"memberCount_"}, {"name":"pendingAdminApproval_"}, {"name":"publicKey_"}, {"name":"revision_"}, {"name":"title_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.Member", + "fields":[{"name":"joinedAtRevision_"}, {"name":"presentation_"}, {"name":"profileKey_"}, {"name":"role_"}, {"name":"userId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.PendingMember", + "fields":[{"name":"addedByUserId_"}, {"name":"member_"}, {"name":"timestamp_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.RequestingMember", + "fields":[{"name":"presentation_"}, {"name":"profileKey_"}, {"name":"timestamp_"}, {"name":"userId_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedApproveMember", + "fields":[{"name":"role_"}, {"name":"uuid_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedBannedMember", + "fields":[{"name":"serviceIdBinary_"}, {"name":"serviceIdBytes_"}, {"name":"timestamp_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedGroup", + "fields":[{"name":"accessControl_"}, {"name":"avatar_"}, {"name":"bannedMembers_"}, {"name":"description_"}, {"name":"disappearingMessagesTimer_"}, {"name":"inviteLinkPassword_"}, {"name":"isAnnouncementGroup_"}, {"name":"members_"}, {"name":"pendingMembers_"}, {"name":"requestingMembers_"}, {"name":"revision_"}, {"name":"title_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedGroupChange", + "fields":[{"name":"deleteBannedMembers_"}, {"name":"deleteMembers_"}, {"name":"deletePendingMembers_"}, {"name":"deleteRequestingMembers_"}, {"name":"editorServiceIdBytes_"}, {"name":"editor_"}, {"name":"modifiedProfileKeys_"}, {"name":"modifyMemberRoles_"}, {"name":"newAttributeAccess_"}, {"name":"newAvatar_"}, {"name":"newBannedMembers_"}, {"name":"newDescription_"}, {"name":"newInviteLinkAccess_"}, {"name":"newInviteLinkPassword_"}, {"name":"newIsAnnouncementGroup_"}, {"name":"newMemberAccess_"}, {"name":"newMembers_"}, {"name":"newPendingMembers_"}, {"name":"newRequestingMembers_"}, {"name":"newTimer_"}, {"name":"newTitle_"}, {"name":"promotePendingMembers_"}, {"name":"promotePendingPniAciMembers_"}, {"name":"promoteRequestingMembers_"}, {"name":"revision_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo", + "fields":[{"name":"addFromInviteLink_"}, {"name":"avatar_"}, {"name":"description_"}, {"name":"isAnnouncementGroup_"}, {"name":"memberCount_"}, {"name":"pendingAdminApproval_"}, {"name":"revision_"}, {"name":"title_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedMember", + "fields":[{"name":"aciBytes_"}, {"name":"joinedAtRevision_"}, {"name":"pniBytes_"}, {"name":"pni_"}, {"name":"profileKey_"}, {"name":"role_"}, {"name":"uuid_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole", + "fields":[{"name":"role_"}, {"name":"uuid_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedPendingMember", + "fields":[{"name":"addedByAci_"}, {"name":"addedByUuid_"}, {"name":"role_"}, {"name":"serviceIdBinary_"}, {"name":"serviceIdBytes_"}, {"name":"serviceIdCipherText_"}, {"name":"timestamp_"}, {"name":"uuidCipherText_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval", + "fields":[{"name":"uuidCipherText_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedRequestingMember", + "fields":[{"name":"aciBytes_"}, {"name":"profileKey_"}, {"name":"timestamp_"}, {"name":"uuid_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedString", + "fields":[{"name":"value_"}] +}, +{ + "name":"org.signal.storageservice.protos.groups.local.DecryptedTimer", + "fields":[{"name":"duration_"}] +}, +{ + "name":"org.slf4j.Logger" +}, +{ + "name":"org.sqlite.JDBC" +}, +{ + "name":"org.whispersystems.signalservice.api.account.AccountAttributes", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"getCapabilities","parameterTypes":[] }, {"name":"getDiscoverableByPhoneNumber","parameterTypes":[] }, {"name":"getFetchesMessages","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPniRegistrationId","parameterTypes":[] }, {"name":"getRecoveryPassword","parameterTypes":[] }, {"name":"getRegistrationId","parameterTypes":[] }, {"name":"getRegistrationLock","parameterTypes":[] }, {"name":"getSignalingKey","parameterTypes":[] }, {"name":"getUnidentifiedAccessKey","parameterTypes":[] }, {"name":"getUnrestrictedUnidentifiedAccess","parameterTypes":[] }, {"name":"getVideo","parameterTypes":[] }, {"name":"getVoice","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.account.AccountAttributes$Capabilities", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"getAnnouncementGroup","parameterTypes":[] }, {"name":"getAttachmentBackfill","parameterTypes":[] }, {"name":"getChangeNumber","parameterTypes":[] }, {"name":"getDeleteSync","parameterTypes":[] }, {"name":"getGiftBadges","parameterTypes":[] }, {"name":"getPaymentActivation","parameterTypes":[] }, {"name":"getPni","parameterTypes":[] }, {"name":"getSenderKey","parameterTypes":[] }, {"name":"getStorage","parameterTypes":[] }, {"name":"getStorageServiceEncryptionV2","parameterTypes":[] }, {"name":"getStories","parameterTypes":[] }, {"name":"getVersionedExpirationTimer","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getDeviceMessages","parameterTypes":[] }, {"name":"getDevicePniSignedPrekeys","parameterTypes":[] }, {"name":"getNumber","parameterTypes":[] }, {"name":"getPniIdentityKey","parameterTypes":[] }, {"name":"getPniRegistrationIds","parameterTypes":[] }, {"name":"getRegistrationLock","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.groupsv2.CredentialResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]" +}, +{ + "name":"org.whispersystems.signalservice.api.keys.OneTimePreKeyCounts", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["java.lang.String","java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, +{ + "name":"org.whispersystems.signalservice.api.messages.calls.HangupMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.api.messages.calls.HangupMessage$Type", + "allDeclaredFields":true, + "allDeclaredMethods":true +}, +{ + "name":"org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.api.messages.calls.OfferMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.api.messages.calls.OfferMessage$Type", + "allDeclaredFields":true, + "allDeclaredMethods":true +}, +{ + "name":"org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfile", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfile$Badge", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfile$Capabilities", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfileWrite", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","byte[]","byte[]","byte[]","byte[]","byte[]","boolean","boolean","byte[]","java.util.List"] }, {"name":"getAbout","parameterTypes":[] }, {"name":"getAboutEmoji","parameterTypes":[] }, {"name":"getAvatar","parameterTypes":[] }, {"name":"getBadgeIds","parameterTypes":[] }, {"name":"getCommitment","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPaymentAddress","parameterTypes":[] }, {"name":"getPhoneNumberSharing","parameterTypes":[] }, {"name":"getSameAvatar","parameterTypes":[] }, {"name":"getVersion","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.provisioning.ProvisioningMessage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.api.push.ServiceId", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["org.signal.libsignal.protocol.ServiceId"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"hashCode","parameterTypes":[] }, {"name":"logString","parameterTypes":[] }, {"name":"toByteArray","parameterTypes":[] }, {"name":"toByteString","parameterTypes":[] }, {"name":"toProtocolAddress","parameterTypes":["int"] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.push.ServiceId$ACI" +}, +{ + "name":"org.whispersystems.signalservice.api.push.ServiceId$Companion", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"fromLibSignal","parameterTypes":["org.signal.libsignal.protocol.ServiceId"] }, {"name":"hashCode","parameterTypes":[] }, {"name":"parseOrNull","parameterTypes":["java.lang.String"] }, {"name":"parseOrNull","parameterTypes":["java.lang.String","boolean"] }, {"name":"parseOrNull","parameterTypes":["okio.ByteString"] }, {"name":"parseOrNull","parameterTypes":["byte[]"] }, {"name":"parseOrThrow","parameterTypes":["java.lang.String"] }, {"name":"parseOrThrow","parameterTypes":["okio.ByteString"] }, {"name":"parseOrThrow","parameterTypes":["byte[]"] }, {"name":"toString","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.push.ServiceId$PNI" +}, +{ + "name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "allDeclaredClasses":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"getSignature","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity$ByteArrayDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity$ByteArraySerializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException$JsonResponse", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"org.whispersystems.signalservice.api.ratelimit.SubmitRecaptchaChallengePayload", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.api.storage.StorageAuthResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.svr.Svr3Credentials", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","byte[]"] }, {"name":"","parameterTypes":["java.lang.String","java.lang.String","byte[]","int","kotlin.jvm.internal.DefaultConstructorMarker"] }, {"name":"getAuthCredentials","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.contacts.crypto.SignatureBodyEntity", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.contacts.entities.DiscoveryRequest", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.internal.contacts.entities.DiscoveryResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.internal.contacts.entities.KeyBackupRequest", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "allDeclaredClasses":true, + "methods":[{"name":"getData","parameterTypes":[] }, {"name":"getIv","parameterTypes":[] }, {"name":"getMac","parameterTypes":[] }, {"name":"getRequestId","parameterTypes":[] }, {"name":"getType","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.contacts.entities.MultiRemoteAttestationResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.internal.contacts.entities.QueryEnvelope", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationRequest", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "allDeclaredClasses":true, + "methods":[{"name":"getClientPublic","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.contacts.entities.TokenResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.devices.DeviceNameProtos$DeviceName", + "fields":[{"name":"bitField0_"}, {"name":"ciphertext_"}, {"name":"ephemeralPublic_"}, {"name":"syntheticIv_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.keybackup.protos.BackupRequest", + "fields":[{"name":"backupId_"}, {"name":"bitField0_"}, {"name":"data_"}, {"name":"pin_"}, {"name":"serviceId_"}, {"name":"token_"}, {"name":"tries_"}, {"name":"validFrom_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.keybackup.protos.BackupResponse", + "fields":[{"name":"bitField0_"}, {"name":"status_"}, {"name":"token_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.keybackup.protos.DeleteRequest", + "fields":[{"name":"backupId_"}, {"name":"bitField0_"}, {"name":"serviceId_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.keybackup.protos.Request", + "fields":[{"name":"backup_"}, {"name":"bitField0_"}, {"name":"delete_"}, {"name":"restore_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.keybackup.protos.Response", + "fields":[{"name":"backup_"}, {"name":"bitField0_"}, {"name":"delete_"}, {"name":"restore_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.keybackup.protos.RestoreRequest", + "fields":[{"name":"backupId_"}, {"name":"bitField0_"}, {"name":"pin_"}, {"name":"serviceId_"}, {"name":"token_"}, {"name":"validFrom_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.keybackup.protos.RestoreResponse", + "fields":[{"name":"bitField0_"}, {"name":"data_"}, {"name":"status_"}, {"name":"token_"}, {"name":"tries_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.AttachmentUploadForm", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["int","java.lang.String","java.util.Map","java.lang.String"] }, {"name":"","parameterTypes":["int","java.lang.String","java.util.Map","java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.AttachmentV2UploadAttributes", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.AuthCredentials", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ByteArrayDeserializerBase64", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.CdsiAuthResponse", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ConfirmCodeMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ConfirmUsernameRequest", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ConfirmUsernameResponse", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.util.UUID"] }, {"name":"","parameterTypes":["java.lang.String","java.util.UUID","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.DeviceCode", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.DeviceId", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.DeviceInfoList", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.GcmRegistrationId", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","boolean"] }, {"name":"getGcmRegistrationId","parameterTypes":[] }, {"name":"getWebSocketChannel","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.GetAciByUsernameResponse", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.GetUsernameFromLinkResponseBody", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }, {"name":"","parameterTypes":["java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.GroupMismatchedDevices", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.GroupStaleDevices", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.KyberPreKeyEntity", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"getKeyId","parameterTypes":[] }, {"name":"getPublicKey","parameterTypes":[] }, {"name":"getSignature","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.KyberPreKeyEntity$ByteArrayDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.KyberPreKeyEntity$ByteArraySerializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.KyberPreKeyEntity$KEMPublicKeyDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.KyberPreKeyEntity$KEMPublicKeySerializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.LinkDeviceRequest", + "allDeclaredFields":true, + "allDeclaredClasses":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"accountAttributes","parameterTypes":[] }, {"name":"aciPqLastResortPreKey","parameterTypes":[] }, {"name":"aciSignedPreKey","parameterTypes":[] }, {"name":"pniPqLastResortPreKey","parameterTypes":[] }, {"name":"pniSignedPreKey","parameterTypes":[] }, {"name":"verificationCode","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.MismatchedDevices", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.OneTimePreKeyCounts", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.OutgoingPushMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "allDeclaredClasses":true, + "methods":[{"name":"getDestinationDeviceId","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.OutgoingPushMessageList", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "allDeclaredClasses":true, + "methods":[{"name":"getDestination","parameterTypes":[] }, {"name":"getMessages","parameterTypes":[] }, {"name":"getTimestamp","parameterTypes":[] }, {"name":"isOnline","parameterTypes":[] }, {"name":"isUrgent","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.PreKeyEntity", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "allDeclaredClasses":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"getKeyId","parameterTypes":[] }, {"name":"getPublicKey","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.PreKeyEntity$ECPublicKeyDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.PreKeyEntity$ECPublicKeySerializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.PreKeyResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.PreKeyResponseItem", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.PreKeyState", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "allDeclaredClasses":true, + "methods":[{"name":"getIdentityKey","parameterTypes":[] }, {"name":"getPreKeys","parameterTypes":[] }, {"name":"getSignedPreKey","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ProfileAvatarUploadAttributes", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ProofRequiredResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ProvisioningMessage", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ProvisioningProtos$ProvisionEnvelope", + "fields":[{"name":"bitField0_"}, {"name":"body_"}, {"name":"publicKey_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ProvisioningProtos$ProvisionMessage", + "fields":[{"name":"aciIdentityKeyPrivate_"}, {"name":"aciIdentityKeyPublic_"}, {"name":"aci_"}, {"name":"bitField0_"}, {"name":"number_"}, {"name":"pniIdentityKeyPrivate_"}, {"name":"pniIdentityKeyPublic_"}, {"name":"pni_"}, {"name":"profileKey_"}, {"name":"provisioningCode_"}, {"name":"provisioningVersion_"}, {"name":"readReceipts_"}, {"name":"userAgent_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ProvisioningProtos$ProvisioningUuid", + "fields":[{"name":"bitField0_"}, {"name":"uuid_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.PushServiceSocket$RegistrationLockFailure", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.PushServiceSocket$RegistrationLockV2", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataJson", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.Integer","java.lang.Integer","java.lang.Integer","boolean","java.util.List","boolean"] }, {"name":"","parameterTypes":["java.lang.String","java.lang.Integer","java.lang.Integer","java.lang.Integer","boolean","java.util.List","boolean","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.RegistrationSessionRequestBody", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","org.whispersystems.signalservice.api.account.AccountAttributes","java.lang.String","java.lang.String","org.whispersystems.signalservice.api.push.SignedPreKeyEntity","org.whispersystems.signalservice.api.push.SignedPreKeyEntity","org.whispersystems.signalservice.internal.push.KyberPreKeyEntity","org.whispersystems.signalservice.internal.push.KyberPreKeyEntity","org.whispersystems.signalservice.internal.push.GcmRegistrationId","boolean","boolean"] }, {"name":"getAccountAttributes","parameterTypes":[] }, {"name":"getAciIdentityKey","parameterTypes":[] }, {"name":"getAciPqLastResortPreKey","parameterTypes":[] }, {"name":"getAciSignedPreKey","parameterTypes":[] }, {"name":"getGcmToken","parameterTypes":[] }, {"name":"getPniIdentityKey","parameterTypes":[] }, {"name":"getPniPqLastResortPreKey","parameterTypes":[] }, {"name":"getPniSignedPreKey","parameterTypes":[] }, {"name":"getRecoveryPassword","parameterTypes":[] }, {"name":"getRequireAtomic","parameterTypes":[] }, {"name":"getSessionId","parameterTypes":[] }, {"name":"getSkipDeviceTransfer","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ReserveUsernameRequest", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.internal.push.ReserveUsernameResponse", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.SendGroupMessageResponse", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.SendMessageResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.SenderCertificate", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.SenderCertificate$ByteArrayDesieralizer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.SetUsernameLinkRequestBody", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }, {"name":"","parameterTypes":["java.lang.String","boolean"] }, {"name":"getKeepLinkHandle","parameterTypes":[] }, {"name":"getUsernameLinkEncryptedValue","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.SetUsernameLinkResponseBody", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.util.UUID"] }, {"name":"","parameterTypes":["java.util.UUID","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.StaleDevices", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.SubmitRecaptchaChallengePayload", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"org.whispersystems.signalservice.internal.push.UpdateVerificationSessionRequestBody", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"getCaptcha","parameterTypes":[] }, {"name":"getMcc","parameterTypes":[] }, {"name":"getMnc","parameterTypes":[] }, {"name":"getPushChallenge","parameterTypes":[] }, {"name":"getPushToken","parameterTypes":[] }, {"name":"getPushTokenType","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.VerificationCodeFailureResponseBody", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["boolean","java.lang.String"] }, {"name":"","parameterTypes":["boolean","java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.VerificationSessionMetadataRequestBody", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"getMcc","parameterTypes":[] }, {"name":"getMnc","parameterTypes":[] }, {"name":"getNumber","parameterTypes":[] }, {"name":"getPushToken","parameterTypes":[] }, {"name":"getPushTokenType","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.VerifyAccountResponse", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.WhoAmIResponse", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String","org.whispersystems.signalservice.internal.push.WhoAmIResponse$Entitlements"] }, {"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String","org.whispersystems.signalservice.internal.push.WhoAmIResponse$Entitlements","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.WhoAmIResponse$BackupEntitlement", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.Long","java.lang.Long"] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.WhoAmIResponse$BadgeEntitlement", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.Boolean","java.lang.Long"] }, {"name":"","parameterTypes":["java.lang.String","java.lang.Boolean","java.lang.Long","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.push.WhoAmIResponse$Entitlements", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.util.List","org.whispersystems.signalservice.internal.push.WhoAmIResponse$BackupEntitlement"] }, {"name":"","parameterTypes":["java.util.List","org.whispersystems.signalservice.internal.push.WhoAmIResponse$BackupEntitlement","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.serialize.protos.AddressProto", + "fields":[{"name":"bitField0_"}, {"name":"e164_"}, {"name":"uuid_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.serialize.protos.MetadataProto", + "fields":[{"name":"address_"}, {"name":"bitField0_"}, {"name":"destinationUuid_"}, {"name":"groupId_"}, {"name":"needsReceipt_"}, {"name":"senderDevice_"}, {"name":"serverDeliveredTimestamp_"}, {"name":"serverGuid_"}, {"name":"serverReceivedTimestamp_"}, {"name":"timestamp_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto", + "fields":[{"name":"bitField0_"}, {"name":"dataCase_"}, {"name":"data_"}, {"name":"localAddress_"}, {"name":"metadata_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.sticker.StickerProtos$Pack", + "fields":[{"name":"author_"}, {"name":"bitField0_"}, {"name":"cover_"}, {"name":"stickers_"}, {"name":"title_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.sticker.StickerProtos$Pack$Sticker", + "fields":[{"name":"bitField0_"}, {"name":"contentType_"}, {"name":"emoji_"}, {"name":"id_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord", + "allDeclaredFields":true, + "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$BackupTierHistory" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$Builder" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$Companion" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$IAPSubscriberData" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$NotificationProfileManualOverride" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$PhoneNumberSharingMode" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$PinnedConversation", + "fields":[{"name":"identifierCase_"}, {"name":"identifier_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$PinnedConversation$Contact", + "fields":[{"name":"e164_"}, {"name":"serviceId_"}, {"name":"uuid_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$UsernameLink", + "allDeclaredFields":true +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AvatarColor" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord", + "allDeclaredFields":true, + "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$Builder" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$Companion" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$IdentityState" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$Name", + "allDeclaredFields":true +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV1Record", + "allDeclaredFields":true, + "fields":[{"name":"archived"}, {"name":"blocked"}, {"name":"id"}, {"name":"markedUnread"}, {"name":"mutedUntilTimestamp"}, {"name":"whitelisted"}], + "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV1Record$Builder" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV1Record$Companion" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record", + "allDeclaredFields":true, + "fields":[{"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"dontNotifyForMentionsIfMuted"}, {"name":"hideStory"}, {"name":"markedUnread"}, {"name":"masterKey"}, {"name":"mutedUntilTimestamp"}, {"name":"storySendMode"}, {"name":"whitelisted"}], + "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record$Builder" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record$Companion" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record$StorySendMode" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ManifestRecord", + "fields":[{"name":"identifiers_"}, {"name":"sourceDevice_"}, {"name":"version_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ManifestRecord$Identifier", + "fields":[{"name":"raw_"}, {"name":"type_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.OptionalBool" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.Payments", + "allDeclaredFields":true +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ReadOperation", + "fields":[{"name":"readKey_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.StorageItem", + "fields":[{"name":"key_"}, {"name":"value_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.StorageItems", + "fields":[{"name":"items_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.StorageManifest", + "fields":[{"name":"value_"}, {"name":"version_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.StorageRecord", + "fields":[{"name":"recordCase_"}, {"name":"record_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.StoryDistributionListRecord", + "allDeclaredFields":true +}, +{ + "name":"org.whispersystems.signalservice.internal.util.JsonUtil$AciDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.util.JsonUtil$IdentityKeyDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.util.JsonUtil$IdentityKeySerializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.util.JsonUtil$ServiceIdDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.util.JsonUtil$UuidDeserializer", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.websocket.WebSocketProtos$WebSocketMessage", + "fields":[{"name":"bitField0_"}, {"name":"request_"}, {"name":"response_"}, {"name":"type_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.websocket.WebSocketProtos$WebSocketRequestMessage", + "fields":[{"name":"bitField0_"}, {"name":"body_"}, {"name":"headers_"}, {"name":"id_"}, {"name":"path_"}, {"name":"verb_"}] +}, +{ + "name":"org.whispersystems.signalservice.internal.websocket.WebSocketProtos$WebSocketResponseMessage", + "fields":[{"name":"bitField0_"}, {"name":"body_"}, {"name":"headers_"}, {"name":"id_"}, {"name":"message_"}, {"name":"status_"}] +}, +{ + "name":"sun.misc.Unsafe", + "allDeclaredFields":true, + "methods":[{"name":"allocateMemory","parameterTypes":["long"] }, {"name":"arrayBaseOffset","parameterTypes":["java.lang.Class"] }, {"name":"arrayIndexScale","parameterTypes":["java.lang.Class"] }, {"name":"copyMemory","parameterTypes":["long","long","long"] }, {"name":"copyMemory","parameterTypes":["java.lang.Object","long","java.lang.Object","long","long"] }, {"name":"freeMemory","parameterTypes":["long"] }, {"name":"getAddress","parameterTypes":["long"] }, {"name":"getBoolean","parameterTypes":["java.lang.Object","long"] }, {"name":"getByte","parameterTypes":["long"] }, {"name":"getByte","parameterTypes":["java.lang.Object","long"] }, {"name":"getDouble","parameterTypes":["long"] }, {"name":"getDouble","parameterTypes":["java.lang.Object","long"] }, {"name":"getFloat","parameterTypes":["long"] }, {"name":"getFloat","parameterTypes":["java.lang.Object","long"] }, {"name":"getInt","parameterTypes":["long"] }, {"name":"getInt","parameterTypes":["java.lang.Object","long"] }, {"name":"getLong","parameterTypes":["long"] }, {"name":"getLong","parameterTypes":["java.lang.Object","long"] }, {"name":"getObject","parameterTypes":["java.lang.Object","long"] }, {"name":"getShort","parameterTypes":["long"] }, {"name":"objectFieldOffset","parameterTypes":["java.lang.reflect.Field"] }, {"name":"putAddress","parameterTypes":["long","long"] }, {"name":"putBoolean","parameterTypes":["java.lang.Object","long","boolean"] }, {"name":"putByte","parameterTypes":["long","byte"] }, {"name":"putByte","parameterTypes":["java.lang.Object","long","byte"] }, {"name":"putDouble","parameterTypes":["long","double"] }, {"name":"putDouble","parameterTypes":["java.lang.Object","long","double"] }, {"name":"putFloat","parameterTypes":["long","float"] }, {"name":"putFloat","parameterTypes":["java.lang.Object","long","float"] }, {"name":"putInt","parameterTypes":["long","int"] }, {"name":"putInt","parameterTypes":["java.lang.Object","long","int"] }, {"name":"putLong","parameterTypes":["long","long"] }, {"name":"putLong","parameterTypes":["java.lang.Object","long","long"] }, {"name":"putObject","parameterTypes":["java.lang.Object","long","java.lang.Object"] }, {"name":"putShort","parameterTypes":["long","short"] }] +}, +{ + "name":"sun.security.provider.DSA$SHA224withDSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.JavaKeyStore$DualFormatJKS", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.JavaKeyStore$JKS", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.NativePRNG$NonBlocking", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA224", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA5$SHA384", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA5$SHA512", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SecureRandom", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.certpath.CollectionCertStore", + "methods":[{"name":"","parameterTypes":["java.security.cert.CertStoreParameters"] }] +}, +{ + "name":"sun.security.provider.certpath.PKIXCertPathValidator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.certpath.SunCertPathBuilder", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.PSSParameters", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAKeyFactory$Legacy", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAPSSSignature", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSASignature$SHA224withRSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSASignature$SHA256withRSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSASignature$SHA512withRSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$TLSContext", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.x509.AuthorityKeyIdentifierExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.BasicConstraintsExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CRLDistributionPointsExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.ExtendedKeyUsageExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.KeyUsageExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectAlternativeNameExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectKeyIdentifierExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +} +] diff --git a/graalvm-config-dir/resource-config.json b/graalvm-config-dir/resource-config.json new file mode 100644 index 00000000..8bd8a7af --- /dev/null +++ b/graalvm-config-dir/resource-config.json @@ -0,0 +1,226 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/maven/org.xerial/sqlite-jdbc/pom.properties\\E" + }, { + "pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E" + }, { + "pattern":"\\QMETA-INF/services/com.sun.net.httpserver.spi.HttpServerProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.nio.file.spi.FileTypeDetector\\E" + }, { + "pattern":"\\QMETA-INF/services/java.sql.Driver\\E" + }, { + "pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoader\\E" + }, { + "pattern":"\\QMETA-INF/services/kotlin.reflect.jvm.internal.impl.resolve.ExternalOverridabilityCondition\\E" + }, { + "pattern":"\\QMETA-INF/services/kotlin.reflect.jvm.internal.impl.util.ModuleVisibilityHelper\\E" + }, { + "pattern":"\\QMETA-INF/services/org.freedesktop.dbus.spi.message.ISocketProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.freedesktop.dbus.spi.transport.ITransportProvider\\E" + }, { + "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AG\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AI\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AR\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AS\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AT\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AU\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AZ\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BB\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BD\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BE\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BM\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BO\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BR\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BS\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CA\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CH\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CI\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CL\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CN\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CO\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CR\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CZ\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DE\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DK\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EC\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EE\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ES\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FI\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FR\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GB\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GR\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HK\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HR\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HU\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ID\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IL\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IN\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IR\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IT\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_JP\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LV\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MM\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MO\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MX\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MY\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NG\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NL\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NZ\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PA\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PE\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PH\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PL\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_RO\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_RU\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SA\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SI\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SK\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TH\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TR\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_UA\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_UG\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VE\\E" + }, { + "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_XK\\E" + }, { + "pattern":"\\Qjni/x86_64-Linux/libjffi-1.2.so\\E" + }, { + "pattern":"\\Qkotlin/annotation/annotation.kotlin_builtins\\E" + }, { + "pattern":"\\Qkotlin/collections/collections.kotlin_builtins\\E" + }, { + "pattern":"\\Qkotlin/coroutines/coroutines.kotlin_builtins\\E" + }, { + "pattern":"\\Qkotlin/internal/internal.kotlin_builtins\\E" + }, { + "pattern":"\\Qkotlin/jvm/jvm.kotlin_builtins\\E" + }, { + "pattern":"\\Qkotlin/kotlin.kotlin_builtins\\E" + }, { + "pattern":"\\Qkotlin/ranges/ranges.kotlin_builtins\\E" + }, { + "pattern":"\\Qkotlin/reflect/reflect.kotlin_builtins\\E" + }, { + "pattern":"\\Qlibsignal_jni.so\\E" + }, { + "pattern":"\\Qlibsignal_jni_aarch64.dylib\\E" + }, { + "pattern":"\\Qlibsignal_jni_amd64.dylib\\E" + }, { + "pattern":"\\Qlibsignal_jni_amd64.so\\E" + }, { + "pattern":"\\Qorg/asamk/signal/manager/config/ias.store\\E" + }, { + "pattern":"\\Qorg/asamk/signal/manager/config/whisper.store\\E" + }, { + "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" + }, { + "pattern":"\\Qorg/sqlite/native/Linux/x86_64/libsqlitejdbc.so\\E" + }, { + "pattern":"\\Qsignal_jni.dll\\E" + }, { + "pattern":"\\Qsignal_jni_amd64.dll\\E" + }, { + "pattern":"\\Qsqlite-jdbc.properties\\E" + }, { + "pattern":"com/google/i18n/phonenumbers/data/.*" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt67b/nfc.nrm\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt67b/uprops.icu\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/nfc.nrm\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/uprops.icu\\E" + }, { + "pattern":"java.base:\\Qsun/net/idn/uidna.spp\\E" + }, { + "pattern":"java.base:\\Qsun/net/www/content-types.properties\\E" + }, { + "pattern":"java.base:\\Qsun/text/resources/LineBreakIteratorData\\E" + }]}, + "bundles":[{ + "name":"net.sourceforge.argparse4j.internal.ArgumentParserImpl", + "locales":["", "de", "en", "und"] + }] +} diff --git a/graalvm-config-dir/serialization-config.json b/graalvm-config-dir/serialization-config.json new file mode 100644 index 00000000..f3d7e06e --- /dev/null +++ b/graalvm-config-dir/serialization-config.json @@ -0,0 +1,8 @@ +{ + "types":[ + ], + "lambdaCapturingTypes":[ + ], + "proxies":[ + ] +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..fec44a43 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,17 @@ +[versions] +slf4j = "2.0.17" + +[libraries] +bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.81" +jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.19.1" +argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0" +dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0" +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.18" + +signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_127" +sqlite = "org.xerial:sqlite-jdbc:3.50.2.0" +hikari = "com.zaxxer:HikariCP:6.3.0" +junit-jupiter = "org.junit.jupiter:junit-jupiter:5.13.2" +junit-launcher = "org.junit.platform:junit-platform-launcher:1.13.2" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c0..8bdaf60c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 12d38de6..2a84e188 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c..ef07e016 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright Š 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,81 +15,115 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions ÂĢ$varÂģ, ÂĢ${var}Âģ, ÂĢ${var:-default}Âģ, ÂĢ${var+SET}Âģ, +# ÂĢ${var#prefix}Âģ, ÂĢ${var%suffix}Âģ, and ÂĢ$( cmd )Âģ; +# * compound commands having a testable exit status, especially ÂĢcaseÂģ; +# * various built-in commands including ÂĢcommandÂģ, ÂĢsetÂģ, and ÂĢulimitÂģ. +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f9..5eed7ee8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,32 +59,34 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts new file mode 100644 index 00000000..c8f22f7c --- /dev/null +++ b/lib/build.gradle.kts @@ -0,0 +1,59 @@ +plugins { + `java-library` + `check-lib-versions` +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + } +} + +val libsignalClientPath = project.findProperty("libsignal_client_path")?.toString() + +dependencies { + if (libsignalClientPath == null) { + implementation(libs.signalservice) + } else { + implementation(libs.signalservice) { + exclude(group = "org.signal", module = "libsignal-client") + } + implementation(files(libsignalClientPath)) + } + implementation(libs.jackson.databind) + implementation(libs.bouncycastle) + implementation(libs.slf4j.api) + implementation(libs.sqlite) + implementation(libs.hikari) + + testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.launcher) +} + +tasks.named("test") { + useJUnitPlatform() +} + +configurations { + implementation { + resolutionStrategy.failOnVersionConflict() + } +} + +tasks.withType().configureEach { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true +} + +tasks.withType { + options.encoding = "UTF-8" +} + +tasks.jar { + manifest { + attributes("Automatic-Module-Name" to "org.asamk.signal.manager") + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java new file mode 100644 index 00000000..ccb661e1 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -0,0 +1,373 @@ +package org.asamk.signal.manager; + +import com.google.i18n.phonenumbers.PhoneNumberUtil; + +import org.asamk.signal.manager.api.AlreadyReceivingException; +import org.asamk.signal.manager.api.AttachmentInvalidException; +import org.asamk.signal.manager.api.CaptchaRejectedException; +import org.asamk.signal.manager.api.CaptchaRequiredException; +import org.asamk.signal.manager.api.Configuration; +import org.asamk.signal.manager.api.Device; +import org.asamk.signal.manager.api.DeviceLimitExceededException; +import org.asamk.signal.manager.api.DeviceLinkUrl; +import org.asamk.signal.manager.api.Group; +import org.asamk.signal.manager.api.GroupId; +import org.asamk.signal.manager.api.GroupInviteLinkUrl; +import org.asamk.signal.manager.api.GroupNotFoundException; +import org.asamk.signal.manager.api.GroupSendingNotAllowedException; +import org.asamk.signal.manager.api.Identity; +import org.asamk.signal.manager.api.IdentityVerificationCode; +import org.asamk.signal.manager.api.InactiveGroupLinkException; +import org.asamk.signal.manager.api.IncorrectPinException; +import org.asamk.signal.manager.api.InvalidDeviceLinkException; +import org.asamk.signal.manager.api.InvalidStickerException; +import org.asamk.signal.manager.api.InvalidUsernameException; +import org.asamk.signal.manager.api.LastGroupAdminException; +import org.asamk.signal.manager.api.Message; +import org.asamk.signal.manager.api.MessageEnvelope; +import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; +import org.asamk.signal.manager.api.NotAGroupMemberException; +import org.asamk.signal.manager.api.NotPrimaryDeviceException; +import org.asamk.signal.manager.api.Pair; +import org.asamk.signal.manager.api.PendingAdminApprovalException; +import org.asamk.signal.manager.api.PinLockMissingException; +import org.asamk.signal.manager.api.PinLockedException; +import org.asamk.signal.manager.api.RateLimitException; +import org.asamk.signal.manager.api.ReceiveConfig; +import org.asamk.signal.manager.api.Recipient; +import org.asamk.signal.manager.api.RecipientIdentifier; +import org.asamk.signal.manager.api.SendGroupMessageResults; +import org.asamk.signal.manager.api.SendMessageResults; +import org.asamk.signal.manager.api.StickerPack; +import org.asamk.signal.manager.api.StickerPackId; +import org.asamk.signal.manager.api.StickerPackInvalidException; +import org.asamk.signal.manager.api.StickerPackUrl; +import org.asamk.signal.manager.api.TypingAction; +import org.asamk.signal.manager.api.UnregisteredRecipientException; +import org.asamk.signal.manager.api.UpdateGroup; +import org.asamk.signal.manager.api.UpdateProfile; +import org.asamk.signal.manager.api.UserStatus; +import org.asamk.signal.manager.api.UsernameLinkUrl; +import org.asamk.signal.manager.api.UsernameStatus; +import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.time.Duration; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public interface Manager extends Closeable { + + static boolean isValidNumber(final String e164Number, final String countryCode) { + return PhoneNumberUtil.getInstance().isPossibleNumber(e164Number, countryCode); + } + + static boolean isSignalClientAvailable() { + final Logger logger = LoggerFactory.getLogger(Manager.class); + try { + try { + org.signal.libsignal.internal.Native.UuidCiphertext_CheckValidContents(new byte[0]); + } catch (Exception e) { + logger.trace("Expected exception when checking libsignal-client: {}", e.getMessage()); + } + return true; + } catch (UnsatisfiedLinkError e) { + logger.warn("Failed to call libsignal-client: {}", e.getMessage()); + return false; + } + } + + String getSelfNumber(); + + /** + * This is used for checking a set of phone numbers for registration on Signal + * + * @param numbers The set of phone number in question + * @return A map of numbers to canonicalized number and uuid. If a number is not registered the uuid is null. + * @throws IOException if it's unable to get the contacts to check if they're registered + */ + Map getUserStatus(Set numbers) throws IOException, RateLimitException; + + Map getUsernameStatus(Set usernames) throws IOException; + + void updateAccountAttributes( + String deviceName, + Boolean unrestrictedUnidentifiedSender, + final Boolean discoverableByNumber, + final Boolean numberSharing + ) throws IOException; + + Configuration getConfiguration(); + + void updateConfiguration(Configuration configuration) throws NotPrimaryDeviceException; + + /** + * Update the user's profile. + * If a field is null, the previous value will be kept. + */ + void updateProfile(UpdateProfile updateProfile) throws IOException; + + String getUsername(); + + UsernameLinkUrl getUsernameLink(); + + /** + * Set a username for the account. + * If the username is null, it will be deleted. + */ + void setUsername(String username) throws IOException, InvalidUsernameException; + + /** + * Set a username for the account. + * If the username is null, it will be deleted. + */ + void deleteUsername() throws IOException; + + void startChangeNumber( + String newNumber, + boolean voiceVerification, + String captcha + ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException, VerificationMethodNotAvailableException; + + void finishChangeNumber( + String newNumber, + String verificationCode, + String pin + ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException, PinLockMissingException; + + void unregister() throws IOException; + + void deleteAccount() throws IOException; + + void submitRateLimitRecaptchaChallenge( + String challenge, + String captcha + ) throws IOException, CaptchaRejectedException; + + List getLinkedDevices() throws IOException; + + void removeLinkedDevices(int deviceId) throws IOException, NotPrimaryDeviceException; + + void addDeviceLink(DeviceLinkUrl linkUri) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException, DeviceLimitExceededException; + + void setRegistrationLockPin(Optional pin) throws IOException, NotPrimaryDeviceException; + + List getGroups(); + + SendGroupMessageResults quitGroup( + GroupId groupId, + Set groupAdmins + ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException, UnregisteredRecipientException; + + void deleteGroup(GroupId groupId) throws IOException; + + Pair createGroup( + String name, + Set members, + String avatarFile + ) throws IOException, AttachmentInvalidException, UnregisteredRecipientException; + + SendGroupMessageResults updateGroup( + final GroupId groupId, + final UpdateGroup updateGroup + ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException, UnregisteredRecipientException; + + Pair joinGroup( + GroupInviteLinkUrl inviteLinkUrl + ) throws IOException, InactiveGroupLinkException, PendingAdminApprovalException; + + SendMessageResults sendTypingMessage( + TypingAction action, + Set recipients + ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException; + + SendMessageResults sendReadReceipt(RecipientIdentifier.Single sender, List messageIds); + + SendMessageResults sendViewedReceipt(RecipientIdentifier.Single sender, List messageIds); + + SendMessageResults sendMessage( + Message message, + Set recipients, + boolean notifySelf + ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; + + SendMessageResults sendEditMessage( + Message message, + Set recipients, + long editTargetTimestamp + ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; + + SendMessageResults sendRemoteDeleteMessage( + long targetSentTimestamp, + Set recipients + ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException; + + SendMessageResults sendMessageReaction( + String emoji, + boolean remove, + RecipientIdentifier.Single targetAuthor, + long targetSentTimestamp, + Set recipients, + final boolean isStory + ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException; + + SendMessageResults sendPaymentNotificationMessage( + byte[] receipt, + String note, + RecipientIdentifier.Single recipient + ) throws IOException; + + SendMessageResults sendEndSessionMessage(Set recipients) throws IOException; + + SendMessageResults sendMessageRequestResponse( + MessageEnvelope.Sync.MessageRequestResponse.Type type, + Set recipientIdentifiers + ); + + void hideRecipient(RecipientIdentifier.Single recipient); + + void deleteRecipient(RecipientIdentifier.Single recipient); + + void deleteContact(RecipientIdentifier.Single recipient); + + void setContactName( + final RecipientIdentifier.Single recipient, + final String givenName, + final String familyName, + final String nickGivenName, + final String nickFamilyName, + final String note + ) throws NotPrimaryDeviceException, UnregisteredRecipientException; + + void setContactsBlocked( + Collection recipient, + boolean blocked + ) throws NotPrimaryDeviceException, IOException, UnregisteredRecipientException; + + void setGroupsBlocked( + Collection groupId, + boolean blocked + ) throws GroupNotFoundException, IOException, NotPrimaryDeviceException; + + /** + * Change the expiration timer for a contact + */ + void setExpirationTimer( + RecipientIdentifier.Single recipient, + int messageExpirationTimer + ) throws IOException, UnregisteredRecipientException; + + /** + * Upload the sticker pack from path. + * + * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file + * @return if successful, returns the URL to install the sticker pack in the signal app + */ + StickerPackUrl uploadStickerPack(File path) throws IOException, StickerPackInvalidException; + + void installStickerPack(StickerPackUrl url) throws IOException; + + List getStickerPacks(); + + void requestAllSyncData() throws IOException; + + /** + * Add a handler to receive new messages. + * Will start receiving messages from server, if not already started. + */ + default void addReceiveHandler(ReceiveMessageHandler handler) { + addReceiveHandler(handler, false); + } + + void addReceiveHandler(ReceiveMessageHandler handler, final boolean isWeakListener); + + /** + * Remove a handler to receive new messages. + * Will stop receiving messages from server, if this was the last registered receiver. + */ + void removeReceiveHandler(ReceiveMessageHandler handler); + + boolean isReceiving(); + + /** + * Receive new messages from server, returns if no new message arrive in a timespan of timeout. + */ + void receiveMessages( + Optional timeout, + Optional maxMessages, + ReceiveMessageHandler handler + ) throws IOException, AlreadyReceivingException; + + void stopReceiveMessages(); + + void setReceiveConfig(ReceiveConfig receiveConfig); + + boolean isContactBlocked(RecipientIdentifier.Single recipient); + + void sendContacts() throws IOException; + + List getRecipients( + boolean onlyContacts, + Optional blocked, + Collection address, + Optional name + ); + + String getContactOrProfileName(RecipientIdentifier.Single recipient); + + Group getGroup(GroupId groupId); + + List getIdentities(); + + List getIdentities(RecipientIdentifier.Single recipient); + + /** + * Trust this the identity with this fingerprint/safetyNumber + * + * @param recipient account of the identity + */ + boolean trustIdentityVerified( + RecipientIdentifier.Single recipient, + IdentityVerificationCode verificationCode + ) throws UnregisteredRecipientException; + + /** + * Trust all keys of this identity without verification + * + * @param recipient account of the identity + */ + boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient) throws UnregisteredRecipientException; + + void addAddressChangedListener(Runnable listener); + + void addClosedListener(Runnable listener); + + InputStream retrieveAttachment(final String id) throws IOException; + + InputStream retrieveContactAvatar(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException; + + InputStream retrieveProfileAvatar(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException; + + InputStream retrieveGroupAvatar(final GroupId groupId) throws IOException; + + InputStream retrieveSticker(final StickerPackId stickerPackId, final int stickerId) throws IOException; + + @Override + void close(); + + interface ReceiveMessageHandler { + + ReceiveMessageHandler EMPTY = (envelope, e) -> { + }; + + void handleMessage(MessageEnvelope envelope, Throwable e); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerLogger.java b/lib/src/main/java/org/asamk/signal/manager/ManagerLogger.java new file mode 100644 index 00000000..c8437882 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerLogger.java @@ -0,0 +1,10 @@ +package org.asamk.signal.manager; + +import org.asamk.signal.manager.internal.LibSignalLogger; + +public class ManagerLogger { + + public static void initLogger() { + LibSignalLogger.initLogger(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java b/lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java new file mode 100644 index 00000000..15b60594 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/MultiAccountManager.java @@ -0,0 +1,29 @@ +package org.asamk.signal.manager; + +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +public interface MultiAccountManager extends AutoCloseable { + + List getAccountNumbers(); + + List getManagers(); + + void addOnManagerAddedHandler(Consumer handler); + + void addOnManagerRemovedHandler(Consumer handler); + + Manager getManager(String phoneNumber); + + URI getNewProvisioningDeviceLinkUri() throws TimeoutException, IOException; + + ProvisioningManager getProvisioningManagerFor(URI deviceLinkUri); + + RegistrationManager getNewRegistrationManager(String account) throws IOException; + + @Override + void close(); +} diff --git a/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java new file mode 100644 index 00000000..09a329f5 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java @@ -0,0 +1,14 @@ +package org.asamk.signal.manager; + +import org.asamk.signal.manager.api.UserAlreadyExistsException; + +import java.io.IOException; +import java.net.URI; +import java.util.concurrent.TimeoutException; + +public interface ProvisioningManager { + + URI getDeviceLinkUri() throws TimeoutException, IOException; + + String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException; +} diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java new file mode 100644 index 00000000..5f4f4463 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -0,0 +1,30 @@ +package org.asamk.signal.manager; + +import org.asamk.signal.manager.api.CaptchaRequiredException; +import org.asamk.signal.manager.api.IncorrectPinException; +import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; +import org.asamk.signal.manager.api.PinLockMissingException; +import org.asamk.signal.manager.api.PinLockedException; +import org.asamk.signal.manager.api.RateLimitException; +import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; + +import java.io.Closeable; +import java.io.IOException; + +public interface RegistrationManager extends Closeable { + + void register( + boolean voiceVerification, + String captcha, + final boolean forceRegister + ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException; + + void verifyAccount( + String verificationCode, + String pin + ) throws IOException, PinLockedException, IncorrectPinException, PinLockMissingException; + + void deleteLocalAccountData() throws IOException; + + boolean isRegistered(); +} diff --git a/lib/src/main/java/org/asamk/signal/manager/Settings.java b/lib/src/main/java/org/asamk/signal/manager/Settings.java new file mode 100644 index 00000000..5fd4e36b --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/Settings.java @@ -0,0 +1,8 @@ +package org.asamk.signal.manager; + +import org.asamk.signal.manager.api.TrustNewIdentity; + +public record Settings(TrustNewIdentity trustNewIdentity, boolean disableMessageSendLog) { + + public static final Settings DEFAULT = new Settings(TrustNewIdentity.ON_FIRST_USE, false); +} diff --git a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java new file mode 100644 index 00000000..cd0b5a21 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java @@ -0,0 +1,208 @@ +package org.asamk.signal.manager; + +import org.asamk.signal.manager.api.AccountCheckException; +import org.asamk.signal.manager.api.NotRegisteredException; +import org.asamk.signal.manager.api.Pair; +import org.asamk.signal.manager.api.ServiceEnvironment; +import org.asamk.signal.manager.config.ServiceConfig; +import org.asamk.signal.manager.config.ServiceEnvironmentConfig; +import org.asamk.signal.manager.internal.AccountFileUpdaterImpl; +import org.asamk.signal.manager.internal.ManagerImpl; +import org.asamk.signal.manager.internal.MultiAccountManagerImpl; +import org.asamk.signal.manager.internal.PathConfig; +import org.asamk.signal.manager.internal.ProvisioningManagerImpl; +import org.asamk.signal.manager.internal.RegistrationManagerImpl; +import org.asamk.signal.manager.storage.SignalAccount; +import org.asamk.signal.manager.storage.accounts.AccountsStore; +import org.asamk.signal.manager.util.KeyUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; + +public class SignalAccountFiles { + + private static final Logger logger = LoggerFactory.getLogger(MultiAccountManager.class); + + private final PathConfig pathConfig; + private final ServiceEnvironment serviceEnvironment; + private final ServiceEnvironmentConfig serviceEnvironmentConfig; + private final String userAgent; + private final Settings settings; + private final AccountsStore accountsStore; + + public SignalAccountFiles( + final File settingsPath, + final ServiceEnvironment serviceEnvironment, + final String userAgent, + final Settings settings + ) throws IOException { + this.pathConfig = PathConfig.createDefault(settingsPath); + this.serviceEnvironment = serviceEnvironment; + this.serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(this.serviceEnvironment, userAgent); + this.userAgent = userAgent; + this.settings = settings; + this.accountsStore = new AccountsStore(pathConfig.dataPath(), serviceEnvironment, accountPath -> { + if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) { + return null; + } + + try { + return SignalAccount.load(pathConfig.dataPath(), accountPath, false, settings); + } catch (Exception e) { + return null; + } + }); + } + + public Set getAllLocalAccountNumbers() throws IOException { + return accountsStore.getAllNumbers(); + } + + public MultiAccountManager initMultiAccountManager() throws IOException, AccountCheckException { + final var managerPairs = accountsStore.getAllAccounts().parallelStream().map(a -> { + try { + return new Pair(initManager(a.number(), a.path()), null); + } catch (NotRegisteredException e) { + logger.warn("Ignoring {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName()); + return null; + } catch (AccountCheckException | IOException e) { + logger.error("Failed to load {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName()); + return new Pair(null, e); + } + }).filter(Objects::nonNull).toList(); + + for (final var pair : managerPairs) { + if (pair.second() instanceof IOException e) { + throw e; + } else if (pair.second() instanceof AccountCheckException e) { + throw e; + } + } + + final var managers = managerPairs.stream().map(Pair::first).toList(); + return new MultiAccountManagerImpl(managers, this); + } + + public Manager initManager(String number) throws IOException, NotRegisteredException, AccountCheckException { + final var accountPath = accountsStore.getPathByNumber(number); + return this.initManager(number, accountPath); + } + + private Manager initManager( + String number, + String accountPath + ) throws IOException, NotRegisteredException, AccountCheckException { + if (accountPath == null) { + throw new NotRegisteredException(); + } + if (!SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) { + throw new NotRegisteredException(); + } + + var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings); + if (!number.equals(account.getNumber())) { + account.close(); + throw new IOException("Number in account file doesn't match expected number: " + account.getNumber()); + } + + if (!account.isRegistered()) { + account.close(); + throw new NotRegisteredException(); + } + + if (account.getServiceEnvironment() != null && account.getServiceEnvironment() != serviceEnvironment) { + throw new IOException("Account is registered in another environment: " + account.getServiceEnvironment()); + } + + account.initDatabase(); + + final var manager = new ManagerImpl(account, + pathConfig, + new AccountFileUpdaterImpl(accountsStore, accountPath), + serviceEnvironmentConfig, + userAgent); + + try { + manager.checkAccountState(); + } catch (DeprecatedVersionException e) { + manager.close(); + throw new AccountCheckException("signal-cli version is too old for the Signal-Server, please update."); + } catch (IOException e) { + manager.close(); + throw new AccountCheckException("Error while checking account " + number + ": " + e.getMessage(), e); + } + + if (account.getServiceEnvironment() == null) { + account.setServiceEnvironment(serviceEnvironment); + accountsStore.updateAccount(accountPath, account.getNumber(), account.getAci()); + } + + return manager; + } + + public ProvisioningManager initProvisioningManager() { + return initProvisioningManager(null); + } + + public ProvisioningManager initProvisioningManager(Consumer newManagerListener) { + return new ProvisioningManagerImpl(pathConfig, + serviceEnvironmentConfig, + userAgent, + newManagerListener, + accountsStore); + } + + public RegistrationManager initRegistrationManager(String number) throws IOException { + return initRegistrationManager(number, null); + } + + public RegistrationManager initRegistrationManager( + String number, + Consumer newManagerListener + ) throws IOException { + final var accountPath = accountsStore.getPathByNumber(number); + if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) { + final var newAccountPath = accountPath == null ? accountsStore.addAccount(number, null) : accountPath; + var aciIdentityKey = KeyUtils.generateIdentityKeyPair(); + var pniIdentityKey = KeyUtils.generateIdentityKeyPair(); + + var profileKey = KeyUtils.createProfileKey(); + var account = SignalAccount.create(pathConfig.dataPath(), + newAccountPath, + number, + serviceEnvironment, + aciIdentityKey, + pniIdentityKey, + profileKey, + settings); + account.initDatabase(); + + return new RegistrationManagerImpl(account, + pathConfig, + serviceEnvironmentConfig, + userAgent, + newManagerListener, + new AccountFileUpdaterImpl(accountsStore, newAccountPath)); + } + + var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings); + if (!number.equals(account.getNumber())) { + account.close(); + throw new IOException("Number in account file doesn't match expected number: " + account.getNumber()); + } + account.initDatabase(); + + return new RegistrationManagerImpl(account, + pathConfig, + serviceEnvironmentConfig, + userAgent, + newManagerListener, + new AccountFileUpdaterImpl(accountsStore, accountPath)); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/HandleAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/HandleAction.java new file mode 100644 index 00000000..85ce555b --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/HandleAction.java @@ -0,0 +1,11 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; + +public interface HandleAction { + + void execute(Context context) throws Throwable; + + default void mergeOther(HandleAction action) { + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/RefreshPreKeysAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/RefreshPreKeysAction.java new file mode 100644 index 00000000..d7d8cd5d --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/RefreshPreKeysAction.java @@ -0,0 +1,20 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; + +public class RefreshPreKeysAction implements HandleAction { + + private static final RefreshPreKeysAction INSTANCE = new RefreshPreKeysAction(); + + private RefreshPreKeysAction() { + } + + public static RefreshPreKeysAction create() { + return INSTANCE; + } + + @Override + public void execute(Context context) throws Throwable { + context.getPreKeyHelper().refreshPreKeysIfNecessary(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/RenewSessionAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/RenewSessionAction.java new file mode 100644 index 00000000..35058940 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/RenewSessionAction.java @@ -0,0 +1,39 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; +import org.asamk.signal.manager.storage.recipients.RecipientId; +import org.whispersystems.signalservice.api.push.ServiceId; + +public class RenewSessionAction implements HandleAction { + + private final RecipientId recipientId; + private final ServiceId serviceId; + private final ServiceId accountId; + + public RenewSessionAction(final RecipientId recipientId, final ServiceId serviceId, final ServiceId accountId) { + this.recipientId = recipientId; + this.serviceId = serviceId; + this.accountId = accountId; + } + + @Override + public void execute(Context context) throws Throwable { + context.getAccount().getAccountData(accountId).getSessionStore().archiveSessions(serviceId); + context.getSendHelper().sendNullMessage(recipientId); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final RenewSessionAction that = (RenewSessionAction) o; + + return recipientId.equals(that.recipientId); + } + + @Override + public int hashCode() { + return recipientId.hashCode(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/ResendMessageAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/ResendMessageAction.java new file mode 100644 index 00000000..a8f67fd6 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/ResendMessageAction.java @@ -0,0 +1,44 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; +import org.asamk.signal.manager.storage.recipients.RecipientId; +import org.asamk.signal.manager.storage.sendLog.MessageSendLogEntry; + +import java.util.Objects; + +public class ResendMessageAction implements HandleAction { + + private final RecipientId recipientId; + private final long timestamp; + private final MessageSendLogEntry messageSendLogEntry; + + public ResendMessageAction( + final RecipientId recipientId, + final long timestamp, + final MessageSendLogEntry messageSendLogEntry + ) { + this.recipientId = recipientId; + this.timestamp = timestamp; + this.messageSendLogEntry = messageSendLogEntry; + } + + @Override + public void execute(Context context) throws Throwable { + context.getSendHelper().resendMessage(recipientId, timestamp, messageSendLogEntry); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final ResendMessageAction that = (ResendMessageAction) o; + return timestamp == that.timestamp + && recipientId.equals(that.recipientId) + && messageSendLogEntry.equals(that.messageSendLogEntry); + } + + @Override + public int hashCode() { + return Objects.hash(recipientId, timestamp, messageSendLogEntry); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/RetrieveProfileAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/RetrieveProfileAction.java new file mode 100644 index 00000000..4859c0db --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/RetrieveProfileAction.java @@ -0,0 +1,33 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; +import org.asamk.signal.manager.storage.recipients.RecipientId; + +public class RetrieveProfileAction implements HandleAction { + + private final RecipientId recipientId; + + public RetrieveProfileAction(final RecipientId recipientId) { + this.recipientId = recipientId; + } + + @Override + public void execute(Context context) throws Throwable { + context.getProfileHelper().refreshRecipientProfile(recipientId); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final RetrieveProfileAction that = (RetrieveProfileAction) o; + + return recipientId.equals(that.recipientId); + } + + @Override + public int hashCode() { + return recipientId.hashCode(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendGroupInfoAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendGroupInfoAction.java new file mode 100644 index 00000000..2a39a38a --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendGroupInfoAction.java @@ -0,0 +1,39 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.api.GroupIdV1; +import org.asamk.signal.manager.helper.Context; +import org.asamk.signal.manager.storage.recipients.RecipientId; + +public class SendGroupInfoAction implements HandleAction { + + private final RecipientId recipientId; + private final GroupIdV1 groupId; + + public SendGroupInfoAction(final RecipientId recipientId, final GroupIdV1 groupId) { + this.recipientId = recipientId; + this.groupId = groupId; + } + + @Override + public void execute(Context context) throws Throwable { + context.getGroupHelper().sendGroupInfoMessage(groupId, recipientId); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final var that = (SendGroupInfoAction) o; + + if (!recipientId.equals(that.recipientId)) return false; + return groupId.equals(that.groupId); + } + + @Override + public int hashCode() { + var result = recipientId.hashCode(); + result = 31 * result + groupId.hashCode(); + return result; + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendGroupInfoRequestAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendGroupInfoRequestAction.java new file mode 100644 index 00000000..e9dc2528 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendGroupInfoRequestAction.java @@ -0,0 +1,39 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.api.GroupIdV1; +import org.asamk.signal.manager.helper.Context; +import org.asamk.signal.manager.storage.recipients.RecipientId; + +public class SendGroupInfoRequestAction implements HandleAction { + + private final RecipientId recipientId; + private final GroupIdV1 groupId; + + public SendGroupInfoRequestAction(final RecipientId recipientId, final GroupIdV1 groupId) { + this.recipientId = recipientId; + this.groupId = groupId; + } + + @Override + public void execute(Context context) throws Throwable { + context.getGroupHelper().sendGroupInfoRequest(groupId, recipientId); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final var that = (SendGroupInfoRequestAction) o; + + if (!recipientId.equals(that.recipientId)) return false; + return groupId.equals(that.groupId); + } + + @Override + public int hashCode() { + var result = recipientId.hashCode(); + result = 31 * result + groupId.hashCode(); + return result; + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendProfileKeyAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendProfileKeyAction.java new file mode 100644 index 00000000..5695e458 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendProfileKeyAction.java @@ -0,0 +1,33 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; +import org.asamk.signal.manager.storage.recipients.RecipientId; + +import java.util.Objects; + +public class SendProfileKeyAction implements HandleAction { + + private final RecipientId recipientId; + + public SendProfileKeyAction(final RecipientId recipientId) { + this.recipientId = recipientId; + } + + @Override + public void execute(Context context) throws Throwable { + context.getSendHelper().sendProfileKey(recipientId); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final SendProfileKeyAction that = (SendProfileKeyAction) o; + return recipientId.equals(that.recipientId); + } + + @Override + public int hashCode() { + return Objects.hash(recipientId); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendReceiptAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendReceiptAction.java new file mode 100644 index 00000000..6a5cfd59 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendReceiptAction.java @@ -0,0 +1,55 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; +import org.asamk.signal.manager.storage.recipients.RecipientId; +import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class SendReceiptAction implements HandleAction { + + private final RecipientId recipientId; + private final SignalServiceReceiptMessage.Type type; + private final List timestamps = new ArrayList<>(); + + public SendReceiptAction( + final RecipientId recipientId, + final SignalServiceReceiptMessage.Type type, + final long timestamp + ) { + this.recipientId = recipientId; + this.type = type; + this.timestamps.add(timestamp); + } + + @Override + public void execute(Context context) throws Throwable { + final var receiptMessage = new SignalServiceReceiptMessage(type, timestamps, System.currentTimeMillis()); + + context.getSendHelper().sendReceiptMessage(receiptMessage, recipientId); + } + + @Override + public void mergeOther(final HandleAction action) { + if (action instanceof SendReceiptAction sendReceiptAction) { + this.timestamps.addAll(sendReceiptAction.timestamps); + } + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final SendReceiptAction that = (SendReceiptAction) o; + // Using only recipientId and type here on purpose + return recipientId.equals(that.recipientId) && type == that.type; + } + + @Override + public int hashCode() { + // Using only recipientId and type here on purpose + return Objects.hash(recipientId, type); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java new file mode 100644 index 00000000..96a94128 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java @@ -0,0 +1,89 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.api.GroupId; +import org.asamk.signal.manager.helper.Context; +import org.asamk.signal.manager.storage.recipients.RecipientId; +import org.signal.libsignal.metadata.ProtocolException; +import org.signal.libsignal.protocol.message.CiphertextMessage; +import org.signal.libsignal.protocol.message.DecryptionErrorMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; +import org.whispersystems.signalservice.internal.push.Envelope; + +import java.util.Optional; + +public class SendRetryMessageRequestAction implements HandleAction { + + private final RecipientId recipientId; + private final ProtocolException protocolException; + private final SignalServiceEnvelope envelope; + + public SendRetryMessageRequestAction( + final RecipientId recipientId, + final ProtocolException protocolException, + final SignalServiceEnvelope envelope + ) { + this.recipientId = recipientId; + this.protocolException = protocolException; + this.envelope = envelope; + } + + @Override + public void execute(Context context) throws Throwable { + int senderDevice = protocolException.getSenderDevice(); + Optional groupId = protocolException.getGroupId().isPresent() ? Optional.of(GroupId.unknownVersion( + protocolException.getGroupId().get())) : Optional.empty(); + + byte[] originalContent; + int envelopeType; + if (protocolException.getUnidentifiedSenderMessageContent().isPresent()) { + final var messageContent = protocolException.getUnidentifiedSenderMessageContent().get(); + originalContent = messageContent.getContent(); + envelopeType = messageContent.getType(); + } else { + originalContent = envelope.getContent(); + envelopeType = envelope.getType() == null + ? CiphertextMessage.WHISPER_TYPE + : envelopeTypeToCiphertextMessageType(envelope.getType()); + } + + DecryptionErrorMessage decryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage(originalContent, + envelopeType, + envelope.getTimestamp(), + senderDevice); + + context.getSendHelper().sendRetryReceipt(decryptionErrorMessage, recipientId, groupId); + } + + private static int envelopeTypeToCiphertextMessageType(int envelopeType) { + final var type = Envelope.Type.fromValue(envelopeType); + if (type == null) { + return CiphertextMessage.WHISPER_TYPE; + } + return switch (type) { + case PREKEY_BUNDLE -> CiphertextMessage.PREKEY_TYPE; + case UNIDENTIFIED_SENDER -> CiphertextMessage.SENDERKEY_TYPE; + case PLAINTEXT_CONTENT -> CiphertextMessage.PLAINTEXT_CONTENT_TYPE; + default -> CiphertextMessage.WHISPER_TYPE; + }; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final SendRetryMessageRequestAction that = (SendRetryMessageRequestAction) o; + + if (!recipientId.equals(that.recipientId)) return false; + if (!protocolException.equals(that.protocolException)) return false; + return envelope.equals(that.envelope); + } + + @Override + public int hashCode() { + int result = recipientId.hashCode(); + result = 31 * result + protocolException.hashCode(); + result = 31 * result + envelope.hashCode(); + return result; + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncBlockedListAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncBlockedListAction.java new file mode 100644 index 00000000..ec94806e --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncBlockedListAction.java @@ -0,0 +1,20 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; + +public class SendSyncBlockedListAction implements HandleAction { + + private static final SendSyncBlockedListAction INSTANCE = new SendSyncBlockedListAction(); + + private SendSyncBlockedListAction() { + } + + public static SendSyncBlockedListAction create() { + return INSTANCE; + } + + @Override + public void execute(Context context) throws Throwable { + context.getSyncHelper().sendBlockedList(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncConfigurationAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncConfigurationAction.java new file mode 100644 index 00000000..25a18386 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncConfigurationAction.java @@ -0,0 +1,20 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; + +public class SendSyncConfigurationAction implements HandleAction { + + private static final SendSyncConfigurationAction INSTANCE = new SendSyncConfigurationAction(); + + private SendSyncConfigurationAction() { + } + + public static SendSyncConfigurationAction create() { + return INSTANCE; + } + + @Override + public void execute(Context context) throws Throwable { + context.getSyncHelper().sendConfigurationMessage(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncContactsAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncContactsAction.java new file mode 100644 index 00000000..dde440fb --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncContactsAction.java @@ -0,0 +1,20 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; + +public class SendSyncContactsAction implements HandleAction { + + private static final SendSyncContactsAction INSTANCE = new SendSyncContactsAction(); + + private SendSyncContactsAction() { + } + + public static SendSyncContactsAction create() { + return INSTANCE; + } + + @Override + public void execute(Context context) throws Throwable { + context.getSyncHelper().sendContacts(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncGroupsAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncGroupsAction.java new file mode 100644 index 00000000..91767698 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncGroupsAction.java @@ -0,0 +1,20 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; + +public class SendSyncGroupsAction implements HandleAction { + + private static final SendSyncGroupsAction INSTANCE = new SendSyncGroupsAction(); + + private SendSyncGroupsAction() { + } + + public static SendSyncGroupsAction create() { + return INSTANCE; + } + + @Override + public void execute(Context context) throws Throwable { + context.getSyncHelper().sendGroups(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncKeysAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncKeysAction.java new file mode 100644 index 00000000..babbc285 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncKeysAction.java @@ -0,0 +1,20 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; + +public class SendSyncKeysAction implements HandleAction { + + private static final SendSyncKeysAction INSTANCE = new SendSyncKeysAction(); + + private SendSyncKeysAction() { + } + + public static SendSyncKeysAction create() { + return INSTANCE; + } + + @Override + public void execute(Context context) throws Throwable { + context.getSyncHelper().sendKeysMessage(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SyncStorageDataAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SyncStorageDataAction.java new file mode 100644 index 00000000..7101b3d1 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SyncStorageDataAction.java @@ -0,0 +1,21 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; +import org.asamk.signal.manager.jobs.SyncStorageJob; + +public class SyncStorageDataAction implements HandleAction { + + private static final SyncStorageDataAction INSTANCE = new SyncStorageDataAction(); + + private SyncStorageDataAction() { + } + + public static SyncStorageDataAction create() { + return INSTANCE; + } + + @Override + public void execute(Context context) throws Throwable { + context.getJobExecutor().enqueueJob(new SyncStorageJob()); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/UpdateAccountAttributesAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/UpdateAccountAttributesAction.java new file mode 100644 index 00000000..da04dd18 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/UpdateAccountAttributesAction.java @@ -0,0 +1,20 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; + +public class UpdateAccountAttributesAction implements HandleAction { + + private static final UpdateAccountAttributesAction INSTANCE = new UpdateAccountAttributesAction(); + + private UpdateAccountAttributesAction() { + } + + public static UpdateAccountAttributesAction create() { + return INSTANCE; + } + + @Override + public void execute(Context context) throws Throwable { + context.getAccountHelper().updateAccountAttributes(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/AccountCheckException.java b/lib/src/main/java/org/asamk/signal/manager/api/AccountCheckException.java new file mode 100644 index 00000000..7968f615 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/AccountCheckException.java @@ -0,0 +1,12 @@ +package org.asamk.signal.manager.api; + +public class AccountCheckException extends Exception { + + public AccountCheckException(String message) { + super(message); + } + + public AccountCheckException(String message, Exception e) { + super(message, e); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/AlreadyReceivingException.java b/lib/src/main/java/org/asamk/signal/manager/api/AlreadyReceivingException.java new file mode 100644 index 00000000..298e3f2f --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/AlreadyReceivingException.java @@ -0,0 +1,12 @@ +package org.asamk.signal.manager.api; + +public class AlreadyReceivingException extends Exception { + + public AlreadyReceivingException(String message) { + super(message); + } + + public AlreadyReceivingException(String message, Exception e) { + super(message, e); + } +} diff --git a/src/main/java/org/asamk/signal/manager/AttachmentInvalidException.java b/lib/src/main/java/org/asamk/signal/manager/api/AttachmentInvalidException.java similarity index 88% rename from src/main/java/org/asamk/signal/manager/AttachmentInvalidException.java rename to lib/src/main/java/org/asamk/signal/manager/api/AttachmentInvalidException.java index 78fba6e0..4c5aa32e 100644 --- a/src/main/java/org/asamk/signal/manager/AttachmentInvalidException.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/AttachmentInvalidException.java @@ -1,4 +1,4 @@ -package org.asamk.signal.manager; +package org.asamk.signal.manager.api; public class AttachmentInvalidException extends Exception { diff --git a/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRejectedException.java b/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRejectedException.java new file mode 100644 index 00000000..a41a168e --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRejectedException.java @@ -0,0 +1,16 @@ +package org.asamk.signal.manager.api; + +public class CaptchaRejectedException extends Exception { + + public CaptchaRejectedException() { + super("Captcha rejected"); + } + + public CaptchaRejectedException(final String message) { + super(message); + } + + public CaptchaRejectedException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRequiredException.java b/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRequiredException.java new file mode 100644 index 00000000..86fcdc93 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRequiredException.java @@ -0,0 +1,23 @@ +package org.asamk.signal.manager.api; + +public class CaptchaRequiredException extends Exception { + + private long nextAttemptTimestamp; + + public CaptchaRequiredException(final long nextAttemptTimestamp) { + super("Captcha required"); + this.nextAttemptTimestamp = nextAttemptTimestamp; + } + + public CaptchaRequiredException(final String message) { + super(message); + } + + public CaptchaRequiredException(final String message, final Throwable cause) { + super(message, cause); + } + + public long getNextAttemptTimestamp() { + return nextAttemptTimestamp; + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Color.java b/lib/src/main/java/org/asamk/signal/manager/api/Color.java new file mode 100644 index 00000000..11f7deb0 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/Color.java @@ -0,0 +1,24 @@ +package org.asamk.signal.manager.api; + +public record Color(int color) { + + public int alpha() { + return color >>> 24; + } + + public int red() { + return (color >> 16) & 0xFF; + } + + public int green() { + return (color >> 8) & 0xFF; + } + + public int blue() { + return color & 0xFF; + } + + public String toHexColor() { + return String.format("#%08x", color); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Configuration.java b/lib/src/main/java/org/asamk/signal/manager/api/Configuration.java new file mode 100644 index 00000000..7256ac64 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/Configuration.java @@ -0,0 +1,20 @@ +package org.asamk.signal.manager.api; + +import org.asamk.signal.manager.storage.configuration.ConfigurationStore; + +import java.util.Optional; + +public record Configuration( + Optional readReceipts, + Optional unidentifiedDeliveryIndicators, + Optional typingIndicators, + Optional linkPreviews +) { + + public static Configuration from(final ConfigurationStore configurationStore) { + return new Configuration(Optional.ofNullable(configurationStore.getReadReceipts()), + Optional.ofNullable(configurationStore.getUnidentifiedDeliveryIndicators()), + Optional.ofNullable(configurationStore.getTypingIndicators()), + Optional.ofNullable(configurationStore.getLinkPreviews())); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Contact.java b/lib/src/main/java/org/asamk/signal/manager/api/Contact.java new file mode 100644 index 00000000..3eef0117 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/Contact.java @@ -0,0 +1,193 @@ +package org.asamk.signal.manager.api; + +import org.whispersystems.signalservice.internal.util.Util; + +public record Contact( + String givenName, + String familyName, + String nickName, + String nickNameGivenName, + String nickNameFamilyName, + String note, + String color, + int messageExpirationTime, + int messageExpirationTimeVersion, + long muteUntil, + boolean hideStory, + boolean isBlocked, + boolean isArchived, + boolean isProfileSharingEnabled, + boolean isHidden, + Long unregisteredTimestamp +) { + + private Contact(final Builder builder) { + this(builder.givenName, + builder.familyName, + builder.nickName, + builder.nickNameGivenName, + builder.nickNameFamilyName, + builder.note, + builder.color, + builder.messageExpirationTime, + builder.messageExpirationTimeVersion, + builder.muteUntil, + builder.hideStory, + builder.isBlocked, + builder.isArchived, + builder.isProfileSharingEnabled, + builder.isHidden, + builder.unregisteredTimestamp); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static Builder newBuilder(final Contact copy) { + Builder builder = new Builder(); + builder.givenName = copy.givenName(); + builder.familyName = copy.familyName(); + builder.nickName = copy.nickName(); + builder.nickNameGivenName = copy.nickNameGivenName(); + builder.nickNameFamilyName = copy.nickNameFamilyName(); + builder.note = copy.note(); + builder.color = copy.color(); + builder.messageExpirationTime = copy.messageExpirationTime(); + builder.messageExpirationTimeVersion = copy.messageExpirationTimeVersion(); + builder.muteUntil = copy.muteUntil(); + builder.hideStory = copy.hideStory(); + builder.isBlocked = copy.isBlocked(); + builder.isArchived = copy.isArchived(); + builder.isProfileSharingEnabled = copy.isProfileSharingEnabled(); + builder.isHidden = copy.isHidden(); + builder.unregisteredTimestamp = copy.unregisteredTimestamp(); + return builder; + } + + public String getName() { + final var noGivenName = Util.isEmpty(givenName); + final var noFamilyName = Util.isEmpty(familyName); + + if (noGivenName && noFamilyName) { + return ""; + } else if (noGivenName) { + return familyName; + } else if (noFamilyName) { + return givenName; + } + + return givenName + " " + familyName; + } + + public static final class Builder { + + private String givenName; + private String familyName; + private String nickName; + private String nickNameGivenName; + private String nickNameFamilyName; + private String note; + private String color; + private int messageExpirationTime; + private int messageExpirationTimeVersion = 1; + private long muteUntil; + private boolean hideStory; + private boolean isBlocked; + private boolean isArchived; + private boolean isProfileSharingEnabled; + private boolean isHidden; + private Long unregisteredTimestamp; + + private Builder() { + } + + public static Builder newBuilder() { + return new Builder(); + } + + public Builder withGivenName(final String val) { + givenName = val; + return this; + } + + public Builder withFamilyName(final String val) { + familyName = val; + return this; + } + + public Builder withNickName(final String val) { + nickName = val; + return this; + } + + public Builder withNickNameGivenName(final String val) { + nickNameGivenName = val; + return this; + } + + public Builder withNickNameFamilyName(final String val) { + nickNameFamilyName = val; + return this; + } + + public Builder withNote(final String val) { + note = val; + return this; + } + + public Builder withColor(final String val) { + color = val; + return this; + } + + public Builder withMessageExpirationTime(final int val) { + messageExpirationTime = val; + return this; + } + + public Builder withMessageExpirationTimeVersion(final int val) { + messageExpirationTimeVersion = val; + return this; + } + + public Builder withMuteUntil(final long val) { + muteUntil = val; + return this; + } + + public Builder withHideStory(final boolean val) { + hideStory = val; + return this; + } + + public Builder withIsBlocked(final boolean val) { + isBlocked = val; + return this; + } + + public Builder withIsArchived(final boolean val) { + isArchived = val; + return this; + } + + public Builder withIsProfileSharingEnabled(final boolean val) { + isProfileSharingEnabled = val; + return this; + } + + public Builder withIsHidden(final boolean val) { + isHidden = val; + return this; + } + + public Builder withUnregisteredTimestamp(final Long val) { + unregisteredTimestamp = val; + return this; + } + + public Contact build() { + return new Contact(this); + } + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Device.java b/lib/src/main/java/org/asamk/signal/manager/api/Device.java new file mode 100644 index 00000000..b37e5074 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/Device.java @@ -0,0 +1,3 @@ +package org.asamk.signal.manager.api; + +public record Device(int id, String name, long created, long lastSeen, boolean isThisDevice) {} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/DeviceLimitExceededException.java b/lib/src/main/java/org/asamk/signal/manager/api/DeviceLimitExceededException.java new file mode 100644 index 00000000..e24cc2ac --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/DeviceLimitExceededException.java @@ -0,0 +1,12 @@ +package org.asamk.signal.manager.api; + +public class DeviceLimitExceededException extends Exception { + + public DeviceLimitExceededException(final String message) { + super(message); + } + + public DeviceLimitExceededException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/DeviceLinkUrl.java b/lib/src/main/java/org/asamk/signal/manager/api/DeviceLinkUrl.java new file mode 100644 index 00000000..2bb63507 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/DeviceLinkUrl.java @@ -0,0 +1,58 @@ +package org.asamk.signal.manager.api; + +import org.asamk.signal.manager.util.Utils; +import org.signal.libsignal.protocol.InvalidKeyException; +import org.signal.libsignal.protocol.ecc.ECPublicKey; + +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import static org.whispersystems.signalservice.internal.util.Util.isEmpty; + +public record DeviceLinkUrl(String deviceIdentifier, ECPublicKey deviceKey) { + + public static DeviceLinkUrl parseDeviceLinkUri(URI linkUri) throws InvalidDeviceLinkException { + final var rawQuery = linkUri.getRawQuery(); + if (isEmpty(rawQuery)) { + throw new RuntimeException("Invalid device link uri"); + } + + var query = Utils.getQueryMap(rawQuery); + var deviceIdentifier = query.get("uuid"); + var publicKeyEncoded = query.get("pub_key"); + + if (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) { + throw new InvalidDeviceLinkException("Invalid device link uri"); + } + + final byte[] publicKeyBytes; + try { + publicKeyBytes = Base64.getDecoder().decode(publicKeyEncoded); + } catch (IllegalArgumentException e) { + throw new InvalidDeviceLinkException("Invalid device link uri", e); + } + ECPublicKey deviceKey; + try { + deviceKey = new ECPublicKey(publicKeyBytes); + } catch (InvalidKeyException e) { + throw new InvalidDeviceLinkException("Invalid device link", e); + } + + return new DeviceLinkUrl(deviceIdentifier, deviceKey); + } + + public URI createDeviceLinkUri() { + final var deviceKeyString = Base64.getEncoder().encodeToString(deviceKey.serialize()).replace("=", ""); + try { + return new URI("sgnl://linkdevice?uuid=" + + URLEncoder.encode(deviceIdentifier, StandardCharsets.UTF_8) + + "&pub_key=" + + URLEncoder.encode(deviceKeyString, StandardCharsets.UTF_8)); + } catch (URISyntaxException e) { + throw new AssertionError(e); + } + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Group.java b/lib/src/main/java/org/asamk/signal/manager/api/Group.java new file mode 100644 index 00000000..4d1fdaa1 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/Group.java @@ -0,0 +1,71 @@ +package org.asamk.signal.manager.api; + +import org.asamk.signal.manager.helper.RecipientAddressResolver; +import org.asamk.signal.manager.storage.groups.GroupInfo; +import org.asamk.signal.manager.storage.recipients.RecipientId; + +import java.util.Set; +import java.util.stream.Collectors; + +public record Group( + GroupId groupId, + String title, + String description, + GroupInviteLinkUrl groupInviteLinkUrl, + Set members, + Set pendingMembers, + Set requestingMembers, + Set adminMembers, + Set bannedMembers, + boolean isBlocked, + int messageExpirationTimer, + GroupPermission permissionAddMember, + GroupPermission permissionEditDetails, + GroupPermission permissionSendMessage, + boolean isMember, + boolean isAdmin +) { + + public static Group from( + final GroupInfo groupInfo, + final RecipientAddressResolver recipientStore, + final RecipientId selfRecipientId + ) { + return new Group(groupInfo.getGroupId(), + groupInfo.getTitle(), + groupInfo.getDescription(), + groupInfo.getGroupInviteLink(), + groupInfo.getMembers() + .stream() + .map(recipientStore::resolveRecipientAddress) + .map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress) + .collect(Collectors.toSet()), + groupInfo.getPendingMembers() + .stream() + .map(recipientStore::resolveRecipientAddress) + .map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress) + .collect(Collectors.toSet()), + groupInfo.getRequestingMembers() + .stream() + .map(recipientStore::resolveRecipientAddress) + .map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress) + .collect(Collectors.toSet()), + groupInfo.getAdminMembers() + .stream() + .map(recipientStore::resolveRecipientAddress) + .map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress) + .collect(Collectors.toSet()), + groupInfo.getBannedMembers() + .stream() + .map(recipientStore::resolveRecipientAddress) + .map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress) + .collect(Collectors.toSet()), + groupInfo.isBlocked(), + groupInfo.getMessageExpirationTimer(), + groupInfo.getPermissionAddMember(), + groupInfo.getPermissionEditDetails(), + groupInfo.getPermissionSendMessage(), + groupInfo.isMember(selfRecipientId), + groupInfo.isAdmin(selfRecipientId)); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/GroupId.java b/lib/src/main/java/org/asamk/signal/manager/api/GroupId.java new file mode 100644 index 00000000..3095392f --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/GroupId.java @@ -0,0 +1,62 @@ +package org.asamk.signal.manager.api; + +import java.util.Arrays; +import java.util.Base64; + +public abstract sealed class GroupId permits GroupIdV1, GroupIdV2 { + + private final byte[] id; + + public static GroupIdV1 v1(byte[] id) { + return new GroupIdV1(id); + } + + public static GroupIdV2 v2(byte[] id) { + return new GroupIdV2(id); + } + + public static GroupId unknownVersion(byte[] id) { + if (id.length == 16) { + return new GroupIdV1(id); + } else if (id.length == 32) { + return new GroupIdV2(id); + } + + throw new AssertionError("Invalid group id of size " + id.length); + } + + public static GroupId fromBase64(String id) throws GroupIdFormatException { + try { + return unknownVersion(java.util.Base64.getDecoder().decode(id)); + } catch (Throwable e) { + throw new GroupIdFormatException(id, e); + } + } + + protected GroupId(final byte[] id) { + this.id = id; + } + + public byte[] serialize() { + return id; + } + + public String toBase64() { + return Base64.getEncoder().encodeToString(id); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final var groupId = (GroupId) o; + + return Arrays.equals(id, groupId.id); + } + + @Override + public int hashCode() { + return Arrays.hashCode(id); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/GroupIdFormatException.java b/lib/src/main/java/org/asamk/signal/manager/api/GroupIdFormatException.java new file mode 100644 index 00000000..c76a7ef1 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/GroupIdFormatException.java @@ -0,0 +1,8 @@ +package org.asamk.signal.manager.api; + +public class GroupIdFormatException extends Exception { + + public GroupIdFormatException(String groupId, Throwable e) { + super("Failed to decode groupId (must be base64) \"" + groupId + "\": " + e.getMessage(), e); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/GroupIdV1.java b/lib/src/main/java/org/asamk/signal/manager/api/GroupIdV1.java new file mode 100644 index 00000000..90e5dde4 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/GroupIdV1.java @@ -0,0 +1,20 @@ +package org.asamk.signal.manager.api; + +import java.util.Base64; + +import static org.asamk.signal.manager.util.KeyUtils.getSecretBytes; + +public final class GroupIdV1 extends GroupId { + + public static GroupIdV1 createRandom() { + return new GroupIdV1(getSecretBytes(16)); + } + + public static GroupIdV1 fromBase64(String groupId) { + return new GroupIdV1(Base64.getDecoder().decode(groupId)); + } + + public GroupIdV1(final byte[] id) { + super(id); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/GroupIdV2.java b/lib/src/main/java/org/asamk/signal/manager/api/GroupIdV2.java new file mode 100644 index 00000000..44cda276 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/GroupIdV2.java @@ -0,0 +1,14 @@ +package org.asamk.signal.manager.api; + +import java.util.Base64; + +public final class GroupIdV2 extends GroupId { + + public static GroupIdV2 fromBase64(String groupId) { + return new GroupIdV2(Base64.getDecoder().decode(groupId)); + } + + public GroupIdV2(final byte[] id) { + super(id); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/GroupInviteLinkUrl.java b/lib/src/main/java/org/asamk/signal/manager/api/GroupInviteLinkUrl.java new file mode 100644 index 00000000..a5a23e56 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/GroupInviteLinkUrl.java @@ -0,0 +1,132 @@ +package org.asamk.signal.manager.api; + +import org.asamk.signal.manager.groups.GroupLinkPassword; +import org.signal.core.util.Base64; +import org.signal.libsignal.zkgroup.InvalidInputException; +import org.signal.libsignal.zkgroup.groups.GroupMasterKey; +import org.signal.storageservice.protos.groups.GroupInviteLink; +import org.signal.storageservice.protos.groups.local.DecryptedGroup; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import okio.ByteString; + +public final class GroupInviteLinkUrl { + + private static final String GROUP_URL_HOST = "signal.group"; + private static final String GROUP_URL_PREFIX = "https://" + GROUP_URL_HOST + "/#"; + + private final GroupMasterKey groupMasterKey; + private final GroupLinkPassword password; + private final String url; + + public static GroupInviteLinkUrl forGroup(GroupMasterKey groupMasterKey, DecryptedGroup group) { + return new GroupInviteLinkUrl(groupMasterKey, + GroupLinkPassword.fromBytes(group.inviteLinkPassword.toByteArray())); + } + + /** + * @return null iff not a group url. + * @throws InvalidGroupLinkException If group url, but cannot be parsed. + */ + public static GroupInviteLinkUrl fromUri(String urlString) throws InvalidGroupLinkException, UnknownGroupLinkVersionException { + var uri = getGroupUrl(urlString); + + if (uri == null) { + return null; + } + + try { + if (!"/".equals(uri.getPath()) && !uri.getPath().isEmpty()) { + throw new InvalidGroupLinkException("No path was expected in uri"); + } + + var encoding = uri.getFragment(); + + if (encoding == null || encoding.isEmpty()) { + throw new InvalidGroupLinkException("No reference was in the uri"); + } + + var bytes = Base64.decode(encoding); + GroupInviteLink groupInviteLink = GroupInviteLink.ADAPTER.decode(bytes); + + if (groupInviteLink.v1Contents != null) { + var groupInviteLinkContentsV1 = groupInviteLink.v1Contents; + var groupMasterKey = new GroupMasterKey(groupInviteLinkContentsV1.groupMasterKey.toByteArray()); + var password = GroupLinkPassword.fromBytes(groupInviteLinkContentsV1.inviteLinkPassword.toByteArray()); + + return new GroupInviteLinkUrl(groupMasterKey, password); + } else { + throw new UnknownGroupLinkVersionException("Url contains no known group link content"); + } + } catch (InvalidInputException | IOException e) { + throw new InvalidGroupLinkException(e); + } + } + + /** + * @return {@link URI} if the host name matches. + */ + private static URI getGroupUrl(String urlString) { + try { + var url = new URI(urlString); + + if (!"https".equalsIgnoreCase(url.getScheme()) && !"sgnl".equalsIgnoreCase(url.getScheme())) { + return null; + } + + return GROUP_URL_HOST.equalsIgnoreCase(url.getHost()) ? url : null; + } catch (URISyntaxException e) { + return null; + } + } + + private GroupInviteLinkUrl(GroupMasterKey groupMasterKey, GroupLinkPassword password) { + this.groupMasterKey = groupMasterKey; + this.password = password; + this.url = createUrl(groupMasterKey, password); + } + + private static String createUrl(GroupMasterKey groupMasterKey, GroupLinkPassword password) { + var groupInviteLink = new GroupInviteLink.Builder().v1Contents(new GroupInviteLink.GroupInviteLinkContentsV1.Builder().groupMasterKey( + ByteString.of(groupMasterKey.serialize())) + .inviteLinkPassword(ByteString.of(password.serialize())) + .build()).build(); + + var encoding = Base64.encodeUrlSafeWithoutPadding(groupInviteLink.encode()); + + return GROUP_URL_PREFIX + encoding; + } + + public String getUrl() { + return url; + } + + public GroupMasterKey getGroupMasterKey() { + return groupMasterKey; + } + + public GroupLinkPassword getPassword() { + return password; + } + + public static final class InvalidGroupLinkException extends Exception { + + public InvalidGroupLinkException(String message) { + super(message); + } + + public InvalidGroupLinkException(Throwable cause) { + super(cause); + } + } + + public static final class UnknownGroupLinkVersionException extends Exception { + + public UnknownGroupLinkVersionException(String message) { + super(message); + } + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/GroupLinkState.java b/lib/src/main/java/org/asamk/signal/manager/api/GroupLinkState.java new file mode 100644 index 00000000..899e8587 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/GroupLinkState.java @@ -0,0 +1,7 @@ +package org.asamk.signal.manager.api; + +public enum GroupLinkState { + ENABLED, + ENABLED_WITH_APPROVAL, + DISABLED, +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/GroupNotFoundException.java b/lib/src/main/java/org/asamk/signal/manager/api/GroupNotFoundException.java new file mode 100644 index 00000000..052354a3 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/GroupNotFoundException.java @@ -0,0 +1,8 @@ +package org.asamk.signal.manager.api; + +public class GroupNotFoundException extends Exception { + + public GroupNotFoundException(GroupId groupId) { + super("Group not found: " + groupId.toBase64()); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/GroupPermission.java b/lib/src/main/java/org/asamk/signal/manager/api/GroupPermission.java new file mode 100644 index 00000000..db6f2ecd --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/GroupPermission.java @@ -0,0 +1,6 @@ +package org.asamk.signal.manager.api; + +public enum GroupPermission { + EVERY_MEMBER, + ONLY_ADMINS, +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/GroupSendingNotAllowedException.java b/lib/src/main/java/org/asamk/signal/manager/api/GroupSendingNotAllowedException.java new file mode 100644 index 00000000..e328e341 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/GroupSendingNotAllowedException.java @@ -0,0 +1,8 @@ +package org.asamk.signal.manager.api; + +public class GroupSendingNotAllowedException extends Exception { + + public GroupSendingNotAllowedException(GroupId groupId, String groupName) { + super("User is not allowed to send message to group: " + groupName + " (" + groupId.toBase64() + ")"); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Identity.java b/lib/src/main/java/org/asamk/signal/manager/api/Identity.java new file mode 100644 index 00000000..91f46750 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/Identity.java @@ -0,0 +1,10 @@ +package org.asamk.signal.manager.api; + +public record Identity( + RecipientAddress recipient, + byte[] fingerprint, + String safetyNumber, + byte[] scannableSafetyNumber, + TrustLevel trustLevel, + long dateAddedTimestamp +) {} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/IdentityVerificationCode.java b/lib/src/main/java/org/asamk/signal/manager/api/IdentityVerificationCode.java new file mode 100644 index 00000000..0f05b480 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/IdentityVerificationCode.java @@ -0,0 +1,28 @@ +package org.asamk.signal.manager.api; + +import org.signal.libsignal.protocol.util.Hex; + +import java.util.Base64; +import java.util.Locale; + +public sealed interface IdentityVerificationCode { + + record Fingerprint(byte[] fingerprint) implements IdentityVerificationCode {} + + record SafetyNumber(String safetyNumber) implements IdentityVerificationCode {} + + record ScannableSafetyNumber(byte[] safetyNumber) implements IdentityVerificationCode {} + + static IdentityVerificationCode parse(String code) throws Exception { + code = code.replaceAll(" ", ""); + if (code.length() == 66) { + final var fingerprintBytes = Hex.fromStringCondensed(code.toLowerCase(Locale.ROOT)); + return new Fingerprint(fingerprintBytes); + } else if (code.length() == 60) { + return new SafetyNumber(code); + } else { + final var scannableSafetyNumber = Base64.getDecoder().decode(code); + return new ScannableSafetyNumber(scannableSafetyNumber); + } + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/InactiveGroupLinkException.java b/lib/src/main/java/org/asamk/signal/manager/api/InactiveGroupLinkException.java new file mode 100644 index 00000000..914af466 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/InactiveGroupLinkException.java @@ -0,0 +1,12 @@ +package org.asamk.signal.manager.api; + +public class InactiveGroupLinkException extends Exception { + + public InactiveGroupLinkException(final String message) { + super(message); + } + + public InactiveGroupLinkException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/IncorrectPinException.java b/lib/src/main/java/org/asamk/signal/manager/api/IncorrectPinException.java new file mode 100644 index 00000000..6de58f5f --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/IncorrectPinException.java @@ -0,0 +1,14 @@ +package org.asamk.signal.manager.api; + +public class IncorrectPinException extends Exception { + + private final int triesRemaining; + + public IncorrectPinException(int triesRemaining) { + this.triesRemaining = triesRemaining; + } + + public int getTriesRemaining() { + return triesRemaining; + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/InvalidDeviceLinkException.java b/lib/src/main/java/org/asamk/signal/manager/api/InvalidDeviceLinkException.java new file mode 100644 index 00000000..6d3c306c --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/InvalidDeviceLinkException.java @@ -0,0 +1,12 @@ +package org.asamk.signal.manager.api; + +public class InvalidDeviceLinkException extends Exception { + + public InvalidDeviceLinkException(final String message) { + super(message); + } + + public InvalidDeviceLinkException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/InvalidNumberException.java b/lib/src/main/java/org/asamk/signal/manager/api/InvalidNumberException.java new file mode 100644 index 00000000..14f37966 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/InvalidNumberException.java @@ -0,0 +1,12 @@ +package org.asamk.signal.manager.api; + +public class InvalidNumberException extends Exception { + + public InvalidNumberException(String message) { + super(message); + } + + InvalidNumberException(String message, Throwable e) { + super(message, e); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/InvalidStickerException.java b/lib/src/main/java/org/asamk/signal/manager/api/InvalidStickerException.java new file mode 100644 index 00000000..2e2ffd4b --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/InvalidStickerException.java @@ -0,0 +1,12 @@ +package org.asamk.signal.manager.api; + +public class InvalidStickerException extends Exception { + + public InvalidStickerException(final String message) { + super(message); + } + + public InvalidStickerException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/InvalidUsernameException.java b/lib/src/main/java/org/asamk/signal/manager/api/InvalidUsernameException.java new file mode 100644 index 00000000..2d3b58a7 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/InvalidUsernameException.java @@ -0,0 +1,12 @@ +package org.asamk.signal.manager.api; + +public class InvalidUsernameException extends Exception { + + public InvalidUsernameException(final String message) { + super(message); + } + + public InvalidUsernameException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/LastGroupAdminException.java b/lib/src/main/java/org/asamk/signal/manager/api/LastGroupAdminException.java new file mode 100644 index 00000000..69d2ffb7 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/LastGroupAdminException.java @@ -0,0 +1,8 @@ +package org.asamk.signal.manager.api; + +public class LastGroupAdminException extends Exception { + + public LastGroupAdminException(GroupId groupId, String groupName) { + super("User is last admin in group: " + groupName + " (" + groupId.toBase64() + ")"); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Message.java b/lib/src/main/java/org/asamk/signal/manager/api/Message.java new file mode 100644 index 00000000..9b372451 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/Message.java @@ -0,0 +1,37 @@ +package org.asamk.signal.manager.api; + +import java.util.List; +import java.util.Optional; + +public record Message( + String messageText, + List attachments, + boolean viewOnce, + List mentions, + Optional quote, + Optional sticker, + List previews, + Optional storyReply, + List textStyles +) { + + public record Mention(RecipientIdentifier.Single recipient, int start, int length) {} + + public record Quote( + long timestamp, + RecipientIdentifier.Single author, + String message, + List mentions, + List textStyles, + List attachments + ) { + + public record Attachment(String contentType, String filename, String preview) {} + } + + public record Sticker(byte[] packId, int stickerId) {} + + public record Preview(String url, String title, String description, Optional image) {} + + public record StoryReply(long timestamp, RecipientIdentifier.Single author) {} +} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java new file mode 100644 index 00000000..d8438f5d --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java @@ -0,0 +1,969 @@ +package org.asamk.signal.manager.api; + +import org.asamk.signal.manager.groups.GroupUtils; +import org.asamk.signal.manager.helper.RecipientAddressResolver; +import org.asamk.signal.manager.storage.recipients.RecipientResolver; +import org.signal.libsignal.metadata.ProtocolException; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; +import org.whispersystems.signalservice.api.messages.SignalServiceContent; +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; +import org.whispersystems.signalservice.api.messages.SignalServiceGroup; +import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext; +import org.whispersystems.signalservice.api.messages.SignalServicePreview; +import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceTextAttachment; +import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; +import org.whispersystems.signalservice.api.messages.calls.AnswerMessage; +import org.whispersystems.signalservice.api.messages.calls.BusyMessage; +import org.whispersystems.signalservice.api.messages.calls.HangupMessage; +import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage; +import org.whispersystems.signalservice.api.messages.calls.OfferMessage; +import org.whispersystems.signalservice.api.messages.calls.OpaqueMessage; +import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; +import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage; +import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; +import org.whispersystems.signalservice.api.messages.multidevice.MessageRequestResponseMessage; +import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; +import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; +import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage; +import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage; +import org.whispersystems.signalservice.api.push.ServiceId; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +public record MessageEnvelope( + Optional sourceAddress, + int sourceDevice, + long timestamp, + long serverReceivedTimestamp, + long serverDeliveredTimestamp, + boolean isUnidentifiedSender, + Optional receipt, + Optional typing, + Optional data, + Optional edit, + Optional sync, + Optional call, + Optional story +) { + + public record Receipt(long when, Type type, List timestamps) { + + static Receipt from(final SignalServiceReceiptMessage receiptMessage) { + return new Receipt(receiptMessage.getWhen(), + Type.from(receiptMessage.getType()), + receiptMessage.getTimestamps()); + } + + public enum Type { + DELIVERY, + READ, + VIEWED, + UNKNOWN; + + static Type from(SignalServiceReceiptMessage.Type type) { + return switch (type) { + case DELIVERY -> DELIVERY; + case READ -> READ; + case VIEWED -> VIEWED; + case UNKNOWN -> UNKNOWN; + }; + } + } + } + + public record Typing(long timestamp, Type type, Optional groupId) { + + public static Typing from(final SignalServiceTypingMessage typingMessage) { + return new Typing(typingMessage.getTimestamp(), + typingMessage.isTypingStarted() ? Type.STARTED : Type.STOPPED, + typingMessage.getGroupId().map(GroupId::unknownVersion)); + } + + public enum Type { + STARTED, + STOPPED, + } + } + + public record Data( + long timestamp, + Optional groupContext, + Optional storyContext, + Optional groupCallUpdate, + Optional body, + int expiresInSeconds, + boolean isExpirationUpdate, + boolean isViewOnce, + boolean isEndSession, + boolean isProfileKeyUpdate, + boolean hasProfileKey, + Optional reaction, + Optional quote, + Optional payment, + List attachments, + Optional remoteDeleteId, + Optional sticker, + List sharedContacts, + List mentions, + List previews, + List textStyles + ) { + + static Data from( + final SignalServiceDataMessage dataMessage, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver, + final AttachmentFileProvider fileProvider + ) { + return new Data(dataMessage.getTimestamp(), + dataMessage.getGroupContext().map(GroupContext::from), + dataMessage.getStoryContext() + .map((SignalServiceDataMessage.StoryContext storyContext) -> StoryContext.from(storyContext, + recipientResolver, + addressResolver)), + dataMessage.getGroupCallUpdate().map(GroupCallUpdate::from), + dataMessage.getBody(), + dataMessage.getExpiresInSeconds(), + dataMessage.isExpirationUpdate(), + dataMessage.isViewOnce(), + dataMessage.isEndSession(), + dataMessage.isProfileKeyUpdate(), + dataMessage.getProfileKey().isPresent(), + dataMessage.getReaction().map(r -> Reaction.from(r, recipientResolver, addressResolver)), + dataMessage.getQuote() + .filter(q -> q.getAuthor() != null && q.getAuthor().isValid()) + .map(q -> Quote.from(q, recipientResolver, addressResolver, fileProvider)), + dataMessage.getPayment().map(p -> p.getPaymentNotification().isPresent() ? Payment.from(p) : null), + dataMessage.getAttachments() + .map(a -> a.stream().map(as -> Attachment.from(as, fileProvider)).toList()) + .orElse(List.of()), + dataMessage.getRemoteDelete().map(SignalServiceDataMessage.RemoteDelete::getTargetSentTimestamp), + dataMessage.getSticker().map(Sticker::from), + dataMessage.getSharedContacts() + .map(a -> a.stream() + .map(sharedContact -> SharedContact.from(sharedContact, fileProvider)) + .toList()) + .orElse(List.of()), + dataMessage.getMentions() + .map(a -> a.stream().map(m -> Mention.from(m, recipientResolver, addressResolver)).toList()) + .orElse(List.of()), + dataMessage.getPreviews() + .map(a -> a.stream().map(preview -> Preview.from(preview, fileProvider)).toList()) + .orElse(List.of()), + dataMessage.getBodyRanges() + .map(a -> a.stream().filter(r -> r.style != null).map(TextStyle::from).toList()) + .orElse(List.of())); + } + + public record GroupContext(GroupId groupId, boolean isGroupUpdate, int revision) { + + static GroupContext from(SignalServiceGroupContext groupContext) { + if (groupContext.getGroupV1().isPresent()) { + return new GroupContext(GroupId.v1(groupContext.getGroupV1().get().getGroupId()), + groupContext.getGroupV1Type() == SignalServiceGroup.Type.UPDATE, + 0); + } else if (groupContext.getGroupV2().isPresent()) { + final var groupV2 = groupContext.getGroupV2().get(); + return new GroupContext(GroupUtils.getGroupIdV2(groupV2.getMasterKey()), + groupV2.hasSignedGroupChange(), + groupV2.getRevision()); + } else { + throw new RuntimeException("Invalid group context state"); + } + } + } + + public record StoryContext(RecipientAddress author, long sentTimestamp) { + + static StoryContext from( + SignalServiceDataMessage.StoryContext storyContext, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver + ) { + return new StoryContext(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient( + storyContext.getAuthorServiceId())).toApiRecipientAddress(), storyContext.getSentTimestamp()); + } + } + + public record GroupCallUpdate(String eraId) { + + static GroupCallUpdate from(SignalServiceDataMessage.GroupCallUpdate groupCallUpdate) { + return new GroupCallUpdate(groupCallUpdate.getEraId()); + } + } + + public record Reaction( + long targetSentTimestamp, RecipientAddress targetAuthor, String emoji, boolean isRemove + ) { + + static Reaction from( + SignalServiceDataMessage.Reaction reaction, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver + ) { + return new Reaction(reaction.getTargetSentTimestamp(), + addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(reaction.getTargetAuthor())) + .toApiRecipientAddress(), + reaction.getEmoji(), + reaction.isRemove()); + } + } + + public record Quote( + long id, + RecipientAddress author, + Optional text, + List mentions, + List attachments, + List textStyles + ) { + + static Quote from( + SignalServiceDataMessage.Quote quote, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver, + final AttachmentFileProvider fileProvider + ) { + return new Quote(quote.getId(), + addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor())) + .toApiRecipientAddress(), + Optional.of(quote.getText()), + quote.getMentions() == null + ? List.of() + : quote.getMentions() + .stream() + .map(m -> Mention.from(m, recipientResolver, addressResolver)) + .toList(), + quote.getAttachments() == null + ? List.of() + : quote.getAttachments().stream().map(a -> Attachment.from(a, fileProvider)).toList(), + quote.getBodyRanges() == null + ? List.of() + : quote.getBodyRanges() + .stream() + .filter(r -> r.style != null) + .map(TextStyle::from) + .toList()); + } + } + + public record Payment(String note, byte[] receipt) { + + static Payment from(SignalServiceDataMessage.Payment payment) { + return new Payment(payment.getPaymentNotification().get().getNote(), + payment.getPaymentNotification().get().getReceipt()); + } + } + + public record Mention(RecipientAddress recipient, int start, int length) { + + static Mention from( + SignalServiceDataMessage.Mention mention, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver + ) { + return new Mention(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(mention.getServiceId())) + .toApiRecipientAddress(), mention.getStart(), mention.getLength()); + } + } + + public record Attachment( + Optional id, + Optional file, + Optional fileName, + String contentType, + Optional uploadTimestamp, + Optional size, + Optional preview, + Optional thumbnail, + Optional caption, + Optional width, + Optional height, + boolean isVoiceNote, + boolean isGif, + boolean isBorderless + ) { + + static Attachment from(SignalServiceAttachment signalAttachment, AttachmentFileProvider fileProvider) { + if (signalAttachment.isPointer()) { + final var a = signalAttachment.asPointer(); + final var attachmentFile = fileProvider.getFile(a); + return new Attachment(Optional.of(attachmentFile.getName()), + Optional.of(attachmentFile), + a.getFileName(), + a.getContentType(), + a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()), + a.getSize().map(Integer::longValue), + a.getPreview(), + Optional.empty(), + a.getCaption().map(c -> c.isEmpty() ? null : c), + a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()), + a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()), + a.getVoiceNote(), + a.isGif(), + a.isBorderless()); + } else { + Attachment attachment = null; + try (final var a = signalAttachment.asStream()) { + attachment = new Attachment(Optional.empty(), + Optional.empty(), + a.getFileName(), + a.getContentType(), + a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()), + Optional.of(a.getLength()), + a.getPreview(), + Optional.empty(), + a.getCaption(), + a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()), + a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()), + a.getVoiceNote(), + a.isGif(), + a.isBorderless()); + return attachment; + } catch (IOException e) { + return attachment; + } + } + } + + static Attachment from( + SignalServiceDataMessage.Quote.QuotedAttachment a, + final AttachmentFileProvider fileProvider + ) { + return new Attachment(Optional.empty(), + Optional.empty(), + Optional.ofNullable(a.getFileName()), + a.getContentType(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + a.getThumbnail() == null + ? Optional.empty() + : Optional.of(Attachment.from(a.getThumbnail(), fileProvider)), + Optional.empty(), + Optional.empty(), + Optional.empty(), + false, + false, + false); + } + } + + public record Sticker(StickerPackId packId, byte[] packKey, int stickerId) { + + static Sticker from(SignalServiceDataMessage.Sticker sticker) { + return new Sticker(StickerPackId.deserialize(sticker.getPackId()), + sticker.getPackKey(), + sticker.getStickerId()); + } + } + + public record SharedContact( + Name name, + Optional avatar, + List phone, + List email, + List
address, + Optional organization + ) { + + static SharedContact from( + org.whispersystems.signalservice.api.messages.shared.SharedContact sharedContact, + final AttachmentFileProvider fileProvider + ) { + return new SharedContact(Name.from(sharedContact.getName()), + sharedContact.getAvatar().map(avatar1 -> Avatar.from(avatar1, fileProvider)), + sharedContact.getPhone().map(p -> p.stream().map(Phone::from).toList()).orElse(List.of()), + sharedContact.getEmail().map(p -> p.stream().map(Email::from).toList()).orElse(List.of()), + sharedContact.getAddress().map(p -> p.stream().map(Address::from).toList()).orElse(List.of()), + sharedContact.getOrganization()); + } + + public record Name( + Optional nickname, + Optional given, + Optional family, + Optional prefix, + Optional suffix, + Optional middle + ) { + + static Name from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Name name) { + return new Name(name.getNickname(), + name.getGiven(), + name.getFamily(), + name.getPrefix(), + name.getSuffix(), + name.getMiddle()); + } + } + + public record Avatar(Attachment attachment, boolean isProfile) { + + static Avatar from( + org.whispersystems.signalservice.api.messages.shared.SharedContact.Avatar avatar, + final AttachmentFileProvider fileProvider + ) { + return new Avatar(Attachment.from(avatar.getAttachment(), fileProvider), avatar.isProfile()); + } + } + + public record Phone( + String value, Type type, Optional label + ) { + + static Phone from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Phone phone) { + return new Phone(phone.getValue(), Type.from(phone.getType()), phone.getLabel()); + } + + public enum Type { + HOME, + WORK, + MOBILE, + CUSTOM; + + static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Phone.Type type) { + return switch (type) { + case HOME -> HOME; + case WORK -> WORK; + case MOBILE -> MOBILE; + case CUSTOM -> CUSTOM; + }; + } + } + } + + public record Email( + String value, Type type, Optional label + ) { + + static Email from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Email email) { + return new Email(email.getValue(), Type.from(email.getType()), email.getLabel()); + } + + public enum Type { + HOME, + WORK, + MOBILE, + CUSTOM; + + static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Email.Type type) { + return switch (type) { + case HOME -> HOME; + case WORK -> WORK; + case MOBILE -> MOBILE; + case CUSTOM -> CUSTOM; + }; + } + } + } + + public record Address( + Type type, + Optional label, + Optional street, + Optional pobox, + Optional neighborhood, + Optional city, + Optional region, + Optional postcode, + Optional country + ) { + + static Address from(org.whispersystems.signalservice.api.messages.shared.SharedContact.PostalAddress address) { + return new Address(Type.from(address.getType()), + address.getLabel(), + address.getStreet(), + address.getPobox(), + address.getNeighborhood(), + address.getCity(), + address.getRegion(), + address.getPostcode(), + address.getCountry()); + } + + public enum Type { + HOME, + WORK, + CUSTOM; + + static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.PostalAddress.Type type) { + return switch (type) { + case HOME -> HOME; + case WORK -> WORK; + case CUSTOM -> CUSTOM; + }; + } + } + } + } + + public record Preview(String title, String description, long date, String url, Optional image) { + + static Preview from(SignalServicePreview preview, final AttachmentFileProvider fileProvider) { + return new Preview(preview.getTitle(), + preview.getDescription(), + preview.getDate(), + preview.getUrl(), + preview.getImage().map(as -> Attachment.from(as, fileProvider))); + } + } + + } + + public record Edit(long targetSentTimestamp, Data dataMessage) { + + public static Edit from( + final SignalServiceEditMessage editMessage, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver, + final AttachmentFileProvider fileProvider + ) { + return new Edit(editMessage.getTargetSentTimestamp(), + Data.from(editMessage.getDataMessage(), recipientResolver, addressResolver, fileProvider)); + } + } + + public record Sync( + Optional sent, + Optional blocked, + List read, + List viewed, + Optional viewOnceOpen, + Optional contacts, + Optional groups, + Optional messageRequestResponse + ) { + + public static Sync from( + final SignalServiceSyncMessage syncMessage, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver, + final AttachmentFileProvider fileProvider + ) { + return new Sync(syncMessage.getSent() + .map(s -> Sent.from(s, recipientResolver, addressResolver, fileProvider)), + syncMessage.getBlockedList().map(b -> Blocked.from(b, recipientResolver, addressResolver)), + syncMessage.getRead() + .map(r -> r.stream().map(rm -> Read.from(rm, recipientResolver, addressResolver)).toList()) + .orElse(List.of()), + syncMessage.getViewed() + .map(r -> r.stream() + .map(rm -> Viewed.from(rm, recipientResolver, addressResolver)) + .toList()) + .orElse(List.of()), + syncMessage.getViewOnceOpen().map(rm -> ViewOnceOpen.from(rm, recipientResolver, addressResolver)), + syncMessage.getContacts().map(Contacts::from), + syncMessage.getGroups().map(Groups::from), + syncMessage.getMessageRequestResponse() + .map(m -> MessageRequestResponse.from(m, recipientResolver, addressResolver))); + } + + public record Sent( + long timestamp, + long expirationStartTimestamp, + Optional destination, + Set recipients, + Optional message, + Optional editMessage, + Optional story + ) { + + static Sent from( + SentTranscriptMessage sentMessage, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver, + final AttachmentFileProvider fileProvider + ) { + return new Sent(sentMessage.getTimestamp(), + sentMessage.getExpirationStartTimestamp(), + sentMessage.getDestination() + .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)) + .toApiRecipientAddress()), + sentMessage.getRecipients() + .stream() + .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)) + .toApiRecipientAddress()) + .collect(Collectors.toSet()), + sentMessage.getDataMessage() + .map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)), + sentMessage.getEditMessage() + .map(message -> Edit.from(message, recipientResolver, addressResolver, fileProvider)), + sentMessage.getStoryMessage().map(s -> Story.from(s, fileProvider))); + } + } + + public record Blocked(List recipients, List groupIds) { + + static Blocked from( + BlockedListMessage blockedListMessage, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver + ) { + return new Blocked(blockedListMessage.individuals.stream() + .map(d -> new RecipientAddress(d.getAci() == null ? null : d.getAci().toString(), + null, + d.getE164(), + null)) + .toList(), blockedListMessage.groupIds.stream().map(GroupId::unknownVersion).toList()); + } + } + + public record Read(RecipientAddress sender, long timestamp) { + + static Read from( + ReadMessage readMessage, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver + ) { + return new Read(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())) + .toApiRecipientAddress(), readMessage.getTimestamp()); + } + } + + public record Viewed(RecipientAddress sender, long timestamp) { + + static Viewed from( + ViewedMessage readMessage, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver + ) { + return new Viewed(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())) + .toApiRecipientAddress(), readMessage.getTimestamp()); + } + } + + public record ViewOnceOpen(RecipientAddress sender, long timestamp) { + + static ViewOnceOpen from( + ViewOnceOpenMessage readMessage, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver + ) { + return new ViewOnceOpen(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient( + readMessage.getSender())).toApiRecipientAddress(), readMessage.getTimestamp()); + } + } + + public record Contacts(boolean isComplete) { + + static Contacts from(ContactsMessage contactsMessage) { + return new Contacts(contactsMessage.isComplete()); + } + } + + public record Groups() { + + static Groups from(SignalServiceAttachment groupsMessage) { + return new Groups(); + } + } + + public record MessageRequestResponse(Type type, Optional groupId, Optional person) { + + static MessageRequestResponse from( + MessageRequestResponseMessage messageRequestResponse, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver + ) { + return new MessageRequestResponse(Type.from(messageRequestResponse.getType()), + messageRequestResponse.getGroupId().map(GroupId::unknownVersion), + messageRequestResponse.getPerson() + .map(p -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(p)) + .toApiRecipientAddress())); + } + + public enum Type { + UNKNOWN, + ACCEPT, + DELETE, + BLOCK, + BLOCK_AND_DELETE, + UNBLOCK_AND_ACCEPT, + SPAM, + BLOCK_AND_SPAM; + + static Type from(MessageRequestResponseMessage.Type type) { + return switch (type) { + case UNKNOWN -> UNKNOWN; + case ACCEPT -> ACCEPT; + case DELETE -> DELETE; + case BLOCK -> BLOCK; + case BLOCK_AND_DELETE -> BLOCK_AND_DELETE; + case UNBLOCK_AND_ACCEPT -> UNBLOCK_AND_ACCEPT; + case SPAM -> SPAM; + case BLOCK_AND_SPAM -> BLOCK_AND_SPAM; + }; + } + } + } + } + + public record Call( + Optional destinationDeviceId, + Optional groupId, + Optional timestamp, + Optional offer, + Optional answer, + Optional hangup, + Optional busy, + List iceUpdate, + Optional opaque, + boolean isUrgent + ) { + + public static Call from(final SignalServiceCallMessage callMessage) { + return new Call(callMessage.getDestinationDeviceId(), + callMessage.getGroupId().map(GroupId::unknownVersion), + callMessage.getTimestamp(), + callMessage.getOfferMessage().map(Offer::from), + callMessage.getAnswerMessage().map(Answer::from), + callMessage.getHangupMessage().map(Hangup::from), + callMessage.getBusyMessage().map(Busy::from), + callMessage.getIceUpdateMessages() + .map(m -> m.stream().map(IceUpdate::from).toList()) + .orElse(List.of()), + callMessage.getOpaqueMessage().map(Opaque::from), + callMessage.isUrgent()); + } + + public record Offer(long id, Type type, byte[] opaque) { + + static Offer from(OfferMessage offerMessage) { + return new Offer(offerMessage.getId(), Type.from(offerMessage.getType()), offerMessage.getOpaque()); + } + + public enum Type { + AUDIO_CALL, + VIDEO_CALL; + + static Type from(OfferMessage.Type type) { + return switch (type) { + case AUDIO_CALL -> AUDIO_CALL; + case VIDEO_CALL -> VIDEO_CALL; + }; + } + } + } + + public record Answer(long id, byte[] opaque) { + + static Answer from(AnswerMessage answerMessage) { + return new Answer(answerMessage.getId(), answerMessage.getOpaque()); + } + } + + public record Busy(long id) { + + static Busy from(BusyMessage busyMessage) { + return new Busy(busyMessage.getId()); + } + } + + public record Hangup(long id, Type type, int deviceId) { + + static Hangup from(HangupMessage hangupMessage) { + return new Hangup(hangupMessage.getId(), + Type.from(hangupMessage.getType()), + hangupMessage.getDeviceId()); + } + + public enum Type { + NORMAL, + ACCEPTED, + DECLINED, + BUSY, + NEED_PERMISSION; + + static Type from(HangupMessage.Type type) { + return switch (type) { + case NORMAL -> NORMAL; + case ACCEPTED -> ACCEPTED; + case DECLINED -> DECLINED; + case BUSY -> BUSY; + case NEED_PERMISSION -> NEED_PERMISSION; + }; + } + } + } + + public record IceUpdate(long id, byte[] opaque) { + + static IceUpdate from(IceUpdateMessage iceUpdateMessage) { + return new IceUpdate(iceUpdateMessage.getId(), iceUpdateMessage.getOpaque()); + } + } + + public record Opaque(byte[] opaque, Urgency urgency) { + + static Opaque from(OpaqueMessage opaqueMessage) { + return new Opaque(opaqueMessage.getOpaque(), Urgency.from(opaqueMessage.getUrgency())); + } + + public enum Urgency { + DROPPABLE, + HANDLE_IMMEDIATELY; + + static Urgency from(OpaqueMessage.Urgency urgency) { + return switch (urgency) { + case DROPPABLE -> DROPPABLE; + case HANDLE_IMMEDIATELY -> HANDLE_IMMEDIATELY; + }; + } + } + } + } + + public record Story( + boolean allowsReplies, + Optional groupId, + Optional fileAttachment, + Optional textAttachment + ) { + + public static Story from(SignalServiceStoryMessage storyMessage, final AttachmentFileProvider fileProvider) { + return new Story(storyMessage.getAllowsReplies().orElse(false), + storyMessage.getGroupContext().map(c -> GroupUtils.getGroupIdV2(c.getMasterKey())), + storyMessage.getFileAttachment().map(f -> Data.Attachment.from(f, fileProvider)), + storyMessage.getTextAttachment().map(t -> TextAttachment.from(t, fileProvider))); + } + + public record TextAttachment( + Optional text, + Optional