diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75d892a8..23e19225 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '21', '23' ] + java: [ '21', '24' ] steps: - uses: actions/checkout@v4 @@ -26,11 +26,22 @@ jobs: distribution: 'zulu' java-version: ${{ matrix.java }} - name: Setup Gradle - uses: gradle/gradle-build-action@v2 + 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 @@ -58,3 +69,28 @@ jobs: 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 ff13469a..f778268a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,7 +35,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 # Override language selection by uncommenting this and choosing your languages # with: # languages: go, javascript, csharp, python, cpp, java @@ -43,7 +43,7 @@ jobs: # 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@v2 + uses: github/codeql-action/autobuild@v3 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -57,4 +57,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b1f4027d..b7ce03f1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -182,7 +182,7 @@ jobs: tar xf ./"${ARCHIVE_DIR}"/*.tar.gz rm -r signal-cli-archive-* signal-cli-native mkdir -p build/install/ - mv ./signal-cli-*/ build/install/signal-cli + mv ./signal-cli-"${GITHUB_REF_NAME#v}"/ build/install/signal-cli - name: Build Image id: build_image diff --git a/CHANGELOG.md b/CHANGELOG.md index 87bc620a..74d0bdc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,112 @@ # 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 diff --git a/README.md b/README.md index 03455d12..c7228c09 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ For this use-case, it has a daemon mode with JSON-RPC interface ([man page](http 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 @@ -55,8 +59,15 @@ of all country codes.) signal-cli -a ACCOUNT register - You can register Signal using a landline 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. + 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: + + ```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) @@ -72,6 +83,12 @@ of all country codes.) 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 -a ACCOUNT send --message-from-stdin RECIPIENT diff --git a/build.gradle.kts b/build.gradle.kts index 76034b85..ea69d97e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,10 +3,13 @@ plugins { application eclipse `check-lib-versions` - id("org.graalvm.buildtools.native") version "0.10.3" + id("org.graalvm.buildtools.native") version "0.10.6" } -version = "0.13.10" +allprojects { + group = "org.asamk" + version = "0.13.19-SNAPSHOT" +} java { sourceCompatibility = JavaVersion.VERSION_21 @@ -21,6 +24,7 @@ java { application { mainClass.set("org.asamk.signal.Main") + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED") } graalvmNative { @@ -29,6 +33,7 @@ graalvmNative { 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) { @@ -43,7 +48,41 @@ graalvmNative { } } +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) @@ -51,7 +90,7 @@ dependencies { implementation(libs.slf4j.api) implementation(libs.slf4j.jul) implementation(libs.logback) - implementation(project(":lib")) + implementation(project(":libsignal-cli")) } configurations { @@ -75,12 +114,13 @@ tasks.withType { attributes( "Implementation-Title" to project.name, "Implementation-Version" to project.version, - "Main-Class" to application.mainClass.get() + "Main-Class" to application.mainClass.get(), + "Enable-Native-Access" to "ALL-UNNAMED", ) } } -task("fatJar", type = Jar::class) { +tasks.register("fatJar", type = Jar::class) { archiveBaseName.set("${project.name}-fat") exclude( "META-INF/*.SF", @@ -89,9 +129,11 @@ task("fatJar", type = Jar::class) { "META-INF/NOTICE*", "META-INF/LICENSE*", "META-INF/INDEX.LIST", - "**/module-info.class" + "**/module-info.class", ) duplicatesStrategy = DuplicatesStrategy.WARN - from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) + doFirst { + from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) + } with(tasks.jar.get()) } diff --git a/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt b/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt index eb74acb2..ae8ae4de 100644 --- a/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt +++ b/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt @@ -1,12 +1,10 @@ @file:Suppress("DEPRECATION") -import groovy.util.XmlSlurper -import groovy.util.slurpersupport.GPathResult -import org.codehaus.groovy.runtime.ResourceGroovyMethods 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) { @@ -28,10 +26,10 @@ class CheckLibVersionsPlugin : Plugin { val name = dependency.name val metaDataUrl = "https://repo1.maven.org/maven2/$path/$name/maven-metadata.xml" try { - val url = ResourceGroovyMethods.toURL(metaDataUrl) - val metaDataText = ResourceGroovyMethods.getText(url) - val metadata = XmlSlurper().parseText(metaDataText) - val newest = (metadata.getProperty("versioning") as GPathResult).getProperty("latest") + 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\"}") } 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/Cargo.lock b/client/Cargo.lock index 5118fb2a..01f28e0c 100644 --- a/client/Cargo.lock +++ b/client/Cargo.lock @@ -1,27 +1,27 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -34,49 +34,50 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell_polyfill", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.87" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -89,25 +90,19 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -118,21 +113,21 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bytes" -version = "1.7.1" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.1.18" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "shlex", ] @@ -145,15 +140,15 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "clap" -version = "4.5.17" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -161,9 +156,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -174,9 +169,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck", "proc-macro2", @@ -186,15 +181,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "combine" @@ -208,9 +203,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -223,19 +218,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "equivalent" -version = "1.0.1" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +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.9" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -255,24 +261,24 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -281,15 +287,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -299,9 +305,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-macro", @@ -314,9 +320,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -325,15 +331,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.4.6" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -350,9 +356,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "heck" @@ -360,17 +366,11 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "http" -version = "1.1.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -389,12 +389,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -402,15 +402,15 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.4.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -428,11 +428,10 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.3" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", "http", "hyper", "hyper-util", @@ -446,44 +445,153 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "libc", "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_collections" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "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.5.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +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" @@ -492,22 +600,24 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jni" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", + "cfg-if", "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -518,9 +628,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jsonrpsee" -version = "0.24.3" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec465b607a36dc5dd45d48b7689bc83f679f66a3ac6b6b21cc787a11e0f8685" +checksum = "1fba77a59c4c644fd48732367624d1bcf6f409f9c9a286fbc71d2f1fc0b2ea16" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-client", @@ -531,9 +641,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.24.3" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e942c55635fbf5dc421938b8558a8141c7e773720640f4f1dbe1f4164ca4e221" +checksum = "693c93cbb7db25f4108ed121304b671a36002c2db67dff2ee4391a688c738547" dependencies = [ "async-trait", "bytes", @@ -547,19 +657,19 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "thiserror", + "thiserror 2.0.12", "tokio", "tokio-stream", + "tower", "tracing", ] [[package]] name = "jsonrpsee-http-client" -version = "0.24.3" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33774602df12b68a2310b38a535733c477ca4a498751739f89fe8dbbb62ec4c" +checksum = "6962d2bd295f75e97dd328891e58fce166894b974c1f7ce2e7597f02eeceb791" dependencies = [ - "async-trait", "base64", "http-body", "hyper", @@ -571,18 +681,17 @@ dependencies = [ "rustls-platform-verifier", "serde", "serde_json", - "thiserror", + "thiserror 2.0.12", "tokio", "tower", - "tracing", "url", ] [[package]] name = "jsonrpsee-proc-macros" -version = "0.24.3" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b07a2daf52077ab1b197aea69a5c990c060143835bf04c77070e98903791715" +checksum = "2fa4f5daed39f982a1bb9d15449a28347490ad42b212f8eaa2a2a344a0dce9e9" dependencies = [ "heck", "proc-macro-crate", @@ -593,109 +702,92 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.24.3" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b67d6e008164f027afbc2e7bb79662650158d26df200040282d2aa1cbb093b" +checksum = "66df7256371c45621b3b7d2fb23aea923d577616b9c0e9c0b950a6ea5c2be0ca" dependencies = [ "http", "serde", "serde_json", - "thiserror", + "thiserror 2.0.12", ] [[package]] name = "libc" -version = "0.2.158" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +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.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ - "hermit-abi", "libc", "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", + "windows-sys 0.59.0", ] [[package]] name = "object" -version = "0.36.4" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +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.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "percent-encoding" @@ -705,18 +797,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", @@ -725,9 +817,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -736,77 +828,85 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "proc-macro-crate" -version = "3.2.0" +name = "potential_utf" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +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.86" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "0.38.36" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "log", "once_cell", @@ -819,38 +919,30 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", ] [[package]] -name = "rustls-pemfile" -version = "2.1.3" +name = "rustls-pki-types" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ - "base64", - "rustls-pki-types", + "zeroize", ] -[[package]] -name = "rustls-pki-types" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" - [[package]] name = "rustls-platform-verifier" -version = "0.3.4" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" +checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ "core-foundation", "core-foundation-sys", @@ -863,8 +955,8 @@ dependencies = [ "rustls-webpki", "security-framework", "security-framework-sys", - "webpki-roots", - "winapi", + "webpki-root-certs 0.26.11", + "windows-sys 0.59.0", ] [[package]] @@ -875,9 +967,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.102.7" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -886,9 +978,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -901,32 +993,31 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "security-framework" -version = "2.11.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", - "num-bigint", "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -934,18 +1025,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -954,9 +1045,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -979,44 +1070,40 @@ dependencies = [ "clap", "futures-util", "jsonrpsee", - "log", "serde", "serde_json", - "thiserror", + "thiserror 2.0.12", "tokio", "tokio-util", ] [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] -name = "spin" -version = "0.9.8" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strsim" @@ -1032,9 +1119,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.77" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -1042,29 +1129,16 @@ dependencies = [ ] [[package]] -name = "terminal_size" -version = "0.3.0" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" -dependencies = [ - "rustix", - "windows-sys 0.48.0", -] +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] -name = "thiserror" -version = "1.0.63" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -1072,31 +1146,78 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.8.0" +name = "terminal_size" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "tinyvec_macros", + "rustix", + "windows-sys 0.59.0", ] [[package]] -name = "tinyvec_macros" -version = "0.1.1" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +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.40.0" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", + "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -1104,9 +1225,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -1115,20 +1236,19 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -1137,9 +1257,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -1150,15 +1270,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "toml_datetime", @@ -1167,18 +1287,16 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.13" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "pin-project", "pin-project-lite", - "tokio", + "sync_wrapper", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -1195,11 +1313,10 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1207,9 +1324,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -1218,9 +1335,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] @@ -1231,26 +1348,11 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "untrusted" @@ -1260,15 +1362,21 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +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" @@ -1296,35 +1404,28 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "webpki-roots" -version = "0.26.5" +name = "webpki-root-certs" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +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" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" version = "0.1.9" @@ -1334,19 +1435,13 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.42.2", ] [[package]] @@ -1368,18 +1463,27 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "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]] @@ -1391,7 +1495,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "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", @@ -1399,10 +1503,26 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +name = "windows-targets" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +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" @@ -1411,10 +1531,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "windows_aarch64_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +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" @@ -1423,10 +1549,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "windows_aarch64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +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" @@ -1434,6 +1566,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" @@ -1441,10 +1579,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "windows_i686_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +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" @@ -1453,10 +1597,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "windows_i686_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +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" @@ -1465,10 +1615,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "windows_x86_64_gnu" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +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" @@ -1477,10 +1633,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "windows_x86_64_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +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" @@ -1489,16 +1651,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.6.18" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +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 index b60f2041..aff9ede4 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,18 +1,17 @@ [package] name = "signal-cli-client" version = "0.0.1" -edition = "2021" +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"] } -log = "0.4" serde = "1" serde_json = "1" tokio = { version = "1", features = ["rt", "macros", "net", "rt-multi-thread"] } -jsonrpsee = { version = "0.24", features = [ +jsonrpsee = { version = "0.25", features = [ "macros", "async-client", "http-client", @@ -20,4 +19,4 @@ jsonrpsee = { version = "0.24", features = [ bytes = "1" tokio-util = "0.7" futures-util = "0.3" -thiserror = "1" +thiserror = "2" diff --git a/client/src/cli.rs b/client/src/cli.rs index a64ab511..7fa5d1c5 100644 --- a/client/src/cli.rs +++ b/client/src/cli.rs @@ -15,6 +15,7 @@ pub struct Cli { 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>, @@ -84,6 +85,8 @@ pub enum CliCommands { }, GetUserStatus { recipient: Vec, + #[arg(long)] + username: Vec, }, JoinGroup { #[arg(long)] @@ -176,6 +179,9 @@ pub enum CliCommands { #[arg(short = 'a', long)] attachment: Vec, + #[arg(long)] + view_once: bool, + #[arg(long)] mention: Vec, @@ -413,7 +419,7 @@ pub enum CliCommands { #[arg(long = "about-emoji")] about_emoji: Option, - #[arg(long = "mobile-coin-address")] + #[arg(long = "mobile-coin-address", visible_alias = "mobilecoin-address")] mobile_coin_address: Option, #[arg(long)] diff --git a/client/src/jsonrpc.rs b/client/src/jsonrpc.rs index b085cde5..66ef9d9d 100644 --- a/client/src/jsonrpc.rs +++ b/client/src/jsonrpc.rs @@ -70,6 +70,7 @@ pub trait Rpc { &self, account: Option, recipients: Vec, + usernames: Vec, ) -> Result; #[method(name = "joinGroup", param_kind = map)] @@ -182,6 +183,7 @@ pub trait Rpc { endSession: bool, message: String, attachments: Vec, + viewOnce: bool, mentions: Vec, textStyle: Vec, quoteTimestamp: Option, @@ -190,10 +192,10 @@ pub trait Rpc { quoteMention: Vec, quoteTextStyle: Vec, quoteAttachment: Vec, - preview_url: Option, - preview_title: Option, - preview_description: Option, - preview_image: Option, + previewUrl: Option, + previewTitle: Option, + previewDescription: Option, + previewImage: Option, sticker: Option, storyTimestamp: Option, storyAuthor: Option, @@ -409,6 +411,7 @@ pub async fn connect_tcp( Ok(ClientBuilder::default().build_with_tokio(sender, receiver)) } +#[cfg(unix)] pub async fn connect_unix( socket_path: impl AsRef, ) -> Result { @@ -417,6 +420,6 @@ pub async fn connect_unix( Ok(ClientBuilder::default().build_with_tokio(sender, receiver)) } -pub async fn connect_http(uri: &str) -> Result { +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 index 35c1ab22..ac12331d 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -60,8 +60,13 @@ async fn handle_command( .delete_local_account_data(cli.account, ignore_registered) .await } - CliCommands::GetUserStatus { recipient } => { - client.get_user_status(cli.account, recipient).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 } => { @@ -70,7 +75,7 @@ async fn handle_command( .await .map_err(|e| RpcError::Custom(format!("JSON-RPC command startLink failed: {e:?}")))? .device_link_uri; - println!("{}", url); + println!("{url}"); client.finish_link(url, name).await } CliCommands::ListAccounts => client.list_accounts().await, @@ -139,6 +144,7 @@ async fn handle_command( end_session, message, attachment, + view_once, mention, text_style, quote_timestamp, @@ -165,6 +171,7 @@ async fn handle_command( end_session, message.unwrap_or_default(), attachment, + view_once, mention, text_style, quote_timestamp, @@ -477,23 +484,30 @@ async fn connect(cli: Cli) -> Result { handle_command(cli, client).await } else { - 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() + #[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}")))?; + .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 + handle_command(cli, client).await + } } } diff --git a/client/src/transports/mod.rs b/client/src/transports/mod.rs index 04f4390f..ed1963a0 100644 --- a/client/src/transports/mod.rs +++ b/client/src/transports/mod.rs @@ -1,10 +1,8 @@ use futures_util::{stream::StreamExt, Sink, SinkExt, Stream}; -use jsonrpsee::core::{ - async_trait, - client::{ReceivedMessage, TransportReceiverT, TransportSenderT}, -}; +use jsonrpsee::core::client::{ReceivedMessage, TransportReceiverT, TransportSenderT}; use thiserror::Error; +#[cfg(unix)] pub mod ipc; mod stream_codec; pub mod tcp; @@ -21,7 +19,6 @@ struct Sender> { inner: T, } -#[async_trait] impl + Unpin + 'static> TransportSenderT for Sender { @@ -31,7 +28,7 @@ impl + Unpin + 'static> T self.inner .send(body) .await - .map_err(|e| Errors::Other(format!("{:?}", e)))?; + .map_err(|e| Errors::Other(format!("{e:?}")))?; Ok(()) } @@ -39,7 +36,7 @@ impl + Unpin + 'static> T self.inner .close() .await - .map_err(|e| Errors::Other(format!("{:?}", e)))?; + .map_err(|e| Errors::Other(format!("{e:?}")))?; Ok(()) } } @@ -48,7 +45,6 @@ struct Receiver { inner: T, } -#[async_trait] impl> + Unpin + 'static> TransportReceiverT for Receiver { @@ -58,7 +54,7 @@ impl> + Unpin + 'static> match self.inner.next().await { None => Err(Errors::Closed), Some(Ok(msg)) => Ok(ReceivedMessage::Text(msg)), - Some(Err(e)) => Err(Errors::Other(format!("{:?}", e))), + Some(Err(e)) => Err(Errors::Other(format!("{e:?}"))), } } } diff --git a/client/src/transports/stream_codec.rs b/client/src/transports/stream_codec.rs index 6f77306f..e46233cb 100644 --- a/client/src/transports/stream_codec.rs +++ b/client/src/transports/stream_codec.rs @@ -41,7 +41,7 @@ impl Decoder for StreamCodec { match str::from_utf8(line.as_ref()) { Ok(s) => Ok(Some(s.to_string())), - Err(_) => Err(io::Error::new(io::ErrorKind::Other, "invalid UTF-8")), + Err(_) => Err(io::Error::other("invalid UTF-8")), } } else { Ok(None) diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml index 63f55e4d..c6c07411 100644 --- a/data/org.asamk.SignalCli.metainfo.xml +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -45,6 +45,30 @@ 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 diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json index fc76b7f9..523a6c03 100644 --- a/graalvm-config-dir/jni-config.json +++ b/graalvm-config-dir/jni-config.json @@ -27,6 +27,10 @@ { "name":"java.lang.ClassNotFoundException" }, +{ + "name":"java.lang.Enum", + "methods":[{"name":"ordinal","parameterTypes":[] }] +}, { "name":"java.lang.IllegalArgumentException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] @@ -48,9 +52,13 @@ { "name":"java.lang.String" }, +{ + "name":"java.lang.Thread", + "methods":[{"name":"currentThread","parameterTypes":[] }, {"name":"getStackTrace","parameterTypes":[] }] +}, { "name":"java.lang.Throwable", - "methods":[{"name":"getMessage","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] + "methods":[{"name":"getMessage","parameterTypes":[] }, {"name":"setStackTrace","parameterTypes":["java.lang.StackTraceElement[]"] }, {"name":"toString","parameterTypes":[] }] }, { "name":"java.lang.UnsatisfiedLinkError", @@ -88,7 +96,11 @@ }, { "name":"org.signal.libsignal.internal.CompletableFuture", - "methods":[{"name":"","parameterTypes":[] }, {"name":"complete","parameterTypes":["java.lang.Object"] }] + "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", @@ -110,6 +122,14 @@ { "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"] }] @@ -187,6 +207,9 @@ "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"}] diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 7a8fe2a1..0bfd1cdf 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -39,6 +39,9 @@ { "name":"[Ljava.sql.Statement;" }, +{ + "name":"[Lorg.asamk.signal.commands.ListStickerPacksCommand$JsonStickerPack$JsonSticker;" +}, { "name":"[Lorg.asamk.signal.json.JsonAttachment;" }, @@ -75,6 +78,9 @@ { "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;" }, @@ -665,6 +671,10 @@ { "name":"long[]" }, +{ + "name":"okhttp3.internal.connection.RealConnectionPool", + "fields":[{"name":"addressStates"}] +}, { "name":"okio.BufferedSink" }, @@ -1403,6 +1413,12 @@ "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, @@ -1614,6 +1630,10 @@ "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":[] }] @@ -2034,7 +2054,10 @@ "name":"org.signal.libsignal.protocol.IdentityKey" }, { - "name":"org.signal.libsignal.protocol.ServiceId" + "name":"org.signal.libsignal.protocol.ServiceId", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true }, { "name":"org.signal.libsignal.protocol.SignalProtocolAddress" @@ -2296,7 +2319,7 @@ "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "methods":[{"name":"getAnnouncementGroup","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":[] }] + "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", @@ -2323,6 +2346,13 @@ { "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, @@ -2390,7 +2420,14 @@ "name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfileWrite", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":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", @@ -2435,6 +2472,12 @@ "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, @@ -2913,7 +2956,28 @@ "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }] + "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", @@ -2940,12 +3004,21 @@ "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" }, @@ -2961,6 +3034,9 @@ "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, @@ -2994,6 +3070,7 @@ { "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":[] }] }, { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 483ea244..fec44a43 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,17 +1,17 @@ [versions] -slf4j = "2.0.16" +slf4j = "2.0.17" [libraries] -bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.79" -jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.18.1" +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.12" +logback = "ch.qos.logback:logback-classic:1.5.18" -signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_112" -sqlite = "org.xerial:sqlite-jdbc:3.47.0.0" -hikari = "com.zaxxer:HikariCP:6.2.1" -junit-jupiter = "org.junit.jupiter:junit-jupiter:5.11.3" -junit-launcher = "org.junit.platform:junit-platform-launcher:1.11.3" +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 a4b76b95..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 df97d72b..2a84e188 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d..ef07e016 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright Š 2015-2021 the original 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. @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019c..5eed7ee8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ 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 diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 85644f61..ccb661e1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -1,5 +1,7 @@ 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; @@ -28,6 +30,7 @@ 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; @@ -49,7 +52,6 @@ import org.asamk.signal.manager.api.UsernameStatus; import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import java.io.Closeable; import java.io.File; @@ -65,7 +67,7 @@ import java.util.Set; public interface Manager extends Closeable { static boolean isValidNumber(final String e164Number, final String countryCode) { - return PhoneNumberFormatter.isValidNumber(e164Number, countryCode); + return PhoneNumberUtil.getInstance().isPossibleNumber(e164Number, countryCode); } static boolean isSignalClientAvailable() { @@ -94,7 +96,7 @@ public interface Manager extends Closeable { */ Map getUserStatus(Set numbers) throws IOException, RateLimitException; - Map getUsernameStatus(Set usernames); + Map getUsernameStatus(Set usernames) throws IOException; void updateAccountAttributes( String deviceName, @@ -139,7 +141,7 @@ public interface Manager extends Closeable { String newNumber, String verificationCode, String pin - ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException; + ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException, PinLockMissingException; void unregister() throws IOException; @@ -237,9 +239,12 @@ public interface Manager extends Closeable { void deleteContact(RecipientIdentifier.Single recipient); void setContactName( - RecipientIdentifier.Single recipient, - String givenName, - final String familyName + final RecipientIdentifier.Single recipient, + final String givenName, + final String familyName, + final String nickGivenName, + final String nickFamilyName, + final String note ) throws NotPrimaryDeviceException, UnregisteredRecipientException; void setContactsBlocked( diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java index 7d358df6..5f4f4463 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -3,6 +3,7 @@ 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; @@ -21,7 +22,7 @@ public interface RegistrationManager extends Closeable { void verifyAccount( String verificationCode, String pin - ) throws IOException, PinLockedException, IncorrectPinException; + ) throws IOException, PinLockedException, IncorrectPinException, PinLockMissingException; void deleteLocalAccountData() throws IOException; diff --git a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java index 4072a9df..cd0b5a21 100644 --- a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java +++ b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java @@ -2,6 +2,7 @@ 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; @@ -63,19 +64,28 @@ public class SignalAccountFiles { return accountsStore.getAllNumbers(); } - public MultiAccountManager initMultiAccountManager() throws IOException { - final var managers = accountsStore.getAllAccounts().parallelStream().map(a -> { + public MultiAccountManager initMultiAccountManager() throws IOException, AccountCheckException { + final var managerPairs = accountsStore.getAllAccounts().parallelStream().map(a -> { try { - return initManager(a.number(), a.path()); - } catch (NotRegisteredException | IOException | AccountCheckException e) { + 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 (Throwable e) { + } catch (AccountCheckException | IOException e) { logger.error("Failed to load {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName()); - throw e; + 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); } 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 index 40117246..35058940 100644 --- a/lib/src/main/java/org/asamk/signal/manager/actions/RenewSessionAction.java +++ b/lib/src/main/java/org/asamk/signal/manager/actions/RenewSessionAction.java @@ -19,9 +19,7 @@ public class RenewSessionAction implements HandleAction { @Override public void execute(Context context) throws Throwable { context.getAccount().getAccountData(accountId).getSessionStore().archiveSessions(serviceId); - if (!recipientId.equals(context.getAccount().getSelfRecipientId())) { - context.getSendHelper().sendNullMessage(recipientId); - } + context.getSendHelper().sendNullMessage(recipientId); } @Override 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 index 865f062f..96a94128 100644 --- a/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java @@ -7,7 +7,6 @@ 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.api.push.ServiceId; import org.whispersystems.signalservice.internal.push.Envelope; import java.util.Optional; @@ -15,29 +14,21 @@ import java.util.Optional; public class SendRetryMessageRequestAction implements HandleAction { private final RecipientId recipientId; - private final ServiceId serviceId; private final ProtocolException protocolException; private final SignalServiceEnvelope envelope; - private final ServiceId accountId; public SendRetryMessageRequestAction( final RecipientId recipientId, - final ServiceId serviceId, final ProtocolException protocolException, - final SignalServiceEnvelope envelope, - final ServiceId accountId + final SignalServiceEnvelope envelope ) { this.recipientId = recipientId; - this.serviceId = serviceId; this.protocolException = protocolException; this.envelope = envelope; - this.accountId = accountId; } @Override public void execute(Context context) throws Throwable { - context.getAccount().getAccountData(accountId).getSessionStore().archiveSessions(serviceId); - int senderDevice = protocolException.getSenderDevice(); Optional groupId = protocolException.getGroupId().isPresent() ? Optional.of(GroupId.unknownVersion( protocolException.getGroupId().get())) : Optional.empty(); 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 index 91cf82df..3eef0117 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Contact.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Contact.java @@ -49,8 +49,12 @@ public record Contact( 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(); 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 index 54271dac..2bb63507 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/DeviceLinkUrl.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/DeviceLinkUrl.java @@ -2,7 +2,6 @@ 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.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; import java.net.URI; @@ -37,7 +36,7 @@ public record DeviceLinkUrl(String deviceIdentifier, ECPublicKey deviceKey) { } ECPublicKey deviceKey; try { - deviceKey = Curve.decodePoint(publicKeyBytes, 0); + deviceKey = new ECPublicKey(publicKeyBytes); } catch (InvalidKeyException e) { throw new InvalidDeviceLinkException("Invalid device link", e); } 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 index 8e6a8064..14f37966 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/InvalidNumberException.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/InvalidNumberException.java @@ -2,7 +2,7 @@ package org.asamk.signal.manager.api; public class InvalidNumberException extends Exception { - InvalidNumberException(String message) { + public InvalidNumberException(String message) { super(message); } 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 index 48277e6d..9b372451 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Message.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Message.java @@ -6,6 +6,7 @@ import java.util.Optional; public record Message( String messageText, List attachments, + boolean viewOnce, List mentions, Optional quote, Optional sticker, diff --git a/lib/src/main/java/org/asamk/signal/manager/api/PinLockMissingException.java b/lib/src/main/java/org/asamk/signal/manager/api/PinLockMissingException.java new file mode 100644 index 00000000..7887d45f --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/PinLockMissingException.java @@ -0,0 +1,3 @@ +package org.asamk.signal.manager.api; + +public class PinLockMissingException extends Exception {} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java b/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java index 53da2aad..b1a3ad61 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java @@ -1,8 +1,8 @@ package org.asamk.signal.manager.api; +import org.asamk.signal.manager.util.PhoneNumberFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.UuidUtil; import java.util.UUID; @@ -24,32 +24,28 @@ public sealed interface RecipientIdentifier { sealed interface Single extends RecipientIdentifier { static Single fromString(String identifier, String localNumber) throws InvalidNumberException { - try { - if (UuidUtil.isUuid(identifier)) { - return new Uuid(UUID.fromString(identifier)); - } - - if (identifier.startsWith("PNI:")) { - final var pni = identifier.substring(4); - if (!UuidUtil.isUuid(pni)) { - throw new InvalidNumberException("Invalid PNI"); - } - return new Pni(UUID.fromString(pni)); - } - - if (identifier.startsWith("u:")) { - return new Username(identifier.substring(2)); - } - - final var normalizedNumber = PhoneNumberFormatter.formatNumber(identifier, localNumber); - if (!normalizedNumber.equals(identifier)) { - final Logger logger = LoggerFactory.getLogger(RecipientIdentifier.class); - logger.debug("Normalized number {} to {}.", identifier, normalizedNumber); - } - return new Number(normalizedNumber); - } catch (org.whispersystems.signalservice.api.util.InvalidNumberException e) { - throw new InvalidNumberException(e.getMessage(), e); + if (UuidUtil.isUuid(identifier)) { + return new Uuid(UUID.fromString(identifier)); } + + if (identifier.startsWith("PNI:")) { + final var pni = identifier.substring(4); + if (!UuidUtil.isUuid(pni)) { + throw new InvalidNumberException("Invalid PNI"); + } + return new Pni(UUID.fromString(pni)); + } + + if (identifier.startsWith("u:")) { + return new Username(identifier.substring(2)); + } + + final var normalizedNumber = PhoneNumberFormatter.formatNumber(identifier, localNumber); + if (!normalizedNumber.equals(identifier)) { + final Logger logger = LoggerFactory.getLogger(RecipientIdentifier.class); + logger.debug("Normalized number {} to {}.", identifier, normalizedNumber); + } + return new Number(normalizedNumber); } static Single fromAddress(RecipientAddress address) { diff --git a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java index 8e844392..c76da017 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java @@ -2,9 +2,9 @@ package org.asamk.signal.manager.config; import org.signal.libsignal.net.Network.Environment; import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.whispersystems.signalservice.api.push.TrustStore; +import org.whispersystems.signalservice.internal.configuration.HttpProxy; import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl; import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl; import org.whispersystems.signalservice.internal.configuration.SignalProxy; @@ -28,8 +28,9 @@ class LiveConfig { private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder() .decode("BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF"); private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57"; - private static final String SVR2_MRENCLAVE = "9314436a9a144992bb3680770ea5fd7934a7ffd29257844a33763a238903d570"; - private static final String SVR2_LEGACY_MRENCLAVE = "a6622ad4656e1abcd0bc0ff17c229477747d2ded0495c4ebee7ed35c1789fa97"; + private static final String SVR2_MRENCLAVE_LEGACY_LEGACY = "9314436a9a144992bb3680770ea5fd7934a7ffd29257844a33763a238903d570"; + private static final String SVR2_MRENCLAVE_LEGACY = "093be9ea32405e85ae28dbb48eb668aebeb7dbe29517b9b86ad4bec4dfe0e6a6"; + private static final String SVR2_MRENCLAVE = "29cd63c87bea751e3bfd0fbd401279192e2e5c99948b4ee9437eafc4968355fb"; private static final String URL = "https://chat.signal.org"; private static final String CDN_URL = "https://cdn.signal.org"; @@ -42,6 +43,7 @@ class LiveConfig { private static final Optional dns = Optional.empty(); private static final Optional proxy = Optional.empty(); + private static final Optional systemProxy = Optional.empty(); private static final byte[] zkGroupServerPublicParams = Base64.getDecoder() .decode("AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0LUlT9vALgh/f2DPVOOmR0RW6bgRvc7DSF20V/omg+YBw=="); @@ -69,6 +71,7 @@ class LiveConfig { interceptors, dns, proxy, + systemProxy, zkGroupServerPublicParams, genericServerPublicParams, backupServerPublicParams, @@ -77,7 +80,7 @@ class LiveConfig { static ECPublicKey getUnidentifiedSenderTrustRoot() { try { - return Curve.decodePoint(UNIDENTIFIED_SENDER_TRUST_ROOT, 0); + return new ECPublicKey(UNIDENTIFIED_SENDER_TRUST_ROOT); } catch (InvalidKeyException e) { throw new AssertionError(e); } @@ -89,7 +92,7 @@ class LiveConfig { createDefaultServiceConfiguration(interceptors), getUnidentifiedSenderTrustRoot(), CDSI_MRENCLAVE, - List.of(SVR2_MRENCLAVE, SVR2_LEGACY_MRENCLAVE)); + List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_LEGACY, SVR2_MRENCLAVE_LEGACY_LEGACY)); } private LiveConfig() { diff --git a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java index 24365cfe..587fc89b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java @@ -20,7 +20,7 @@ public class ServiceConfig { public static final int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024; public static final long MAX_ENVELOPE_SIZE = 0; - public static final int MAX_MESSAGE_BODY_SIZE = 2000; + public static final int MAX_MESSAGE_SIZE_BYTES = 2000; public static final long AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE = 10 * 1024 * 1024; public static final boolean AUTOMATIC_NETWORK_RETRY = true; public static final int GROUP_MAX_SIZE = 1001; @@ -30,7 +30,8 @@ public class ServiceConfig { public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) { final var deleteSync = !isPrimaryDevice; final var storageEncryptionV2 = !isPrimaryDevice; - return new AccountAttributes.Capabilities(true, deleteSync, true, storageEncryptionV2); + final var attachmentBackfill = !isPrimaryDevice; + return new AccountAttributes.Capabilities(true, deleteSync, true, storageEncryptionV2, attachmentBackfill); } public static ServiceEnvironmentConfig getServiceEnvironmentConfig( diff --git a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java index 741afa16..e25fe21b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java @@ -2,9 +2,9 @@ package org.asamk.signal.manager.config; import org.signal.libsignal.net.Network; import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.whispersystems.signalservice.api.push.TrustStore; +import org.whispersystems.signalservice.internal.configuration.HttpProxy; import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl; import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl; import org.whispersystems.signalservice.internal.configuration.SignalProxy; @@ -28,8 +28,9 @@ class StagingConfig { private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder() .decode("BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx"); private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57"; - private static final String SVR2_MRENCLAVE = "38e01eff4fe357dc0b0e8ef7a44b4abc5489fbccba3a78780f3872c277f62bf3"; - private static final String SVR2_LEGACY_MRENCLAVE = "acb1973aa0bbbd14b3b4e06f145497d948fd4a98efc500fcce363b3b743ec482"; + private static final String SVR2_MRENCLAVE_LEGACY_LEGACY = "38e01eff4fe357dc0b0e8ef7a44b4abc5489fbccba3a78780f3872c277f62bf3"; + private static final String SVR2_MRENCLAVE_LEGACY = "2e8cefe6e3f389d8426adb24e9b7fb7adf10902c96f06f7bbcee36277711ed91"; + private static final String SVR2_MRENCLAVE = "a75542d82da9f6914a1e31f8a7407053b99cc99a0e7291d8fbd394253e19b036"; private static final String URL = "https://chat.staging.signal.org"; private static final String CDN_URL = "https://cdn-staging.signal.org"; @@ -42,6 +43,7 @@ class StagingConfig { private static final Optional dns = Optional.empty(); private static final Optional proxy = Optional.empty(); + private static final Optional systemProxy = Optional.empty(); private static final byte[] zkGroupServerPublicParams = Base64.getDecoder() .decode("ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCNWI+wwkyFCqNDXz/qxl1gAntuCJtSfq9OC3NkdhQlgYQ=="); @@ -69,6 +71,7 @@ class StagingConfig { interceptors, dns, proxy, + systemProxy, zkGroupServerPublicParams, genericServerPublicParams, backupServerPublicParams, @@ -77,7 +80,7 @@ class StagingConfig { static ECPublicKey getUnidentifiedSenderTrustRoot() { try { - return Curve.decodePoint(UNIDENTIFIED_SENDER_TRUST_ROOT, 0); + return new ECPublicKey(UNIDENTIFIED_SENDER_TRUST_ROOT); } catch (InvalidKeyException e) { throw new AssertionError(e); } @@ -89,7 +92,7 @@ class StagingConfig { createDefaultServiceConfiguration(interceptors), getUnidentifiedSenderTrustRoot(), CDSI_MRENCLAVE, - List.of(SVR2_MRENCLAVE, SVR2_LEGACY_MRENCLAVE)); + List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_LEGACY, SVR2_MRENCLAVE_LEGACY_LEGACY)); } private StagingConfig() { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java index 209ca711..de3e2402 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java @@ -4,6 +4,7 @@ import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.DeviceLinkUrl; 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; @@ -32,6 +33,7 @@ import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignedPreKeyEntity; +import org.whispersystems.signalservice.api.push.UsernameLinkComponents; import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException; @@ -50,7 +52,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Objects; -import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; import okio.ByteString; @@ -102,9 +104,9 @@ public class AccountHelper { checkWhoAmiI(); } if (!account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) { - context.getSyncHelper().requestSyncPniIdentity(); + throw new IOException("Missing PNI identity key, relinking required"); } - if (account.getPreviousStorageVersion() < 4 + if (account.getPreviousStorageVersion() < 10 && account.isPrimaryDevice() && account.getRegistrationLockPin() != null) { migrateRegistrationPin(); @@ -184,7 +186,7 @@ public class AccountHelper { String newNumber, String verificationCode, String pin - ) throws IncorrectPinException, PinLockedException, IOException { + ) throws IncorrectPinException, PinLockedException, IOException, PinLockMissingException { for (var attempts = 0; attempts < 5; attempts++) { try { finishChangeNumberInternal(newNumber, verificationCode, pin); @@ -204,7 +206,7 @@ public class AccountHelper { String newNumber, String verificationCode, String pin - ) throws IncorrectPinException, PinLockedException, IOException { + ) throws IncorrectPinException, PinLockedException, IOException, PinLockMissingException { final var pniIdentity = KeyUtils.generateIdentityKeyPair(); final var encryptedDeviceMessages = new ArrayList(); final var devicePniSignedPreKeys = new HashMap(); @@ -289,12 +291,13 @@ public class AccountHelper { context.getPinHelper(), (sessionId1, verificationCode1, registrationLock) -> { final var registrationApi = dependencies.getRegistrationApi(); + final var accountApi = dependencies.getAccountApi(); try { handleResponseException(registrationApi.verifyAccount(sessionId1, verificationCode1)); } catch (AlreadyVerifiedException e) { // Already verified so can continue changing number } - return handleResponseException(registrationApi.changeNumber(new ChangePhoneNumberRequest(sessionId1, + return handleResponseException(accountApi.changeNumber(new ChangePhoneNumberRequest(sessionId1, null, newNumber, registrationLock, @@ -378,7 +381,7 @@ public class AccountHelper { candidateHashes.add(Base64.encodeUrlSafeWithoutPadding(candidate.getHash())); } - final var response = dependencies.getAccountManager().reserveUsername(candidateHashes); + final var response = handleResponseException(dependencies.getAccountApi().reserveUsername(candidateHashes)); final var hashIndex = candidateHashes.indexOf(response.getUsernameHash()); if (hashIndex == -1) { logger.warn("[reserveUsername] The response hash could not be found in our set of candidateHashes."); @@ -388,7 +391,7 @@ public class AccountHelper { logger.debug("[reserveUsername] Successfully reserved username."); final var username = candidates.get(hashIndex); - final var linkComponents = dependencies.getAccountManager().confirmUsernameAndCreateNewLink(username); + final var linkComponents = confirmUsernameAndCreateNewLink(username); account.setUsername(username.getUsername()); account.setUsernameLink(linkComponents); account.getRecipientStore().resolveSelfRecipientTrusted(account.getSelfRecipientAddress()); @@ -396,6 +399,40 @@ public class AccountHelper { logger.debug("[confirmUsername] Successfully confirmed username."); } + public UsernameLinkComponents createUsernameLink(Username username) throws IOException { + try { + Username.UsernameLink link = username.generateLink(); + return handleResponseException(dependencies.getAccountApi().createUsernameLink(link)); + } catch (BaseUsernameException e) { + throw new AssertionError(e); + } + } + + private UsernameLinkComponents confirmUsernameAndCreateNewLink(Username username) throws IOException { + try { + Username.UsernameLink link = username.generateLink(); + UUID serverId = handleResponseException(dependencies.getAccountApi().confirmUsername(username, link)); + + return new UsernameLinkComponents(link.getEntropy(), serverId); + } catch (BaseUsernameException e) { + throw new AssertionError(e); + } + } + + private UsernameLinkComponents reclaimUsernameAndLink( + Username username, + UsernameLinkComponents linkComponents + ) throws IOException { + try { + Username.UsernameLink link = username.generateLink(linkComponents.getEntropy()); + UUID serverId = handleResponseException(dependencies.getAccountApi().confirmUsername(username, link)); + + return new UsernameLinkComponents(link.getEntropy(), serverId); + } catch (BaseUsernameException e) { + throw new AssertionError(e); + } + } + public void refreshCurrentUsername() throws IOException, BaseUsernameException { final var localUsername = account.getUsername(); if (localUsername == null) { @@ -438,14 +475,14 @@ public class AccountHelper { final var usernameLink = account.getUsernameLink(); if (usernameLink == null) { - dependencies.getAccountManager() - .reserveUsername(List.of(Base64.encodeUrlSafeWithoutPadding(username.getHash()))); + handleResponseException(dependencies.getAccountApi() + .reserveUsername(List.of(Base64.encodeUrlSafeWithoutPadding(username.getHash())))); logger.debug("[reserveUsername] Successfully reserved existing username."); - final var linkComponents = dependencies.getAccountManager().confirmUsernameAndCreateNewLink(username); + final var linkComponents = confirmUsernameAndCreateNewLink(username); account.setUsernameLink(linkComponents); logger.debug("[confirmUsername] Successfully confirmed existing username."); } else { - final var linkComponents = dependencies.getAccountManager().reclaimUsernameAndLink(username, usernameLink); + final var linkComponents = reclaimUsernameAndLink(username, usernameLink); account.setUsernameLink(linkComponents); logger.debug("[confirmUsername] Successfully reclaimed existing username and link."); } @@ -455,7 +492,7 @@ public class AccountHelper { private void tryToSetUsernameLink(Username username) { for (var i = 1; i < 4; i++) { try { - final var linkComponents = dependencies.getAccountManager().createUsernameLink(username); + final var linkComponents = createUsernameLink(username); account.setUsernameLink(linkComponents); break; } catch (IOException e) { @@ -465,9 +502,8 @@ public class AccountHelper { } public void deleteUsername() throws IOException { - dependencies.getAccountManager().deleteUsernameLink(); + handleResponseException(dependencies.getAccountApi().deleteUsername()); account.setUsernameLink(null); - dependencies.getAccountManager().deleteUsername(); account.setUsername(null); logger.debug("[deleteUsername] Successfully deleted the username."); } @@ -479,7 +515,7 @@ public class AccountHelper { } public void updateAccountAttributes() throws IOException { - dependencies.getAccountManager().setAccountAttributes(account.getAccountAttributes(null)); + handleResponseException(dependencies.getAccountApi().setAccountAttributes(account.getAccountAttributes(null))); } public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, org.asamk.signal.manager.api.DeviceLimitExceededException { @@ -500,9 +536,9 @@ public class AccountHelper { account.getAciIdentityKeyPair(), account.getPniIdentityKeyPair(), account.getProfileKey(), + account.getOrCreateAccountEntropyPool(), account.getOrCreatePinMasterKey(), account.getOrCreateMediaRootBackupKey(), - account.getOrCreateAccountEntropyPool(), verificationCode.getVerificationCode(), null)); account.setMultiDevice(true); @@ -510,8 +546,8 @@ public class AccountHelper { } public void removeLinkedDevices(int deviceId) throws IOException { - dependencies.getAccountManager().removeDevice(deviceId); - var devices = dependencies.getAccountManager().getDevices(); + handleResponseException(dependencies.getLinkDeviceApi().removeDevice(deviceId)); + var devices = handleResponseException(dependencies.getLinkDeviceApi().getDevices()); account.setMultiDevice(devices.size() > 1); } @@ -519,14 +555,16 @@ public class AccountHelper { var masterKey = account.getOrCreatePinMasterKey(); context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey); - dependencies.getAccountManager().enableRegistrationLock(masterKey); + handleResponseException(dependencies.getAccountApi() + .enableRegistrationLock(masterKey.deriveRegistrationLock())); } public void setRegistrationPin(String pin) throws IOException { var masterKey = account.getOrCreatePinMasterKey(); context.getPinHelper().setRegistrationLockPin(pin, masterKey); - dependencies.getAccountManager().enableRegistrationLock(masterKey); + handleResponseException(dependencies.getAccountApi() + .enableRegistrationLock(masterKey.deriveRegistrationLock())); account.setRegistrationLockPin(pin); updateAccountAttributes(); @@ -535,7 +573,7 @@ public class AccountHelper { public void removeRegistrationPin() throws IOException { // Remove KBS Pin context.getPinHelper().removeRegistrationLockPin(); - dependencies.getAccountManager().disableRegistrationLock(); + handleResponseException(dependencies.getAccountApi().disableRegistrationLock()); account.setRegistrationLockPin(null); } @@ -544,7 +582,7 @@ public class AccountHelper { // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false. // If this is the primary device, other users can't send messages to this number anymore. // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore. - dependencies.getAccountManager().setGcmId(Optional.empty()); + handleResponseException(dependencies.getAccountApi().clearFcmToken()); account.setRegistered(false); unregisteredListener.call(); @@ -558,7 +596,7 @@ public class AccountHelper { } account.setRegistrationLockPin(null); - dependencies.getAccountManager().deleteAccount(); + handleResponseException(dependencies.getAccountApi().deleteAccount()); account.setRegistered(false); unregisteredListener.call(); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java index 0ffa62ed..08526c72 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java @@ -9,6 +9,7 @@ import org.asamk.signal.manager.util.IOUtils; import org.signal.libsignal.protocol.InvalidMessageException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; @@ -44,14 +45,20 @@ public class AttachmentHelper { } public List uploadAttachments(final List attachments) throws AttachmentInvalidException, IOException { - var attachmentStreams = createAttachmentStreams(attachments); + final var attachmentStreams = createAttachmentStreams(attachments); - // Upload attachments here, so we only upload once even for multiple recipients - var attachmentPointers = new ArrayList(attachmentStreams.size()); - for (var attachmentStream : attachmentStreams) { - attachmentPointers.add(uploadAttachment(attachmentStream)); + try { + // Upload attachments here, so we only upload once even for multiple recipients + final var attachmentPointers = new ArrayList(attachmentStreams.size()); + for (final var attachmentStream : attachmentStreams) { + attachmentPointers.add(uploadAttachment(attachmentStream)); + } + return attachmentPointers; + } finally { + for (final var attachmentStream : attachmentStreams) { + attachmentStream.close(); + } } - return attachmentPointers; } private List createAttachmentStreams(List attachments) throws AttachmentInvalidException, IOException { @@ -132,9 +139,15 @@ public class AttachmentHelper { SignalServiceAttachmentPointer pointer, File tmpFile ) throws IOException { + if (pointer.getDigest().isEmpty()) { + throw new IOException("Attachment pointer has no digest."); + } try { return dependencies.getMessageReceiver() - .retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE); + .retrieveAttachment(pointer, + tmpFile, + ServiceConfig.MAX_ATTACHMENT_SIZE, + AttachmentCipherInputStream.IntegrityCheck.forEncryptedDigest(pointer.getDigest().get())); } catch (MissingConfigurationException | InvalidMessageException e) { throw new IOException(e); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java index fafb9ffe..8461fbe0 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java @@ -17,7 +17,14 @@ public class ContactHelper { return sourceContact != null && sourceContact.isBlocked(); } - public void setContactName(final RecipientId recipientId, final String givenName, final String familyName) { + public void setContactName( + final RecipientId recipientId, + final String givenName, + final String familyName, + final String nickGivenName, + final String nickFamilyName, + final String note + ) { var contact = account.getContactStore().getContact(recipientId); final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); builder.withIsHidden(false); @@ -27,6 +34,15 @@ public class ContactHelper { if (familyName != null) { builder.withFamilyName(familyName); } + if (nickGivenName != null) { + builder.withNickNameGivenName(nickGivenName); + } + if (nickFamilyName != null) { + builder.withNickNameFamilyName(nickFamilyName); + } + if (note != null) { + builder.withNote(note); + } account.getContactStore().storeContact(recipientId, builder.build()); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index 0d838204..682bd996 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -551,6 +551,9 @@ public class GroupHelper { while (true) { final var page = context.getGroupV2Helper() .getDecryptedGroupHistoryPage(groupSecretParams, fromRevision, sendEndorsementsExpirationMs); + if (page == null) { + break; + } page.getChangeLogs() .stream() .map(DecryptedGroupChangeLog::getChange) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java index a3330d5c..8eb66843 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java @@ -28,6 +28,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.groupsv2.DecryptChangeVerificationMode; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupResponse; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; import org.whispersystems.signalservice.api.groupsv2.GroupCandidate; @@ -43,6 +44,7 @@ import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.util.UuidUtil; +import org.whispersystems.signalservice.internal.push.exceptions.NotInGroupException; import java.io.IOException; import java.util.ArrayList; @@ -118,6 +120,8 @@ class GroupV2Helper { groupsV2AuthorizationString, false, sendEndorsementsExpirationMs); + } catch (NotInGroupException e) { + throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null); } catch (NonSuccessfulResponseCodeException e) { if (e.code == 403) { throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null); @@ -652,11 +656,13 @@ class GroupV2Helper { DecryptedGroupChange getDecryptedGroupChange(byte[] signedGroupChange, GroupMasterKey groupMasterKey) { if (signedGroupChange != null) { - var groupOperations = dependencies.getGroupsV2Operations() - .forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey)); + final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); + final var groupOperations = dependencies.getGroupsV2Operations().forGroup(groupSecretParams); + final var groupId = groupSecretParams.getPublicParams().getGroupIdentifier(); try { - return groupOperations.decryptChange(GroupChange.ADAPTER.decode(signedGroupChange), true).orElse(null); + return groupOperations.decryptChange(GroupChange.ADAPTER.decode(signedGroupChange), + DecryptChangeVerificationMode.verify(groupId)).orElse(null); } catch (VerificationFailedException | InvalidGroupStateException | IOException e) { return null; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 5deffb3e..dd9cb38f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -41,6 +41,7 @@ import org.signal.libsignal.metadata.ProtocolNoSessionException; import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; import org.signal.libsignal.metadata.SelfSendException; import org.signal.libsignal.protocol.InvalidMessageException; +import org.signal.libsignal.protocol.UsePqRatchet; import org.signal.libsignal.protocol.groups.GroupSessionBuilder; import org.signal.libsignal.protocol.message.DecryptionErrorMessage; import org.signal.libsignal.zkgroup.InvalidInputException; @@ -105,7 +106,7 @@ public final class IncomingMessageHandler { try { final var cipherResult = dependencies.getCipher(destination == null || destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI) - .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp()); + .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp(), UsePqRatchet.NO); content = validate(envelope.getProto(), cipherResult, envelope.getServerDeliveredTimestamp()); if (content == null) { return new Pair<>(List.of(), null); @@ -143,7 +144,7 @@ public final class IncomingMessageHandler { try { final var cipherResult = dependencies.getCipher(destination == null || destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI) - .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp()); + .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp(), UsePqRatchet.NO); content = validate(envelope.getProto(), cipherResult, envelope.getServerDeliveredTimestamp()); if (content == null) { return new Pair<>(List.of(), null); @@ -157,6 +158,9 @@ public final class IncomingMessageHandler { } catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolNoSessionException | ProtocolInvalidMessageException e) { logger.debug("Failed to decrypt incoming message", e); + if (e instanceof ProtocolInvalidKeyIdException) { + actions.add(RefreshPreKeysAction.create()); + } final var sender = account.getRecipientResolver().resolveRecipient(e.getSender()); if (context.getContactHelper().isContactBlocked(sender)) { logger.debug("Received invalid message from blocked contact, ignoring."); @@ -165,12 +169,11 @@ public final class IncomingMessageHandler { if (serviceId != null) { final var isSelf = sender.equals(account.getSelfRecipientId()) && e.getSenderDevice() == account.getDeviceId(); + logger.debug("Received invalid message, queuing renew session action."); + actions.add(new RenewSessionAction(sender, serviceId, destination)); if (!isSelf) { logger.debug("Received invalid message, requesting message resend."); - actions.add(new SendRetryMessageRequestAction(sender, serviceId, e, envelope, destination)); - } else { - logger.debug("Received invalid message, queuing renew session action."); - actions.add(new RenewSessionAction(sender, serviceId, destination)); + actions.add(new SendRetryMessageRequestAction(sender, e, envelope)); } } else { logger.debug("Received invalid message from invalid sender: {}", e.getSender()); @@ -962,7 +965,7 @@ public final class IncomingMessageHandler { private DeviceAddress getDestination(SignalServiceEnvelope envelope) { final var destination = envelope.getDestinationServiceId(); - if (destination == null) { + if (destination == null || destination.isUnknown()) { return new DeviceAddress(account.getSelfRecipientId(), account.getAci(), account.getDeviceId()); } return new DeviceAddress(account.getRecipientResolver().resolveRecipient(destination), diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java index a95635cf..40878647 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java @@ -88,7 +88,11 @@ public class PinHelper { IOException exception = null; for (final var secureValueRecovery : secureValueRecoveries) { try { - return getRegistrationLockData(secureValueRecovery, svr2Credentials, pin); + final var lockData = getRegistrationLockData(secureValueRecovery, svr2Credentials, pin); + if (lockData == null) { + continue; + } + return lockData; } catch (IOException e) { exception = e; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java index b17a9206..bf7ad580 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java @@ -11,17 +11,19 @@ import org.signal.libsignal.protocol.state.PreKeyRecord; import org.signal.libsignal.protocol.state.SignedPreKeyRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.NetworkResultUtil; import org.whispersystems.signalservice.api.account.PreKeyUpload; +import org.whispersystems.signalservice.api.keys.OneTimePreKeyCounts; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.internal.push.OneTimePreKeyCounts; import java.io.IOException; import java.util.List; import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_STALE_AGE; import static org.asamk.signal.manager.config.ServiceConfig.SIGNED_PREKEY_ROTATE_AGE; +import static org.asamk.signal.manager.util.Utils.handleResponseException; public class PreKeyHelper { @@ -82,7 +84,7 @@ public class PreKeyHelper { ) throws IOException { OneTimePreKeyCounts preKeyCounts; try { - preKeyCounts = dependencies.getAccountManager().getPreKeyCounts(serviceIdType); + preKeyCounts = handleResponseException(dependencies.getKeysApi().getAvailablePreKeyCounts(serviceIdType)); } catch (AuthorizationFailedException e) { logger.debug("Failed to get pre key count, ignoring: " + e.getClass().getSimpleName()); preKeyCounts = new OneTimePreKeyCounts(0, 0); @@ -143,7 +145,7 @@ public class PreKeyHelper { kyberPreKeyRecords); var needsReset = false; try { - dependencies.getAccountManager().setPreKeys(preKeyUpload); + NetworkResultUtil.toPreKeysLegacy(dependencies.getKeysApi().setPreKeys(preKeyUpload)); try { if (preKeyRecords != null) { account.addPreKeys(serviceIdType, preKeyRecords); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java index 0905bb30..af16ed1d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java @@ -23,6 +23,7 @@ import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.NetworkResultUtil; import org.whispersystems.signalservice.api.crypto.SealedSenderAccess; import org.whispersystems.signalservice.api.profiles.AvatarUploadParams; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; @@ -196,9 +197,10 @@ public final class ProfileHelper { : avatar == null ? AvatarUploadParams.unchanged(true) : AvatarUploadParams.unchanged(false); final var paymentsAddress = Optional.ofNullable(newProfile.getMobileCoinAddress()) .map(address -> PaymentUtils.signPaymentsAddress(address, - account.getAciIdentityKeyPair().getPrivateKey())); + account.getAciIdentityKeyPair().getPrivateKey())) + .orElse(null); logger.debug("Uploading new profile"); - final var avatarPath = dependencies.getAccountManager() + final var avatarPath = NetworkResultUtil.toSetProfileLegacy(dependencies.getProfileApi() .setVersionedProfile(account.getAci(), account.getProfileKey(), newProfile.getInternalServiceName(), @@ -208,9 +210,9 @@ public final class ProfileHelper { avatarUploadParams, List.of(/* TODO implement support for badges */), account.getConfigurationStore().getPhoneNumberSharingMode() - == PhoneNumberSharingMode.EVERYBODY); + == PhoneNumberSharingMode.EVERYBODY)); if (!avatarUploadParams.keepTheSame) { - builder.withAvatarUrlPath(avatarPath.orElse(null)); + builder.withAvatarUrlPath(avatarPath); } newProfile = builder.build(); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java index ab75bd16..71f69081 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java @@ -11,10 +11,10 @@ import org.asamk.signal.manager.storage.messageCache.CachedMessage; import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.SignalWebSocket; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI; +import org.whispersystems.signalservice.api.websocket.SignalWebSocket; import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState; import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException; @@ -28,7 +28,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeoutException; -import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.schedulers.Schedulers; public class ReceiveHelper { @@ -94,14 +93,14 @@ public class ReceiveHelper { // Use a Map here because java Set doesn't have a get method ... Map queuedActions = new HashMap<>(); - final var signalWebSocket = dependencies.getSignalWebSocket(); - final var webSocketStateDisposable = Observable.merge(signalWebSocket.getUnidentifiedWebSocketState(), - signalWebSocket.getWebSocketState()) + final var signalWebSocket = dependencies.getAuthenticatedSignalWebSocket(); + final var webSocketStateDisposable = signalWebSocket.getState() .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.computation()) .distinctUntilChanged() .subscribe(this::onWebSocketStateChange); signalWebSocket.connect(); + signalWebSocket.registerKeepAliveToken("receive"); try { receiveMessagesInternal(signalWebSocket, timeout, returnOnTimeout, maxMessages, handler, queuedActions); @@ -109,6 +108,7 @@ public class ReceiveHelper { hasCaughtUpWithOldMessages = false; handleQueuedActions(queuedActions.keySet()); queuedActions.clear(); + signalWebSocket.removeKeepAliveToken("receive"); signalWebSocket.disconnect(); webSocketStateDisposable.dispose(); shouldStop = false; @@ -116,7 +116,7 @@ public class ReceiveHelper { } private void receiveMessagesInternal( - final SignalWebSocket signalWebSocket, + final SignalWebSocket.AuthenticatedWebSocket signalWebSocket, Duration timeout, boolean returnOnTimeout, Integer maxMessages, diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java index 125fa0d8..a73bb741 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java @@ -10,13 +10,14 @@ import org.signal.libsignal.usernames.BaseUsernameException; import org.signal.libsignal.usernames.Username; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.cds.CdsiV2Service; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidArgumentException; import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException; -import org.whispersystems.signalservice.api.services.CdsiV2Service; +import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import java.io.IOException; import java.util.Collection; @@ -25,8 +26,10 @@ import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.UUID; import static org.asamk.signal.manager.config.ServiceConfig.MAXIMUM_ONE_OFF_REQUEST_SIZE; +import static org.asamk.signal.manager.util.Utils.handleResponseException; public class RecipientHelper { @@ -66,7 +69,7 @@ public class RecipientHelper { .toSignalServiceAddress(); } - public Set resolveRecipients(Collection recipients) throws UnregisteredRecipientException { + public Set resolveRecipients(Collection recipients) throws UnregisteredRecipientException, IOException { final var recipientIds = new HashSet(recipients.size()); for (var number : recipients) { final var recipientId = resolveRecipient(number); @@ -76,12 +79,11 @@ public class RecipientHelper { } public RecipientId resolveRecipient(final RecipientIdentifier.Single recipient) throws UnregisteredRecipientException { - if (recipient instanceof RecipientIdentifier.Uuid uuidRecipient) { - return account.getRecipientResolver().resolveRecipient(ACI.from(uuidRecipient.uuid())); - } else if (recipient instanceof RecipientIdentifier.Pni pniRecipient) { - return account.getRecipientResolver().resolveRecipient(PNI.from(pniRecipient.pni())); - } else if (recipient instanceof RecipientIdentifier.Number numberRecipient) { - final var number = numberRecipient.number(); + if (recipient instanceof RecipientIdentifier.Uuid(UUID uuid)) { + return account.getRecipientResolver().resolveRecipient(ACI.from(uuid)); + } else if (recipient instanceof RecipientIdentifier.Pni(UUID pni)) { + return account.getRecipientResolver().resolveRecipient(PNI.from(pni)); + } else if (recipient instanceof RecipientIdentifier.Number(String number)) { return account.getRecipientStore().resolveRecipientByNumber(number, () -> { try { return getRegisteredUserByNumber(number); @@ -89,9 +91,12 @@ public class RecipientHelper { return null; } }); - } else if (recipient instanceof RecipientIdentifier.Username usernameRecipient) { - var username = usernameRecipient.username(); - return resolveRecipientByUsernameOrLink(username, false); + } else if (recipient instanceof RecipientIdentifier.Username(String username)) { + try { + return resolveRecipientByUsernameOrLink(username, false); + } catch (Exception e) { + return null; + } } throw new AssertionError("Unexpected RecipientIdentifier: " + recipient); } @@ -99,7 +104,7 @@ public class RecipientHelper { public RecipientId resolveRecipientByUsernameOrLink( String username, boolean forceRefresh - ) throws UnregisteredRecipientException { + ) throws UnregisteredRecipientException, IOException { final Username finalUsername; try { finalUsername = getUsernameFromUsernameOrLink(username); @@ -108,18 +113,22 @@ public class RecipientHelper { } if (forceRefresh) { try { - final var aci = dependencies.getAccountManager().getAciByUsername(finalUsername); + final var aci = handleResponseException(dependencies.getUsernameApi().getAciByUsername(finalUsername)); return account.getRecipientStore().resolveRecipientTrusted(aci, finalUsername.getUsername()); - } catch (IOException e) { - throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, - null, - null, - username)); + } catch (NonSuccessfulResponseCodeException e) { + if (e.code == 404) { + throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, + null, + null, + username)); + } + logger.debug("Failed to get uuid for username: {}", username, e); + throw e; } } return account.getRecipientStore().resolveRecipientByUsername(finalUsername.getUsername(), () -> { try { - return dependencies.getAccountManager().getAciByUsername(finalUsername); + return handleResponseException(dependencies.getUsernameApi().getAciByUsername(finalUsername)); } catch (Exception e) { return null; } @@ -130,8 +139,8 @@ public class RecipientHelper { try { final var usernameLinkUrl = UsernameLinkUrl.fromUri(username); final var components = usernameLinkUrl.getComponents(); - final var encryptedUsername = dependencies.getAccountManager() - .getEncryptedUsernameFromLinkServerId(components.getServerId()); + final var encryptedUsername = handleResponseException(dependencies.getUsernameApi() + .getEncryptedUsernameFromLinkServerId(components.getServerId())); final var link = new Username.UsernameLink(components.getEntropy(), encryptedUsername); return Username.fromLink(link); @@ -144,8 +153,8 @@ public class RecipientHelper { try { return Optional.of(resolveRecipient(recipient)); } catch (UnregisteredRecipientException e) { - if (recipient instanceof RecipientIdentifier.Number r) { - return account.getRecipientStore().resolveRecipientByNumberOptional(r.number()); + if (recipient instanceof RecipientIdentifier.Number(String number)) { + return account.getRecipientStore().resolveRecipientByNumberOptional(number); } else { return Optional.empty(); } @@ -234,8 +243,8 @@ public class RecipientHelper { final CdsiV2Service.Response response; try { - response = dependencies.getAccountManager() - .getRegisteredUsersWithCdsi(token.isEmpty() ? Set.of() : previousNumbers, + response = handleResponseException(dependencies.getCdsApi() + .getRegisteredUsers(token.isEmpty() ? Set.of() : previousNumbers, newNumbers, account.getRecipientStore().getServiceIdToProfileKeyMap(), token, @@ -256,7 +265,7 @@ public class RecipientHelper { account.setCdsiToken(newToken); account.setLastRecipientsRefresh(System.currentTimeMillis()); } - }); + })); } catch (CdsiInvalidTokenException | CdsiInvalidArgumentException e) { account.setCdsiToken(null); account.getCdsiStore().clearAll(); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java index 740c0b5e..14db7c13 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java @@ -198,17 +198,6 @@ public class StorageHelper { logger.debug("Pre-Merge ID Difference :: {}", idDifference); - if (!idDifference.localOnlyIds().isEmpty()) { - final var updated = account.getRecipientStore() - .removeStorageIdsFromLocalOnlyUnregisteredRecipients(connection, idDifference.localOnlyIds()); - - if (updated > 0) { - logger.warn( - "Found {} records that were deleted remotely but only marked unregistered locally. Removed those from local store.", - updated); - } - } - if (!idDifference.isEmpty()) { final var remoteOnlyRecords = getSignalStorageRecords(storageKey, remoteManifest, @@ -221,6 +210,18 @@ public class StorageHelper { remoteOnlyRecords.size()); } + if (!idDifference.localOnlyIds().isEmpty()) { + final var updated = account.getRecipientStore() + .removeStorageIdsFromLocalOnlyUnregisteredRecipients(connection, + idDifference.localOnlyIds()); + + if (updated > 0) { + logger.warn( + "Found {} records that were deleted remotely but only marked unregistered locally. Removed those from local store.", + updated); + } + } + final var unknownInserts = processKnownRecords(connection, remoteOnlyRecords); final var unknownDeletes = idDifference.localOnlyIds() .stream() @@ -278,10 +279,22 @@ public class StorageHelper { try (final var connection = account.getAccountDatabase().getConnection()) { connection.setAutoCommit(false); - final var localStorageIds = getAllLocalStorageIds(connection); - final var idDifference = findIdDifference(remoteManifest.storageIds, localStorageIds); + var localStorageIds = getAllLocalStorageIds(connection); + var idDifference = findIdDifference(remoteManifest.storageIds, localStorageIds); logger.debug("ID Difference :: {}", idDifference); + final var unknownOnlyLocal = idDifference.localOnlyIds() + .stream() + .filter(id -> !KNOWN_TYPES.contains(id.getType())) + .toList(); + + if (!unknownOnlyLocal.isEmpty()) { + logger.debug("Storage ids with unknown type: {} to delete", unknownOnlyLocal.size()); + account.getUnknownStorageIdStore().deleteUnknownStorageIds(connection, unknownOnlyLocal); + localStorageIds = getAllLocalStorageIds(connection); + idDifference = findIdDifference(remoteManifest.storageIds, localStorageIds); + } + final var remoteDeletes = idDifference.remoteOnlyIds().stream().map(StorageId::getRaw).toList(); final var remoteInserts = buildLocalStorageRecords(connection, idDifference.localOnlyIds()); // TODO check if local storage record proto matches remote, then reset to remote storage_id @@ -352,7 +365,8 @@ public class StorageHelper { final var storageId = newContactStorageIds.get(recipientId); if (storageId.getType() == ManifestRecord.Identifier.Type.ACCOUNT.getValue()) { final var recipient = account.getRecipientStore().getRecipient(connection, recipientId); - final var accountRecord = StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(), + final var accountRecord = StorageSyncModels.localToRemoteRecord(connection, + account.getConfigurationStore(), recipient, account.getUsernameLink()); newStorageRecords.add(new SignalStorageRecord(storageId, @@ -550,7 +564,8 @@ public class StorageHelper { final var selfRecipient = account.getRecipientStore() .getRecipient(connection, account.getSelfRecipientId()); - final var record = StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(), + final var record = StorageSyncModels.localToRemoteRecord(connection, + account.getConfigurationStore(), selfRecipient, account.getUsernameLink()); yield new SignalStorageRecord(storageId, new StorageRecord.Builder().account(record).build()); @@ -592,7 +607,7 @@ public class StorageHelper { final var remote = remoteByRawId.get(rawId); final var local = localByRawId.get(rawId); - if (remote.getType() != local.getType() && local.getType() != 0) { + if (remote.getType() != local.getType() && KNOWN_TYPES.contains(local.getType())) { remoteOnlyRawIds.remove(rawId); localOnlyRawIds.remove(rawId); hasTypeMismatch = true; diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java index 39726df8..108ba1a4 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java @@ -70,17 +70,12 @@ public class SyncHelper { requestSyncData(SyncMessage.Request.Type.BLOCKED); requestSyncData(SyncMessage.Request.Type.CONFIGURATION); requestSyncKeys(); - requestSyncPniIdentity(); } public void requestSyncKeys() { requestSyncData(SyncMessage.Request.Type.KEYS); } - public void requestSyncPniIdentity() { - requestSyncData(SyncMessage.Request.Type.PNI_IDENTITY); - } - public SendMessageResult sendSyncFetchProfileMessage() { return context.getSendHelper() .sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE)); @@ -165,7 +160,7 @@ public class SyncHelper { final var contact = contactPair.second(); final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId); - final var deviceContact = getDeviceContact(address, recipientId, contact); + final var deviceContact = getDeviceContact(address, contact); out.write(deviceContact); deviceContact.getAvatar().ifPresent(a -> { try { @@ -180,7 +175,7 @@ public class SyncHelper { final var address = account.getSelfRecipientAddress(); final var recipientId = account.getSelfRecipientId(); final var contact = account.getContactStore().getContact(recipientId); - final var deviceContact = getDeviceContact(address, recipientId, contact); + final var deviceContact = getDeviceContact(address, contact); out.write(deviceContact); deviceContact.getAvatar().ifPresent(a -> { try { @@ -216,34 +211,14 @@ public class SyncHelper { } @NotNull - private DeviceContact getDeviceContact( - final RecipientAddress address, - final RecipientId recipientId, - final Contact contact - ) throws IOException { - var currentIdentity = address.serviceId().isEmpty() - ? null - : account.getIdentityKeyStore().getIdentityInfo(address.serviceId().get()); - VerifiedMessage verifiedMessage = null; - if (currentIdentity != null) { - verifiedMessage = new VerifiedMessage(address.toSignalServiceAddress(), - currentIdentity.getIdentityKey(), - currentIdentity.getTrustLevel().toVerifiedState(), - currentIdentity.getDateAddedTimestamp()); - } - - var profileKey = account.getProfileStore().getProfileKey(recipientId); + private DeviceContact getDeviceContact(final RecipientAddress address, final Contact contact) throws IOException { return new DeviceContact(address.aci(), address.number(), Optional.ofNullable(contact == null ? null : contact.getName()), createContactAvatarAttachment(address), - Optional.ofNullable(contact == null ? null : contact.color()), - Optional.ofNullable(verifiedMessage), - Optional.ofNullable(profileKey), Optional.ofNullable(contact == null ? null : contact.messageExpirationTime()), Optional.ofNullable(contact == null ? null : contact.messageExpirationTimeVersion()), - Optional.empty(), - contact != null && contact.isArchived()); + Optional.empty()); } public SendMessageResult sendBlockedList() { @@ -366,7 +341,7 @@ public class SyncHelper { c = s.read(); } catch (IOException e) { if (e.getMessage() != null && e.getMessage().contains("Missing contact address!")) { - logger.warn("Sync contacts contained invalid contact, ignoring: {}", e.getMessage()); + logger.debug("Sync contacts contained invalid contact, ignoring: {}", e.getMessage()); continue; } else { throw e; @@ -376,9 +351,6 @@ public class SyncHelper { break; } final var address = new RecipientAddress(c.getAci(), Optional.empty(), c.getE164(), Optional.empty()); - if (address.matches(account.getSelfRecipientAddress()) && c.getProfileKey().isPresent()) { - account.setProfileKey(c.getProfileKey().get()); - } final var recipientId = account.getRecipientTrustedResolver().resolveRecipientTrusted(address); var contact = account.getContactStore().getContact(recipientId); final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); @@ -390,19 +362,6 @@ public class SyncHelper { builder.withGivenName(c.getName().get()); builder.withFamilyName(null); } - if (c.getColor().isPresent()) { - builder.withColor(c.getColor().get()); - } - if (c.getProfileKey().isPresent()) { - account.getProfileStore().storeProfileKey(recipientId, c.getProfileKey().get()); - } - if (c.getVerified().isPresent()) { - final var verifiedMessage = c.getVerified().get(); - account.getIdentityKeyStore() - .setIdentityTrustLevel(verifiedMessage.getDestination().getServiceId(), - verifiedMessage.getIdentityKey(), - TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); - } if (c.getExpirationTimer().isPresent()) { if (c.getExpirationTimerVersion().isPresent() && ( contact == null || c.getExpirationTimerVersion().get() > contact.messageExpirationTimeVersion() @@ -417,7 +376,6 @@ public class SyncHelper { contact == null ? 1 : contact.messageExpirationTimeVersion()); } } - builder.withIsArchived(c.isArchived()); account.getContactStore().storeContact(recipientId, builder.build()); if (c.getAvatar().isPresent()) { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java index fb39789e..cd2719a1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java @@ -18,6 +18,8 @@ import java.io.IOException; import java.util.List; import java.util.concurrent.TimeUnit; +import static org.asamk.signal.manager.util.Utils.handleResponseException; + public class UnidentifiedAccessHelper { private static final Logger logger = LoggerFactory.getLogger(UnidentifiedAccessHelper.class); @@ -109,7 +111,8 @@ public class UnidentifiedAccessHelper { return privacySenderCertificate.getSerialized(); } try { - final var certificate = dependencies.getAccountManager().getSenderCertificateForPhoneNumberPrivacy(); + final var certificate = handleResponseException(dependencies.getCertificateApi() + .getSenderCertificateForPhoneNumberPrivacy()); privacySenderCertificate = new SenderCertificate(certificate); return certificate; } catch (IOException | InvalidCertificateException e) { @@ -125,7 +128,7 @@ public class UnidentifiedAccessHelper { return senderCertificate.getSerialized(); } try { - final var certificate = dependencies.getAccountManager().getSenderCertificate(); + final var certificate = handleResponseException(dependencies.getCertificateApi().getSenderCertificate()); this.senderCertificate = new SenderCertificate(certificate); return certificate; } catch (IOException | InvalidCertificateException e) { diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java index dcdead73..726d0aaf 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java @@ -35,6 +35,7 @@ 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.InvalidNumberException; import org.asamk.signal.manager.api.InvalidStickerException; import org.asamk.signal.manager.api.InvalidUsernameException; import org.asamk.signal.manager.api.LastGroupAdminException; @@ -47,6 +48,7 @@ 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.PhoneNumberSharingMode; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.Profile; import org.asamk.signal.manager.api.RateLimitException; @@ -68,7 +70,6 @@ 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.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.helper.AccountFileUpdater; import org.asamk.signal.manager.helper.Context; @@ -88,12 +89,12 @@ import org.asamk.signal.manager.storage.stickers.StickerPack; import org.asamk.signal.manager.util.AttachmentUtils; import org.asamk.signal.manager.util.KeyUtils; import org.asamk.signal.manager.util.MimeUtils; +import org.asamk.signal.manager.util.PhoneNumberFormatter; import org.asamk.signal.manager.util.StickerUtils; import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.usernames.BaseUsernameException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServicePreview; @@ -107,8 +108,6 @@ import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhauste import org.whispersystems.signalservice.api.push.exceptions.UsernameMalformedException; import org.whispersystems.signalservice.api.push.exceptions.UsernameTakenException; import org.whispersystems.signalservice.api.util.DeviceNameUtil; -import org.whispersystems.signalservice.api.util.InvalidNumberException; -import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.StreamDetails; import org.whispersystems.signalservice.internal.util.Hex; import org.whispersystems.signalservice.internal.util.Util; @@ -133,13 +132,18 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.schedulers.Schedulers; +import okio.Utf8; + +import static org.asamk.signal.manager.config.ServiceConfig.MAX_MESSAGE_SIZE_BYTES; +import static org.asamk.signal.manager.util.Utils.handleResponseException; +import static org.signal.core.util.StringExtensionsKt.splitByByteLength; public class ManagerImpl implements Manager { @@ -158,6 +162,7 @@ public class ManagerImpl implements Manager { private final List closedListeners = new ArrayList<>(); private final List addressChangedListeners = new ArrayList<>(); private final CompositeDisposable disposable = new CompositeDisposable(); + private final AtomicLong lastMessageTimestamp = new AtomicLong(); public ManagerImpl( SignalAccount account, @@ -168,15 +173,7 @@ public class ManagerImpl implements Manager { ) { this.account = account; - final var sessionLock = new SignalSessionLock() { - private final ReentrantLock LEGACY_LOCK = new ReentrantLock(); - - @Override - public Lock acquire() { - LEGACY_LOCK.lock(); - return LEGACY_LOCK::unlock; - } - }; + final var sessionLock = new ReentrantSignalSessionLock(); this.dependencies = new SignalDependencies(serviceEnvironmentConfig, userAgent, account.getCredentialsProvider(), @@ -288,7 +285,7 @@ public class ManagerImpl implements Manager { } @Override - public Map getUsernameStatus(Set usernames) { + public Map getUsernameStatus(Set usernames) throws IOException { final var registeredUsers = new HashMap(); for (final var username : usernames) { try { @@ -432,7 +429,7 @@ public class ManagerImpl implements Manager { String newNumber, String verificationCode, String pin - ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException { + ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException, PinLockMissingException { if (!account.isPrimaryDevice()) { throw new NotPrimaryDeviceException(); } @@ -454,10 +451,10 @@ public class ManagerImpl implements Manager { String challenge, String captcha ) throws IOException, CaptchaRejectedException { - captcha = captcha == null ? null : captcha.replace("signalcaptcha://", ""); + captcha = captcha == null ? "" : captcha.replace("signalcaptcha://", ""); try { - dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha); + handleResponseException(dependencies.getRateLimitChallengeApi().submitCaptchaChallenge(challenge, captcha)); } catch (org.whispersystems.signalservice.internal.push.exceptions.CaptchaRejectedException ignored) { throw new CaptchaRejectedException(); } @@ -465,7 +462,7 @@ public class ManagerImpl implements Manager { @Override public List getLinkedDevices() throws IOException { - var devices = dependencies.getAccountManager().getDevices(); + var devices = handleResponseException(dependencies.getLinkDeviceApi().getDevices()); account.setMultiDevice(devices.size() > 1); var identityKey = account.getAciIdentityKeyPair().getPrivateKey(); return devices.stream().map(d -> { @@ -604,6 +601,24 @@ public class ManagerImpl implements Manager { return context.getGroupHelper().joinGroup(inviteLinkUrl); } + private long getNextMessageTimestamp() { + while (true) { + final var last = lastMessageTimestamp.get(); + final var timestamp = System.currentTimeMillis(); + if (last == timestamp) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + continue; + } + if (lastMessageTimestamp.compareAndSet(last, timestamp)) { + return timestamp; + } + } + } + private SendMessageResults sendMessage( SignalServiceDataMessage.Builder messageBuilder, Set recipients, @@ -619,7 +634,7 @@ public class ManagerImpl implements Manager { Optional editTargetTimestamp ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { var results = new HashMap>(); - long timestamp = System.currentTimeMillis(); + long timestamp = getNextMessageTimestamp(); messageBuilder.withTimestamp(timestamp); for (final var recipient : recipients) { if (recipient instanceof RecipientIdentifier.NoteToSelf || ( @@ -659,7 +674,7 @@ public class ManagerImpl implements Manager { Set recipients ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { var results = new HashMap>(); - final var timestamp = System.currentTimeMillis(); + final var timestamp = getNextMessageTimestamp(); for (var recipient : recipients) { if (recipient instanceof RecipientIdentifier.Single single) { final var message = new SignalServiceTypingMessage(action, timestamp, Optional.empty()); @@ -691,7 +706,7 @@ public class ManagerImpl implements Manager { @Override public SendMessageResults sendReadReceipt(RecipientIdentifier.Single sender, List messageIds) { - final var timestamp = System.currentTimeMillis(); + final var timestamp = getNextMessageTimestamp(); var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp); @@ -701,7 +716,7 @@ public class ManagerImpl implements Manager { @Override public SendMessageResults sendViewedReceipt(RecipientIdentifier.Single sender, List messageIds) { - final var timestamp = System.currentTimeMillis(); + final var timestamp = getNextMessageTimestamp(); var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED, messageIds, timestamp); @@ -763,17 +778,24 @@ public class ManagerImpl implements Manager { final Message message ) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException { final var additionalAttachments = new ArrayList(); - if (message.messageText().length() > ServiceConfig.MAX_MESSAGE_BODY_SIZE) { - final var messageBytes = message.messageText().getBytes(StandardCharsets.UTF_8); - final var uploadSpec = dependencies.getMessageSender().getResumableUploadSpec(); - final var streamDetails = new StreamDetails(new ByteArrayInputStream(messageBytes), - MimeUtils.LONG_TEXT, - messageBytes.length); - final var textAttachment = AttachmentUtils.createAttachmentStream(streamDetails, - Optional.empty(), - uploadSpec); - messageBuilder.withBody(message.messageText().substring(0, ServiceConfig.MAX_MESSAGE_BODY_SIZE)); - additionalAttachments.add(context.getAttachmentHelper().uploadAttachment(textAttachment)); + if (Utf8.size(message.messageText()) > MAX_MESSAGE_SIZE_BYTES) { + final var result = splitByByteLength(message.messageText(), MAX_MESSAGE_SIZE_BYTES); + final var trimmed = result.getFirst(); + final var remainder = result.getSecond(); + if (remainder != null) { + final var messageBytes = message.messageText().getBytes(StandardCharsets.UTF_8); + final var uploadSpec = dependencies.getMessageSender().getResumableUploadSpec(); + final var streamDetails = new StreamDetails(new ByteArrayInputStream(messageBytes), + MimeUtils.LONG_TEXT, + messageBytes.length); + final var textAttachment = AttachmentUtils.createAttachmentStream(streamDetails, + Optional.empty(), + uploadSpec); + messageBuilder.withBody(trimmed); + additionalAttachments.add(context.getAttachmentHelper().uploadAttachment(textAttachment)); + } else { + messageBuilder.withBody(message.messageText()); + } } else { messageBuilder.withBody(message.messageText()); } @@ -788,6 +810,7 @@ public class ManagerImpl implements Manager { } else if (!additionalAttachments.isEmpty()) { messageBuilder.withAttachments(additionalAttachments); } + messageBuilder.withViewOnce(message.viewOnce()); if (!message.mentions().isEmpty()) { messageBuilder.withMentions(resolveMentions(message.mentions())); } @@ -1039,15 +1062,23 @@ public class ManagerImpl implements Manager { @Override public void setContactName( - RecipientIdentifier.Single recipient, - String givenName, - final String familyName + final RecipientIdentifier.Single recipient, + final String givenName, + final String familyName, + final String nickGivenName, + final String nickFamilyName, + final String note ) throws NotPrimaryDeviceException, UnregisteredRecipientException { if (!account.isPrimaryDevice()) { throw new NotPrimaryDeviceException(); } context.getContactHelper() - .setContactName(context.getRecipientHelper().resolveRecipient(recipient), givenName, familyName); + .setContactName(context.getRecipientHelper().resolveRecipient(recipient), + givenName, + familyName, + nickGivenName, + nickFamilyName, + note); syncRemoteStorage(); } @@ -1576,7 +1607,8 @@ public class ManagerImpl implements Manager { context.close(); executor.close(); - dependencies.getSignalWebSocket().disconnect(); + dependencies.getAuthenticatedSignalWebSocket().disconnect(); + dependencies.getUnauthenticatedSignalWebSocket().disconnect(); dependencies.getPushServiceSocket().close(); disposable.dispose(); diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java index fcef536a..eeca842a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java @@ -29,12 +29,10 @@ import org.asamk.signal.manager.util.KeyUtils; import org.signal.libsignal.protocol.IdentityKeyPair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.SignalServiceAccountManager; -import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; -import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; +import org.whispersystems.signalservice.api.registration.ProvisioningApi; import org.whispersystems.signalservice.api.util.DeviceNameUtil; import org.whispersystems.signalservice.internal.push.ProvisioningSocket; import org.whispersystems.signalservice.internal.push.PushServiceSocket; @@ -58,7 +56,7 @@ public class ProvisioningManagerImpl implements ProvisioningManager { private final Consumer newManagerListener; private final AccountsStore accountsStore; - private final SignalServiceAccountManager accountManager; + private final ProvisioningApi provisioningApi; private final IdentityKeyPair tempIdentityKey; private final String password; @@ -77,8 +75,6 @@ public class ProvisioningManagerImpl implements ProvisioningManager { tempIdentityKey = KeyUtils.generateIdentityKeyPair(); password = KeyUtils.createPassword(); - final var clientZkOperations = ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()); - final var groupsV2Operations = new GroupsV2Operations(clientZkOperations, ServiceConfig.GROUP_MAX_SIZE); final var credentialsProvider = new DynamicCredentialsProvider(null, null, null, @@ -87,23 +83,22 @@ public class ProvisioningManagerImpl implements ProvisioningManager { final var pushServiceSocket = new PushServiceSocket(serviceEnvironmentConfig.signalServiceConfiguration(), credentialsProvider, userAgent, - clientZkOperations.getProfileOperations(), ServiceConfig.AUTOMATIC_NETWORK_RETRY); - accountManager = new SignalServiceAccountManager(pushServiceSocket, - new ProvisioningSocket(serviceEnvironmentConfig.signalServiceConfiguration(), userAgent), - groupsV2Operations); + final var provisioningSocket = new ProvisioningSocket(serviceEnvironmentConfig.signalServiceConfiguration(), + userAgent); + this.provisioningApi = new ProvisioningApi(pushServiceSocket, provisioningSocket, credentialsProvider); } @Override public URI getDeviceLinkUri() throws TimeoutException, IOException { - var deviceUuid = accountManager.getNewDeviceUuid(); + var deviceUuid = provisioningApi.getNewDeviceUuid(); return new DeviceLinkUrl(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri(); } @Override public String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException { - var ret = accountManager.getNewDeviceRegistration(tempIdentityKey); + var ret = provisioningApi.getNewDeviceRegistration(tempIdentityKey); var number = ret.getNumber(); var aci = ret.getAci(); var pni = ret.getPni(); @@ -160,7 +155,7 @@ public class ProvisioningManagerImpl implements ProvisioningManager { final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI)); logger.debug("Finishing new device registration"); - var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(), + var deviceId = provisioningApi.finishNewDeviceRegistration(ret.getProvisioningCode(), account.getAccountAttributes(null), aciPreKeys, pniPreKeys); diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ReentrantSignalSessionLock.java b/lib/src/main/java/org/asamk/signal/manager/internal/ReentrantSignalSessionLock.java new file mode 100644 index 00000000..78304469 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ReentrantSignalSessionLock.java @@ -0,0 +1,16 @@ +package org.asamk.signal.manager.internal; + +import org.whispersystems.signalservice.api.SignalSessionLock; + +import java.util.concurrent.locks.ReentrantLock; + +class ReentrantSignalSessionLock implements SignalSessionLock { + + private final ReentrantLock LEGACY_LOCK = new ReentrantLock(); + + @Override + public Lock acquire() { + LEGACY_LOCK.lock(); + return LEGACY_LOCK::unlock; + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java index 5faf1371..72c4b63f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java @@ -21,6 +21,7 @@ import org.asamk.signal.manager.RegistrationManager; 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.UpdateProfile; @@ -32,14 +33,11 @@ import org.asamk.signal.manager.helper.PinHelper; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.util.KeyUtils; import org.asamk.signal.manager.util.NumberVerificationUtils; -import org.asamk.signal.manager.util.Utils; import org.signal.libsignal.usernames.BaseUsernameException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.account.PreKeyCollection; -import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; -import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId.PNI; @@ -48,13 +46,13 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException; import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException; import org.whispersystems.signalservice.api.svr.SecureValueRecovery; -import org.whispersystems.signalservice.internal.push.PushServiceSocket; import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; import java.io.IOException; import java.util.function.Consumer; import static org.asamk.signal.manager.util.KeyUtils.generatePreKeysForType; +import static org.asamk.signal.manager.util.Utils.handleResponseException; public class RegistrationManagerImpl implements RegistrationManager { @@ -132,12 +130,15 @@ public class RegistrationManagerImpl implements RegistrationManager { } final var registrationApi = unauthenticatedAccountManager.getRegistrationApi(); + logger.trace("Creating verification session"); String sessionId = NumberVerificationUtils.handleVerificationSession(registrationApi, account.getSessionId(account.getNumber()), id -> account.setSessionId(account.getNumber(), id), voiceVerification, captcha); + logger.trace("Requesting verification code"); NumberVerificationUtils.requestVerificationCode(registrationApi, sessionId, voiceVerification); + logger.debug("Successfully requested verification code"); account.setRegistered(false); } catch (DeprecatedVersionException e) { logger.debug("Signal-Server returned deprecated version exception", e); @@ -149,7 +150,7 @@ public class RegistrationManagerImpl implements RegistrationManager { public void verifyAccount( String verificationCode, String pin - ) throws IOException, PinLockedException, IncorrectPinException { + ) throws IOException, PinLockedException, IncorrectPinException, PinLockMissingException { if (account.isRegistered()) { throw new IOException("Account is already registered"); } @@ -199,7 +200,7 @@ public class RegistrationManagerImpl implements RegistrationManager { final var aciPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.ACI)); final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI)); final var registrationApi = unauthenticatedAccountManager.getRegistrationApi(); - final var response = Utils.handleResponseException(registrationApi.registerAccount(null, + final var response = handleResponseException(registrationApi.registerAccount(null, recoveryPassword, account.getAccountAttributes(null), aciPreKeys, @@ -221,8 +222,14 @@ public class RegistrationManagerImpl implements RegistrationManager { private boolean attemptReactivateAccount() { try { - final var accountManager = createAuthenticatedSignalServiceAccountManager(); - accountManager.setAccountAttributes(account.getAccountAttributes(null)); + final var dependencies = new SignalDependencies(serviceEnvironmentConfig, + userAgent, + account.getCredentialsProvider(), + account.getSignalServiceDataStore(), + null, + new ReentrantSignalSessionLock()); + handleResponseException(dependencies.getAccountApi() + .setAccountAttributes(account.getAccountAttributes(null))); account.setRegistered(true); logger.info("Reactivated existing account, verify is not necessary."); if (newManagerListener != null) { @@ -241,17 +248,6 @@ public class RegistrationManagerImpl implements RegistrationManager { return false; } - private SignalServiceAccountManager createAuthenticatedSignalServiceAccountManager() { - final var clientZkOperations = ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()); - final var pushServiceSocket = new PushServiceSocket(serviceEnvironmentConfig.signalServiceConfiguration(), - account.getCredentialsProvider(), - userAgent, - clientZkOperations.getProfileOperations(), - ServiceConfig.AUTOMATIC_NETWORK_RETRY); - final var groupsV2Operations = new GroupsV2Operations(clientZkOperations, ServiceConfig.GROUP_MAX_SIZE); - return new SignalServiceAccountManager(pushServiceSocket, null, groupsV2Operations); - } - private VerifyAccountResponse verifyAccountWithCode( final String sessionId, final String verificationCode, @@ -261,11 +257,11 @@ public class RegistrationManagerImpl implements RegistrationManager { ) throws IOException { final var registrationApi = unauthenticatedAccountManager.getRegistrationApi(); try { - Utils.handleResponseException(registrationApi.verifyAccount(sessionId, verificationCode)); + handleResponseException(registrationApi.verifyAccount(sessionId, verificationCode)); } catch (AlreadyVerifiedException e) { // Already verified so can continue registering } - return Utils.handleResponseException(registrationApi.registerAccount(sessionId, + return handleResponseException(registrationApi.registerAccount(sessionId, null, account.getAccountAttributes(registrationLock), aciPreKeys, diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index 90e3b159..0bc895cb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -2,42 +2,58 @@ package org.asamk.signal.manager.internal; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; +import org.asamk.signal.manager.util.Utils; import org.signal.libsignal.metadata.certificate.CertificateValidator; import org.signal.libsignal.net.Network; +import org.signal.libsignal.protocol.UsePqRatchet; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceDataStore; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalSessionLock; -import org.whispersystems.signalservice.api.SignalWebSocket; +import org.whispersystems.signalservice.api.account.AccountApi; +import org.whispersystems.signalservice.api.attachment.AttachmentApi; +import org.whispersystems.signalservice.api.cds.CdsApi; +import org.whispersystems.signalservice.api.certificate.CertificateApi; import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; +import org.whispersystems.signalservice.api.keys.KeysApi; import org.whispersystems.signalservice.api.link.LinkDeviceApi; +import org.whispersystems.signalservice.api.message.MessageApi; +import org.whispersystems.signalservice.api.profiles.ProfileApi; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi; import org.whispersystems.signalservice.api.registration.RegistrationApi; import org.whispersystems.signalservice.api.services.ProfileService; import org.whispersystems.signalservice.api.storage.StorageServiceApi; import org.whispersystems.signalservice.api.storage.StorageServiceRepository; import org.whispersystems.signalservice.api.svr.SecureValueRecovery; +import org.whispersystems.signalservice.api.username.UsernameApi; import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.api.util.UptimeSleepTimer; -import org.whispersystems.signalservice.api.websocket.WebSocketFactory; -import org.whispersystems.signalservice.internal.push.ProvisioningSocket; +import org.whispersystems.signalservice.api.websocket.SignalWebSocket; import org.whispersystems.signalservice.internal.push.PushServiceSocket; import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection; -import org.whispersystems.signalservice.internal.websocket.WebSocketConnection; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; public class SignalDependencies { + private static final Logger logger = LoggerFactory.getLogger(SignalDependencies.class); + private final Object LOCK = new Object(); private final ServiceEnvironmentConfig serviceEnvironmentConfig; @@ -50,22 +66,31 @@ public class SignalDependencies { private boolean allowStories = true; private SignalServiceAccountManager accountManager; + private AccountApi accountApi; + private RateLimitChallengeApi rateLimitChallengeApi; + private CdsApi cdsApi; + private UsernameApi usernameApi; private GroupsV2Api groupsV2Api; private RegistrationApi registrationApi; private LinkDeviceApi linkDeviceApi; private StorageServiceApi storageServiceApi; + private CertificateApi certificateApi; + private AttachmentApi attachmentApi; + private MessageApi messageApi; + private KeysApi keysApi; private GroupsV2Operations groupsV2Operations; private ClientZkOperations clientZkOperations; private PushServiceSocket pushServiceSocket; - private ProvisioningSocket provisioningSocket; private Network libSignalNetwork; - private SignalWebSocket signalWebSocket; + private SignalWebSocket.AuthenticatedWebSocket authenticatedSignalWebSocket; + private SignalWebSocket.UnauthenticatedWebSocket unauthenticatedSignalWebSocket; private SignalServiceMessageReceiver messageReceiver; private SignalServiceMessageSender messageSender; private List secureValueRecovery; private ProfileService profileService; + private ProfileApi profileApi; SignalDependencies( final ServiceEnvironmentConfig serviceEnvironmentConfig, @@ -95,7 +120,12 @@ public class SignalDependencies { this.registrationApi = null; this.secureValueRecovery = null; } - getSignalWebSocket().forceNewWebSockets(); + if (this.authenticatedSignalWebSocket != null) { + this.authenticatedSignalWebSocket.forceNewWebSocket(); + } + if (this.unauthenticatedSignalWebSocket != null) { + this.unauthenticatedSignalWebSocket.forceNewWebSocket(); + } } /** @@ -118,25 +148,45 @@ public class SignalDependencies { () -> pushServiceSocket = new PushServiceSocket(serviceEnvironmentConfig.signalServiceConfiguration(), credentialsProvider, userAgent, - getClientZkProfileOperations(), ServiceConfig.AUTOMATIC_NETWORK_RETRY)); } - public ProvisioningSocket getProvisioningSocket() { - return getOrCreate(() -> provisioningSocket, - () -> provisioningSocket = new ProvisioningSocket(getServiceEnvironmentConfig().signalServiceConfiguration(), - userAgent)); + public Network getLibSignalNetwork() { + return getOrCreate(() -> libSignalNetwork, () -> { + libSignalNetwork = new Network(serviceEnvironmentConfig.netEnvironment(), userAgent); + setSignalNetworkProxy(libSignalNetwork); + }); } - public Network getLibSignalNetwork() { - return getOrCreate(() -> libSignalNetwork, - () -> libSignalNetwork = new Network(serviceEnvironmentConfig.netEnvironment(), userAgent)); + private void setSignalNetworkProxy(Network libSignalNetwork) { + final var proxy = Utils.getHttpsProxy(); + if (proxy.address() instanceof InetSocketAddress addr) { + switch (proxy.type()) { + case Proxy.Type.DIRECT -> { + } + case Proxy.Type.HTTP -> { + try { + libSignalNetwork.setProxy("http", addr.getHostName(), addr.getPort(), null, null); + } catch (IOException e) { + logger.warn("Failed to set http proxy", e); + } + } + case Proxy.Type.SOCKS -> { + try { + libSignalNetwork.setProxy("socks", addr.getHostName(), addr.getPort(), null, null); + } catch (IOException e) { + logger.warn("Failed to set socks proxy", e); + } + } + } + } } public SignalServiceAccountManager getAccountManager() { return getOrCreate(() -> accountManager, - () -> accountManager = new SignalServiceAccountManager(getPushServiceSocket(), - getProvisioningSocket(), + () -> accountManager = new SignalServiceAccountManager(getAuthenticatedSignalWebSocket(), + getAccountApi(), + getPushServiceSocket(), getGroupsV2Operations())); } @@ -152,6 +202,23 @@ public class SignalDependencies { ServiceConfig.GROUP_MAX_SIZE); } + public AccountApi getAccountApi() { + return getOrCreate(() -> accountApi, () -> accountApi = new AccountApi(getAuthenticatedSignalWebSocket())); + } + + public RateLimitChallengeApi getRateLimitChallengeApi() { + return getOrCreate(() -> rateLimitChallengeApi, + () -> rateLimitChallengeApi = new RateLimitChallengeApi(getAuthenticatedSignalWebSocket())); + } + + public CdsApi getCdsApi() { + return getOrCreate(() -> cdsApi, () -> cdsApi = new CdsApi(getAuthenticatedSignalWebSocket())); + } + + public UsernameApi getUsernameApi() { + return getOrCreate(() -> usernameApi, () -> usernameApi = new UsernameApi(getUnauthenticatedSignalWebSocket())); + } + public GroupsV2Api getGroupsV2Api() { return getOrCreate(() -> groupsV2Api, () -> groupsV2Api = getAccountManager().getGroupsV2Api()); } @@ -161,18 +228,41 @@ public class SignalDependencies { } public LinkDeviceApi getLinkDeviceApi() { - return getOrCreate(() -> linkDeviceApi, () -> linkDeviceApi = new LinkDeviceApi(getPushServiceSocket())); + return getOrCreate(() -> linkDeviceApi, + () -> linkDeviceApi = new LinkDeviceApi(getAuthenticatedSignalWebSocket())); } private StorageServiceApi getStorageServiceApi() { return getOrCreate(() -> storageServiceApi, - () -> storageServiceApi = new StorageServiceApi(getPushServiceSocket())); + () -> storageServiceApi = new StorageServiceApi(getAuthenticatedSignalWebSocket(), + getPushServiceSocket())); } public StorageServiceRepository getStorageServiceRepository() { return new StorageServiceRepository(getStorageServiceApi()); } + public CertificateApi getCertificateApi() { + return getOrCreate(() -> certificateApi, + () -> certificateApi = new CertificateApi(getAuthenticatedSignalWebSocket())); + } + + public AttachmentApi getAttachmentApi() { + return getOrCreate(() -> attachmentApi, + () -> attachmentApi = new AttachmentApi(getAuthenticatedSignalWebSocket(), getPushServiceSocket())); + } + + public MessageApi getMessageApi() { + return getOrCreate(() -> messageApi, + () -> messageApi = new MessageApi(getAuthenticatedSignalWebSocket(), + getUnauthenticatedSignalWebSocket())); + } + + public KeysApi getKeysApi() { + return getOrCreate(() -> keysApi, + () -> keysApi = new KeysApi(getAuthenticatedSignalWebSocket(), getUnauthenticatedSignalWebSocket())); + } + public GroupsV2Operations getGroupsV2Operations() { return getOrCreate(() -> groupsV2Operations, () -> groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()), @@ -189,33 +279,35 @@ public class SignalDependencies { return clientZkOperations.getProfileOperations(); } - public SignalWebSocket getSignalWebSocket() { - return getOrCreate(() -> signalWebSocket, () -> { + public SignalWebSocket.AuthenticatedWebSocket getAuthenticatedSignalWebSocket() { + return getOrCreate(() -> authenticatedSignalWebSocket, () -> { final var timer = new UptimeSleepTimer(); final var healthMonitor = new SignalWebSocketHealthMonitor(timer); - final var webSocketFactory = new WebSocketFactory() { - @Override - public WebSocketConnection createWebSocket() { - return new OkHttpWebSocketConnection("normal", - serviceEnvironmentConfig.signalServiceConfiguration(), - Optional.of(credentialsProvider), - userAgent, - healthMonitor, - allowStories); - } - @Override - public WebSocketConnection createUnidentifiedWebSocket() { - return new OkHttpWebSocketConnection("unidentified", - serviceEnvironmentConfig.signalServiceConfiguration(), - Optional.empty(), - userAgent, - healthMonitor, - allowStories); - } - }; - signalWebSocket = new SignalWebSocket(webSocketFactory); - healthMonitor.monitor(signalWebSocket); + authenticatedSignalWebSocket = new SignalWebSocket.AuthenticatedWebSocket(() -> new OkHttpWebSocketConnection( + "normal", + serviceEnvironmentConfig.signalServiceConfiguration(), + Optional.of(credentialsProvider), + userAgent, + healthMonitor, + allowStories), () -> true, timer, TimeUnit.SECONDS.toMillis(10)); + healthMonitor.monitor(authenticatedSignalWebSocket); + }); + } + + public SignalWebSocket.UnauthenticatedWebSocket getUnauthenticatedSignalWebSocket() { + return getOrCreate(() -> unauthenticatedSignalWebSocket, () -> { + final var timer = new UptimeSleepTimer(); + final var healthMonitor = new SignalWebSocketHealthMonitor(timer); + + unauthenticatedSignalWebSocket = new SignalWebSocket.UnauthenticatedWebSocket(() -> new OkHttpWebSocketConnection( + "unidentified", + serviceEnvironmentConfig.signalServiceConfiguration(), + Optional.empty(), + userAgent, + healthMonitor, + allowStories), () -> true, timer, TimeUnit.SECONDS.toMillis(10)); + healthMonitor.monitor(unauthenticatedSignalWebSocket); }); } @@ -229,10 +321,14 @@ public class SignalDependencies { () -> messageSender = new SignalServiceMessageSender(getPushServiceSocket(), dataStore, sessionLock, - getSignalWebSocket(), + getAttachmentApi(), + getMessageApi(), + getKeysApi(), Optional.empty(), executor, - ServiceConfig.MAX_ENVELOPE_SIZE)); + ServiceConfig.MAX_ENVELOPE_SIZE, + () -> true, + UsePqRatchet.NO)); } public List getSecureValueRecovery() { @@ -243,11 +339,19 @@ public class SignalDependencies { .toList()); } + public ProfileApi getProfileApi() { + return getOrCreate(() -> profileApi, + () -> profileApi = new ProfileApi(getAuthenticatedSignalWebSocket(), + getUnauthenticatedSignalWebSocket(), + getPushServiceSocket(), + getClientZkProfileOperations())); + } + public ProfileService getProfileService() { return getOrCreate(() -> profileService, () -> profileService = new ProfileService(getClientZkProfileOperations(), - getMessageReceiver(), - getSignalWebSocket())); + getAuthenticatedSignalWebSocket(), + getUnauthenticatedSignalWebSocket())); } public SignalServiceCipher getCipher(ServiceIdType serviceIdType) { diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java index 0fb1585a..7d927ddd 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java @@ -2,195 +2,157 @@ package org.asamk.signal.manager.internal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.SignalWebSocket; import org.whispersystems.signalservice.api.util.Preconditions; import org.whispersystems.signalservice.api.util.SleepTimer; import org.whispersystems.signalservice.api.websocket.HealthMonitor; +import org.whispersystems.signalservice.api.websocket.SignalWebSocket; import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState; import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection; -import java.util.Arrays; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import io.reactivex.rxjava3.schedulers.Schedulers; +import kotlin.Unit; -/** - * Monitors the health of the identified and unidentified WebSockets. If either one appears to be - * unhealthy, will trigger restarting both. - *

- * The monitor is also responsible for sending heartbeats/keep-alive messages to prevent - * timeouts. - */ final class SignalWebSocketHealthMonitor implements HealthMonitor { private static final Logger logger = LoggerFactory.getLogger(SignalWebSocketHealthMonitor.class); + /** + * This is the amount of time in between sent keep alives. Must be greater than [KEEP_ALIVE_TIMEOUT] + */ private static final long KEEP_ALIVE_SEND_CADENCE = TimeUnit.SECONDS.toMillis(OkHttpWebSocketConnection.KEEPALIVE_FREQUENCY_SECONDS); - private static final long MAX_TIME_SINCE_SUCCESSFUL_KEEP_ALIVE = KEEP_ALIVE_SEND_CADENCE * 3; - private SignalWebSocket signalWebSocket; + /** + * This is the amount of time we will wait for a response to the keep alive before we consider the websockets dead. + * It is required that this value be less than [KEEP_ALIVE_SEND_CADENCE] + */ + private static final long KEEP_ALIVE_TIMEOUT = TimeUnit.SECONDS.toMillis(20); + + private final Executor executor = Executors.newSingleThreadExecutor(); private final SleepTimer sleepTimer; - - private volatile KeepAliveSender keepAliveSender; - - private final HealthState identified = new HealthState(); - private final HealthState unidentified = new HealthState(); + private SignalWebSocket webSocket = null; + private volatile KeepAliveSender keepAliveSender = null; + private boolean needsKeepAlive = false; + private long lastKeepAliveReceived = 0; public SignalWebSocketHealthMonitor(SleepTimer sleepTimer) { this.sleepTimer = sleepTimer; } - public void monitor(SignalWebSocket signalWebSocket) { - Preconditions.checkNotNull(signalWebSocket); - Preconditions.checkArgument(this.signalWebSocket == null, "monitor can only be called once"); + void monitor(SignalWebSocket webSocket) { + Preconditions.checkNotNull(webSocket); + Preconditions.checkArgument(this.webSocket == null, "monitor can only be called once"); - this.signalWebSocket = signalWebSocket; + executor.execute(() -> { - //noinspection ResultOfMethodCallIgnored - signalWebSocket.getWebSocketState() - .subscribeOn(Schedulers.computation()) - .observeOn(Schedulers.computation()) - .distinctUntilChanged() - .subscribe(s -> onStateChange(s, identified)); + this.webSocket = webSocket; - //noinspection ResultOfMethodCallIgnored - signalWebSocket.getUnidentifiedWebSocketState() - .subscribeOn(Schedulers.computation()) - .observeOn(Schedulers.computation()) - .distinctUntilChanged() - .subscribe(s -> onStateChange(s, unidentified)); + webSocket.getState() + .subscribeOn(Schedulers.computation()) + .observeOn(Schedulers.computation()) + .distinctUntilChanged() + .subscribe(this::onStateChanged); + + webSocket.addKeepAliveChangeListener(() -> { + executor.execute(this::updateKeepAliveSenderStatus); + return Unit.INSTANCE; + }); + }); } - private synchronized void onStateChange(WebSocketConnectionState connectionState, HealthState healthState) { - switch (connectionState) { - case CONNECTED -> logger.debug("WebSocket is now connected"); - case AUTHENTICATION_FAILED -> logger.debug("WebSocket authentication failed"); - case FAILED -> logger.debug("WebSocket connection failed"); - } + private void onStateChanged(WebSocketConnectionState connectionState) { + executor.execute(() -> { + needsKeepAlive = connectionState == WebSocketConnectionState.CONNECTED; - healthState.needsKeepAlive = connectionState == WebSocketConnectionState.CONNECTED; + updateKeepAliveSenderStatus(); + }); + } - if (keepAliveSender == null && isKeepAliveNecessary()) { + @Override + public void onKeepAliveResponse(long sentTimestamp, boolean isIdentifiedWebSocket) { + final var keepAliveTime = System.currentTimeMillis(); + executor.execute(() -> lastKeepAliveReceived = keepAliveTime); + } + + @Override + public void onMessageError(int status, boolean isIdentifiedWebSocket) { + } + + private void updateKeepAliveSenderStatus() { + if (keepAliveSender == null && sendKeepAlives()) { keepAliveSender = new KeepAliveSender(); keepAliveSender.start(); - } else if (keepAliveSender != null && !isKeepAliveNecessary()) { + } else if (keepAliveSender != null && !sendKeepAlives()) { keepAliveSender.shutdown(); keepAliveSender = null; } } - @Override - public void onKeepAliveResponse(long sentTimestamp, boolean isIdentifiedWebSocket) { - if (isIdentifiedWebSocket) { - identified.lastKeepAliveReceived = System.currentTimeMillis(); - } else { - unidentified.lastKeepAliveReceived = System.currentTimeMillis(); - } - } - - @Override - public void onMessageError(int status, boolean isIdentifiedWebSocket) { - if (status == 409) { - HealthState healthState = (isIdentifiedWebSocket ? identified : unidentified); - if (healthState.mismatchErrorTracker.addSample(System.currentTimeMillis())) { - logger.warn("Received too many mismatch device errors, forcing new websockets."); - signalWebSocket.forceNewWebSockets(); - signalWebSocket.connect(); - } - } - } - - private boolean isKeepAliveNecessary() { - return identified.needsKeepAlive || unidentified.needsKeepAlive; - } - - private static class HealthState { - - private final HttpErrorTracker mismatchErrorTracker = new HttpErrorTracker(5, TimeUnit.MINUTES.toMillis(1)); - - private volatile boolean needsKeepAlive; - private volatile long lastKeepAliveReceived; + private boolean sendKeepAlives() { + return needsKeepAlive && webSocket != null && webSocket.shouldSendKeepAlives(); } /** - * Sends periodic heartbeats/keep-alives over both WebSockets to prevent connection timeouts. If - * either WebSocket fails 3 times to get a return heartbeat both are forced to be recreated. + * Sends periodic heartbeats/keep-alives over the WebSocket to prevent connection timeouts. If + * the WebSocket fails to get a return heartbeat after [KEEP_ALIVE_TIMEOUT] seconds, it is forced to be recreated. */ - private class KeepAliveSender extends Thread { + private final class KeepAliveSender extends Thread { private volatile boolean shouldKeepRunning = true; + @Override public void run() { - identified.lastKeepAliveReceived = System.currentTimeMillis(); - unidentified.lastKeepAliveReceived = System.currentTimeMillis(); + logger.debug("[KeepAliveSender({})] started", this.threadId()); + lastKeepAliveReceived = System.currentTimeMillis(); - while (shouldKeepRunning && isKeepAliveNecessary()) { + var keepAliveSendTime = System.currentTimeMillis(); + while (shouldKeepRunning && sendKeepAlives()) { try { - sleepTimer.sleep(KEEP_ALIVE_SEND_CADENCE); + final var nextKeepAliveSendTime = keepAliveSendTime + KEEP_ALIVE_SEND_CADENCE; + sleepUntil(nextKeepAliveSendTime); - if (shouldKeepRunning && isKeepAliveNecessary()) { - long keepAliveRequiredSinceTime = System.currentTimeMillis() - - MAX_TIME_SINCE_SUCCESSFUL_KEEP_ALIVE; + if (shouldKeepRunning && sendKeepAlives()) { + keepAliveSendTime = System.currentTimeMillis(); + webSocket.sendKeepAlive(); + } - if (identified.lastKeepAliveReceived < keepAliveRequiredSinceTime - || unidentified.lastKeepAliveReceived < keepAliveRequiredSinceTime) { - logger.warn("Missed keep alives, identified last: " - + identified.lastKeepAliveReceived - + " unidentified last: " - + unidentified.lastKeepAliveReceived - + " needed by: " - + keepAliveRequiredSinceTime); - signalWebSocket.forceNewWebSockets(); - signalWebSocket.connect(); - } else { - signalWebSocket.sendKeepAlive(); + final var responseRequiredTime = keepAliveSendTime + KEEP_ALIVE_TIMEOUT; + sleepUntil(responseRequiredTime); + + if (shouldKeepRunning && sendKeepAlives()) { + if (lastKeepAliveReceived < keepAliveSendTime) { + logger.debug("Missed keep alive, last: {} needed by: {}", + lastKeepAliveReceived, + responseRequiredTime); + webSocket.forceNewWebSocket(); } } } catch (Throwable e) { - logger.warn("Error occurred in KeepAliveSender, ignoring ...", e); + logger.warn("Keep alive sender failed", e); + } + } + logger.debug("[KeepAliveSender({})] ended", threadId()); + } + + void sleepUntil(long timeMillis) { + while (System.currentTimeMillis() < timeMillis) { + final var waitTime = timeMillis - System.currentTimeMillis(); + if (waitTime > 0) { + try { + sleepTimer.sleep(waitTime); + } catch (InterruptedException e) { + logger.warn("WebSocket health monitor interrupted", e); + } } } } - public void shutdown() { + void shutdown() { shouldKeepRunning = false; } } - - private static final class HttpErrorTracker { - - private final long[] timestamps; - private final long errorTimeRange; - - public HttpErrorTracker(int samples, long errorTimeRange) { - this.timestamps = new long[samples]; - this.errorTimeRange = errorTimeRange; - } - - public synchronized boolean addSample(long now) { - long errorsMustBeAfter = now - errorTimeRange; - int count = 1; - int minIndex = 0; - - for (int i = 0; i < timestamps.length; i++) { - if (timestamps[i] < errorsMustBeAfter) { - timestamps[i] = 0; - } else if (timestamps[i] != 0) { - count++; - } - - if (timestamps[i] < timestamps[minIndex]) { - minIndex = i; - } - } - - timestamps[minIndex] = now; - - if (count >= timestamps.length) { - Arrays.fill(timestamps, 0); - return true; - } - return false; - } - } } + diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/Database.java b/lib/src/main/java/org/asamk/signal/manager/storage/Database.java index 2fb09a4f..1d84daef 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/Database.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/Database.java @@ -95,10 +95,12 @@ public abstract class Database implements AutoCloseable { sqliteConfig.setTransactionMode(SQLiteConfig.TransactionMode.IMMEDIATE); HikariConfig config = new HikariConfig(); - config.setJdbcUrl("jdbc:sqlite:" + databaseFile); + config.setJdbcUrl("jdbc:sqlite:" + databaseFile + "?foreign_keys=ON&journal_mode=wal"); config.setDataSourceProperties(sqliteConfig.toProperties()); config.setMinimumIdle(1); - config.setConnectionInitSql("PRAGMA foreign_keys=ON"); + config.setConnectionTimeout(90_000); + config.setMaximumPoolSize(50); + config.setMaxLifetime(0); return new HikariDataSource(config); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index c4cf1484..83040906 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -116,7 +116,7 @@ public class SignalAccount implements Closeable { private static final Logger logger = LoggerFactory.getLogger(SignalAccount.class); private static final int MINIMUM_STORAGE_VERSION = 1; - private static final int CURRENT_STORAGE_VERSION = 9; + private static final int CURRENT_STORAGE_VERSION = 10; private final Object LOCK = new Object(); @@ -827,6 +827,7 @@ public class SignalAccount implements Closeable { if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyPreKeyStore() != null) { logger.debug("Migrating legacy pre key store."); + aciAccountData.getPreKeyStore().removeAllPreKeys(); for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) { try { aciAccountData.getPreKeyStore().storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue())); @@ -838,6 +839,7 @@ public class SignalAccount implements Closeable { if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySignedPreKeyStore() != null) { logger.debug("Migrating legacy signed pre key store."); + aciAccountData.getSignedPreKeyStore().removeAllSignedPreKeys(); for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) { try { aciAccountData.getSignedPreKeyStore() @@ -1550,9 +1552,7 @@ public class SignalAccount implements Closeable { return key; } - pinMasterKey = KeyUtils.createMasterKey(); - save(); - return pinMasterKey; + return getOrCreateAccountEntropyPool().deriveMasterKey(); } private MasterKey getMasterKey() { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java index a0fa3012..7be4a401 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java @@ -1,6 +1,7 @@ package org.asamk.signal.manager.storage.accounts; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.i18n.phonenumbers.PhoneNumberUtil; import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.ServiceEnvironment; @@ -10,7 +11,6 @@ import org.asamk.signal.manager.util.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.push.ServiceId.ACI; -import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -181,7 +181,7 @@ public class AccountsStore { return Arrays.stream(files) .filter(File::isFile) .map(File::getName) - .filter(file -> PhoneNumberFormatter.isValidNumber(file, null)) + .filter(file -> PhoneNumberUtil.getInstance().isPossibleNumber(file, null)) .collect(Collectors.toSet()); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/configuration/ConfigurationStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/configuration/ConfigurationStore.java index ef4a4545..88f05725 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/configuration/ConfigurationStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/configuration/ConfigurationStore.java @@ -36,6 +36,10 @@ public class ConfigurationStore { return keyValueStore.getEntry(readReceipts); } + public Boolean getReadReceipts(final Connection connection) throws SQLException { + return keyValueStore.getEntry(connection, readReceipts); + } + public void setReadReceipts(final boolean value) { if (keyValueStore.storeEntry(readReceipts, value)) { recipientStore.rotateSelfStorageId(); @@ -52,6 +56,10 @@ public class ConfigurationStore { return keyValueStore.getEntry(unidentifiedDeliveryIndicators); } + public Boolean getUnidentifiedDeliveryIndicators(final Connection connection) throws SQLException { + return keyValueStore.getEntry(connection, unidentifiedDeliveryIndicators); + } + public void setUnidentifiedDeliveryIndicators(final boolean value) { if (keyValueStore.storeEntry(unidentifiedDeliveryIndicators, value)) { recipientStore.rotateSelfStorageId(); @@ -71,6 +79,10 @@ public class ConfigurationStore { return keyValueStore.getEntry(typingIndicators); } + public Boolean getTypingIndicators(final Connection connection) throws SQLException { + return keyValueStore.getEntry(connection, typingIndicators); + } + public void setTypingIndicators(final boolean value) { if (keyValueStore.storeEntry(typingIndicators, value)) { recipientStore.rotateSelfStorageId(); @@ -87,6 +99,10 @@ public class ConfigurationStore { return keyValueStore.getEntry(linkPreviews); } + public Boolean getLinkPreviews(final Connection connection) throws SQLException { + return keyValueStore.getEntry(connection, linkPreviews); + } + public void setLinkPreviews(final boolean value) { if (keyValueStore.storeEntry(linkPreviews, value)) { recipientStore.rotateSelfStorageId(); @@ -103,6 +119,10 @@ public class ConfigurationStore { return keyValueStore.getEntry(phoneNumberUnlisted); } + public Boolean getPhoneNumberUnlisted(final Connection connection) throws SQLException { + return keyValueStore.getEntry(connection, phoneNumberUnlisted); + } + public void setPhoneNumberUnlisted(final boolean value) { if (keyValueStore.storeEntry(phoneNumberUnlisted, value)) { recipientStore.rotateSelfStorageId(); @@ -119,6 +139,10 @@ public class ConfigurationStore { return keyValueStore.getEntry(phoneNumberSharingMode); } + public PhoneNumberSharingMode getPhoneNumberSharingMode(final Connection connection) throws SQLException { + return keyValueStore.getEntry(connection, phoneNumberSharingMode); + } + public void setPhoneNumberSharingMode(final PhoneNumberSharingMode value) { if (keyValueStore.storeEntry(phoneNumberSharingMode, value)) { recipientStore.rotateSelfStorageId(); @@ -138,6 +162,10 @@ public class ConfigurationStore { return keyValueStore.getEntry(usernameLinkColor); } + public String getUsernameLinkColor(final Connection connection) throws SQLException { + return keyValueStore.getEntry(connection, usernameLinkColor); + } + public void setUsernameLinkColor(final String color) { if (keyValueStore.storeEntry(usernameLinkColor, color)) { recipientStore.rotateSelfStorageId(); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java index 5e32ba12..a2d03a21 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java @@ -8,6 +8,7 @@ import org.asamk.signal.manager.storage.recipients.RecipientStore; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.state.IdentityKeyStore.Direction; +import org.signal.libsignal.protocol.state.IdentityKeyStore.IdentityChange; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.push.ServiceId; @@ -62,11 +63,11 @@ public class IdentityKeyStore { return identityChanges; } - public boolean saveIdentity(final ServiceId serviceId, final IdentityKey identityKey) { + public IdentityChange saveIdentity(final ServiceId serviceId, final IdentityKey identityKey) { return saveIdentity(serviceId.toString(), identityKey); } - public boolean saveIdentity( + public IdentityChange saveIdentity( final Connection connection, final ServiceId serviceId, final IdentityKey identityKey @@ -74,9 +75,9 @@ public class IdentityKeyStore { return saveIdentity(connection, serviceId.toString(), identityKey); } - boolean saveIdentity(final String address, final IdentityKey identityKey) { + IdentityChange saveIdentity(final String address, final IdentityKey identityKey) { if (isRetryingDecryption) { - return false; + return IdentityChange.NEW_OR_UNCHANGED; } try (final var connection = database.getConnection()) { return saveIdentity(connection, address, identityKey); @@ -85,20 +86,24 @@ public class IdentityKeyStore { } } - private boolean saveIdentity( + private IdentityChange saveIdentity( final Connection connection, final String address, final IdentityKey identityKey ) throws SQLException { final var identityInfo = loadIdentity(connection, address); - if (identityInfo != null && identityInfo.getIdentityKey().equals(identityKey)) { + if (identityInfo == null) { + saveNewIdentity(connection, address, identityKey, true); + return IdentityChange.NEW_OR_UNCHANGED; + } + if (identityInfo.getIdentityKey().equals(identityKey)) { // Identity already exists, not updating the trust level logger.trace("Not storing new identity for recipient {}, identity already stored", address); - return false; + return IdentityChange.NEW_OR_UNCHANGED; } - saveNewIdentity(connection, address, identityKey, identityInfo == null); - return true; + saveNewIdentity(connection, address, identityKey, false); + return IdentityChange.REPLACED_EXISTING; } public void setRetryingDecryption(final boolean retryingDecryption) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/identities/SignalIdentityKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/identities/SignalIdentityKeyStore.java index 5a1a676e..1ac7c9a6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/identities/SignalIdentityKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/identities/SignalIdentityKeyStore.java @@ -33,7 +33,7 @@ public class SignalIdentityKeyStore implements org.signal.libsignal.protocol.sta } @Override - public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { + public IdentityChange saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { return identityKeyStore.saveIdentity(address.getName(), identityKey); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/keyValue/KeyValueStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/keyValue/KeyValueStore.java index e5bbc188..2f59c9d2 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/keyValue/KeyValueStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/keyValue/KeyValueStore.java @@ -10,6 +10,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; +import java.util.HashMap; import java.util.Objects; public class KeyValueStore { @@ -18,6 +19,7 @@ public class KeyValueStore { private static final Logger logger = LoggerFactory.getLogger(KeyValueStore.class); private final Database database; + private final HashMap, Object> cache = new HashMap<>(); public static void createSql(Connection connection) throws SQLException { // When modifying the CREATE statement here, also add a migration in AccountDatabase.java @@ -36,11 +38,18 @@ public class KeyValueStore { this.database = database; } + @SuppressWarnings("unchecked") public T getEntry(KeyValueEntry key) { + synchronized (cache) { + if (cache.containsKey(key)) { + logger.trace("Got entry for key {} from cache", key.key()); + return (T) cache.get(key); + } + } try (final var connection = database.getConnection()) { return getEntry(connection, key); } catch (SQLException e) { - throw new RuntimeException("Failed read from pre_key store", e); + throw new RuntimeException("Failed read from key_value store", e); } } @@ -52,7 +61,7 @@ public class KeyValueStore { } } - private T getEntry(final Connection connection, final KeyValueEntry key) throws SQLException { + public T getEntry(final Connection connection, final KeyValueEntry key) throws SQLException { final var sql = ( """ SELECT key, value @@ -63,11 +72,17 @@ public class KeyValueStore { try (final var statement = connection.prepareStatement(sql)) { statement.setString(1, key.key()); - final var result = Utils.executeQueryForOptional(statement, - resultSet -> readValueFromResultSet(key, resultSet)).orElse(null); + var result = Utils.executeQueryForOptional(statement, resultSet -> readValueFromResultSet(key, resultSet)) + .orElse(null); if (result == null) { - return key.defaultValue(); + logger.trace("Got entry for key {} from default value", key.key()); + result = key.defaultValue(); + } else { + logger.trace("Got entry for key {} from db", key.key()); + } + synchronized (cache) { + cache.put(key, result); } return result; } @@ -78,7 +93,7 @@ public class KeyValueStore { final KeyValueEntry key, final T value ) throws SQLException { - final var entry = getEntry(key); + final var entry = getEntry(connection, key); if (Objects.equals(entry, value)) { return false; } @@ -95,6 +110,9 @@ public class KeyValueStore { setParameterValue(statement, 2, key.clazz(), value); statement.executeUpdate(); } + synchronized (cache) { + cache.put(key, value); + } return true; } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java b/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java index 51882a44..e4fd8c01 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java @@ -10,6 +10,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Collections; import java.util.Objects; @@ -75,7 +76,7 @@ public class MessageCache { return cachedMessage; } logger.debug("Moving cached message {} to {}", cachedMessage.getFile().toPath(), cacheFile.toPath()); - Files.move(cachedMessage.getFile().toPath(), cacheFile.toPath()); + Files.move(cachedMessage.getFile().toPath(), cacheFile.toPath(), StandardCopyOption.REPLACE_EXISTING); return new CachedMessage(cacheFile); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java index d2d55710..8e8bd2ab 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java @@ -4,8 +4,9 @@ import org.asamk.signal.manager.storage.Database; import org.asamk.signal.manager.storage.Utils; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyIdException; -import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; +import org.signal.libsignal.protocol.ecc.ECPrivateKey; +import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.signal.libsignal.protocol.state.PreKeyRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -176,8 +177,8 @@ public class PreKeyStore implements SignalServicePreKeyStore { private PreKeyRecord getPreKeyRecordFromResultSet(ResultSet resultSet) throws SQLException { try { final var keyId = resultSet.getInt("key_id"); - final var publicKey = Curve.decodePoint(resultSet.getBytes("public_key"), 0); - final var privateKey = Curve.decodePrivatePoint(resultSet.getBytes("private_key")); + final var publicKey = new ECPublicKey(resultSet.getBytes("public_key")); + final var privateKey = new ECPrivateKey(resultSet.getBytes("private_key")); return new PreKeyRecord(keyId, new ECKeyPair(publicKey, privateKey)); } catch (InvalidKeyException e) { return null; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java index d62e939d..4644c3ba 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java @@ -4,8 +4,9 @@ import org.asamk.signal.manager.storage.Database; import org.asamk.signal.manager.storage.Utils; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyIdException; -import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; +import org.signal.libsignal.protocol.ecc.ECPrivateKey; +import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.signal.libsignal.protocol.state.SignedPreKeyRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -238,8 +239,8 @@ public class SignedPreKeyStore implements org.signal.libsignal.protocol.state.Si private SignedPreKeyRecord getSignedPreKeyRecordFromResultSet(ResultSet resultSet) throws SQLException { try { final var keyId = resultSet.getInt("key_id"); - final var publicKey = Curve.decodePoint(resultSet.getBytes("public_key"), 0); - final var privateKey = Curve.decodePrivatePoint(resultSet.getBytes("private_key")); + final var publicKey = new ECPublicKey(resultSet.getBytes("public_key")); + final var privateKey = new ECPrivateKey(resultSet.getBytes("private_key")); final var signature = resultSet.getBytes("signature"); final var timestamp = resultSet.getLong("timestamp"); return new SignedPreKeyRecord(keyId, timestamp, new ECKeyPair(publicKey, privateKey), signature); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java index b631f632..e1cf9a88 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java @@ -65,7 +65,7 @@ public class SignalProtocolStore implements SignalServiceAccountDataStore { } @Override - public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { + public IdentityChange saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { return identityKeyStore.saveIdentity(address, identityKey); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java index 95c17102..1454c8a7 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java @@ -39,7 +39,7 @@ public class MergeRecipientHelper { ) ) || recipient.address().aci().equals(address.aci())) { logger.debug("Got existing recipient {}, updating with high trust address", recipient.id()); - store.updateRecipientAddress(recipient.id(), recipient.address().withIdentifiersFrom(address)); + store.updateRecipientAddress(recipient.id(), address.withOtherIdentifiersFrom(recipient.address())); return new Pair<>(recipient.id(), List.of()); } @@ -83,24 +83,25 @@ public class MergeRecipientHelper { recipientsToBeStripped.add(recipient); } - logger.debug("Got separate recipients for high trust identifiers {}, need to merge ({}) and strip ({})", + logger.debug("Got separate recipients for high trust identifiers {}, need to merge ({}, {}) and strip ({})", address, - recipientsToBeMerged.stream().map(r -> r.id().toString()).collect(Collectors.joining(", ")), - recipientsToBeStripped.stream().map(r -> r.id().toString()).collect(Collectors.joining(", "))); + resultingRecipient.map(RecipientWithAddress::address), + recipientsToBeMerged.stream().map(r -> r.address().toString()).collect(Collectors.joining(", ")), + recipientsToBeStripped.stream().map(r -> r.address().toString()).collect(Collectors.joining(", "))); RecipientAddress finalAddress = resultingRecipient.map(RecipientWithAddress::address).orElse(null); for (final var recipient : recipientsToBeMerged) { if (finalAddress == null) { finalAddress = recipient.address(); } else { - finalAddress = finalAddress.withIdentifiersFrom(recipient.address()); + finalAddress = finalAddress.withOtherIdentifiersFrom(recipient.address()); } store.removeRecipientAddress(recipient.id()); } if (finalAddress == null) { finalAddress = address; } else { - finalAddress = finalAddress.withIdentifiersFrom(address); + finalAddress = address.withOtherIdentifiersFrom(finalAddress); } for (final var recipient : recipientsToBeStripped) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientAddress.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientAddress.java index ba2b9f26..25dc3a3f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientAddress.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientAddress.java @@ -79,11 +79,11 @@ public record RecipientAddress( this(Optional.of(serviceId), Optional.empty()); } - public RecipientAddress withIdentifiersFrom(RecipientAddress address) { - return new RecipientAddress(address.aci.or(this::aci), - address.pni.or(this::pni), - address.number.or(this::number), - address.username.or(this::username)); + public RecipientAddress withOtherIdentifiersFrom(RecipientAddress address) { + return new RecipientAddress(this.aci.or(address::aci), + this.pni.or(address::pni), + this.number.or(address::number), + this.username.or(address::username)); } public RecipientAddress removeIdentifiersFrom(RecipientAddress address) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java index aa245369..442cbf4e 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java @@ -934,7 +934,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } public void markUndiscoverablePossiblyUnregistered(final Set numbers) { - logger.debug("Marking {} numbers as unregistered", numbers.size()); + logger.debug("Marking {} numbers as undiscoverable", numbers.size()); try (final var connection = database.getConnection()) { connection.setAutoCommit(false); for (final var number : numbers) { @@ -994,7 +994,12 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re ) throws SQLException { markUnregistered(connection, recipientId); final var address = resolveRecipientAddress(connection, recipientId); - if (address.aci().isPresent() && address.pni().isPresent()) { + final var needSplit = address.aci().isPresent() && address.pni().isPresent(); + logger.trace("Marking unregistered recipient {} as unregistered (and split={}): {}", + recipientId, + needSplit, + address); + if (needSplit) { final var numberAddress = new RecipientAddress(address.pni().get(), address.number().orElse(null)); updateRecipientAddress(connection, recipientId, address.removeIdentifiersFrom(numberAddress)); addNewRecipient(connection, numberAddress); diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java index b2f7dd29..30816060 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java @@ -2,7 +2,6 @@ package org.asamk.signal.manager.syncStorage; import org.asamk.signal.manager.api.Profile; import org.asamk.signal.manager.internal.JobExecutor; -import org.asamk.signal.manager.jobs.CheckWhoAmIJob; import org.asamk.signal.manager.jobs.DownloadProfileAvatarJob; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.util.KeyUtils; @@ -11,6 +10,7 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.push.UsernameLinkComponents; +import org.whispersystems.signalservice.api.storage.IAPSubscriptionId; import org.whispersystems.signalservice.api.storage.SignalAccountRecord; import org.whispersystems.signalservice.api.storage.StorageId; import org.whispersystems.signalservice.api.util.UuidUtil; @@ -22,8 +22,12 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Optional; +import okio.ByteString; + import static org.asamk.signal.manager.util.Utils.firstNonEmpty; -import static org.asamk.signal.manager.util.Utils.firstNonNull; +import static org.whispersystems.signalservice.api.storage.AccountRecordExtensionsKt.safeSetBackupsSubscriber; +import static org.whispersystems.signalservice.api.storage.AccountRecordExtensionsKt.safeSetPayments; +import static org.whispersystems.signalservice.api.storage.AccountRecordExtensionsKt.safeSetSubscriber; /** * Processes {@link SignalAccountRecord}s. @@ -48,7 +52,8 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor 0 + ? remote.payments + : local.payments; + + final ByteString donationSubscriberId; + final String donationSubscriberCurrencyCode; + + if (remote.subscriberId.size() > 0) { + donationSubscriberId = remote.subscriberId; + donationSubscriberCurrencyCode = remote.subscriberCurrencyCode; + } else { + donationSubscriberId = local.subscriberId; + donationSubscriberCurrencyCode = local.subscriberCurrencyCode; + } + + final ByteString backupsSubscriberId; + final IAPSubscriptionId backupsPurchaseToken; + + final var remoteBackupSubscriberData = remote.backupSubscriberData; + if (remoteBackupSubscriberData != null && remoteBackupSubscriberData.subscriberId.size() > 0) { + backupsSubscriberId = remoteBackupSubscriberData.subscriberId; + backupsPurchaseToken = IAPSubscriptionId.Companion.from(remoteBackupSubscriberData); + } else { + backupsSubscriberId = local.backupSubscriberData != null + ? local.backupSubscriberData.subscriberId + : ByteString.EMPTY; + backupsPurchaseToken = IAPSubscriptionId.Companion.from(local.backupSubscriberData); + } + + final var mergedBuilder = remote.newBuilder() .givenName(givenName) .familyName(familyName) .avatarUrlPath(firstNonEmpty(remote.avatarUrlPath, local.avatarUrlPath)) @@ -96,9 +130,6 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor> implemen final var local = getMatching(remote); if (local.isEmpty()) { - debug(remote.getId(), remote, "No matching local record. Inserting."); + debug(remote.getId(), remote, "[Local Insert] No matching local record. Inserting."); insertLocal(remote); return; } @@ -70,7 +70,7 @@ abstract class DefaultStorageRecordProcessor> implemen if (!merged.equals(local.get())) { final var update = new StorageRecordUpdate<>(local.get(), merged); - debug(remote.getId(), remote, "[Local Update] " + update); + debug(remote.getId(), remote, "[Local Update] " + local.get().describeDiff(merged)); updateLocal(update); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV1RecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV1RecordProcessor.java index 22d18c8b..a5c3e3e0 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV1RecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV1RecordProcessor.java @@ -74,7 +74,7 @@ public final class GroupV1RecordProcessor extends DefaultStorageRecordProcessor< final var remote = remoteRecord.getProto(); final var local = localRecord.getProto(); - final var mergedBuilder = SignalGroupV1Record.Companion.newBuilder(remote.unknownFields().toByteArray()) + final var mergedBuilder = remote.newBuilder() .id(remote.id) .blocked(remote.blocked) .whitelisted(remote.whitelisted) diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV2RecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV2RecordProcessor.java index af77e857..dfbe7c4b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV2RecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV2RecordProcessor.java @@ -53,7 +53,7 @@ public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor< final var remote = remoteRecord.getProto(); final var local = localRecord.getProto(); - final var mergedBuilder = SignalGroupV2Record.Companion.newBuilder(remote.unknownFields().toByteArray()) + final var mergedBuilder = remote.newBuilder() .masterKey(remote.masterKey) .blocked(remote.blocked) .whitelisted(remote.whitelisted) @@ -62,7 +62,8 @@ public final class GroupV2RecordProcessor extends DefaultStorageRecordProcessor< .mutedUntilTimestamp(remote.mutedUntilTimestamp) .dontNotifyForMentionsIfMuted(remote.dontNotifyForMentionsIfMuted) .hideStory(remote.hideStory) - .storySendMode(remote.storySendMode); + .storySendMode(remote.storySendMode) + .avatarColor(remote.avatarColor); final var merged = mergedBuilder.build(); final var matchesRemote = doProtosMatch(merged, remote); diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageRecordProcessor.java index 45a99562..2e14f648 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageRecordProcessor.java @@ -8,7 +8,7 @@ import java.sql.SQLException; * Handles processing a remote record, which involves applying any local changes that need to be * made based on the remote records. */ -interface StorageRecordProcessor { +interface StorageRecordProcessor> { void process(E remoteRecord) throws SQLException; } diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageRecordUpdate.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageRecordUpdate.java index 8dcb7b34..da8ccaf6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageRecordUpdate.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageRecordUpdate.java @@ -5,7 +5,7 @@ import org.whispersystems.signalservice.api.storage.SignalRecord; /** * Represents a pair of records: one old, and one new. The new record should replace the old. */ -record StorageRecordUpdate(E oldRecord, E newRecord) { +record StorageRecordUpdate>(E oldRecord, E newRecord) { @Override public String toString() { diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageSyncModels.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageSyncModels.java index ceea180c..7ca71bfc 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageSyncModels.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageSyncModels.java @@ -22,6 +22,8 @@ import org.whispersystems.signalservice.internal.storage.protos.ContactRecord.Id import org.whispersystems.signalservice.internal.storage.protos.GroupV1Record; import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record; +import java.sql.Connection; +import java.sql.SQLException; import java.util.Optional; import okio.ByteString; @@ -49,10 +51,11 @@ public final class StorageSyncModels { } public static AccountRecord localToRemoteRecord( + final Connection connection, ConfigurationStore configStore, Recipient self, UsernameLinkComponents usernameLinkComponents - ) { + ) throws SQLException { final var builder = SignalAccountRecord.Companion.newBuilder(self.getStorageRecord()); if (self.getProfileKey() != null) { builder.profileKey(ByteString.of(self.getProfileKey().serialize())); @@ -62,19 +65,18 @@ public final class StorageSyncModels { .familyName(emptyIfNull(self.getProfile().getFamilyName())) .avatarUrlPath(emptyIfNull(self.getProfile().getAvatarUrlPath())); } - builder.typingIndicators(Optional.ofNullable(configStore.getTypingIndicators()).orElse(true)) - .readReceipts(Optional.ofNullable(configStore.getReadReceipts()).orElse(true)) - .sealedSenderIndicators(Optional.ofNullable(configStore.getUnidentifiedDeliveryIndicators()) + builder.typingIndicators(Optional.ofNullable(configStore.getTypingIndicators(connection)).orElse(true)) + .readReceipts(Optional.ofNullable(configStore.getReadReceipts(connection)).orElse(true)) + .sealedSenderIndicators(Optional.ofNullable(configStore.getUnidentifiedDeliveryIndicators(connection)) .orElse(true)) - .linkPreviews(Optional.ofNullable(configStore.getLinkPreviews()).orElse(true)) - .unlistedPhoneNumber(Optional.ofNullable(configStore.getPhoneNumberUnlisted()).orElse(false)) - .phoneNumberSharingMode(Optional.ofNullable(configStore.getPhoneNumberSharingMode()) + .linkPreviews(Optional.ofNullable(configStore.getLinkPreviews(connection)).orElse(true)) + .unlistedPhoneNumber(Optional.ofNullable(configStore.getPhoneNumberUnlisted(connection)).orElse(false)) + .phoneNumberSharingMode(Optional.ofNullable(configStore.getPhoneNumberSharingMode(connection)) .map(StorageSyncModels::localToRemote) .orElse(AccountRecord.PhoneNumberSharingMode.UNKNOWN)) - .e164(self.getAddress().number().orElse("")) .username(self.getAddress().username().orElse("")); if (usernameLinkComponents != null) { - final var linkColor = configStore.getUsernameLinkColor(); + final var linkColor = configStore.getUsernameLinkColor(connection); builder.usernameLink(new UsernameLink.Builder().entropy(ByteString.of(usernameLinkComponents.getEntropy())) .serverId(UuidUtil.toByteString(usernameLinkComponents.getServerId())) .color(linkColor == null ? UsernameLink.Color.UNKNOWN : UsernameLink.Color.valueOf(linkColor)) @@ -89,7 +91,7 @@ public final class StorageSyncModels { final var builder = SignalContactRecord.Companion.newBuilder(recipient.getStorageRecord()) .aci(address.aci().map(ACI::toString).orElse("")) .e164(address.number().orElse("")) - .pni(address.pni().map(PNI::toString).orElse("")) + .pni(address.pni().map(PNI::toStringWithoutPrefix).orElse("")) .username(address.username().orElse("")) .profileKey(recipient.getProfileKey() == null ? ByteString.EMPTY diff --git a/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java index 21c8aa96..31c3cd39 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java @@ -4,7 +4,7 @@ import org.asamk.signal.manager.storage.SignalAccount; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.IdentityKeyPair; import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.ecc.Curve; +import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.ecc.ECPrivateKey; import org.signal.libsignal.protocol.kem.KEMKeyPair; import org.signal.libsignal.protocol.kem.KEMKeyType; @@ -15,7 +15,6 @@ import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.whispersystems.signalservice.api.account.PreKeyCollection; import org.whispersystems.signalservice.api.backup.MediaRootBackupKey; -import org.whispersystems.signalservice.api.kbs.MasterKey; import java.security.SecureRandom; import java.util.ArrayList; @@ -34,8 +33,8 @@ public class KeyUtils { public static IdentityKeyPair getIdentityKeyPair(byte[] publicKeyBytes, byte[] privateKeyBytes) { try { - IdentityKey publicKey = new IdentityKey(publicKeyBytes); - ECPrivateKey privateKey = Curve.decodePrivatePoint(privateKeyBytes); + final var publicKey = new IdentityKey(publicKeyBytes); + final var privateKey = new ECPrivateKey(privateKeyBytes); return new IdentityKeyPair(publicKey, privateKey); } catch (InvalidKeyException e) { @@ -44,7 +43,7 @@ public class KeyUtils { } public static IdentityKeyPair generateIdentityKeyPair() { - var djbKeyPair = Curve.generateKeyPair(); + var djbKeyPair = ECKeyPair.generate(); var djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey()); var djbPrivateKey = djbKeyPair.getPrivateKey(); @@ -55,7 +54,7 @@ public class KeyUtils { var records = new ArrayList(PREKEY_BATCH_SIZE); for (var i = 0; i < PREKEY_BATCH_SIZE; i++) { var preKeyId = (offset + i) % PREKEY_MAXIMUM_ID; - var keyPair = Curve.generateKeyPair(); + var keyPair = ECKeyPair.generate(); var record = new PreKeyRecord(preKeyId, keyPair); records.add(record); @@ -67,13 +66,9 @@ public class KeyUtils { final int signedPreKeyId, final ECPrivateKey privateKey ) { - var keyPair = Curve.generateKeyPair(); + var keyPair = ECKeyPair.generate(); byte[] signature; - try { - signature = Curve.calculateSignature(privateKey, keyPair.getPublicKey().serialize()); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } + signature = privateKey.calculateSignature(keyPair.getPublicKey().serialize()); return new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature); } @@ -109,10 +104,6 @@ public class KeyUtils { return getSecretBytes(32); } - public static MasterKey createMasterKey() { - return MasterKey.createNew(secureRandom); - } - public static MediaRootBackupKey createMediaRootBackupKey() { return new MediaRootBackupKey(getSecretBytes(32)); } diff --git a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java index e0c4b619..659a8c67 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java @@ -4,6 +4,7 @@ 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.Pair; +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; @@ -11,9 +12,9 @@ import org.asamk.signal.manager.helper.PinHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.kbs.MasterKey; +import org.whispersystems.signalservice.api.push.exceptions.ChallengeRequiredException; import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.api.push.exceptions.PushChallengeRequiredException; import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException; import org.whispersystems.signalservice.api.registration.RegistrationApi; import org.whispersystems.signalservice.internal.push.LockedException; @@ -39,8 +40,7 @@ public class NumberVerificationUtils { RegistrationSessionMetadataResponse sessionResponse; try { sessionResponse = getValidSession(registrationApi, sessionId); - } catch (PushChallengeRequiredException | - org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) { + } catch (ChallengeRequiredException e) { if (captcha != null) { sessionResponse = submitCaptcha(registrationApi, sessionId, captcha); } else { @@ -48,38 +48,38 @@ public class NumberVerificationUtils { } } - sessionId = sessionResponse.getBody().getId(); + sessionId = sessionResponse.getMetadata().getId(); sessionIdSaver.accept(sessionId); - if (sessionResponse.getBody().getVerified()) { + if (sessionResponse.getMetadata().getVerified()) { return sessionId; } - if (sessionResponse.getBody().getAllowedToRequestCode()) { + if (sessionResponse.getMetadata().getAllowedToRequestCode()) { return sessionId; } final var nextAttempt = voiceVerification - ? sessionResponse.getBody().getNextCall() - : sessionResponse.getBody().getNextSms(); + ? sessionResponse.getMetadata().getNextCall() + : sessionResponse.getMetadata().getNextSms(); if (nextAttempt == null) { throw new VerificationMethodNotAvailableException(); } else if (nextAttempt > 0) { - final var timestamp = sessionResponse.getHeaders().getTimestamp() + nextAttempt * 1000; + final var timestamp = sessionResponse.getClientReceivedAtMilliseconds() + nextAttempt * 1000; throw new RateLimitException(timestamp); } - final var nextVerificationAttempt = sessionResponse.getBody().getNextVerificationAttempt(); + final var nextVerificationAttempt = sessionResponse.getMetadata().getNextVerificationAttempt(); if (nextVerificationAttempt != null && nextVerificationAttempt > 0) { - final var timestamp = sessionResponse.getHeaders().getTimestamp() + nextVerificationAttempt * 1000; + final var timestamp = sessionResponse.getClientReceivedAtMilliseconds() + nextVerificationAttempt * 1000; throw new CaptchaRequiredException(timestamp); } - if (sessionResponse.getBody().getRequestedInformation().contains("captcha")) { + if (sessionResponse.getMetadata().getRequestedInformation().contains("captcha")) { if (captcha != null) { sessionResponse = submitCaptcha(registrationApi, sessionId, captcha); } - if (!sessionResponse.getBody().getAllowedToRequestCode()) { + if (!sessionResponse.getMetadata().getAllowedToRequestCode()) { throw new CaptchaRequiredException("Captcha Required"); } } @@ -99,7 +99,7 @@ public class NumberVerificationUtils { voiceVerification ? VerificationCodeTransport.VOICE : VerificationCodeTransport.SMS); try { Utils.handleResponseException(response); - } catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) { + } catch (ChallengeRequiredException e) { throw new CaptchaRequiredException(e.getMessage(), e); } catch (org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException e) { throw new NonNormalizedPhoneNumberException("Phone number is not normalized (" @@ -115,7 +115,7 @@ public class NumberVerificationUtils { String pin, PinHelper pinHelper, Verifier verifier - ) throws IOException, PinLockedException, IncorrectPinException { + ) throws IOException, PinLockedException, IncorrectPinException, PinLockMissingException { verificationCode = verificationCode.replace("-", ""); try { final var response = verifier.verify(sessionId, verificationCode, null); @@ -128,7 +128,7 @@ public class NumberVerificationUtils { final var registrationLockData = pinHelper.getRegistrationLockData(pin, e); if (registrationLockData == null) { - throw e; + throw new PinLockMissingException(); } var registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock(); @@ -179,9 +179,7 @@ public class NumberVerificationUtils { captcha = captcha == null ? null : captcha.replace("signalcaptcha://", ""); try { return Utils.handleResponseException(registrationApi.submitCaptchaToken(sessionId, captcha)); - } catch (PushChallengeRequiredException | - org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException | - TokenNotAcceptedException _e) { + } catch (ChallengeRequiredException | TokenNotAcceptedException _e) { throw new CaptchaRequiredException("Captcha not accepted"); } catch (NonSuccessfulResponseCodeException e) { if (e.code == 400) { diff --git a/lib/src/main/java/org/asamk/signal/manager/util/PaymentUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/PaymentUtils.java index 36275d8f..fd8e46ef 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/PaymentUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/PaymentUtils.java @@ -23,8 +23,8 @@ public class PaymentUtils { public static PaymentAddress signPaymentsAddress(byte[] publicAddressBytes, ECPrivateKey privateKey) { byte[] signature = privateKey.calculateSignature(publicAddressBytes); - return new PaymentAddress.Builder().mobileCoinAddress(new PaymentAddress.MobileCoinAddress.Builder().address( - ByteString.of(publicAddressBytes)).signature(ByteString.of(signature)).build()).build(); + return new PaymentAddress.Builder().mobileCoin(new PaymentAddress.MobileCoin.Builder().publicAddress(ByteString.of( + publicAddressBytes)).signature(ByteString.of(signature)).build()).build(); } /** @@ -33,13 +33,15 @@ public class PaymentUtils { * Returns the validated bytes if so, otherwise returns null. */ public static byte[] verifyPaymentsAddress(PaymentAddress paymentAddress, ECPublicKey publicKey) { - final var mobileCoinAddress = paymentAddress.mobileCoinAddress; - if (mobileCoinAddress == null || mobileCoinAddress.address == null || mobileCoinAddress.signature == null) { + final var mobileCoinAddress = paymentAddress.mobileCoin; + if (mobileCoinAddress == null + || mobileCoinAddress.publicAddress == null + || mobileCoinAddress.signature == null) { logger.debug("Got payment address without mobile coin address, ignoring."); return null; } - byte[] bytes = mobileCoinAddress.address.toByteArray(); + byte[] bytes = mobileCoinAddress.publicAddress.toByteArray(); byte[] signature = mobileCoinAddress.signature.toByteArray(); if (signature.length != 64 || !publicKey.verifySignature(bytes, signature)) { diff --git a/lib/src/main/java/org/asamk/signal/manager/util/PhoneNumberFormatter.java b/lib/src/main/java/org/asamk/signal/manager/util/PhoneNumberFormatter.java new file mode 100644 index 00000000..cc0c6503 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/util/PhoneNumberFormatter.java @@ -0,0 +1,59 @@ +package org.asamk.signal.manager.util; + +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat; +import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; + +import org.asamk.signal.manager.api.InvalidNumberException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PhoneNumberFormatter { + + private static final Logger logger = LoggerFactory.getLogger(PhoneNumberFormatter.class); + + private static String impreciseFormatNumber(String number, String localNumber) { + number = number.replaceAll("[^0-9+]", ""); + + if (number.charAt(0) == '+') return number; + + if (localNumber.charAt(0) == '+') localNumber = localNumber.substring(1); + + if (localNumber.length() == number.length() || number.length() > localNumber.length()) return "+" + number; + + int difference = localNumber.length() - number.length(); + + return "+" + localNumber.substring(0, difference) + number; + } + + public static String formatNumber(String number, String localNumber) throws InvalidNumberException { + if (number == null) { + throw new InvalidNumberException("Null String passed as number."); + } + + if (number.contains("@")) { + throw new InvalidNumberException("Possible attempt to use email address."); + } + + number = number.replaceAll("[^0-9+]", ""); + + if (number.isEmpty()) { + throw new InvalidNumberException("No valid characters found."); + } + + try { + PhoneNumberUtil util = PhoneNumberUtil.getInstance(); + PhoneNumber localNumberObject = util.parse(localNumber, null); + + String localCountryCode = util.getRegionCodeForNumber(localNumberObject); + logger.trace("Got local CC: {}", localCountryCode); + + PhoneNumber numberObject = util.parse(number, localCountryCode); + return util.format(numberObject, PhoneNumberFormat.E164); + } catch (NumberParseException e) { + logger.debug("{}: {}", e.getClass().getSimpleName(), e.getMessage()); + return impreciseFormatNumber(number, localNumber); + } + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java index 945b2756..daee1d77 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java @@ -7,6 +7,7 @@ import org.signal.libsignal.protocol.fingerprint.NumericFingerprintGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.NetworkResult; +import org.whispersystems.signalservice.api.NetworkResultUtil; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.util.StreamDetails; @@ -15,6 +16,10 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; @@ -150,15 +155,7 @@ public class Utils { } public static T handleResponseException(final NetworkResult response) throws IOException { - final var throwableOptional = response.getCause(); - if (throwableOptional != null) { - if (throwableOptional instanceof IOException ioException) { - throw ioException; - } else { - throw new IOException(throwableOptional); - } - } - return response.successOrThrow(); + return NetworkResultUtil.toBasicLegacy(response); } public static ByteString firstNonEmpty(ByteString... strings) { @@ -202,4 +199,19 @@ public class Utils { public static String nullIfEmpty(String string) { return string == null || string.isEmpty() ? null : string; } + + public static Proxy getHttpsProxy() { + final URI uri; + try { + uri = new URI("https://example"); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + final var proxies = ProxySelector.getDefault().select(uri); + if (proxies.isEmpty()) { + return Proxy.NO_PROXY; + } else { + return proxies.getFirst(); + } + } } diff --git a/lib/src/test/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelperTest.java b/lib/src/test/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelperTest.java index 0931b008..f2f427c1 100644 --- a/lib/src/test/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelperTest.java +++ b/lib/src/test/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelperTest.java @@ -111,18 +111,20 @@ class MergeRecipientHelperTest { new T(Set.of(rec(1, ADDR_A.ACI), rec(2, ADDR_A.PNI), rec(3, ADDR_A.NUM)), ADDR_A.FULL, Set.of(rec(1, ADDR_A.FULL))), - new T(Set.of(rec(1, ADDR_A.ACI.withIdentifiersFrom(ADDR_B.PNI)), rec(2, ADDR_A.PNI), rec(3, ADDR_A.NUM)), - ADDR_A.FULL, - Set.of(rec(1, ADDR_A.FULL))), - new T(Set.of(rec(1, ADDR_A.ACI.withIdentifiersFrom(ADDR_B.NUM)), rec(2, ADDR_A.PNI), rec(3, ADDR_A.NUM)), - ADDR_A.FULL, - Set.of(rec(1, ADDR_A.FULL))), - new T(Set.of(rec(1, ADDR_A.ACI), rec(2, ADDR_A.PNI), rec(3, ADDR_A.NUM.withIdentifiersFrom(ADDR_B.ACI))), + new T(Set.of(rec(1, ADDR_B.PNI.withOtherIdentifiersFrom(ADDR_A.ACI)), + rec(2, ADDR_A.PNI), + rec(3, ADDR_A.NUM)), ADDR_A.FULL, Set.of(rec(1, ADDR_A.FULL))), + new T(Set.of(rec(1, ADDR_B.NUM.withOtherIdentifiersFrom(ADDR_A.ACI)), + rec(2, ADDR_A.PNI), + rec(3, ADDR_A.NUM)), ADDR_A.FULL, Set.of(rec(1, ADDR_A.FULL))), + new T(Set.of(rec(1, ADDR_A.ACI), + rec(2, ADDR_A.PNI), + rec(3, ADDR_B.ACI.withOtherIdentifiersFrom(ADDR_A.NUM))), ADDR_A.FULL, Set.of(rec(1, ADDR_A.FULL), rec(3, ADDR_B.ACI))), - new T(Set.of(rec(1, ADDR_A.ACI), rec(2, ADDR_A.PNI.withIdentifiersFrom(ADDR_B.ACI)), rec(3, ADDR_A.NUM)), - ADDR_A.FULL, - Set.of(rec(1, ADDR_A.FULL), rec(2, ADDR_B.ACI))), + new T(Set.of(rec(1, ADDR_A.ACI), + rec(2, ADDR_B.ACI.withOtherIdentifiersFrom(ADDR_A.PNI)), + rec(3, ADDR_A.NUM)), ADDR_A.FULL, Set.of(rec(1, ADDR_A.FULL), rec(2, ADDR_B.ACI))), }; @ParameterizedTest diff --git a/man/Makefile b/man/Makefile index 55d27fd3..e3aca110 100644 --- a/man/Makefile +++ b/man/Makefile @@ -1,4 +1,6 @@ A2X = a2x +MKDIR = mkdir +GZIP = gzip MANPAGESRC = signal-cli.1 signal-cli-dbus.5 signal-cli-jsonrpc.5 @@ -8,3 +10,13 @@ all: $(MANPAGESRC) %: %.adoc @echo "Generating manpage for $@" $(A2X) --no-xmllint --doctype manpage --format manpage "$^" + +.PHONY: install +install: all + $(MKDIR) -p man1 man5 + for f in *.1; do $(GZIP) < "$$f" > man1/"$$f".gz ; done + for f in *.5; do $(GZIP) < "$$f" > man5/"$$f".gz ; done + +.PHONY: clean +clean: + rm -rf *.1 *.5 man1 man5 diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 4196f991..73286aac 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -316,6 +316,11 @@ Data URI encoded attachments must follow the RFC 2397. Additionally a file name can be added: e.g.: `data:;filename=;base64,` +*--view-once*:: +Send the message as a view once message. +A conformant client will only allow the receiver to view the message once. +View Once is only supported for messages that include an image attachment. + *--sticker* STICKER:: Send a sticker of a locally known sticker pack (syntax: stickerPackId:stickerId). Shouldn't be used together with `-m` as the official clients don't support this. @@ -657,7 +662,7 @@ Path to the new avatar image file. *--remove-avatar*:: Remove the avatar -*--mobile-coin-address*:: +*--mobile-coin-address*, **--mobilecoin-address**:: New MobileCoin address (Base64 encoded public address) === updateContact @@ -669,11 +674,20 @@ If the contact doesn't exist yet, it will be added. RECIPIENT:: Specify the recipient. -*--given-name* NAME, *--name* NAME:: -New (given) name. +*--given-name* GIVEN_NAME, *--name* NAME:: +New system given name. *--family-name* FAMILY_NAME:: -New family name. +New system family name. + +*--nick-given-name* NICK_GIVEN_NAME:: +New nick given name. + +*--nick-family-name* NICK_FAMILY_NAME:: +New nick family name. + +*--note* NOTE:: +New note. *-e*, *--expiration* EXPIRATION_SECONDS:: Set expiration time of messages (seconds). diff --git a/run_tests.sh b/run_tests.sh index 036ba370..339d9d7b 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash if [ $# -ne 2 ]; then echo "Usage: $0 NUMBER_1 NUMBER_2" exit 1 @@ -133,7 +133,7 @@ mkfifo "$FIFO_FILE" run_main -a "$NUMBER_1" send "$NUMBER_2" -m hi run_main -a "$NUMBER_2" jsonRpc < "$FIFO_FILE" & -exec 3<> "$FIFO_FILE" +exec 3> "$FIFO_FILE" echo '{"jsonrpc":"2.0","id":"id","method":"updateContact","params":{"recipient":"'"$NUMBER_1"'","name":"NUMBER_1","expiration":10}}' >&3 echo '{"jsonrpc":"2.0","id":5,"method":"block","params":{"recipient":"'"$NUMBER_1"'"}}' >&3 echo '{"jsonrpc":"2.0","id":null,"method":"unblock","params":{"recipient":"'"$NUMBER_1"'"}}' >&3 @@ -141,6 +141,7 @@ exec 3<> "$FIFO_FILE" echo '{"jsonrpc":"2.0","id":"id","method":"listGroups"}' >&3 echo '{"jsonrpc":"2.0","id":"id","method":"listDevices"}' >&3 echo '{"jsonrpc":"2.0","id":"id","method":"listIdentities"}' >&3 + echo '{"jsonrpc":"2.0","id":"id","method":"listStickerPacks"}' >&3 echo '{"jsonrpc":"2.0","id":"id","method":"sendSyncRequest"}' >&3 echo '{"jsonrpc":"2.0","id":"id","method":"sendContacts"}' >&3 echo '{"jsonrpc":"2.0","id":"id","method":"version"}' >&3 @@ -175,6 +176,17 @@ run_main -a "$NUMBER_2" receive run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi run_main -a "$NUMBER_1" receive run_main -a "$NUMBER_2" receive +run_main -a "$NUMBER_1" updateAccount --discoverable-by-number=true +run_main -a "$NUMBER_2" removeContact --forget "$NUMBER_1" +run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi +run_main -a "$NUMBER_2" send "$NUMBER_1" -m hii +run_main -a "$NUMBER_1" updateAccount --discoverable-by-number=false +run_main -a "$NUMBER_1" receive +run_main -a "$NUMBER_2" receive +run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi +run_main -a "$NUMBER_2" send "$NUMBER_1" -m hii +run_main -a "$NUMBER_1" receive +run_main -a "$NUMBER_2" receive ## Groups GROUP_ID=$(run_main -a "$NUMBER_1" --output=json updateGroup -n GRUPPE -a LICENSE -m "$NUMBER_1" | jq -r '.groupId') run_main -a "$NUMBER_1" send "$NUMBER_2" -m first @@ -237,7 +249,9 @@ for OUTPUT in "plain-text" "json"; do run_linked -a "$NUMBER_1" --output="$OUTPUT" receive done -run_main -a "$NUMBER_1" removeDevice -d 2 +run_main -a "$NUMBER_1" --output="$OUTPUT" receive +run_main -a "$NUMBER_1" removeDevice -d 2 || true +run_main -a "$NUMBER_1" removeDevice -d 2 || true ## Unregister if [ "$TEST_REGISTER" -eq 1 ]; then diff --git a/settings.gradle.kts b/settings.gradle.kts index fe7c45a9..45e6390c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,4 +6,6 @@ dependencyResolutionManagement { } rootProject.name = "signal-cli" -include("lib") + +include("libsignal-cli") +project(":libsignal-cli").projectDir = file("lib") diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index d3d74c3e..6352aa13 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -99,6 +99,9 @@ public class App { .help("Disable message send log (for resending messages that recipient couldn't decrypt)") .action(Arguments.storeTrue()); + parser.epilog( + "The global arguments are shown with 'signal-cli -h' and need to come before the subcommand, while the subcommand-specific arguments (shown with 'signal-cli SUBCOMMAND -h') need to be given after the subcommand."); + var subparsers = parser.addSubparsers().title("subcommands").dest("command"); Commands.getCommandSubparserAttachers().forEach((key, value) -> { @@ -288,6 +291,8 @@ public class App { commandHandler.handleMultiLocalCommand(command, multiAccountManager); } catch (IOException e) { throw new IOErrorException("Failed to load local accounts file", e); + } catch (AccountCheckException e) { + throw new UnexpectedErrorException("Failed to load account file", e); } } diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index e25bfa14..730485bc 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/7.26.1"); + .orElse("Signal-Android/7.47.1"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; diff --git a/src/main/java/org/asamk/signal/Shutdown.java b/src/main/java/org/asamk/signal/Shutdown.java index a59497c9..c51c12e4 100644 --- a/src/main/java/org/asamk/signal/Shutdown.java +++ b/src/main/java/org/asamk/signal/Shutdown.java @@ -1,5 +1,6 @@ package org.asamk.signal; +import org.asamk.signal.commands.exceptions.CommandException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,7 +12,7 @@ import sun.misc.Signal; public class Shutdown { private static final Logger logger = LoggerFactory.getLogger(Shutdown.class); - private static final CompletableFuture shutdown = new CompletableFuture<>(); + private static final CompletableFuture shutdown = new CompletableFuture<>(); private static final CompletableFuture shutdownComplete = new CompletableFuture<>(); private static boolean initialized = false; @@ -43,9 +44,17 @@ public class Shutdown { shutdown.complete(null); } - public static void waitForShutdown() throws InterruptedException { + public static void triggerShutdown(CommandException exception) { + logger.debug("Triggering shutdown with exception.", exception); + shutdown.complete(exception); + } + + public static void waitForShutdown() throws InterruptedException, CommandException { try { - shutdown.get(); + final var result = shutdown.get(); + if (result instanceof CommandException e) { + throw e; + } } catch (ExecutionException e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java index 197e1286..6790c65d 100644 --- a/src/main/java/org/asamk/signal/commands/DaemonCommand.java +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -97,7 +97,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand { final OutputWriter outputWriter ) throws CommandException { Shutdown.installHandler(); - logger.info("Starting daemon in single-account mode for " + m.getSelfNumber()); + logger.info("Starting daemon in single-account mode for {}", m.getSelfNumber()); final var noReceiveStdOut = Boolean.TRUE.equals(ns.getBoolean("no-receive-stdout")); final var receiveMode = ns.get("receive-mode"); final var receiveConfig = getReceiveConfig(ns); diff --git a/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java b/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java index 8fce4dbf..1242c194 100644 --- a/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java +++ b/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java @@ -9,6 +9,7 @@ import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.IncorrectPinException; import org.asamk.signal.manager.api.NotPrimaryDeviceException; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.output.OutputWriter; @@ -50,6 +51,8 @@ public class FinishChangeNumberCommand implements JsonRpcLocalCommand { + "\nUse '--pin PIN_CODE' to specify the registration lock PIN"); } catch (IncorrectPinException e) { throw new UserErrorException("Verification failed! Invalid pin, tries remaining: " + e.getTriesRemaining()); + } catch (PinLockMissingException e) { + throw new UserErrorException("Account is pin locked, but pin data has been deleted on the server."); } catch (NotPrimaryDeviceException e) { throw new UserErrorException("This command doesn't work on linked devices."); } catch (IOException e) { diff --git a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java index e3d36d85..eab4155c 100644 --- a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java +++ b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java @@ -64,9 +64,16 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand { } final var usernames = ns.getList("username"); - final var registeredUsernames = usernames == null - ? Map.of() - : m.getUsernameStatus(new HashSet<>(usernames)); + final Map registeredUsernames; + try { + registeredUsernames = usernames == null ? Map.of() : m.getUsernameStatus(new HashSet<>(usernames)); + } catch (IOException e) { + throw new IOErrorException("Unable to check if users are registered: " + + e.getMessage() + + " (" + + e.getClass().getSimpleName() + + ")", e); + } // Output switch (outputWriter) { diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index 9784171a..98051b23 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -66,6 +66,9 @@ public class SendCommand implements JsonRpcLocalCommand { .help("Add an attachment. " + "Can be either a file path or a data URI. Data URI encoded attachments must follow the RFC 2397. Additionally a file name can be added, e.g. " + "data:;filename=;base64,."); + subparser.addArgument("--view-once") + .action(Arguments.storeTrue()) + .help("Send the message as a view once message"); subparser.addArgument("-e", "--end-session", "--endsession") .help("Clear session state and send end session message.") .action(Arguments.storeTrue()); @@ -164,6 +167,7 @@ public class SendCommand implements JsonRpcLocalCommand { if (attachments == null) { attachments = List.of(); } + final var viewOnce = Boolean.TRUE.equals(ns.getBoolean("view-once")); final var selfNumber = m.getSelfNumber(); @@ -179,6 +183,9 @@ public class SendCommand implements JsonRpcLocalCommand { final var quoteTimestamp = ns.getLong("quote-timestamp"); if (quoteTimestamp != null) { final var quoteAuthor = ns.getString("quote-author"); + if (quoteAuthor == null) { + throw new UserErrorException("Quote author parameter is missing"); + } final var quoteMessage = ns.getString("quote-message"); final var quoteMentionStrings = ns.getList("quote-mention"); final var quoteMentions = quoteMentionStrings == null @@ -236,6 +243,7 @@ public class SendCommand implements JsonRpcLocalCommand { try { final var message = new Message(messageText, attachments, + viewOnce, mentions, Optional.ofNullable(quote), Optional.ofNullable(sticker), @@ -247,8 +255,14 @@ public class SendCommand implements JsonRpcLocalCommand { : m.sendMessage(message, recipientIdentifiers, notifySelf); outputResult(outputWriter, results); } catch (AttachmentInvalidException | IOException e) { - throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() - .getSimpleName() + ")", e); + if (e instanceof IOException io && io.getMessage().contains("No prekeys available")) { + throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() + .getSimpleName() + "), maybe one of the devices of the recipient wasn't online for a while.", + e); + } else { + throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() + .getSimpleName() + ")", e); + } } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { throw new UserErrorException(e.getMessage()); } catch (UnregisteredRecipientException e) { diff --git a/src/main/java/org/asamk/signal/commands/UpdateContactCommand.java b/src/main/java/org/asamk/signal/commands/UpdateContactCommand.java index 815c15da..312ac8e8 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateContactCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateContactCommand.java @@ -26,8 +26,11 @@ public class UpdateContactCommand implements JsonRpcLocalCommand { subparser.help("Update the details of a given contact"); subparser.addArgument("recipient").help("Contact number"); subparser.addArgument("-n", "--name").help("New contact name"); - subparser.addArgument("--given-name").help("New contact given name"); - subparser.addArgument("--family-name").help("New contact family name"); + subparser.addArgument("--given-name").help("New system given name"); + subparser.addArgument("--family-name").help("New system family name"); + subparser.addArgument("--nick-given-name").help("New nick given name"); + subparser.addArgument("--nick-family-name").help("New nick family name"); + subparser.addArgument("--note").help("New note"); subparser.addArgument("-e", "--expiration").type(int.class).help("Set expiration time of messages (seconds)"); } @@ -54,8 +57,15 @@ public class UpdateContactCommand implements JsonRpcLocalCommand { familyName = ""; } } - if (givenName != null || familyName != null) { - m.setContactName(recipient, givenName, familyName); + var nickGivenName = ns.getString("nick-given-name"); + var nickFamilyName = ns.getString("nick-family-name"); + var note = ns.getString("note"); + if (givenName != null + || familyName != null + || nickGivenName != null + || nickFamilyName != null + || note != null) { + m.setContactName(recipient, givenName, familyName, nickGivenName, nickFamilyName, note); } } catch (IOException e) { throw new IOErrorException("Update contact error: " + e.getMessage(), e); diff --git a/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java b/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java index b122f58c..7a3281fe 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java @@ -27,7 +27,8 @@ public class UpdateProfileCommand implements JsonRpcLocalCommand { subparser.addArgument("--family-name").help("New profile family name (optional)"); subparser.addArgument("--about").help("New profile about text"); subparser.addArgument("--about-emoji").help("New profile about emoji"); - subparser.addArgument("--mobile-coin-address").help("New MobileCoin address (Base64 encoded public address)"); + subparser.addArgument("--mobile-coin-address", "--mobilecoin-address") + .help("New MobileCoin address (Base64 encoded public address)"); final var avatarOptions = subparser.addMutuallyExclusiveGroup(); avatarOptions.addArgument("--avatar").help("Path to new profile avatar"); diff --git a/src/main/java/org/asamk/signal/commands/VerifyCommand.java b/src/main/java/org/asamk/signal/commands/VerifyCommand.java index a8543c5a..6a5046bc 100644 --- a/src/main/java/org/asamk/signal/commands/VerifyCommand.java +++ b/src/main/java/org/asamk/signal/commands/VerifyCommand.java @@ -11,6 +11,7 @@ import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.RegistrationManager; import org.asamk.signal.manager.api.IncorrectPinException; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.output.JsonWriter; import org.slf4j.Logger; @@ -76,6 +77,8 @@ public class VerifyCommand implements RegistrationCommand, JsonRpcRegistrationCo + "\nUse '--pin PIN_CODE' to specify the registration lock PIN"); } catch (IncorrectPinException e) { throw new UserErrorException("Verification failed! Invalid pin, tries remaining: " + e.getTriesRemaining()); + } catch (PinLockMissingException e) { + throw new UserErrorException("Account is pin locked, but pin data has been deleted on the server."); } catch (IOException e) { throw new IOErrorException("Verify error: " + e.getMessage(), e); } diff --git a/src/main/java/org/asamk/signal/dbus/DbusHandler.java b/src/main/java/org/asamk/signal/dbus/DbusHandler.java index b6799de7..2ed6d11f 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusHandler.java +++ b/src/main/java/org/asamk/signal/dbus/DbusHandler.java @@ -1,17 +1,21 @@ package org.asamk.signal.dbus; import org.asamk.signal.DbusConfig; +import org.asamk.signal.Shutdown; import org.asamk.signal.commands.exceptions.CommandException; +import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.UnexpectedErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.MultiAccountManager; +import org.freedesktop.dbus.connections.IDisconnectCallback; import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder; import org.freedesktop.dbus.exceptions.DBusException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -94,7 +98,9 @@ public class DbusHandler implements AutoCloseable { final var busType = isDbusSystem ? DBusConnection.DBusBusType.SYSTEM : DBusConnection.DBusBusType.SESSION; logger.debug("Starting DBus server on {} bus: {}", busType, busname); try { - dBusConnection = DBusConnectionBuilder.forType(busType).build(); + dBusConnection = DBusConnectionBuilder.forType(busType) + .withDisconnectCallback(new DisconnectCallback()) + .build(); dbusRunner.run(dBusConnection); } catch (DBusException e) { throw new UnexpectedErrorException("Dbus command failed: " + e.getMessage(), e); @@ -141,4 +147,13 @@ public class DbusHandler implements AutoCloseable { void run(DBusConnection connection) throws DBusException; } + + private static final class DisconnectCallback implements IDisconnectCallback { + + @Override + public void disconnectOnError(IOException ex) { + logger.debug("DBus daemon disconnected unexpectedly, shutting down"); + Shutdown.triggerShutdown(new IOErrorException("Unexpected dbus daemon disconnect", ex)); + } + } } diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index 159407e1..5658d0d3 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -516,7 +516,10 @@ public class DbusManagerImpl implements Manager { public void setContactName( final RecipientIdentifier.Single recipient, final String givenName, - final String familyName + final String familyName, + final String nickGivenName, + final String nickFamilyName, + final String note ) throws NotPrimaryDeviceException { signal.setContactName(recipient.getIdentifier(), givenName); } diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java index cf6999a9..50150a98 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java @@ -10,6 +10,7 @@ import org.asamk.signal.manager.RegistrationManager; 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.UserAlreadyExistsException; @@ -105,6 +106,8 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { + (e.getTimeRemaining() / 1000 / 60 / 60)); } catch (IncorrectPinException e) { throw new Error.Failure("Verification failed! Invalid pin, tries remaining: " + e.getTriesRemaining()); + } catch (PinLockMissingException e) { + throw new Error.Failure("Account is pin locked, but pin data has been deleted on the server."); } } diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index bbae26b5..725df8b1 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -236,6 +236,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable { try { final var message = new Message(messageText, attachments, + false, List.of(), Optional.empty(), Optional.empty(), @@ -399,6 +400,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable { try { final var message = new Message(messageText, attachments, + false, List.of(), Optional.empty(), Optional.empty(), @@ -444,6 +446,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable { try { final var message = new Message(messageText, attachments, + false, List.of(), Optional.empty(), Optional.empty(), @@ -531,7 +534,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable { @Override public void setContactName(final String number, final String name) { try { - m.setContactName(getSingleRecipientIdentifier(number, m.getSelfNumber()), name, ""); + m.setContactName(getSingleRecipientIdentifier(number, m.getSelfNumber()), name, "", null, null, null); } catch (NotPrimaryDeviceException e) { throw new Error.Failure("This command doesn't work on linked devices."); } catch (UnregisteredRecipientException e) { diff --git a/src/main/java/org/asamk/signal/http/HttpServerHandler.java b/src/main/java/org/asamk/signal/http/HttpServerHandler.java index e1780665..542d928b 100644 --- a/src/main/java/org/asamk/signal/http/HttpServerHandler.java +++ b/src/main/java/org/asamk/signal/http/HttpServerHandler.java @@ -156,7 +156,7 @@ public class HttpServerHandler implements AutoCloseable { } try { - final var queryString = httpExchange.getRequestURI().getQuery(); + final var queryString = httpExchange.getRequestURI().getRawQuery(); final var query = queryString == null ? Map.of() : Util.getQueryMap(queryString); List managers = getManagerFromQuery(query); diff --git a/src/main/java/org/asamk/signal/jsonrpc/JsonRpcReader.java b/src/main/java/org/asamk/signal/jsonrpc/JsonRpcReader.java index 84805e10..327342a2 100644 --- a/src/main/java/org/asamk/signal/jsonrpc/JsonRpcReader.java +++ b/src/main/java/org/asamk/signal/jsonrpc/JsonRpcReader.java @@ -151,6 +151,13 @@ public class JsonRpcReader { } private JsonRpcMessage parseJsonRpcMessage(final String input) { + if (input.trim().isEmpty()) { + jsonRpcSender.sendResponse(JsonRpcResponse.forError(new JsonRpcResponse.Error(JsonRpcResponse.Error.PARSE_ERROR, + "Empty input line", + null), null)); + return null; + } + final JsonNode jsonNode; try { jsonNode = objectMapper.readTree(input);