From 14043da7481d79054a1b00db84389845e562b8e7 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Tue, 14 Oct 2025 08:14:52 +0000 Subject: [PATCH 01/25] github/workflows: macos-13 -> macos-15-intel (#6362) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 04c70546fb..a86d4790a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -373,7 +373,7 @@ jobs: cli_asset_name: simplex-chat-macos-aarch64 desktop_asset_name: simplex-desktop-macos-aarch64.dmg openssl_dir: "/opt/homebrew/opt" - - os: macos-13 + - os: macos-15-intel ghc: ${{ needs.variables.outputs.GHC_VER }} cli_asset_name: simplex-chat-macos-x86-64 desktop_asset_name: simplex-desktop-macos-x86_64.dmg From e432f7a060ff4e4d0434396daac5a71654b7b6dc Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Sat, 20 Dec 2025 17:19:00 +0000 Subject: [PATCH 02/25] scripts/simplex-chat-reproduce-builds-android: add script (#6497) * script/simplex-chat-reproduce-builds-android: initial structure * Dockerfile.build: add dependencies * scripts/build-android: allow setting custom vercode * scripts/simplex-chat-reproduce-builds-android: populate functions * Dockerfile.build: setup regular user * Dockerfile.build: fix env * Dockerfile.build: switch user to ubuntu * Dockerfile.build: set USER variable * Dockerfile.build: create ubuntu user (aarch64 doesn't have it) * ci/build: remove permissions workaround * Dockerfile.build: fix groupadd * ci/build: adjust permissions before build * Dockerfile.build: allow to dynamically set user/group * ci/build: set uid and gid in Docker * ci/build: remove unneeded step * Dockerfile.build: also create /out * sync changes, add debugging * add verification function, fixes * Dockerfile: add android scripts * fixes, remove debugging * more fixes * fix download apk and add message at the end * scripts/simplex-chat-reproduce-builds.sh: add user uid and gid * fix vercode * add logging * refactor and make vars saner * fixes --- .github/workflows/build.yml | 9 +- Dockerfile.build | 37 ++- scripts/android/build-android.sh | 7 +- .../simplex-chat-reproduce-builds-android.sh | 253 ++++++++++++++++++ scripts/simplex-chat-reproduce-builds.sh | 2 + 5 files changed, 298 insertions(+), 10 deletions(-) create mode 100755 scripts/simplex-chat-reproduce-builds-android.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a86d4790a8..f73bfa7927 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -147,6 +147,12 @@ jobs: with: swap-size-gb: 30 + - name: Get UID and GID + id: ids + run: | + echo "uid=$(id -u)" >> $GITHUB_OUTPUT + echo "gid=$(id -g)" >> $GITHUB_OUTPUT + # Otherwise we run out of disk space with Docker build - name: Free disk space if: matrix.should_run == true @@ -177,6 +183,8 @@ jobs: build-args: | TAG=${{ matrix.os }} GHC=${{ matrix.ghc }} + USER_UID=${{ steps.ids.outputs.uid }} + USER_GID=${{ steps.ids.outputs.gid }} # Docker needs these flags for AppImage build: # --device /dev/fuse @@ -209,7 +217,6 @@ jobs: if: matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | - chmod -R 777 dist-newstyle ~/.cabal && git config --global --add safe.directory '*' cabal clean cabal update cabal build -j --enable-tests diff --git a/Dockerfile.build b/Dockerfile.build index fddc96b6c2..3ddff59d12 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -13,6 +13,10 @@ ARG JAVA_HASH_ARM64=2b460859b681757b33a7591b6238ecaf51569d05d2684984e5f0a89c6514 ENV TZ=Etc/UTC \ DEBIAN_FRONTEND=noninteractive +ARG USER_UID=1000 +ARG USER_GID=1000 +ARG USER_NAME=builder + # Install curl, git and and simplex-chat dependencies RUN apt-get update && \ apt-get install -y curl \ @@ -38,6 +42,11 @@ RUN apt-get update && \ file \ appstream \ gpg \ + zipalign \ + apksigner \ + python3 \ + python3-venv \ + xz-utils \ unzip &&\ ln -s /bin/fusermount /bin/fusermount3 || : @@ -67,6 +76,12 @@ RUN export JAVA_FILENAME='java-corretto.deb' \ echo "Checksum mismatch" && exit 1; \ fi +RUN userdel -r ubuntu || : +RUN groupadd -g ${USER_GID} ${USER_NAME} || :; useradd -u ${USER_UID} -g ${USER_GID} --create-home --shell /bin/bash ${USER_NAME} || : +RUN mkdir /nix /out && chown ${USER_NAME}:${USER_NAME} /nix /out +USER ${USER_NAME} +WORKDIR /home/${USER_NAME} + # Specify bootstrap Haskell versions ENV BOOTSTRAP_HASKELL_GHC_VERSION=${GHC} ENV BOOTSTRAP_HASKELL_CABAL_VERSION=${CABAL} @@ -78,8 +93,10 @@ ENV BOOTSTRAP_HASKELL_INSTALL_NO_STACK_HOOK=true # Install ghcup RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 sh +# Setup basic env variables (required) +ENV HOME="/home/${USER_NAME}" USER="${USER_NAME}" # Adjust PATH -ENV PATH="/root/.cabal/bin:/root/.ghcup/bin:$PATH" +ENV PATH="$HOME/.cabal/bin:$HOME/.ghcup/bin:$PATH" # Set both as default RUN ghcup set ghc "${GHC}" && \ @@ -90,8 +107,8 @@ RUN ghcup set ghc "${GHC}" && \ #===================== ARG SDK_VERSION=13114758 -ENV SDK_VERSION=$SDK_VERSION \ - ANDROID_HOME=/root +ENV SDK_VERSION="$SDK_VERSION" \ + ANDROID_HOME="$HOME" RUN curl -L -o tools.zip "https://dl.google.com/android/repository/commandlinetools-linux-${SDK_VERSION}_latest.zip" && \ unzip tools.zip && rm tools.zip && \ @@ -101,11 +118,17 @@ RUN curl -L -o tools.zip "https://dl.google.com/android/repository/commandlineto ENV PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/cmdline-tools/tools/bin" # https://askubuntu.com/questions/885658/android-sdk-repositories-cfg-could-not-be-loaded -RUN mkdir -p ~/.android ~/.gradle && \ - touch ~/.android/repositories.cfg && \ - echo 'org.gradle.console=plain' > ~/.gradle/gradle.properties &&\ +RUN mkdir -p "$HOME/.android" "$HOME/.gradle" && \ + touch "$HOME/.android/repositories.cfg" && \ + echo 'org.gradle.console=plain' > "$HOME/.gradle/gradle.properties" &&\ yes | sdkmanager --licenses >/dev/null -ENV PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools +ENV PATH="$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools" + +# Android reproducibility scripts +RUN python3 -m venv "$HOME/.venv" +RUN "$HOME/.venv/bin/pip" install apksigcopier repro-apk + +ENV PATH="$HOME/.venv/bin:$PATH" WORKDIR /project diff --git a/scripts/android/build-android.sh b/scripts/android/build-android.sh index 90d6092385..afd13011c9 100755 --- a/scripts/android/build-android.sh +++ b/scripts/android/build-android.sh @@ -102,6 +102,7 @@ build() { sed -i.bak 's/jniLibs.useLegacyPackaging =.*/jniLibs.useLegacyPackaging = true/' "$folder/apps/multiplatform/android/build.gradle.kts" sed -i.bak '/android {/a lint {abortOnError = false}' "$folder/apps/multiplatform/android/build.gradle.kts" sed -i.bak '/tasks/Q' "$folder/apps/multiplatform/android/build.gradle.kts" + sed -i.bak "s/android.version_code=.*/android.version_code=${vercode}/" "$folder/apps/multiplatform/gradle.properties" for arch in $arches; do if [ "$arch" = "armv7a" ]; then @@ -169,8 +170,10 @@ pre() { done shift $(( $OPTIND - 1 )) - - commit="${1:-HEAD}" + + vercode="${1}" + + commit="${2:-HEAD}" } main() { diff --git a/scripts/simplex-chat-reproduce-builds-android.sh b/scripts/simplex-chat-reproduce-builds-android.sh new file mode 100755 index 0000000000..ac026f95cf --- /dev/null +++ b/scripts/simplex-chat-reproduce-builds-android.sh @@ -0,0 +1,253 @@ +#!/usr/bin/env sh +set -eu + +SIMPLEX_KEY='3C:52:C4:FD:3C:AD:1C:07:C9:B0:0A:70:80:E3:58:FA:B9:FE:FC:B8:AF:5A:EC:14:77:65:F1:6D:0F:21:AD:85' + +REPO_NAME="simplex-chat" +REPO="https://github.com/simplex-chat/${REPO_NAME}" + +IMAGE_NAME='sx-local-android' +CONTAINER_NAME='sx-builder-android' +DOCKER_PATH_PROJECT='/project' +DOCKER_PATH_VERIFY='/verify' + +export DOCKER_BUILDKIT=1 + +SIMPLEX_REPO='simplex-chat/simplex-chat' +CMDS="curl git docker" + +INIT_DIR="$PWD" +TEMPDIR="$(mktemp -d)" + +ARCHES="${ARCHES:-aarch64 armv7a}" + +COLOR_CYAN="\033[36m" +COLOR_RESET="\033[0m" + +SUFFIX_BUILT='built' +SUFFIX_DOWNLOADED='downloaded' +SUFFIX_BUILT_WITH_SIGNATURE='built-with-downloaded-signature' + +cleanup() { + rm -rf -- "${TEMPDIR}" + docker rm --force "${CONTAINER_NAME}" 2>/dev/null || : + docker image rm "${IMAGE_NAME}" 2>/dev/null || : +} +trap 'cleanup' EXIT INT + +check() { + set +u + + for i in $commands; do + case $i in + *) + if ! command -v "$i" > /dev/null 2>&1; then + commands_failed="$i $commands_failed" + fi + ;; + esac + done + + if [ -n "$commands_failed" ]; then + commands_failed=${commands_failed% *} + printf "%s is not found in your \$PATH. Please install them and re-run the script.\n" "$commands_failed" + exit 1 + fi + + set -u +} + +download_apk() { + tag="$1" + filename="$2" + file_out="$3" + + curl -L "${REPO}/releases/download/${tag}/${filename}" -o "$file_out" +} + +setup_git() { + workdir="$1" + name="$2" + + git -C "$workdir" clone "${REPO}.git" "$name" +} + +checkout_git() { + git_dir="$1" + tag="$2" + + git -C "$git_dir" reset --hard + git -C "$git_dir" clean -dfx + git -C "$git_dir" checkout "$tag" +} + +check_apk() { + apk_name="$1" + expected="$2" + + actual=$(docker exec "${CONTAINER_NAME}" apksigner verify --print-certs "${DOCKER_PATH_VERIFY}/${apk_name}" | grep 'SHA-256' | awk '{print $NF}' | fold -w2 | paste -sd: | tr '[:lower:]' '[:upper:]') + + if [ "$expected" = "$actual" ]; then + return 0 + else + return 1 + fi +} + +verify_apk() { + apk_name="$1" + + # https://github.com/obfusk/apksigcopier?tab=readme-ov-file#what-about-signatures-made-by-apksigner-from-build-tools--3500-rc1 + docker exec "${CONTAINER_NAME}" repro-apk zipalign --page-size 16 --pad-like-apksigner --replace "${DOCKER_PATH_VERIFY}/${apk_name}.${SUFFIX_BUILT}" \ + "${DOCKER_PATH_VERIFY}/${apk_name}.aligned" + docker exec "${CONTAINER_NAME}" mv "${DOCKER_PATH_VERIFY}/${apk_name}.aligned" \ + "${DOCKER_PATH_VERIFY}/${apk_name}.${SUFFIX_BUILT}" + + docker exec "${CONTAINER_NAME}" apksigcopier copy "${DOCKER_PATH_VERIFY}/${apk_name}.${SUFFIX_DOWNLOADED}" \ + "${DOCKER_PATH_VERIFY}/${apk_name}.${SUFFIX_BUILT}" \ + "${DOCKER_PATH_VERIFY}/${apk_name}.${SUFFIX_BUILT_WITH_SIGNATURE}" + + downloaded_apk_hash=$(docker exec "${CONTAINER_NAME}" sha256sum "${DOCKER_PATH_VERIFY}/${apk_name}.${SUFFIX_DOWNLOADED}" | awk '{print $1}') + built_apk_hash=$(docker exec "${CONTAINER_NAME}" sha256sum "${DOCKER_PATH_VERIFY}/${apk_name}.${SUFFIX_BUILT_WITH_SIGNATURE}" | awk '{print $1}') + + if [ "$downloaded_apk_hash" = "$built_apk_hash" ]; then + return 0 + else + return 1 + fi +} + +print_vercode() { + build_dir="$1" + awk -F'=' '/android.version_code=/ {print $2}' "${build_dir}/apps/multiplatform/gradle.properties" +} + +setup_container() { + dir_git="$1" + dir_apk="$2" + + docker build \ + --no-cache \ + -f "${dir_git}/Dockerfile.build" \ + -t "${IMAGE_NAME}" \ + --build-arg=USER_UID="$(id -u)" \ + --build-arg=USER_GID="$(id -g)" \ + . + + # Run container in background + docker run -t -d \ + --name "${CONTAINER_NAME}" \ + --device /dev/fuse \ + --cap-add SYS_ADMIN \ + --security-opt apparmor:unconfined \ + --security-opt seccomp:unconfined \ + -v "${dir_git}:${DOCKER_PATH_PROJECT}" \ + -v "${dir_apk}:${DOCKER_PATH_VERIFY}" \ + "${IMAGE_NAME}" +} + +build_apk() { + arch="$1" + vercode="$2" + + apk_out="simplex-${arch}.apk.${SUFFIX_BUILT}" + + # Gradle setup + docker exec -i "${CONTAINER_NAME}" sh << EOF +cd $DOCKER_PATH_PROJECT/apps/multiplatform +./gradlew +EOF + + docker exec -i "${CONTAINER_NAME}" sh << EOF +GRADLE_BIN=\$(find \$HOME/.gradle/wrapper/dists -name "gradle" -type f -executable 2>/dev/null | head -1) +GRADLE_DIR=\$(dirname "\$GRADLE_BIN") +export PATH="\$GRADLE_DIR:\$PATH" + +ARCHES="$arch" ./scripts/android/build-android.sh -gs "$vercode" || ARCHES="$arch" ./scripts/android/build-android.sh -gs "$vercode" + +APK_FILE=\$(find . -maxdepth 1 -type f -name '*.apk') + +mv "\$APK_FILE" $DOCKER_PATH_VERIFY/$apk_out +EOF +} + +main() { + tag="$1" + + build_directory="${TEMPDIR}/${REPO_NAME}" + final_directory="$INIT_DIR/${tag}-${REPO_NAME}" + apk_directory="${final_directory}/android" + + printf 'This script will: +1) build docker container. +2) download APK from GitHub and validate signatures. +3) build core library with nix (12-24 hours). +4) build APK and compare with downloaded one + +Continue?' + + read _ + + check + + mkdir -p "${apk_directory}" + + # Setup initial git for Dockerfile.build + setup_git "$TEMPDIR" "$REPO_NAME" + checkout_git "$build_directory" "$tag" + + printf "${COLOR_CYAN}Building Docker container...${COLOR_RESET}\n" + setup_container "$build_directory" "$apk_directory" + + # Check phase + for arch in $ARCHES; do + filename="simplex-${arch}.apk" + + download_apk "$tag" "$filename" "${apk_directory}/${filename}.${SUFFIX_DOWNLOADED}" + + if check_apk "${filename}.${SUFFIX_DOWNLOADED}" "$SIMPLEX_KEY"; then + printf "${COLOR_CYAN}APK for %s is signed by valid key.${COLOR_RESET}\n" "$arch" + else + printf "${COLOR_CYAN}Signature of APK for %s is invalid., aborting the script.${COLOR_RESET}\n" "$arch" + exit 1 + fi + done + + # Build phase + for arch in $ARCHES; do + case "$arch" in + armv7a) + build_tag="${tag}-armv7a" + ;; + aarch64) + build_tag="${tag}" + ;; + *) + printf "${COLOR_CYAN}Unknown architecture: %s! Skipping the build...${COLOR_RESET}\n" "$arch" + continue + esac + + # Setup the code + checkout_git "$build_directory" "$build_tag" + vercode=$(print_vercode "$build_directory") + + printf "${COLOR_CYAN}Building APK for for %s...${COLOR_RESET}\n" "$arch" + build_apk "$arch" "$vercode" + done + + # Verification phase + for arch in $ARCHES; do + filename="simplex-${arch}.apk" + + if ! verify_apk "$filename"; then + printf "${COLOR_CYAN}Failed to verify %s! Aborting.\n${COLOR_RESET}" "$filename" + exit 1 + fi + done + + printf "${COLOR_CYAN}%s is reproducible.${COLOR_RESET}\n" "$tag" + + cleanup +} + +main "$@" diff --git a/scripts/simplex-chat-reproduce-builds.sh b/scripts/simplex-chat-reproduce-builds.sh index e1a62dc73a..0ca2522fa0 100755 --- a/scripts/simplex-chat-reproduce-builds.sh +++ b/scripts/simplex-chat-reproduce-builds.sh @@ -50,6 +50,8 @@ for os in '22.04' '24.04'; do --no-cache \ --build-arg TAG="${os}" \ --build-arg GHC="${ghc}" \ + --build-arg=USER_UID="$(id -u)" \ + --build-arg=USER_GID="$(id -g)" \ -f "${tempdir}/${repo_name}/Dockerfile.build" \ -t "${image_name}" \ . From 4f65763d59cda9f6d7ffc4deecbe83fc1f428a91 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:01:27 +0000 Subject: [PATCH 03/25] scripts/simplex-chat-reproduce-builds-android: fixes (#6523) * scripts/simplex-chat-reproduce-builds-android: fix check * drop the case --- scripts/simplex-chat-reproduce-builds-android.sh | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/scripts/simplex-chat-reproduce-builds-android.sh b/scripts/simplex-chat-reproduce-builds-android.sh index ac026f95cf..0cef17c091 100755 --- a/scripts/simplex-chat-reproduce-builds-android.sh +++ b/scripts/simplex-chat-reproduce-builds-android.sh @@ -36,16 +36,14 @@ cleanup() { trap 'cleanup' EXIT INT check() { + commands="$1" + set +u for i in $commands; do - case $i in - *) - if ! command -v "$i" > /dev/null 2>&1; then - commands_failed="$i $commands_failed" - fi - ;; - esac + if ! command -v "$i" > /dev/null 2>&1; then + commands_failed="$i $commands_failed" + fi done if [ -n "$commands_failed" ]; then @@ -188,7 +186,7 @@ Continue?' read _ - check + check "$CMDS" mkdir -p "${apk_directory}" From 5096acc9e776139931dda651424868c495f04d1d Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:01:56 +0000 Subject: [PATCH 04/25] scripts/android/compress-and-sign-apk: fix sorting (remove -z) (#6524) --- scripts/android/compress-and-sign-apk.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/android/compress-and-sign-apk.sh b/scripts/android/compress-and-sign-apk.sh index d9e46a015f..b1c9b6b5e1 100755 --- a/scripts/android/compress-and-sign-apk.sh +++ b/scripts/android/compress-and-sign-apk.sh @@ -41,7 +41,7 @@ for ORIG_NAME in "${ORIG_NAMES[@]}"; do if [ $case_insensitive -eq 1 ]; then # For case-insensitive file systems - list_of_files=$(unzip -l "$ORIG_NAME_COPY" | grep res/ | sed -e "s|.*res/|res/|" | sort -z) + list_of_files=$(unzip -l "$ORIG_NAME_COPY" | grep res/ | sed -e "s|.*res/|res/|" | sort) for file in $list_of_files; do unzip -o -q -d apk "$ORIG_NAME_COPY" "$file" ( From 5066c5cccaa2d020f48705bfe67dc92213f96924 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 22 Dec 2025 21:53:49 +0000 Subject: [PATCH 05/25] ios: fix new chat sheet closing on new message in iOS 26 (#6525) --- apps/ios/Shared/Views/ChatList/ChatHelp.swift | 3 ++- apps/ios/Shared/Views/ChatList/ChatListView.swift | 7 ++++++- apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift | 8 ++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatHelp.swift b/apps/ios/Shared/Views/ChatList/ChatHelp.swift index 7abab33177..3047572236 100644 --- a/apps/ios/Shared/Views/ChatList/ChatHelp.swift +++ b/apps/ios/Shared/Views/ChatList/ChatHelp.swift @@ -10,6 +10,7 @@ import SwiftUI struct ChatHelp: View { @EnvironmentObject var chatModel: ChatModel + @State private var showNewChatSheet = false let dismissSettingsSheet: DismissAction var body: some View { @@ -38,7 +39,7 @@ struct ChatHelp: View { HStack(spacing: 8) { Text("Tap button ") - NewChatMenuButton() + NewChatMenuButton(showNewChatSheet: $showNewChatSheet) Text("above, then choose:") } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 0450bd439c..efaba518a9 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -140,6 +140,7 @@ struct ChatListView: View { @StateObject private var connectProgressManager = ConnectProgressManager.shared @EnvironmentObject var theme: AppTheme @Binding var activeUserPickerSheet: UserPickerSheet? + @State private var showNewChatSheet = false @State private var searchMode = false @FocusState private var searchFocussed @State private var searchText = "" @@ -189,6 +190,10 @@ struct ChatListView: View { onDismiss: { chatModel.laRequest = nil }, content: { UserPickerSheetView(sheet: $0) } ) + .appSheet(isPresented: $showNewChatSheet) { + NewChatSheet() + .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) + } .onChange(of: activeUserPickerSheet) { if $0 != nil { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { @@ -331,7 +336,7 @@ struct ChatListView: View { @ViewBuilder var trailingToolbarItem: some View { switch chatModel.chatRunning { - case .some(true): NewChatMenuButton() + case .some(true): NewChatMenuButton(showNewChatSheet: $showNewChatSheet) case .some(false): chatStoppedIcon() case .none: EmptyView() } diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift index 2e3119a8b8..7adb04cb7e 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -12,7 +12,7 @@ import SimpleXChat struct NewChatMenuButton: View { // do not use chatModel here because it prevents showing AddGroupMembersView after group creation and QR code after link creation on iOS 16 // @EnvironmentObject var chatModel: ChatModel - @State private var showNewChatSheet = false + @Binding var showNewChatSheet: Bool @State private var alert: SomeAlert? = nil var body: some View { @@ -25,10 +25,6 @@ struct NewChatMenuButton: View { .scaledToFit() .frame(width: 24, height: 24) } - .appSheet(isPresented: $showNewChatSheet) { - NewChatSheet() - .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) - } .alert(item: $alert) { a in return a.alert } @@ -471,5 +467,5 @@ struct DeletedChats: View { } #Preview { - NewChatMenuButton() + NewChatMenuButton(showNewChatSheet: Binding.constant(false)) } From 81d31e6c9e36d28abf35e69eeaeca02c176ed783 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 22 Dec 2025 21:58:44 +0000 Subject: [PATCH 06/25] core: 6.5.0.6 --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal.project b/cabal.project index 1f331bdf74..c61c19a8af 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 2ca440dd2dfd494ff2bb40cc0409d08069d02e04 + tag: 77ac4521908da6bddf0529e28de494d090cd1807 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 61ede61996..74875d4179 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."2ca440dd2dfd494ff2bb40cc0409d08069d02e04" = "1jc1a9vh59l0l5hxlin1spv03afrgmmiml5xnakhbi4rk67n0wwr"; + "https://github.com/simplex-chat/simplexmq.git"."77ac4521908da6bddf0529e28de494d090cd1807" = "1sbl5si9pns9zxn3fip43icfjh1kgv35axwwba2paaiigk669wyx"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index b630c2ef65..ee3b2c551d 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.5.0.5 +version: 6.5.0.6 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 6530c9840220ded6a7ed02c36a5a0217041877bd Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:24:30 +0000 Subject: [PATCH 07/25] ios: 6.5-beta.3 (build 316) --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 2d08d1159b..88670f2513 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -178,8 +178,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -545,8 +545,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -708,8 +708,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -795,8 +795,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.5-C9YeXjshpBqGb2o75TqxUY.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a */, ); path = Libraries; sourceTree = ""; @@ -2003,7 +2003,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2053,7 +2053,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2095,7 +2095,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2115,7 +2115,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2140,7 +2140,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2177,7 +2177,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2214,7 +2214,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2265,7 +2265,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2316,7 +2316,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2350,7 +2350,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 315; + CURRENT_PROJECT_VERSION = 316; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; From bf0750735fa593040d5cd592dfda48ea0fab144b Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 23 Dec 2025 08:38:12 +0000 Subject: [PATCH 08/25] core: update query plans --- .../Chat/Store/SQLite/Migrations/agent_query_plans.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt index 3c15a4303c..efbadf7c53 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt @@ -882,7 +882,7 @@ Query: FROM rcv_queues q JOIN servers s ON q.host = s.host AND q.port = s.port JOIN connections c ON q.conn_id = c.conn_id - WHERE c.deleted = 0 AND q.deleted = 0 AND c.user_id = ? AND q.host = ? AND q.port = ? + WHERE c.deleted = 0 AND q.deleted = 0 AND c.user_id = ? AND q.host = ? AND q.port = ? AND COALESCE(q.server_key_hash, s.key_hash) = ? Plan: SEARCH s USING PRIMARY KEY (host=? AND port=?) SEARCH q USING PRIMARY KEY (host=? AND port=?) @@ -894,7 +894,7 @@ Query: FROM rcv_queues q JOIN servers s ON q.host = s.host AND q.port = s.port JOIN connections c ON q.conn_id = c.conn_id - WHERE q.to_subscribe = 1 AND c.deleted = 0 AND q.deleted = 0 AND c.user_id = ? AND q.host = ? AND q.port = ? + WHERE q.to_subscribe = 1 AND c.deleted = 0 AND q.deleted = 0 AND c.user_id = ? AND q.host = ? AND q.port = ? AND COALESCE(q.server_key_hash, s.key_hash) = ? Plan: SEARCH q USING INDEX idx_rcv_queues_to_subscribe (to_subscribe=? AND host=? AND port=?) SEARCH c USING PRIMARY KEY (conn_id=?) From 67fbe62ae19281ba3a32b7ba0f5ff22db1ea8c55 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 23 Dec 2025 14:16:05 +0000 Subject: [PATCH 09/25] core: update simplexmq (support any 127.x.x.x address as loopback), show other addresses in CLI during remote connection, update tests (#6526) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/View.hs | 9 ++++----- tests/RemoteTests.hs | 20 ++++++++++++++++++-- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/cabal.project b/cabal.project index 642b252b44..aee9490aef 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 538dcb6a4c53e115655dc07266c4ff386d45511d + tag: 2ea98db9d8ad0dc43820227a89c744822dfadb5d source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 9bbc469869..9eed446658 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."538dcb6a4c53e115655dc07266c4ff386d45511d" = "01g6dicws9cdgk4slmlqyvl3i84nw3gyqwmqwr2rwbq0yx7fcmmq"; + "https://github.com/simplex-chat/simplexmq.git"."2ea98db9d8ad0dc43820227a89c744822dfadb5d" = "1vh9s66yywqi33df0la340jws9z3hag7ym7q03rc9x8cjcg6s8sw"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 89bc58171c..1b022f2a1f 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -257,11 +257,10 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte rhi_ ] CRRemoteHostList hs -> viewRemoteHosts hs - CRRemoteHostStarted {remoteHost_, invitation, localAddrs = RCCtrlAddress {address} :| _, ctrlPort} -> - [ plain $ maybe ("new remote host" <> started) (\RemoteHostInfo {remoteHostId = rhId} -> "remote host " <> show rhId <> started) remoteHost_, - "Remote session invitation:", - plain invitation - ] + CRRemoteHostStarted {remoteHost_, invitation, localAddrs = RCCtrlAddress {address} :| addrs, ctrlPort} -> + [plain $ maybe ("new remote host" <> started) (\RemoteHostInfo {remoteHostId = rhId} -> "remote host " <> show rhId <> started) remoteHost_] + <> [plain $ "other addresses: " <> intercalate " " (map (\RCCtrlAddress {address = a} -> B.unpack (strEncode a)) addrs) | not (null addrs)] + <> ["Remote session invitation:", plain invitation] where started = " started on " <> B.unpack (strEncode address) <> ":" <> ctrlPort CRRemoteFileStored rhId (CryptoFile filePath cfArgs_) -> diff --git a/tests/RemoteTests.hs b/tests/RemoteTests.hs index 9790681f3b..6f215a5dc8 100644 --- a/tests/RemoteTests.hs +++ b/tests/RemoteTests.hs @@ -9,9 +9,11 @@ import ChatClient import ChatTests.DBUtils import ChatTests.Utils import Control.Logger.Simple +import Control.Monad import qualified Data.Aeson as J import qualified Data.ByteString as B import qualified Data.ByteString.Lazy.Char8 as LB +import Data.List (find, isPrefixOf) import qualified Data.Map.Strict as M import Simplex.Chat.Controller (versionNumber) import qualified Simplex.Chat.Controller as Controller @@ -116,6 +118,7 @@ remoteHandshakeRejectTest = testChat3 aliceProfile aliceDesktopProfile bobProfil mobileBob <## "ok" desktop ##> "/start remote host 1" desktop <##. "remote host 1 started on " + desktop <##. "other addresses: " desktop <## "Remote session invitation:" inv <- getTermLine desktop mobileBob ##> ("/connect remote ctrl " <> inv) @@ -143,8 +146,18 @@ storedBindingsTest = testChat2 aliceProfile aliceDesktopProfile $ \mobile deskto mobile ##> "/set device name Mobile" mobile <## "ok" - desktop ##> "/start remote host new addr=127.0.0.1 iface=\"lo\" port=52230" - desktop <##. "new remote host started on 127.0.0.1:52230" -- TODO: show ip? + desktop ##> "/start remote host new" + desktop <##. "new remote host started on " + addrs <- words . dropStrPrefix "other addresses: " <$> getTermLine desktop + Just localAddress <- pure $ find ("127." `isPrefixOf`) addrs + desktop <## "Remote session invitation:" + void $ getTermLine desktop + desktop ##> "/stop remote host new" + desktop <## "ok" + + desktop ##> ("/start remote host new addr=" <> localAddress <> " iface=\"lo\" port=52230") + desktop <## ("new remote host started on " <> localAddress <> ":52230") + desktop <##. "other addresses: " desktop <## "Remote session invitation:" inv <- getTermLine desktop @@ -497,6 +510,7 @@ startRemote mobile desktop = do mobile <## "ok" desktop ##> "/start remote host new" desktop <##. "new remote host started on " + desktop <##. "other addresses: " desktop <## "Remote session invitation:" inv <- getTermLine desktop mobile ##> ("/connect remote ctrl " <> inv) @@ -512,6 +526,7 @@ startRemoteStored :: TestCC -> TestCC -> IO () startRemoteStored mobile desktop = do desktop ##> "/start remote host 1" desktop <##. "remote host 1 started on " + desktop <##. "other addresses: " desktop <## "Remote session invitation:" inv <- getTermLine desktop mobile ##> ("/connect remote ctrl " <> inv) @@ -526,6 +541,7 @@ startRemoteDiscover :: TestCC -> TestCC -> IO () startRemoteDiscover mobile desktop = do desktop ##> "/start remote host 1 multicast=on" desktop <##. "remote host 1 started on " + desktop <##. "other addresses: " desktop <## "Remote session invitation:" _inv <- getTermLine desktop -- will use multicast instead mobile ##> "/find remote ctrl" From cf11d4587ef6a415dafd2668019cc168d86194d3 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 23 Dec 2025 14:42:00 +0000 Subject: [PATCH 10/25] core: 6.4.9.0 --- simplex-chat.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index c66c7b51ee..986562a5e9 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.4.8.0 +version: 6.4.9.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 113e5a032b8112670c056f2fa572213b01eec9e9 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:44:52 +0000 Subject: [PATCH 11/25] ios: 6.4.9 (build 317) --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 56 +++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 654eaf783c..a03b893eb0 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -178,8 +178,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -545,8 +545,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -708,8 +708,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -795,8 +795,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.8.0-EN6xWtYjnbH1vySqqRtbFW.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.9.0-LCSYjdvysGrAMYKjn8SrSb.a */, ); path = Libraries; sourceTree = ""; @@ -2003,7 +2003,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 317; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2028,7 +2028,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.4.8; + MARKETING_VERSION = 6.4.9; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2053,7 +2053,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 317; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2078,7 +2078,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.4.8; + MARKETING_VERSION = 6.4.9; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2095,11 +2095,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 317; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.4.8; + MARKETING_VERSION = 6.4.9; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2115,11 +2115,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 317; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.4.8; + MARKETING_VERSION = 6.4.9; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2140,7 +2140,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 317; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2155,7 +2155,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.4.8; + MARKETING_VERSION = 6.4.9; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2177,7 +2177,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 317; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2192,7 +2192,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.4.8; + MARKETING_VERSION = 6.4.9; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2214,7 +2214,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 317; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2240,7 +2240,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.4.8; + MARKETING_VERSION = 6.4.9; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2265,7 +2265,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 317; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2291,7 +2291,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.4.8; + MARKETING_VERSION = 6.4.9; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2316,7 +2316,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 317; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2331,7 +2331,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.4.8; + MARKETING_VERSION = 6.4.9; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2350,7 +2350,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 314; + CURRENT_PROJECT_VERSION = 317; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2365,7 +2365,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.4.8; + MARKETING_VERSION = 6.4.9; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; From 89b72effaf4ab68e3aaa3971f5f8f8f23cce8d10 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 23 Dec 2025 18:48:09 +0000 Subject: [PATCH 12/25] core: 6.5.0.7 (simplexmq 6.5.0.6) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal.project b/cabal.project index 38b1c15f03..e9842b1138 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 70d1b99fb4f55ab52060b40a85e16b43fb1717b1 + tag: 5f73d1e629a8807f1b9d94f8b411d6480a0a59fb source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 58a9dff6a1..3cfc2a05af 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."70d1b99fb4f55ab52060b40a85e16b43fb1717b1" = "18waigy1k29j4ijyx4nj4yq397nx49mdp7kvqh9ab9fzcwap588h"; + "https://github.com/simplex-chat/simplexmq.git"."5f73d1e629a8807f1b9d94f8b411d6480a0a59fb" = "1w5mxw9rwiiiqphbg2rdyp4cvv9hz2l64f7fpfhncw6gncfx7ggw"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index ee3b2c551d..155fc0d4d2 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.5.0.6 +version: 6.5.0.7 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 5141339fa377d0e66ba098081f6fb26735819532 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Tue, 23 Dec 2025 19:42:06 +0000 Subject: [PATCH 13/25] ios: 6.5-beta.3 (build 318) --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 88670f2513..fa9a4efdf7 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -178,8 +178,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -545,8 +545,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -708,8 +708,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -795,8 +795,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.6-3CrJZmckwcAqjBkuX4uMa.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a */, ); path = Libraries; sourceTree = ""; @@ -2003,7 +2003,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 318; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2053,7 +2053,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 318; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2095,7 +2095,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 318; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2115,7 +2115,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 318; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2140,7 +2140,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 318; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2177,7 +2177,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 318; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2214,7 +2214,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 318; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2265,7 +2265,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 318; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2316,7 +2316,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 318; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2350,7 +2350,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 316; + CURRENT_PROJECT_VERSION = 318; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; From 44227bcee8ffa90cafe6b0ad577d06115a12512f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 23 Dec 2025 21:36:56 +0000 Subject: [PATCH 14/25] 6.5-beta.3: android 331, desktop 128 --- apps/multiplatform/gradle.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 1501cb43dc..38ef04daaa 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,13 +24,13 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.5-beta.2 -android.version_code=330 +android.version_name=6.5-beta.3 +android.version_code=331 android.bundle=false -desktop.version_name=6.5-beta.2 -desktop.version_code=127 +desktop.version_name=6.5-beta.3 +desktop.version_code=128 kotlin.version=2.1.20 gradle.plugin.version=8.7.0 From fceb987906c19a0516510f2b0f91104a0174630b Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Wed, 24 Dec 2025 20:31:46 +0000 Subject: [PATCH 15/25] docs: verify and reproduce builds (#6515) * docs/REPRODUCIBLE: add new * docs/REPRODUCIBLE: clarify Android requirements * rename to REPRODUCE * expand and fix sections * website * docs, site: change links to simplex.apk to simplex-aarch64.apk --------- Co-authored-by: Evgeny Poberezkin --- README.md | 6 +- docs/DOWNLOADS.md | 6 +- docs/REPRODUCE.md | 203 ++++++++++++++++++ docs/TRANSLATIONS.md | 2 +- docs/WEBRTC.md | 3 +- docs/lang/cs/README.md | 6 +- docs/lang/fr/README.md | 6 +- docs/lang/pl/README.md | 10 +- website/langs/en.json | 1 + website/src/_data/docs_dropdown.json | 4 + website/src/_data/docs_sidebar.json | 1 + website/src/_includes/contact_page.html | 30 +-- website/src/_includes/hero.html | 2 +- website/src/_includes/navbar.html | 2 +- .../src/_includes/sections/join_simplex.html | 2 +- website/src/css/doc.css | 2 +- website/src/index.html | 2 +- 17 files changed, 250 insertions(+), 38 deletions(-) create mode 100644 docs/REPRODUCE.md diff --git a/README.md b/README.md index b1d2556072..f8abf8adfb 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,11 @@   [iOS TestFlight](https://testflight.apple.com/join/DWuT2LQu)   -[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk) +[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk) - 🖲 Protects your messages and metadata - who you talk to and when. - 🔐 Double ratchet end-to-end encryption, with additional encryption layer. -- 📱 Mobile apps for Android ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)) and [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084). +- 📱 Mobile apps for Android ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk)) and [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084). - 🚀 [TestFlight preview for iOS](https://testflight.apple.com/join/DWuT2LQu) with the new features 1-2 weeks earlier - **limited to 10,000 users**! - 🖥 Available as a terminal (console) [app / CLI](#zap-quick-installation-of-a-terminal-app) on Linux, MacOS, Windows. @@ -435,4 +435,4 @@ Graphic designs, artworks and layouts are not licensed for re-use. If you want t   [iOS TestFlight](https://testflight.apple.com/join/DWuT2LQu)   -[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk) +[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk) diff --git a/docs/DOWNLOADS.md b/docs/DOWNLOADS.md index a23a920bbb..b9889fe7a7 100644 --- a/docs/DOWNLOADS.md +++ b/docs/DOWNLOADS.md @@ -24,6 +24,8 @@ You can link your mobile device with desktop to use the same profile remotely, b - Ubuntu 22.04 and Debian-based distros ([x86_64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-ubuntu-22_04-x86_64.deb), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-ubuntu-22_04-aarch64.deb)). - Ubuntu 24.04 ([x86_64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-ubuntu-24_04-x86_64.deb), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-ubuntu-24_04-aarch64.deb)). +You can [verify and reproduce](./REPRODUCE.md) Linux builds. + **Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-macos-aarch64.dmg) (Apple Silicon). **Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-desktop-windows-x86_64.msi). @@ -32,7 +34,9 @@ You can link your mobile device with desktop to use the same profile remotely, b **iOS**: [App store](https://apps.apple.com/us/app/simplex-chat/id1605771084), [TestFlight](https://testflight.apple.com/join/DWuT2LQu). -**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-armv7a.apk). +**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-armv7a.apk). + +You can [verify and reproduce](./REPRODUCE.md) Android APKs. ## Terminal (console) app diff --git a/docs/REPRODUCE.md b/docs/REPRODUCE.md new file mode 100644 index 0000000000..03fb6b4336 --- /dev/null +++ b/docs/REPRODUCE.md @@ -0,0 +1,203 @@ +--- +title: Verify and reproduce builds +permalink: /reproduce/index.html +revision: 19.12.2025 +--- + +# Verifying and reproducing release builds + +- [Obtain release signing key](#obtain-release-signing-key) +- [Verify release signature](#verify-release-signature) +- [How to reproduce builds](#how-to-reproduce-builds) + - [Server binaries](#server-binaries) + - [Linux desktop apps and CLI](#linux-desktop-apps-and-cli) + - [Android apps](#android-apps) + +## Obtain release signing key + +To verify the signature of `_sha256sums` or apks you need to obtain the signing key. You can do it from keyservers: + +```sh +gpg --keyserver hkps://keys.openpgp.org --search build@simplex.chat +gpg --keyserver hkps://keyserver.ubuntu.com --search build@simplex.chat +``` + +```sh +gpg --list-keys build@simplex.chat +``` + +Once you obtain the signing key, verify that its fingerprint is: + +``` +BBDF 7BDA D154 8B16 836A F5B9 D53B DFD1 53C3 66BA +``` + +Additionally, compare the key fingerprint with: + +- [simplexchat.eth](https://app.ens.domains/simplexchat.eth) (release key record) +- [Mastodon](https://mastodon.social/@simplex) (profile) +- [Reddit](https://www.reddit.com/r/SimpleXChat/) (side panel) + +You can set the imported key as "ultimately trusted": + +```sh +echo -e "trust\n5\ny\nquit" | gpg --command-fd 0 --edit-key build@simplex.chat +``` + +## Verify release signature + +**Linux dekstop apps and CLI**: + +Download the file with executable hashes and the signature. For example, to verify the `v6.5.0-beta.3` release: + +```sh +curl -LO 'https://github.com/simplex-chat/simplex-chat/releases/download/v6.5.0-beta.3/_sha256sums.asc' +curl -LO 'https://github.com/simplex-chat/simplex-chat/releases/download/v6.5.0-beta.3/_sha256sums' +``` + +Verify the signature: + +```sh +gpg --verify _sha256sums.asc _sha256sums +``` + +**Android APKs**: + +Download the APK files and signatures. For example, to verify the `v6.5.0-beta.3` release: + +```sh +curl -LO 'https://github.com/simplex-chat/simplex-chat/releases/download/v6.5.0-beta.3/simplex-aarch64.apk' +curl -LO 'https://github.com/simplex-chat/simplex-chat/releases/download/v6.5.0-beta.3/_simplex-aarch64.apk.asc' +curl -LO 'https://github.com/simplex-chat/simplex-chat/releases/download/v6.5.0-beta.3/simplex-armv7a.apk' +curl -LO 'https://github.com/simplex-chat/simplex-chat/releases/download/v6.5.0-beta.3/_simplex-armv7a.apk.asc' +``` + +Verify the signatures: + +```sh +gpg --verify _simplex-armv7a.apk.asc simplex-armv7a.apk +gpg --verify _simplex-aarch64.apk.asc simplex-aarch64.apk +``` + +## How to reproduce builds + +To reproduce the build you must have: + +- Linux machine +- `x86-64` architecture +- Installed `docker`, `curl` and `git` + +### Server binaries + +1. Download script: + + ```sh + curl -LO 'https://raw.githubusercontent.com/simplex-chat/simplexmq/refs/heads/master/scripts/simplexmq-reproduce-builds.sh' + ``` + +2. Make it executable: + + ```sh + chmod +x simplexmq-reproduce-builds.sh + ``` + +3. Execute the script with the required tag: + + ```sh + ./simplexmq-reproduce-builds.sh 'v6.3.1' + ``` + + The script executes these steps (please review the script to confirm): + + 1) builds all server binaries for the release in docker container. + 2) downloads binaries from the same GitHub release and compares them with the built binaries. + 3) if they all match, generates _sha256sums file with their checksums. + + This will take a while. + +4. After compilation, you should see the folder named as the tag and repository name (e.g., `v6.3.1-simplexmq`) with two subfolders: + + ```sh + ls v6.3.1-simplexmq + ``` + + ```sh + from-source prebuilt _sha256sums + ``` + + The file _sha256sums contains the hashes of all builds - you can compare it with the same file in GitHub release. + +### Linux desktop apps and CLI + +1. Download script: + + ```sh + curl -LO 'https://raw.githubusercontent.com/simplex-chat/simplex-chat/refs/heads/master/scripts/simplex-chat-reproduce-builds.sh' + ``` + +2. Make it executable: + + ```sh + chmod +x simplex-chat-reproduce-builds.sh + ``` + +3. Execute the script with the required tag: + + ```sh + ./simplex-chat-reproduce-builds.sh 'v6.4.8' + ``` + + The script executes these steps (please review the script to confirm): + + 1) builds all Linux CLI and Dekstop binaries for the release in docker container. + 2) downloads binaries from the same GitHub release and compares them with the built binaries. + 3) if they all match, generates _sha256sums file with their checksums. + + This will take a while. + +4. After compilation, you should see the folder named as the tag and reprository name (e.g., `v6.4.8-simplex-chat`) with two subfolders: + + ```sh + ls v6.4.8-simplex-chat + ``` + + ```sh + from-source prebuilt _sha256sums + ``` + + The file _sha256sums contains the hashes of all builds - you can compare it with the same file in GitHub release. + +### Android apps + +In addition to basic requirments, Android build will: + +- Take ~150gb of disc space +- Take ~20h to build all the architectures (depends on core count) +- Require at least 16gb of RAM + +1. Download script: + + ```sh + curl -LO 'https://raw.githubusercontent.com/simplex-chat/simplex-chat/refs/heads/master/scripts/simplex-chat-reproduce-builds-android.sh' + ``` + +2. Make it executable: + + ```sh + chmod +x simplex-chat-reproduce-builds-android.sh + ``` + +3. Execute the script with the required tag: + + ```sh + ./simplex-chat-reproduce-builds-android.sh 'v6.5.0-beta.3' + ``` + + The script executes these steps (please review the script to confirm): + + 1) Downloads and checks that APKs from GitHub are signed with valid key. + 2) Builds Android APKs in a docker container. + 3) Compares the releases by copying the signature from downloaded APKs to locally built APKs. + 4) If the resulting build is bit-by-bit identical, prints the message that this tag was reproduced. + + This will take a while. diff --git a/docs/TRANSLATIONS.md b/docs/TRANSLATIONS.md index d5c1cdef0b..2b1febb6f2 100644 --- a/docs/TRANSLATIONS.md +++ b/docs/TRANSLATIONS.md @@ -1,5 +1,5 @@ --- -title: Contributing translations to SimpleX Chat +title: Contributing SimpleX app translations revision: 19.03.2023 --- diff --git a/docs/WEBRTC.md b/docs/WEBRTC.md index 8ce31bf959..a48cd12b00 100644 --- a/docs/WEBRTC.md +++ b/docs/WEBRTC.md @@ -1,5 +1,5 @@ --- -title: Using custom WebRTC ICE servers in SimpleX Chat +title: Using custom WebRTC ICE servers revision: 31.01.2023 --- @@ -155,4 +155,3 @@ This is it - you now can make audio and video calls via your own server, without If results show `srflx` and `relay` candidates, everything is set up correctly! - diff --git a/docs/lang/cs/README.md b/docs/lang/cs/README.md index 7eab61395e..d1ff8a0eca 100644 --- a/docs/lang/cs/README.md +++ b/docs/lang/cs/README.md @@ -18,11 +18,11 @@   [iOS TestFlight](https://testflight.apple.com/join/DWuT2LQu)   -[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk) +[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk) - 🖲 Chrání vaše zprávy a metadata - s kým a kdy mluvíte. - 🔐 Koncové šifrování s další vrstvou šifrování. -- 📱 Mobilní aplikace pro Android ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)) a [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084). +- 📱 Mobilní aplikace pro Android ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk)) a [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084). - 🚀 [TestFlight preview for iOS](https://testflight.apple.com/join/DWuT2LQu) s novými funkcemi o 1-2 týdny dříve - **omezeno na 10 000 uživatelů**! - 🖥 K dispozici jako terminálová (konzolová) [aplikace / CLI](#zap-quick-installation-of-a-terminal-app) v systémech Linux, MacOS, Windows. @@ -324,4 +324,4 @@ Jakákoli zjištění možných útoků korelace provozu umožňujících korelo   [iOS TestFlight](https://testflight.apple.com/join/DWuT2LQu)   -[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk) +[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk) diff --git a/docs/lang/fr/README.md b/docs/lang/fr/README.md index 69401da5a2..d6bf517446 100644 --- a/docs/lang/fr/README.md +++ b/docs/lang/fr/README.md @@ -32,11 +32,11 @@   [iOS TestFlight](https://testflight.apple.com/join/DWuT2LQu)   -[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk) +[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk) - 🖲 Protégez vos messages et vos métadonnées - avec qui vous parlez et quand. - 🔐 Chiffrement de bout en bout à double ratchet, avec couche de chiffrement supplémentaire. -- 📱 Apps mobiles pour Android ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)) et [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084). +- 📱 Apps mobiles pour Android ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk)) et [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084). - 🚀 [Bêta TestFlight pour iOS](https://testflight.apple.com/join/DWuT2LQu) avec les nouvelles fonctionnalités 1 à 2 semaines plus tôt - **limitée à 10 000 utilisateurs** ! - 🖥 Disponible en tant que [terminal (console) / CLI](#⚡-installation-rapide-dune-application-pour-terminal) sur Linux, MacOS, Windows. @@ -351,4 +351,4 @@ Veuillez traiter toute découverte d'une éventuelle attaque par corrélation de   [iOS TestFlight](https://testflight.apple.com/join/DWuT2LQu)   -[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk) +[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk) diff --git a/docs/lang/pl/README.md b/docs/lang/pl/README.md index 23ca00c3e6..9f107dc825 100644 --- a/docs/lang/pl/README.md +++ b/docs/lang/pl/README.md @@ -32,11 +32,11 @@   [iOS TestFlight](https://testflight.apple.com/join/DWuT2LQu)   -[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk) +[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk) - 🖲 Chroni Twoje wiadomości i metadane - z kim rozmawiasz i kiedy. - 🔐 Szyfrowanie end-to-end double ratchet, z dodatkową warstwą szyfrowania. -- 📱 Aplikacje mobilne dla Androida ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk)) oraz [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084). +- 📱 Aplikacje mobilne dla Androida ([Google Play](https://play.google.com/store/apps/details?id=chat.simplex.app), [APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk)) oraz [iOS](https://apps.apple.com/us/app/simplex-chat/id1605771084). - 🚀 [TestFlight dla iOS](https://testflight.apple.com/join/DWuT2LQu) z nowymi funkcjami na tydzień-dwa wcześniej - **limitowane do 10,000 użytkowników**! - 🖥 Dostępny jako terminalowa (konsolowa) [aplikacja / CLI](#zap-quick-installation-of-a-terminal-app) na Linuxa, MacOSa, Windowsa. @@ -198,9 +198,9 @@ Twórca SimpleX Chat. ## Dlaczego prywatność ma znaczenie -Każdy powinien dbać o prywatność i bezpieczeństwo swojej komunikacji - nieszkodliwe rozmowy mogą narazić Cię na niebezpieczeństwo, nawet jeśli nie masz nic do ukrycia. +Każdy powinien dbać o prywatność i bezpieczeństwo swojej komunikacji - nieszkodliwe rozmowy mogą narazić Cię na niebezpieczeństwo, nawet jeśli nie masz nic do ukrycia. -Jedną z najbardziej wstrząsających historii jest doświadczenie [Mohamedou Ould Salahi](https://en.wikipedia.org/wiki/Mohamedou_Ould_Slahi). opisane w jego pamiętniku i pokazane w filmie Mauretańczyk (2021). Został on umieszczony w obozie Guantanamo, bez procesu, i był tam torturowany przez 15 lat po telefonie do swojego krewnego w Afganistanie, pod zarzutem udziału w atakach 9/11, mimo że przez poprzednie 10 lat mieszkał w Niemczech. +Jedną z najbardziej wstrząsających historii jest doświadczenie [Mohamedou Ould Salahi](https://en.wikipedia.org/wiki/Mohamedou_Ould_Slahi). opisane w jego pamiętniku i pokazane w filmie Mauretańczyk (2021). Został on umieszczony w obozie Guantanamo, bez procesu, i był tam torturowany przez 15 lat po telefonie do swojego krewnego w Afganistanie, pod zarzutem udziału w atakach 9/11, mimo że przez poprzednie 10 lat mieszkał w Niemczech. Używanie szyfrowanego komunikatora end-to-end nie jest wystarczające. Powinniśmy używać komunikatorów, które zapewniają prywatność naszym powiązaniom, czyli tym z kim jesteśmy jakkolwiek połączeni. @@ -432,4 +432,4 @@ Prosimy o traktowanie wszelkich ustaleń dotyczących możliwych ataków korelac   [iOS TestFlight](https://testflight.apple.com/join/DWuT2LQu)   -[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex.apk) +[APK](https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk) diff --git a/website/langs/en.json b/website/langs/en.json index dc97d9ab66..eca5f5beb8 100644 --- a/website/langs/en.json +++ b/website/langs/en.json @@ -241,6 +241,7 @@ "docs-dropdown-11": "FAQ", "docs-dropdown-12": "Security", "docs-dropdown-14": "SimpleX for business", + "docs-dropdown-15": "Verify & reproduce builds", "newer-version-of-eng-msg": "There is a newer version of this page in English.", "click-to-see": "Click to see", "menu": "Menu", diff --git a/website/src/_data/docs_dropdown.json b/website/src/_data/docs_dropdown.json index 94bc69f8f3..172145d86a 100644 --- a/website/src/_data/docs_dropdown.json +++ b/website/src/_data/docs_dropdown.json @@ -32,6 +32,10 @@ "title": "docs-dropdown-9", "url": "/downloads/" }, + { + "title": "docs-dropdown-15", + "url": "/reproduce/" + }, { "title": "docs-dropdown-10", "url": "/transparency/" diff --git a/website/src/_data/docs_sidebar.json b/website/src/_data/docs_sidebar.json index e370ccc078..f9b4d15b54 100644 --- a/website/src/_data/docs_sidebar.json +++ b/website/src/_data/docs_sidebar.json @@ -28,6 +28,7 @@ "WEBRTC.md", "XFTP-SERVER.md", "DOWNLOADS.md", + "REPRODUCE.md", "TRANSPARENCY.md", "SECURITY.md", "FAQ.md" diff --git a/website/src/_includes/contact_page.html b/website/src/_includes/contact_page.html index b5f7442a75..f628180bba 100644 --- a/website/src/_includes/contact_page.html +++ b/website/src/_includes/contact_page.html @@ -21,7 +21,7 @@ - + @@ -30,7 +30,7 @@
- + @@ -92,7 +92,7 @@ diff --git a/website/src/css/doc.css b/website/src/css/doc.css index 937268b166..20599f0afb 100644 --- a/website/src/css/doc.css +++ b/website/src/css/doc.css @@ -268,7 +268,7 @@ header { } .dark #doc main aside ul li a.active { - color: #70F0F9; + color: white; } #doc main aside p { diff --git a/website/src/index.html b/website/src/index.html index 443b4a3302..c044d5d17a 100644 --- a/website/src/index.html +++ b/website/src/index.html @@ -88,7 +88,7 @@ active_home: true - +
From 83cef2d52bb02aeaddff462f7c928a6103accc87 Mon Sep 17 00:00:00 2001 From: Oleksandr Kryvytskyi Date: Thu, 25 Dec 2025 14:53:52 +0200 Subject: [PATCH 16/25] docs: list available bot SDKs (#6516) --- bots/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bots/README.md b/bots/README.md index 9449e9d847..409f404dbd 100644 --- a/bots/README.md +++ b/bots/README.md @@ -192,6 +192,13 @@ It is usually simpler to run your bot process on the same machine where you run If you have to run your bot on another machine, you need to secure access to bot CLI via any web proxy that supports WebSockets, e.g. Caddy or Nginx. You must configure TLS termination in the proxy and connect CLI process from bot via a secure TLS connection. If you connect to bot via a public network, you also must configure HTTP basic auth to prevent unauthorized access. You can validate TLS security of your proxy via a free test at [SSLLabs.com](https://www.ssllabs.com/ssltest/). You can also configure firewall on the machine where you run SimpleX CLI to only allow connections from the IP address of your bot. +## Available libraries + +#### Libraries with full bot API support + +- [The official TypeScript SDK](https://www.npmjs.com/package/simplex-chat) +- [Unofficial Rust SDK](https://crates.io/crates/simploxide-client) + ## Useful bots - [Broadcast bot](../apps/simplex-broadcast-bot/) (Haskell) - we use it to send [status and release updates](https://status.simplex.chat/status/public). From 0c0342550c3296f6576fef78ab216792bc390e66 Mon Sep 17 00:00:00 2001 From: Alex <4998865+alexjohnyoung@users.noreply.github.com> Date: Thu, 25 Dec 2025 12:54:41 +0000 Subject: [PATCH 17/25] docs: add Nodify to bots list (#6527) --- bots/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/bots/README.md b/bots/README.md index 409f404dbd..80c6689dce 100644 --- a/bots/README.md +++ b/bots/README.md @@ -204,3 +204,4 @@ If you have to run your bot on another machine, you need to secure access to bot - [Broadcast bot](../apps/simplex-broadcast-bot/) (Haskell) - we use it to send [status and release updates](https://status.simplex.chat/status/public). - [Moderation bot](https://github.com/NCalex42/simplex-bot) (Java) - [Matterbridge bot](https://github.com/UnkwUsr/matterbridge-simplex) (JavaScript) +- [Nodify](https://nodify.ie) (Low-Code) \ No newline at end of file From fe4ff8993d2a95c723448e1876b6c29caf760c13 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 26 Dec 2025 13:29:48 +0000 Subject: [PATCH 18/25] core: finalize introductions -> member relations vector stage 2 migration (drop introductions) (#6490) * core: finalize introductions -> member relations vector stage 2 migration (drop introductions) * remove comment * skip down migration check * fix * plans * postgres schema * skip down migration comparison * do not drop group_member_intros table, rename migrations --------- Co-authored-by: Evgeny Poberezkin --- simplex-chat.cabal | 4 +- src/Simplex/Chat/Library/Commands.hs | 22 -- src/Simplex/Chat/Library/Internal.hs | 82 ++++---- src/Simplex/Chat/Library/Subscriber.hs | 15 +- src/Simplex/Chat/Store/Groups.hs | 191 +----------------- src/Simplex/Chat/Store/Messages.hs | 16 +- src/Simplex/Chat/Store/Postgres/Migrations.hs | 6 +- .../M20251117_member_relations_vector.hs | 2 +- ... => M20251128_migrate_member_relations.hs} | 26 +-- .../Store/Postgres/Migrations/chat_schema.sql | 10 - src/Simplex/Chat/Store/SQLite/Migrations.hs | 6 +- .../M20251117_member_relations_vector.hs | 2 +- ... => M20251128_migrate_member_relations.hs} | 18 +- .../SQLite/Migrations/chat_query_plans.txt | 76 +------ .../Store/SQLite/Migrations/chat_schema.sql | 4 - src/Simplex/Chat/Types.hs | 43 ---- tests/ChatTests/Groups.hs | 15 -- tests/PostgresSchemaDump.hs | 4 +- tests/SchemaDump.hs | 4 +- 19 files changed, 90 insertions(+), 456 deletions(-) rename src/Simplex/Chat/Store/Postgres/Migrations/{M20251128_member_relations_vector_stage_2.hs => M20251128_migrate_member_relations.hs} (65%) rename src/Simplex/Chat/Store/SQLite/Migrations/{M20251128_member_relations_vector_stage_2.hs => M20251128_migrate_member_relations.hs} (67%) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 155fc0d4d2..65bca0611f 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -123,7 +123,7 @@ library Simplex.Chat.Store.Postgres.Migrations.M20251007_connections_sync Simplex.Chat.Store.Postgres.Migrations.M20251017_chat_tags_cascade Simplex.Chat.Store.Postgres.Migrations.M20251117_member_relations_vector - Simplex.Chat.Store.Postgres.Migrations.M20251128_member_relations_vector_stage_2 + Simplex.Chat.Store.Postgres.Migrations.M20251128_migrate_member_relations else exposed-modules: Simplex.Chat.Archive @@ -270,7 +270,7 @@ library Simplex.Chat.Store.SQLite.Migrations.M20251007_connections_sync Simplex.Chat.Store.SQLite.Migrations.M20251017_chat_tags_cascade Simplex.Chat.Store.SQLite.Migrations.M20251117_member_relations_vector - Simplex.Chat.Store.SQLite.Migrations.M20251128_member_relations_vector_stage_2 + Simplex.Chat.Store.SQLite.Migrations.M20251128_migrate_member_relations other-modules: Paths_simplex_chat hs-source-dirs: diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index f7891cae3a..57f4244fab 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -167,9 +167,6 @@ startChatController mainApp enableSndFiles = do runExceptT (syncConnections' users) >>= \case Left e -> liftIO $ putStrLn $ "Error synchronizing connections: " <> show e Right _ -> pure () - runExceptT migrateMemberRelations >>= \case - Left e -> liftIO $ putStrLn $ "Error migrating member relations: " <> show e - Right _ -> pure () restoreCalls s <- asks agentAsync readTVarIO s >>= maybe (start s users) (pure . fst) @@ -181,10 +178,6 @@ startChatController mainApp enableSndFiles = do (userDiff, connDiff) <- withAgent (\a -> syncConnections a aUserIds connIds) withFastStore' setConnectionsSyncTs toView $ CEvtConnectionsDiff (AgentUserId <$> userDiff) (AgentConnId <$> connDiff) - migrateMemberRelations = - when mainApp $ - whenM (withStore' hasMembersWithoutVector) $ - void $ forkIO runRelationsVectorMigration start s users = do a1 <- async agentSubscriber a2 <- @@ -4176,21 +4169,6 @@ agentSubscriber = do type AgentSubResult = Map ConnId (Either AgentErrorType (Maybe ClientServiceId)) -runRelationsVectorMigration :: CM () -runRelationsVectorMigration = do - liftIO $ threadDelay' 5000000 -- 5 seconds (initial delay) - migrateMembers - where - stepDelay = 1000000 -- 1 second - migrateMembers = flip catchAllErrors eToView $ do - lift waitChatStartedAndActivated - gmIds <- withStore' getGMsWithoutVectorIds - forM_ gmIds $ \gmId -> do - lift waitChatStartedAndActivated - withStore' (`migrateMemberRelationsVector'` gmId) `catchAllErrors` eToView - liftIO $ threadDelay' stepDelay - unless (null gmIds) migrateMembers - cleanupManager :: CM () cleanupManager = do interval <- asks (cleanupManagerInterval . config) diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 9ee00b0dc5..5b445cf460 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -1030,11 +1030,11 @@ introduceToModerators vr user gInfo@GroupInfo {groupId} m@GroupMember {memberRol else XMsgNew $ MCSimple $ extMsgContent (MCText pendingReviewMessage) Nothing void $ sendDirectMemberMessage mConn msg groupId modMs <- withStore' $ \db -> getGroupModerators db vr user gInfo - let rcpModMs = filter shouldIntroduce modMs - introduceMember vr user gInfo m rcpModMs (Just $ MSMember $ memberId' m) + let rcpModMs = filter shouldIntroduceToMod modMs + introduceMember user gInfo m rcpModMs (Just $ MSMember $ memberId' m) where - shouldIntroduce :: GroupMember -> Bool - shouldIntroduce mem = + shouldIntroduceToMod :: GroupMember -> Bool + shouldIntroduceToMod mem = memberCurrent mem && groupMemberId' mem /= groupMemberId' m && maxVersion (memberChatVRange mem) >= groupKnockingVersion @@ -1042,42 +1042,33 @@ introduceToModerators vr user gInfo@GroupInfo {groupId} m@GroupMember {memberRol introduceToAll :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM () introduceToAll vr user gInfo m = do members <- withStore' $ \db -> getGroupMembers db vr user gInfo - vector_ <- withStore' (`getMemberRelationsVector_` m) - let recipients = filter (shouldIntroduce vector_) members - introduceMember vr user gInfo m recipients Nothing - where - shouldIntroduce :: Maybe ByteString -> GroupMember -> Bool - shouldIntroduce vector_ m' = - memberCurrent m' - && groupMemberId' m' /= groupMemberId' m - && maybe True (\v -> getRelation (indexInGroup m') v == MRNew) vector_ + vector <- withStore (`getMemberRelationsVector` m) + let recipients = filter (shouldIntroduce m vector) members + introduceMember user gInfo m recipients Nothing introduceToRemaining :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM () introduceToRemaining vr user gInfo m = do members <- withStore' $ \db -> getGroupMembers db vr user gInfo - vector_ <- withStore' (`getMemberRelationsVector_` m) - recipients <- filterRecipients vector_ members - introduceMember vr user gInfo m recipients Nothing - where - filterRecipients :: Maybe ByteString -> [GroupMember] -> CM [GroupMember] - filterRecipients vector_ members = do - newRelation <- case vector_ of - Nothing -> do - introducedGMIds <- S.fromList <$> withStore' (`getIntroducedGroupMemberIds` m) - pure $ \m' -> groupMemberId' m' `S.notMember` introducedGMIds - Just vec -> pure $ \m' -> getRelation (indexInGroup m') vec == MRNew - pure $ filter (\m' -> groupMemberId' m' /= groupMemberId' m && memberCurrent m' && newRelation m') members + vector <- withStore (`getMemberRelationsVector` m) + let recipients = filter (shouldIntroduce m vector) members + introduceMember user gInfo m recipients Nothing -introduceMember :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> [GroupMember] -> Maybe MsgScope -> CM () -introduceMember _ _ _ GroupMember {activeConn = Nothing} _ _ = throwChatError $ CEInternalError "member connection not active" -introduceMember vr user gInfo@GroupInfo {groupId} toMember@GroupMember {activeConn = Just conn} introduceToMembers msgScope = do +shouldIntroduce :: GroupMember -> ByteString -> GroupMember -> Bool +shouldIntroduce m vec mem = + memberCurrent mem + && groupMemberId' mem /= groupMemberId' m + && getRelation (indexInGroup mem) vec == MRNew + +introduceMember :: User -> GroupInfo -> GroupMember -> [GroupMember] -> Maybe MsgScope -> CM () +introduceMember _ _ GroupMember {activeConn = Nothing} _ _ = throwChatError $ CEInternalError "member connection not active" +introduceMember user gInfo@GroupInfo {groupId} toMember@GroupMember {activeConn = Just conn} introduceToMembers msgScope = do void . sendGroupMessage' user gInfo introduceToMembers $ XGrpMemNew (memberInfo gInfo toMember) msgScope sendIntroductions introduceToMembers where sendIntroductions reMembers = do updateToMemberVector reMembers - reMembers' <- withStore' $ \db -> createIntrosOrUpdateVectors db vr reMembers toMember - shuffledReMembers <- liftIO $ shuffleMembers reMembers' + updateReMembersVectors reMembers + shuffledReMembers <- liftIO $ shuffleMembers reMembers if toMember `supportsVersion` batchSendVersion then do let events = map memberIntro shuffledReMembers @@ -1089,6 +1080,10 @@ introduceMember vr user gInfo@GroupInfo {groupId} toMember@GroupMember {activeCo updateToMemberVector reMembers = do let relations = map (\GroupMember {indexInGroup} -> (indexInGroup, (IDReferencedIntroduced, MRIntroduced))) reMembers withStore' $ \db -> setMemberVectorNewRelations db toMember relations + updateReMembersVectors :: [GroupMember] -> CM () + updateReMembersVectors reMembers = do + let GroupMember {indexInGroup} = toMember + withStore' $ \db -> setMembersVectorsNewRelation db reMembers indexInGroup IDSubjectIntroduced MRIntroduced memberIntro :: GroupMember -> ChatMsgEvent 'Json memberIntro reMember = let mInfo = memberInfo gInfo reMember @@ -2020,7 +2015,7 @@ sendGroupMessages_ _user gInfo@GroupInfo {groupId} recipientMembers events = do pendingReq SndMessage {msgId} = (groupMemberId, msgId) createPendingMsg :: DB.Connection -> (GroupMemberId, MessageId) -> IO (Either ChatError ()) createPendingMsg db (groupMemberId, msgId) = - createPendingGroupMessage db groupMemberId msgId Nothing $> Right () + createPendingGroupMessage db groupMemberId msgId $> Right () data MemberSendAction = MSASend Connection | MSASendBatched Connection | MSAPending | MSAForwarded @@ -2083,32 +2078,25 @@ readyMemberConn GroupMember {groupMemberId, activeConn = Just conn@Connection {c | otherwise = Nothing readyMemberConn GroupMember {activeConn = Nothing} = Nothing -sendGroupMemberMessage :: MsgEncodingI e => GroupInfo -> GroupMember -> ChatMsgEvent e -> Maybe GroupMemberIntro -> CM () -> CM () -sendGroupMemberMessage gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId} chatMsgEvent intro_ postDeliver = do +sendGroupMemberMessage :: MsgEncodingI e => GroupInfo -> GroupMember -> ChatMsgEvent e -> CM () +sendGroupMemberMessage gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId} chatMsgEvent = do msg <- createSndMessage chatMsgEvent (GroupId groupId) messageMember msg `catchAllErrors` eToView where messageMember :: SndMessage -> CM () messageMember SndMessage {msgId, msgBody} = forM_ (memberSendAction gInfo (chatMsgEvent :| []) [m] m) $ \case - MSASend conn -> deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId >> postDeliver - MSASendBatched conn -> deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId >> postDeliver - MSAPending -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId (introId <$> intro_) + MSASend conn -> void $ deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId + MSASendBatched conn -> void $ deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId + MSAPending -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId MSAForwarded -> pure () -- TODO ensure order - pending messages interleave with user input messages sendPendingGroupMessages :: User -> GroupMember -> Connection -> CM () sendPendingGroupMessages user GroupMember {groupMemberId} conn = do - pgms <- withStore' $ \db -> getPendingGroupMessages db groupMemberId - forM_ (L.nonEmpty pgms) $ \pgms' -> do - let msgs = L.map (\(sndMsg, _, _) -> sndMsg) pgms' - void $ batchSendConnMessages user conn MsgFlags {notification = True} msgs - lift . void . withStoreBatch' $ \db -> L.map (\SndMessage {msgId} -> deletePendingGroupMessage db groupMemberId msgId) msgs - lift . void . withStoreBatch' $ \db -> L.map (\(_, tag, introId_) -> updateIntro_ db tag introId_) pgms' - where - updateIntro_ :: DB.Connection -> ACMEventTag -> Maybe Int64 -> IO () - updateIntro_ db tag introId_ = case (tag, introId_) of - (ACMEventTag _ XGrpMemFwd_, Just introId) -> updateIntroStatus db introId GMIntroInvForwarded - _ -> pure () + msgs <- withStore' $ \db -> getPendingGroupMessages db groupMemberId + forM_ (L.nonEmpty msgs) $ \msgs' -> do + void $ batchSendConnMessages user conn MsgFlags {notification = True} msgs' + lift . void . withStoreBatch' $ \db -> L.map (\SndMessage {msgId} -> deletePendingGroupMessage db groupMemberId msgId) msgs' saveDirectRcvMSG :: MsgEncodingI e => Connection -> MsgMeta -> MsgBody -> ChatMessage e -> CM (Connection, RcvMessage) saveDirectRcvMSG conn@Connection {connId} agentMsgMeta msgBody ChatMessage {chatVRange, msgId = sharedMsgId_, chatMsgEvent} = do diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index c1fc1e917f..625e879607 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -2615,14 +2615,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = GCInviteeMember -> withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Left _ -> messageError "x.grp.mem.inv error: referenced member does not exist" - Right reMember -> do - intro_ <- withStore' $ \db -> getIntroduction db reMember m - update intro_ GMIntroInvReceived - sendGroupMemberMessage gInfo reMember (XGrpMemFwd (memberInfo gInfo m) introInv) intro_ $ - update intro_ GMIntroInvForwarded - where - update (Just GroupMemberIntro {introId}) status = withStore' $ \db -> updateIntroStatus db introId status - update Nothing _ = pure () + Right reMember -> sendGroupMemberMessage gInfo reMember $ XGrpMemFwd (memberInfo gInfo m) introInv _ -> messageError "x.grp.mem.inv can be only sent by invitee member" xGrpMemFwd :: GroupInfo -> GroupMember -> MemberInfo -> IntroInvitation -> CM () @@ -2718,8 +2711,6 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xGrpMemCon :: GroupInfo -> GroupMember -> MemberId -> CM () xGrpMemCon gInfo sendingMem memId = do refMem <- withStore $ \db -> getGroupMemberByMemberId db vr user gInfo memId - withStore' (`migrateMemberRelationsVector` sendingMem) - withStore' (`migrateMemberRelationsVector` refMem) -- Updating vectors in separate transactions to avoid deadlocks. withStore $ \db -> setMemberVectorRelationConnected db sendingMem refMem MRSubjectConnected withStore $ \db -> setMemberVectorRelationConnected db refMem sendingMem MRReferencedConnected @@ -2783,7 +2774,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let GroupMember {memberId} = m memberName = Just $ memberShortenedName m event = XGrpMsgForward memberId memberName chatMsg brokerTs - sendGroupMemberMessage gInfo member event Nothing (pure ()) + sendGroupMemberMessage gInfo member event -- TODO [channels fwd] base on differentiation between groups and channels isUserGrpFwdRelay :: GroupInfo -> Bool @@ -3228,7 +3219,7 @@ runDeliveryJobWorker a deliveryKey Worker {doWork} = do unless (null ms) $ deliver body ms where buildMemberList sender = do - vec <- withStore $ \db -> migrateGetMemberRelationsVector db sender + vec <- withStore (`getMemberRelationsVector` sender) -- this excludes the sender let introducedMemsIdxs = getRelationsIndexes MRIntroduced vec case jobScope of diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 9219094cbc..a0fdb07046 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -99,18 +99,10 @@ module Simplex.Chat.Store.Groups deleteGroupMember, deleteGroupMemberConnection, updateGroupMemberRole, - createIntroductions, - createIntrosOrUpdateVectors, setMemberVectorNewRelations, setMembersVectorsNewRelation, setMemberVectorRelationConnected, - migrateGetMemberRelationsVector, - migrateMemberRelationsVector, - migrateMemberRelationsVector', - getMemberRelationsVector_, - updateIntroStatus, - getIntroduction, - getIntroducedGroupMemberIds, + getMemberRelationsVector, createIntroReMember, createIntroToMemberContact, getMatchingContacts, @@ -151,8 +143,6 @@ module Simplex.Chat.Store.Groups setGroupChatTTL, getGroupChatTTL, getUserGroupsToExpire, - hasMembersWithoutVector, - getGMsWithoutVectorIds, updateGroupAlias, ) where @@ -166,7 +156,6 @@ import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.Char (toLower) import Data.Either (rights) -import Data.Foldable (foldrM) import Data.Int (Int64) import Data.List (partition, sortOn) import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing) @@ -1602,75 +1591,6 @@ updateGroupMemberRole :: DB.Connection -> User -> GroupMember -> GroupMemberRole updateGroupMemberRole db User {userId} GroupMember {groupMemberId} memRole = DB.execute db "UPDATE group_members SET member_role = ? WHERE user_id = ? AND group_member_id = ?" (memRole, userId, groupMemberId) -createIntroductions :: DB.Connection -> VersionChat -> [GroupMember] -> GroupMember -> IO [GroupMember] -createIntroductions db chatV reMembers toMember - | null reMembers = pure [] - | otherwise = do - currentTs <- getCurrentTime - catMaybes <$> mapM (createIntro_ currentTs) reMembers - where - createIntro_ :: UTCTime -> GroupMember -> IO (Maybe GroupMember) - createIntro_ ts reMember = - -- when members connect concurrently, host would try to create introductions between them in both directions; - -- this check avoids creating second (redundant) introduction - checkInverseIntro >>= \case - Just _ -> pure Nothing - Nothing -> do - DB.execute - db - [sql| - INSERT INTO group_member_intros - (re_group_member_id, to_group_member_id, intro_status, intro_chat_protocol_version, created_at, updated_at) - VALUES (?,?,?,?,?,?) - |] - (groupMemberId' reMember, groupMemberId' toMember, GMIntroPending, chatV, ts, ts) - pure $ Just reMember - where - checkInverseIntro :: IO (Maybe Int64) - checkInverseIntro = - maybeFirstRow fromOnly $ - DB.query - db - "SELECT 1 FROM group_member_intros WHERE re_group_member_id = ? AND to_group_member_id = ? LIMIT 1" - (groupMemberId' toMember, groupMemberId' reMember) - --- Create introductions for members without vectors and update vectors for members with vectors. --- Partitioning and updates happen in same transaction to avoid race conditions. -createIntrosOrUpdateVectors :: DB.Connection -> VersionRangeChat -> [GroupMember] -> GroupMember -> IO [GroupMember] -createIntrosOrUpdateVectors db vr reMembers toMember - | null reMembers = pure [] - | otherwise = do - (memsWithVec, memsWithoutVec) <- partitionByVector reMembers - let GroupMember {indexInGroup} = toMember - setMembersVectorsNewRelation db memsWithVec indexInGroup IDSubjectIntroduced MRIntroduced - memsWithoutVec' <- createIntroductions db (maxVersion vr) memsWithoutVec toMember - pure $ memsWithoutVec' <> memsWithVec - where - partitionByVector :: [GroupMember] -> IO ([GroupMember], [GroupMember]) -#if defined(dbPostgres) - partitionByVector members = do - let memberIds = map groupMemberId' members - -- Lock rows first to ensure partitioning doesn't change in case of concurrent updates - _ :: [Only Int] <- - DB.query - db - "SELECT 1 FROM group_members WHERE group_member_id IN ? FOR UPDATE" - (Only $ In memberIds) - memberIdsWithVec <- S.fromList . map fromOnly <$> - DB.query - db - "SELECT group_member_id FROM group_members WHERE group_member_id IN ? AND member_relations_vector IS NOT NULL" - (Only $ In memberIds) - pure $ partition (\m -> groupMemberId' m `S.member` memberIdsWithVec) members -#else - partitionByVector = foldrM checkMember ([], []) - where - checkMember m (withVec, withoutVec) = do - hasVec <- isJust <$> maybeFirstRow fromOnly - (DB.query db "SELECT 1 FROM group_members WHERE group_member_id = ? AND member_relations_vector IS NOT NULL" (Only $ groupMemberId' m) :: IO [Only Int64]) - pure $ if hasVec then (m : withVec, withoutVec) else (withVec, m : withoutVec) -#endif - setMemberVectorNewRelations :: DB.Connection -> GroupMember -> [(Int64, (IntroductionDirection, MemberRelation))] -> IO () setMemberVectorNewRelations db GroupMember {groupMemberId} relations = do v_ <- maybeFirstRow fromOnly $ @@ -1735,100 +1655,14 @@ setMemberVectorRelationConnected db GroupMember {groupMemberId} GroupMember {ind |] (Binary v', currentTs, groupMemberId) -migrateGetMemberRelationsVector :: DB.Connection -> GroupMember -> ExceptT StoreError IO ByteString -migrateGetMemberRelationsVector db m@GroupMember {groupMemberId} = do - liftIO $ migrateMemberRelationsVector db m +getMemberRelationsVector :: DB.Connection -> GroupMember -> ExceptT StoreError IO ByteString +getMemberRelationsVector db GroupMember {groupMemberId} = ExceptT . firstRow fromOnly (SEGroupMemberNotFound groupMemberId) $ DB.query db "SELECT member_relations_vector FROM group_members WHERE group_member_id = ?" (Only groupMemberId) -migrateMemberRelationsVector :: DB.Connection -> GroupMember -> IO () -migrateMemberRelationsVector db GroupMember {groupMemberId} = - migrateMemberRelationsVector' db groupMemberId - -migrateMemberRelationsVector' :: DB.Connection -> GroupMemberId -> IO () -migrateMemberRelationsVector' db groupMemberId = do - currentTs <- liftIO getCurrentTime - liftIO $ do -#if defined(dbPostgres) - -- Lock the row first to ensure computation runs only after lock is acquired - _ :: [Only Int] <- - DB.query - db - "SELECT 1 FROM group_members WHERE group_member_id = ? AND member_relations_vector IS NULL FOR UPDATE" - (Only groupMemberId) -#endif - DB.execute - db - [sql| - UPDATE group_members - SET - member_relations_vector = ( - SELECT migrate_relations_vector(idx, direction, intro_status) - FROM ( - SELECT m.index_in_group AS idx, 0 AS direction, i.intro_status - FROM group_member_intros i - JOIN group_members m ON m.group_member_id = i.to_group_member_id - WHERE i.re_group_member_id = group_members.group_member_id - UNION ALL - SELECT m.index_in_group AS idx, 1 AS direction, i.intro_status - FROM group_member_intros i - JOIN group_members m ON m.group_member_id = i.re_group_member_id - WHERE i.to_group_member_id = group_members.group_member_id - ) AS relations - ), - updated_at = ? - WHERE group_member_id = ? - AND member_relations_vector IS NULL - |] - (currentTs, groupMemberId) - -getMemberRelationsVector_ :: DB.Connection -> GroupMember -> IO (Maybe ByteString) -getMemberRelationsVector_ db GroupMember {groupMemberId} = - maybeFirstRow fromOnly $ - DB.query - db - "SELECT member_relations_vector FROM group_members WHERE group_member_id = ?" - (Only groupMemberId) - -updateIntroStatus :: DB.Connection -> Int64 -> GroupMemberIntroStatus -> IO () -updateIntroStatus db introId introStatus = do - currentTs <- getCurrentTime - DB.execute - db - [sql| - UPDATE group_member_intros - SET intro_status = ?, updated_at = ? - WHERE group_member_intro_id = ? - |] - (introStatus, currentTs, introId) - -getIntroduction :: DB.Connection -> GroupMember -> GroupMember -> IO (Maybe GroupMemberIntro) -getIntroduction db reMember toMember = - maybeFirstRow toIntro $ - DB.query - db - [sql| - SELECT group_member_intro_id, intro_status - FROM group_member_intros - WHERE re_group_member_id = ? AND to_group_member_id = ? - |] - (groupMemberId' reMember, groupMemberId' toMember) - where - toIntro :: (Int64, GroupMemberIntroStatus) -> GroupMemberIntro - toIntro (introId, introStatus) = - GroupMemberIntro {introId, reMember, toMember, introStatus} - -getIntroducedGroupMemberIds :: DB.Connection -> GroupMember -> IO [GroupMemberId] -getIntroducedGroupMemberIds db invitee = - map fromOnly <$> - DB.query - db - "SELECT re_group_member_id FROM group_member_intros WHERE to_group_member_id = ?" - (Only $ groupMemberId' invitee) - createIntroReMember :: DB.Connection -> User -> GroupInfo -> GroupMember -> VersionChat -> MemberInfo -> Maybe MemberRestrictions -> (CommandId, ConnId) -> SubscriptionMode -> ExceptT StoreError IO GroupMember createIntroReMember db @@ -2718,25 +2552,6 @@ getUserGroupsToExpire db User {userId} globalTTL = where cond = if globalTTL == 0 then "" else " OR chat_item_ttl IS NULL" -hasMembersWithoutVector :: DB.Connection -> IO Bool -hasMembersWithoutVector db = - fromOnly . head - <$> DB.query_ - db - "SELECT EXISTS (SELECT 1 FROM group_members WHERE member_relations_vector IS NULL LIMIT 1)" - -getGMsWithoutVectorIds :: DB.Connection -> IO [GroupMemberId] -getGMsWithoutVectorIds db = - map fromOnly <$> - DB.query_ - db - [sql| - SELECT group_member_id - FROM group_members - WHERE member_relations_vector IS NULL - LIMIT 1000 - |] - updateGroupAlias :: DB.Connection -> UserId -> GroupInfo -> LocalAlias -> IO GroupInfo updateGroupAlias db userId g@GroupInfo {groupId} localAlias = do updatedAt <- getCurrentTime diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 72101c37e3..797d0cba11 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -335,24 +335,24 @@ updateSndMsgDeliveryStatus db connId agentMsgId sndMsgDeliveryStatus = do |] (sndMsgDeliveryStatus, currentTs, connId, agentMsgId) -createPendingGroupMessage :: DB.Connection -> Int64 -> MessageId -> Maybe Int64 -> IO () -createPendingGroupMessage db groupMemberId messageId introId_ = do +createPendingGroupMessage :: DB.Connection -> Int64 -> MessageId -> IO () +createPendingGroupMessage db groupMemberId messageId = do currentTs <- getCurrentTime DB.execute db [sql| INSERT INTO pending_group_messages - (group_member_id, message_id, group_member_intro_id, created_at, updated_at) VALUES (?,?,?,?,?) + (group_member_id, message_id, created_at, updated_at) VALUES (?,?,?,?) |] - (groupMemberId, messageId, introId_, currentTs, currentTs) + (groupMemberId, messageId, currentTs, currentTs) -getPendingGroupMessages :: DB.Connection -> Int64 -> IO [(SndMessage, ACMEventTag, Maybe Int64)] +getPendingGroupMessages :: DB.Connection -> Int64 -> IO [SndMessage] getPendingGroupMessages db groupMemberId = map pendingGroupMessage <$> DB.query db [sql| - SELECT pgm.message_id, m.shared_msg_id, m.msg_body, m.chat_msg_event, pgm.group_member_intro_id + SELECT pgm.message_id, m.shared_msg_id, m.msg_body FROM pending_group_messages pgm JOIN messages m USING (message_id) WHERE pgm.group_member_id = ? @@ -360,8 +360,8 @@ getPendingGroupMessages db groupMemberId = |] (Only groupMemberId) where - pendingGroupMessage (msgId, sharedMsgId, msgBody, cmEventTag, introId_) = - (SndMessage {msgId, sharedMsgId, msgBody}, cmEventTag, introId_) + pendingGroupMessage (msgId, sharedMsgId, msgBody) = + SndMessage {msgId, sharedMsgId, msgBody} deletePendingGroupMessage :: DB.Connection -> Int64 -> MessageId -> IO () deletePendingGroupMessage db groupMemberId messageId = diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index 9dd388be0a..5fd2607f48 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -22,7 +22,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20250922_remove_unused_connection import Simplex.Chat.Store.Postgres.Migrations.M20251007_connections_sync import Simplex.Chat.Store.Postgres.Migrations.M20251017_chat_tags_cascade import Simplex.Chat.Store.Postgres.Migrations.M20251117_member_relations_vector --- import Simplex.Chat.Store.Postgres.Migrations.M20251128_member_relations_vector_stage_2 +import Simplex.Chat.Store.Postgres.Migrations.M20251128_migrate_member_relations import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Text, Maybe Text)] @@ -44,8 +44,8 @@ schemaMigrations = ("20250922_remove_unused_connections", m20250922_remove_unused_connections, Just down_m20250922_remove_unused_connections), ("20251007_connections_sync", m20251007_connections_sync, Just down_m20251007_connections_sync), ("20251017_chat_tags_cascade", m20251017_chat_tags_cascade, Just down_m20251017_chat_tags_cascade), - ("20251117_member_relations_vector", m20251117_member_relations_vector, Just down_m20251117_member_relations_vector) - -- ("20251128_member_relations_vector_stage_2", m20251128_member_relations_vector_stage_2, Just down_m20251128_member_relations_vector_stage_2) + ("20251117_member_relations_vector", m20251117_member_relations_vector, Just down_m20251117_member_relations_vector), + ("20251128_migrate_member_relations", m20251128_migrate_member_relations, Just down_m20251128_migrate_member_relations) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20251117_member_relations_vector.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20251117_member_relations_vector.hs index 33583cc076..d442d93ffa 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/M20251117_member_relations_vector.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20251117_member_relations_vector.hs @@ -7,7 +7,7 @@ import qualified Data.Text as T import Text.RawString.QQ (r) -- This migration creates custom aggregate function migrate_relations_vector(idx, direction, intro_status). --- Used in live migration and stage 2 migration (M20251128_member_relations_vector_stage_2). +-- Used in live migration and stage 2 migration (M20251128_migrate_member_relations). -- -- Vector byte encoding: 4 reserved | 1 direction | 3 status -- Direction: 0 = IDSubjectIntroduced, 1 = IDReferencedIntroduced diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20251128_member_relations_vector_stage_2.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20251128_migrate_member_relations.hs similarity index 65% rename from src/Simplex/Chat/Store/Postgres/Migrations/M20251128_member_relations_vector_stage_2.hs rename to src/Simplex/Chat/Store/Postgres/Migrations/M20251128_migrate_member_relations.hs index 7cfc273d62..5345ca7932 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/M20251128_member_relations_vector_stage_2.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20251128_migrate_member_relations.hs @@ -1,9 +1,9 @@ +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Store.Postgres.Migrations.M20251128_member_relations_vector_stage_2 where +module Simplex.Chat.Store.Postgres.Migrations.M20251128_migrate_member_relations where import Data.Text (Text) -import qualified Data.Text as T import Text.RawString.QQ (r) -- Build member_relations_vector for all members that don't have it yet. @@ -13,11 +13,9 @@ import Text.RawString.QQ (r) -- - direction 0 (IDSubjectIntroduced): current member (subject) is re_group_member_id, was introduced to referenced member -- - direction 1 (IDReferencedIntroduced): current member (subject) is to_group_member_id, referenced member was introduced to it --- TODO [relations vector] drop group_member_intros in the end of migration -m20251128_member_relations_vector_stage_2 :: Text -m20251128_member_relations_vector_stage_2 = - T.pack - [r| +m20251128_migrate_member_relations :: Text +m20251128_migrate_member_relations = + [r| UPDATE group_members SET member_relations_vector = ( SELECT migrate_relations_vector(idx, direction, intro_status) @@ -34,12 +32,14 @@ SET member_relations_vector = ( ) AS relations ) WHERE member_relations_vector IS NULL; + +DROP INDEX idx_pending_group_messages_group_member_intro_id; +ALTER TABLE pending_group_messages DROP COLUMN group_member_intro_id; |] --- TODO [relations vector] re-create group_member_intros -down_m20251128_member_relations_vector_stage_2 :: Text -down_m20251128_member_relations_vector_stage_2 = - T.pack - [r| - +down_m20251128_migrate_member_relations :: Text +down_m20251128_migrate_member_relations = + [r| +ALTER TABLE pending_group_messages ADD COLUMN group_member_intro_id BIGINT REFERENCES group_member_intros ON DELETE CASCADE; +CREATE INDEX idx_pending_group_messages_group_member_intro_id ON pending_group_messages(group_member_intro_id); |] diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index 567aca0144..a3ca7693a6 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -1033,7 +1033,6 @@ CREATE TABLE test_chat_schema.pending_group_messages ( pending_group_message_id bigint NOT NULL, group_member_id bigint NOT NULL, message_id bigint NOT NULL, - group_member_intro_id bigint, created_at timestamp with time zone DEFAULT now() NOT NULL, updated_at timestamp with time zone DEFAULT now() NOT NULL ); @@ -2274,10 +2273,6 @@ CREATE INDEX idx_pending_group_messages_group_member_id ON test_chat_schema.pend -CREATE INDEX idx_pending_group_messages_group_member_intro_id ON test_chat_schema.pending_group_messages USING btree (group_member_intro_id); - - - CREATE INDEX idx_pending_group_messages_message_id ON test_chat_schema.pending_group_messages USING btree (message_id); @@ -2939,11 +2934,6 @@ ALTER TABLE ONLY test_chat_schema.pending_group_messages -ALTER TABLE ONLY test_chat_schema.pending_group_messages - ADD CONSTRAINT pending_group_messages_group_member_intro_id_fkey FOREIGN KEY (group_member_intro_id) REFERENCES test_chat_schema.group_member_intros(group_member_intro_id) ON DELETE CASCADE; - - - ALTER TABLE ONLY test_chat_schema.pending_group_messages ADD CONSTRAINT pending_group_messages_message_id_fkey FOREIGN KEY (message_id) REFERENCES test_chat_schema.messages(message_id) ON DELETE CASCADE; diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs index 0358ae621d..2e5866beae 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -145,7 +145,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20250922_remove_unused_connections import Simplex.Chat.Store.SQLite.Migrations.M20251007_connections_sync import Simplex.Chat.Store.SQLite.Migrations.M20251017_chat_tags_cascade import Simplex.Chat.Store.SQLite.Migrations.M20251117_member_relations_vector --- import Simplex.Chat.Store.SQLite.Migrations.M20251128_member_relations_vector_stage_2 +import Simplex.Chat.Store.SQLite.Migrations.M20251128_migrate_member_relations import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -290,8 +290,8 @@ schemaMigrations = ("20250922_remove_unused_connections", m20250922_remove_unused_connections, Just down_m20250922_remove_unused_connections), ("20251007_connections_sync", m20251007_connections_sync, Just down_m20251007_connections_sync), ("20251017_chat_tags_cascade", m20251017_chat_tags_cascade, Just down_m20251017_chat_tags_cascade), - ("20251117_member_relations_vector", m20251117_member_relations_vector, Just down_m20251117_member_relations_vector) - -- ("20251128_member_relations_vector_stage_2", m20251128_member_relations_vector_stage_2, Just down_m20251128_member_relations_vector_stage_2) + ("20251117_member_relations_vector", m20251117_member_relations_vector, Just down_m20251117_member_relations_vector), + ("20251128_migrate_member_relations", m20251128_migrate_member_relations, Just down_m20251128_migrate_member_relations) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20251117_member_relations_vector.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20251117_member_relations_vector.hs index 3e4b4157f0..c4056245e8 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20251117_member_relations_vector.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20251117_member_relations_vector.hs @@ -15,7 +15,7 @@ import Simplex.Messaging.Agent.Store.SQLite.Util (SQLiteFunc, SQLiteFuncFinal, m -- This module defines custom aggregate function migrate_relations_vector(idx, direction, intro_status). -- It is passed via DBOpts and registered on DB open. --- Used in live migration and stage 2 migration (M20251128_member_relations_vector_stage_2). +-- Used in live migration and stage 2 migration (M20251128_migrate_member_relations). -- -- Vector byte encoding: 4 reserved | 1 direction | 3 status -- Direction: 0 = IDSubjectIntroduced, 1 = IDReferencedIntroduced diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20251128_member_relations_vector_stage_2.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20251128_migrate_member_relations.hs similarity index 67% rename from src/Simplex/Chat/Store/SQLite/Migrations/M20251128_member_relations_vector_stage_2.hs rename to src/Simplex/Chat/Store/SQLite/Migrations/M20251128_migrate_member_relations.hs index f510a50410..7bb7643514 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20251128_member_relations_vector_stage_2.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20251128_migrate_member_relations.hs @@ -1,6 +1,6 @@ {-# LANGUAGE QuasiQuotes #-} -module Simplex.Chat.Store.SQLite.Migrations.M20251128_member_relations_vector_stage_2 where +module Simplex.Chat.Store.SQLite.Migrations.M20251128_migrate_member_relations where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) @@ -12,9 +12,8 @@ import Database.SQLite.Simple.QQ (sql) -- - direction 0 (IDSubjectIntroduced): current member (subject) is re_group_member_id, was introduced to referenced member -- - direction 1 (IDReferencedIntroduced): current member (subject) is to_group_member_id, referenced member was introduced to it --- TODO [relations vector] drop group_member_intros in the end of migration -m20251128_member_relations_vector_stage_2 :: Query -m20251128_member_relations_vector_stage_2 = +m20251128_migrate_member_relations :: Query +m20251128_migrate_member_relations = [sql| UPDATE group_members SET member_relations_vector = ( @@ -32,11 +31,14 @@ SET member_relations_vector = ( ) ) WHERE member_relations_vector IS NULL; + +DROP INDEX idx_pending_group_messages_group_member_intro_id; +ALTER TABLE pending_group_messages DROP COLUMN group_member_intro_id; |] --- TODO [relations vector] re-create group_member_intros -down_m20251128_member_relations_vector_stage_2 :: Query -down_m20251128_member_relations_vector_stage_2 = +down_m20251128_migrate_member_relations :: Query +down_m20251128_migrate_member_relations = [sql| - +ALTER TABLE pending_group_messages ADD COLUMN group_member_intro_id INTEGER REFERENCES group_member_intros ON DELETE CASCADE; +CREATE INDEX idx_pending_group_messages_group_member_intro_id ON pending_group_messages(group_member_intro_id); |] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 91f2ea3899..69fa665911 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -3427,14 +3427,6 @@ Query: Plan: SEARCH chat_item_reactions USING INDEX idx_chat_item_reactions_group (group_id=? AND shared_msg_id=?) -Query: - SELECT group_member_intro_id, intro_status - FROM group_member_intros - WHERE re_group_member_id = ? AND to_group_member_id = ? - -Plan: -SEARCH group_member_intros USING INDEX sqlite_autoindex_group_member_intros_1 (re_group_member_id=? AND to_group_member_id=?) - Query: SELECT group_scope_tag, group_scope_group_member_id FROM chat_items @@ -3484,7 +3476,7 @@ SEARCH m USING INDEX idx_group_members_user_id (user_id=?) SEARCH p USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT pgm.message_id, m.shared_msg_id, m.msg_body, m.chat_msg_event, pgm.group_member_intro_id + SELECT pgm.message_id, m.shared_msg_id, m.msg_body FROM pending_group_messages pgm JOIN messages m USING (message_id) WHERE pgm.group_member_id = ? @@ -3715,52 +3707,6 @@ SEARCH connections USING INDEX idx_connections_group_member_id (group_member_id= LIST SUBQUERY 1 SCAN group_members USING COVERING INDEX idx_group_members_user_id_local_display_name -Query: - UPDATE group_member_intros SET intro_status='fwd' - WHERE re_group_member_id IN (SELECT group_member_id FROM group_members WHERE local_display_name = ?) - AND to_group_member_id IN (SELECT group_member_id FROM group_members WHERE local_display_name = ?) - -Plan: -SEARCH group_member_intros USING INDEX sqlite_autoindex_group_member_intros_1 (re_group_member_id=? AND to_group_member_id=?) -LIST SUBQUERY 1 -SCAN group_members USING COVERING INDEX idx_group_members_user_id_local_display_name -LIST SUBQUERY 2 -SCAN group_members USING COVERING INDEX idx_group_members_user_id_local_display_name - -Query: - UPDATE group_members - SET - member_relations_vector = ( - SELECT migrate_relations_vector(idx, direction, intro_status) - FROM ( - SELECT m.index_in_group AS idx, 0 AS direction, i.intro_status - FROM group_member_intros i - JOIN group_members m ON m.group_member_id = i.to_group_member_id - WHERE i.re_group_member_id = group_members.group_member_id - UNION ALL - SELECT m.index_in_group AS idx, 1 AS direction, i.intro_status - FROM group_member_intros i - JOIN group_members m ON m.group_member_id = i.re_group_member_id - WHERE i.to_group_member_id = group_members.group_member_id - ) AS relations - ), - updated_at = ? - WHERE group_member_id = ? - AND member_relations_vector IS NULL - -Plan: -SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) -CORRELATED SCALAR SUBQUERY 3 -CO-ROUTINE relations -COMPOUND QUERY -LEFT-MOST SUBQUERY -SEARCH i USING INDEX idx_group_member_intros_re_group_member_id (re_group_member_id=?) -SEARCH m USING INTEGER PRIMARY KEY (rowid=?) -UNION ALL -SEARCH i USING INDEX idx_group_member_intros_to_group_member_id (to_group_member_id=?) -SEARCH m USING INTEGER PRIMARY KEY (rowid=?) -SCAN relations - Query: UPDATE group_members SET contact_id = ?, local_display_name = ?, contact_profile_id = ?, updated_at = ? @@ -4432,7 +4378,7 @@ Plan: Query: INSERT INTO pending_group_messages - (group_member_id, message_id, group_member_intro_id, created_at, updated_at) VALUES (?,?,?,?,?) + (group_member_id, message_id, created_at, updated_at) VALUES (?,?,?,?) Plan: @@ -6089,10 +6035,6 @@ Plan: Query: INSERT INTO xftp_file_descriptions (user_id, file_descr_text, file_descr_part_no, file_descr_complete, created_at, updated_at) VALUES (?,?,?,?,?,?) Plan: -Query: SELECT 1 FROM group_members WHERE group_member_id = ? AND member_relations_vector IS NOT NULL -Plan: -SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) - Query: SELECT 1 FROM settings WHERE user_id = ? LIMIT 1 Plan: SEARCH settings USING COVERING INDEX idx_settings_user_id (user_id=?) @@ -6123,12 +6065,6 @@ SCAN CONSTANT ROW SCALAR SUBQUERY 1 SEARCH chat_items USING COVERING INDEX idx_chat_items_contacts_created_at (user_id=? AND contact_id=?) -Query: SELECT EXISTS (SELECT 1 FROM group_members WHERE member_relations_vector IS NULL LIMIT 1) -Plan: -SCAN CONSTANT ROW -SCALAR SUBQUERY 1 -SCAN group_members - Query: SELECT accepted_at FROM operator_usage_conditions WHERE server_operator_id = ? AND conditions_commit = ? Plan: SEARCH operator_usage_conditions USING INDEX idx_operator_usage_conditions_conditions_commit (conditions_commit=? AND server_operator_id=?) @@ -6509,14 +6445,6 @@ Query: UPDATE files SET private_snd_file_descr = ?, updated_at = ? WHERE user_id Plan: SEARCH files USING INTEGER PRIMARY KEY (rowid=?) -Query: UPDATE group_member_intros SET intro_status='con' -Plan: -SCAN group_member_intros - -Query: UPDATE group_member_intros SET intro_status='fwd' -Plan: -SCAN group_member_intros - Query: UPDATE group_members SET contact_id = ?, updated_at = ? WHERE contact_profile_id = ? Plan: SEARCH group_members USING COVERING INDEX idx_group_members_contact_profile_id (contact_profile_id=?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 34336a38ee..1e2db113c3 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -393,7 +393,6 @@ CREATE TABLE pending_group_messages( pending_group_message_id INTEGER PRIMARY KEY, group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE, message_id INTEGER NOT NULL REFERENCES messages ON DELETE CASCADE, - group_member_intro_id INTEGER REFERENCES group_member_intros ON DELETE CASCADE, created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')) ); @@ -804,9 +803,6 @@ CREATE INDEX idx_group_profiles_user_id ON group_profiles(user_id); CREATE INDEX idx_groups_chat_item_id ON groups(chat_item_id); CREATE INDEX idx_groups_group_profile_id ON groups(group_profile_id); CREATE INDEX idx_messages_group_id ON messages(group_id); -CREATE INDEX idx_pending_group_messages_group_member_intro_id ON pending_group_messages( - group_member_intro_id -); CREATE INDEX idx_pending_group_messages_message_id ON pending_group_messages( message_id ); diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index c08c0827ae..7aba1c2ad5 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -1747,49 +1747,6 @@ instance TextEncoding ConnType where ConnMember -> "member" ConnUserContact -> "user_contact" -data GroupMemberIntro = GroupMemberIntro - { introId :: Int64, - reMember :: GroupMember, - toMember :: GroupMember, - introStatus :: GroupMemberIntroStatus - } - deriving (Show) - -data GroupMemberIntroStatus - = GMIntroPending - | GMIntroSent - | GMIntroInvReceived - | GMIntroInvForwarded - | GMIntroReConnected - | GMIntroToConnected - | GMIntroConnected - deriving (Eq, Show) - -instance FromField GroupMemberIntroStatus where fromField = fromTextField_ introStatusT - -instance ToField GroupMemberIntroStatus where toField = toField . serializeIntroStatus - -introStatusT :: Text -> Maybe GroupMemberIntroStatus -introStatusT = \case - "new" -> Just GMIntroPending - "sent" -> Just GMIntroSent - "rcv" -> Just GMIntroInvReceived - "fwd" -> Just GMIntroInvForwarded - "re-con" -> Just GMIntroReConnected - "to-con" -> Just GMIntroToConnected - "con" -> Just GMIntroConnected - _ -> Nothing - -serializeIntroStatus :: GroupMemberIntroStatus -> Text -serializeIntroStatus = \case - GMIntroPending -> "new" - GMIntroSent -> "sent" - GMIntroInvReceived -> "rcv" - GMIntroInvForwarded -> "fwd" - GMIntroReConnected -> "re-con" - GMIntroToConnected -> "to-con" - GMIntroConnected -> "con" - type CommandId = Int64 aCorrId :: CommandId -> ACorrId diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index b0f4fc99dc..aa9a48f279 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -1774,8 +1774,6 @@ testGroupDelayedModeration ps = do -- imitate not implemented group forwarding -- (real client wouldn't have forwarding code, but tests use "current code" with configured version, -- and forwarding client doesn't check compatibility) - void $ withCCTransaction alice $ \db -> - DB.execute_ db "UPDATE group_member_intros SET intro_status='con'" updateGroupForwardingVectors alice "bob" "cath" MRConnected cath #> "#team hi" -- message is pending for bob @@ -1821,8 +1819,6 @@ testGroupDelayedModerationFullDelete ps = do -- imitate not implemented group forwarding -- (real client wouldn't have forwarding code, but tests use "current code" with configured version, -- and forwarding client doesn't check compatibility) - void $ withCCTransaction alice $ \db -> - DB.execute_ db "UPDATE group_member_intros SET intro_status='con'" updateGroupForwardingVectors alice "bob" "cath" MRConnected cath #> "#team hi" -- message is pending for bob @@ -5049,15 +5045,6 @@ setupGroupForwarding host invitee1 invitee2 = do WHERE group_member_id IN (SELECT group_member_id FROM group_members WHERE local_display_name = ?) |] (Only invitee1Name) - void $ withCCTransaction host $ \db -> - DB.execute - db - [sql| - UPDATE group_member_intros SET intro_status='fwd' - WHERE re_group_member_id IN (SELECT group_member_id FROM group_members WHERE local_display_name = ?) - AND to_group_member_id IN (SELECT group_member_id FROM group_members WHERE local_display_name = ?) - |] - (invitee1Name, invitee2Name) setupGroupForwardingVectors host invitee1 invitee2 @@ -5110,8 +5097,6 @@ testGroupMsgForwardDeduplicate = createGroup3 "team" alice bob cath threadDelay 1000000 -- delay so member relations don't get overwritten to connected - void $ withCCTransaction alice $ \db -> - DB.execute_ db "UPDATE group_member_intros SET intro_status='fwd'" setupGroupForwardingVectors alice bob cath bob #> "#team hi there" diff --git a/tests/PostgresSchemaDump.hs b/tests/PostgresSchemaDump.hs index 61ea5f8fdd..7df0beb2fa 100644 --- a/tests/PostgresSchemaDump.hs +++ b/tests/PostgresSchemaDump.hs @@ -76,5 +76,7 @@ postgresSchemaDumpTest migrations testDBOpts@DBOpts {connstr, schema = testDBSch skipComparisonForDownMigrations :: [String] skipComparisonForDownMigrations = [ -- via_group field moves - "20250922_remove_unused_connections" + "20250922_remove_unused_connections", + -- group_member_intro_id field moves + "20251128_migrate_member_relations" ] diff --git a/tests/SchemaDump.hs b/tests/SchemaDump.hs index 8a716ddf11..5646031cc2 100644 --- a/tests/SchemaDump.hs +++ b/tests/SchemaDump.hs @@ -132,7 +132,9 @@ skipComparisonForDownMigrations = -- index moves down to the end of the file "20250721_indexes", -- indexes move down to the end of the file - "20250922_remove_unused_connections" + "20250922_remove_unused_connections", + -- group_member_intros table moves down to the end of the file + "20251128_migrate_member_relations" ] getSchema :: FilePath -> FilePath -> IO String From c9137cb01530cd05303f6bf5fb5ec3b072dc7871 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 29 Dec 2025 20:47:13 +0000 Subject: [PATCH 19/25] rfc: simplified community vouchers (#6498) * rfc: simplified community vouchers * update --- docs/rfcs/2025-12-10-vouchers-2.md | 98 ++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 docs/rfcs/2025-12-10-vouchers-2.md diff --git a/docs/rfcs/2025-12-10-vouchers-2.md b/docs/rfcs/2025-12-10-vouchers-2.md new file mode 100644 index 0000000000..60e80215dd --- /dev/null +++ b/docs/rfcs/2025-12-10-vouchers-2.md @@ -0,0 +1,98 @@ +# Community Vouchers contract + +This document simplifies [the previous RFC](./2025-10-23-vouchers.md) in two ways: +- only one type of credits (previous design had conversion from community to operator credits). +- vouchers can support any amount and change. +- using a single fixed-size Merkle tree for vouchers. +- proposal for balancing privacy and the size of the tree that the client has to load. + +## Objectives + +- voucher expiration - fixed period per contract. +- make assignment of voucher to redeemer (a community) private, not allowing further transactions/re-assignments. +- vouchers support any amount. +- allow assigning and redeeming part of the voucher, with change. + +## Design proposal + +### General ideas + +- Voucher is issued by the smart contract in exchange for stable-coin deposit, and recorded as blinded commitment added to Merkle tree. +- Voucher is nominated in infrastructure credits with 18 decimals, as all ETH tokens. +- Voucher assignments and redemptions (partial or full) are done via zero-knowledge proof and recorded as nullifier to prevent double spends, with the new commitment for the remaining amount and transaction count (can be 0). +- Commitment includes: + - the amount. + - the whether the voucher is assigned. + - the expiration time. +- Merkle tree of commitments has a fixed depth of say 20-40 levels (for approx 10^6-10^12 voucher capacity). +- Assignments and redemptions should prove consistency between the old and new commitments, without revealing them, and include the nullifier to prevent "double spends". +- Redemptions also include the blockchain address and ID of the operator. +- Release of funds with revenue sharing can be done via call to another contract. + +### Data storage + +Smart contract: +- total deposit amounts bucketed by time ranges (to allow the release of funds to network after expiration). +- redemption amounts bucketed by time ranges with homomorphic encryption (to prevent reduction of anonymity set). +- nullifiers bucketed by creation time (to allow removing old nullifiers). +- the path in the Merkle tree on "the front" of the filled part of the tree. +- multiple last Merkle tree roots: + - to accept the proof after the root changes. + - to frustrate timing correlation by computing proofs in advance (for better usability). + +Transaction event log: +- Merkle tree of commitments are recorded in event log + +### Voucher lifecycle + +1. Issuance: blinded commitment recorded as described above in exchange for stablecoin deposit. + +The contract should be able to verify that: +- commitment amount is the same as deposit amount. +- expiration time is in 12 months from now (possibly rounded up to 1-3 months). +- not assigned to redeemer. + +Initially, the deposit UX could be dApp generating some token to be used in the app to assign voucher to the community via app (to preserve privacy). + +2. Computing zero-knowledge proof for assignment or redemption. + +To compute the proof the client needs to have a path from commitment to tree root. + +As the path to commitment changes when the tree gets filled, we need to find a balance between privacy and usability - to request as little as possible and have as large anonymity set as possible. + + +3. Assignment. + +Includes 2 new commitments (to destination and a "change") and nullifier are submitted. + +ZK proof should prove that: +- the old commitment expiration time is greater than transaction submission time (possibly has to be included in parameters to be part of the proof). +- the total amount of 2 new commitments is the same as the amount of nullified old commitment. +- the new commitments expiration time is the same as the old. +- the old commitment is not assigned to redeemer. +- the new commitments is assigned to redeemer. + +Questions: +1) how would the redeemer know that it received a voucher? Probably, community owners would be notified by chat relays. +2) do we want to conceal from the relays when and to which group donation was made? If yes, community owners can monitor blockchain themselves. +3) can we have view keys to delegate the detection of incoming transactions to relays? +4) should we round expiration time to a month or even 3 months? (to avoid exposing expiration time to the assignment recipient, as it also leaks purchase time) If so, we should also have overlapping rounding ranges to avoid leaking purchase time in case it is assigned straight after purchase, and straight after range change. + + +4. Redemption. + +Includes one new commitment (with the change) and public record of funds released to operator and network. + +The revenues received by the operators are publicly visible. + +ZK proof should prove that: +- the old commitment expiration time is greater than transaction submission time. +- the total amount of the new commitment and of released funds is the same as the amount of nullified commitment. +- the new commitment expiration time is the same as the old. +- the old commitment is assigned to the redeemer that redeems the voucher. +- the new commitment has the same assignment. + + +5. Releasing deposits for expired vouchers to the network. + +That would require tracking purchases in buckets (in the clear) and redemptions in homomorphicly encrypted structure, so an observer cannot correlate redemptions to purchases (as the same structure will change, and it won't be possible to establish which bucket). From 448f0f1ca1364d6fd08972c567a0a3fc0a91b11c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 29 Dec 2025 21:27:30 +0000 Subject: [PATCH 20/25] website: update vouchers page --- website/src/token.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/website/src/token.md b/website/src/token.md index b77e67d59b..f1b4c5909a 100644 --- a/website/src/token.md +++ b/website/src/token.md @@ -116,11 +116,7 @@ Server operators will receive up to 70% of the infrastructure payments. A higher **What is technology design?** -[Early ideas about Community Vouchers](https://github.com/simplex-chat/simplex-chat/blob/master/docs/rfcs/2024-04-26-commercial-model.md). - -[The most recent design](https://github.com/simplex-chat/simplex-chat/blob/master/docs/rfcs/2025-10-23-vouchers.md) based on zero-knowledge proofs. - -[Previosly shared FAQ](https://github.com/simplex-chat/simplex-chat/blob/master/docs/rfcs/2025-12-17-community-vouchers-faq.md). +[The conceptual design](https://github.com/simplex-chat/simplex-chat/blob/master/docs/rfcs/2025-12-10-vouchers-2.md) for Community Vouchers uses zero-knowledge proofs, making the purchase, assigning vouchers to groups and their redemptions unlinkable. A whitepaper will be published in February 2026. From b4fe127c6d76f8d2101783294ffde629e03bd975 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 29 Dec 2025 21:52:11 +0000 Subject: [PATCH 21/25] docs: update XMR donations address --- README.md | 4 ++-- blog/20220808-simplex-chat-v3.1-chat-groups.md | 2 +- blog/20220901-simplex-chat-v3.2-incognito-mode.md | 2 +- blog/20220928-simplex-chat-v4-encrypted-database.md | 4 ++-- ...-simplex-chat-v4.2-security-audit-new-website.md | 4 ++-- blog/20221206-simplex-chat-v4.3-voice-messages.md | 6 +++--- ...30103-simplex-chat-v4.4-disappearing-messages.md | 7 ++++--- docs/DONATIONS.md | 2 +- docs/lang/cs/README.md | 10 +++++----- docs/lang/fr/README.md | 10 +++++----- docs/lang/pl/README.md | 13 +++++-------- 11 files changed, 31 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index f8abf8adfb..3364c28284 100644 --- a/README.md +++ b/README.md @@ -145,9 +145,9 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat) (commission-free) or [OpenCollective](https://opencollective.com/simplex-chat) (~10% commission). - BTC: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u -- XMR: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt +- XMR: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c - BCH: bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg -- ETH/USDT (Ethereum, Arbitrum One): 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a +- ETH/USDT (Ethereum, Arbitrum One): 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) - ZEC: t1fwjQW5gpFhDqXNhxqDWyF9j9WeKvVS5Jg - ZEC shielded: u16rnvkflumf5uw9frngc2lymvmzgdr2mmc9unyu0l44unwfmdcpfm0axujd2w34ct3ye709azxsqge45705lpvvqu264ltzvfay55ygyq - DOGE: D99pV4n9TrPxBPCkQGx4w4SMSa6QjRBxPf diff --git a/blog/20220808-simplex-chat-v3.1-chat-groups.md b/blog/20220808-simplex-chat-v3.1-chat-groups.md index cce22393fb..7a93cecbc8 100644 --- a/blog/20220808-simplex-chat-v3.1-chat-groups.md +++ b/blog/20220808-simplex-chat-v3.1-chat-groups.md @@ -113,7 +113,7 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat): it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat): it also accepts donations in crypto-currencies, but charges a commission. -- Monero wallet: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt +- Monero wallet: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c Thank you, diff --git a/blog/20220901-simplex-chat-v3.2-incognito-mode.md b/blog/20220901-simplex-chat-v3.2-incognito-mode.md index 73df29c8d7..ff6ef5bc30 100644 --- a/blog/20220901-simplex-chat-v3.2-incognito-mode.md +++ b/blog/20220901-simplex-chat-v3.2-incognito-mode.md @@ -99,7 +99,7 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat): it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat): it also accepts donations in crypto-currencies, but charges a commission. -- Monero wallet: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt +- Monero wallet: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c Thank you, diff --git a/blog/20220928-simplex-chat-v4-encrypted-database.md b/blog/20220928-simplex-chat-v4-encrypted-database.md index 6f8064454f..b1ae048a51 100644 --- a/blog/20220928-simplex-chat-v4-encrypted-database.md +++ b/blog/20220928-simplex-chat-v4-encrypted-database.md @@ -132,8 +132,8 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat): it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat): it also accepts donations in crypto-currencies, but charges a commission. -- Monero wallet: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt -- Bitcoin wallet: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG +- Monero wallet: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Bitcoin wallet: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u Thank you, diff --git a/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md b/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md index 51dba8818c..e77a625b8f 100644 --- a/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md +++ b/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md @@ -177,8 +177,8 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat) - it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat) - it charges a commission, and also accepts donations in many crypto-currencies. -- Monero wallet: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt -- Bitcoin wallet: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG +- Monero wallet: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Bitcoin wallet: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u - please let us know, via GitHub issue or chat, if you want to make a donation in some other cryptocurrency - we will add the address to the list. Thank you, diff --git a/blog/20221206-simplex-chat-v4.3-voice-messages.md b/blog/20221206-simplex-chat-v4.3-voice-messages.md index 07a6e227f0..a5af09d374 100644 --- a/blog/20221206-simplex-chat-v4.3-voice-messages.md +++ b/blog/20221206-simplex-chat-v4.3-voice-messages.md @@ -127,9 +127,9 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat) - it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat) - it charges a commission, and also accepts donations in many crypto-currencies. -- Monero address: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt -- Bitcoin address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- Ethereum address: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 +- Monero address: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Bitcoin address: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u +- Ethereum address: 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) - please let us know, via GitHub issue or chat, if you want to make a donation in some other cryptocurrency - we will add the address to the list. Thank you, diff --git a/blog/20230103-simplex-chat-v4.4-disappearing-messages.md b/blog/20230103-simplex-chat-v4.4-disappearing-messages.md index ab9010535f..b9b84c7ed9 100644 --- a/blog/20230103-simplex-chat-v4.4-disappearing-messages.md +++ b/blog/20230103-simplex-chat-v4.4-disappearing-messages.md @@ -91,9 +91,10 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat) - it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat) - it charges a commission, and also accepts donations in crypto-currencies. -- Monero address: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt - Bitcoin address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- BCH address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- Ethereum address: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 +- Monero address: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Bitcoin address: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u +- BCH address: bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg +- Ethereum address: 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) - please let us know, via GitHub issue or chat, if you want to create a donation in some other cryptocurrency - we will add the address to the list. Thank you, diff --git a/docs/DONATIONS.md b/docs/DONATIONS.md index 276a57ed8a..50eb62a401 100644 --- a/docs/DONATIONS.md +++ b/docs/DONATIONS.md @@ -17,7 +17,7 @@ Please donate via: - [GitHub](https://github.com/sponsors/simplex-chat) (commission-free) or [OpenCollective](https://opencollective.com/simplex-chat) (~10% commission) - BTC: [bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u](bitcoin:bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u) -- XMR: [8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt](monero:8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt) +- XMR: [8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c](monero:8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c) - ETH/USDT (Ethereum, Arbitrum One): [0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a](ethereum:0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a) ([donate.simplexchat.eth](ethereum:0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a)) - [Other cryptocurrencies](https://github.com/simplex-chat/simplex-chat#please-support-us-with-your-donations) diff --git a/docs/lang/cs/README.md b/docs/lang/cs/README.md index d1ff8a0eca..5ba6a87803 100644 --- a/docs/lang/cs/README.md +++ b/docs/lang/cs/README.md @@ -277,11 +277,11 @@ Přispět je možné prostřednictvím: - [GitHub](https://github.com/sponsors/simplex-chat) - je to pro nás bez provize. - OpenCollective](https://opencollective.com/simplex-chat) - účtuje si provizi a přijímá také dary v kryptoměnách. -- Adresa Monero: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt. -- Bitcoinová adresa: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- BCH adresa: BCH: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- Ethereum adresa: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 -- Adresa Solana: 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L +- Adresa Monero: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c. +- Bitcoinová adresa: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u +- BCH adresa: bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg +- ETH/USDT (Ethereum, Arbitrum One) adresa: 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) +- Adresa Solana: 7JCf5m3TiHmYKZVr6jCu1KeZVtb9Y1jRMQDU69p5ARnu - dejte nám prosím vědět prostřednictvím GitHub issue nebo chatu, pokud chcete vytvořit příspěvek v nějaké jiné kryptoměně - přidáme adresu do seznamu. Děkujeme, diff --git a/docs/lang/fr/README.md b/docs/lang/fr/README.md index d6bf517446..bc06e9e228 100644 --- a/docs/lang/fr/README.md +++ b/docs/lang/fr/README.md @@ -112,11 +112,11 @@ Il est possible de faire un don via : - [GitHub](https://github.com/sponsors/simplex-chat) - il n'y a pas de commission à payer. - [OpenCollective](https://opencollective.com/simplex-chat) - ils prélèvent une commission et acceptent également les dons en crypto-monnaies. -- Adresse Monero : 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt -- Adresse Bitcoin : 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- Adresse BCH : 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- Adresse Ethereum : 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 -- Adresse Solana : 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L +- Adresse Monero : 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Adresse Bitcoin : bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u +- Adresse BCH : bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg +- Adresse ETH/USDT (Ethereum, Arbitrum One) : 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) +- Adresse Solana : 7JCf5m3TiHmYKZVr6jCu1KeZVtb9Y1jRMQDU69p5ARnu Nous vous remercions, diff --git a/docs/lang/pl/README.md b/docs/lang/pl/README.md index 9f107dc825..fc94863658 100644 --- a/docs/lang/pl/README.md +++ b/docs/lang/pl/README.md @@ -164,14 +164,11 @@ Możesz nas wesprzeć za pomocą: - [GitHuba](https://github.com/sponsors/simplex-chat) - jest to dla nas wolne od prowizji. - [OpenCollective](https://opencollective.com/simplex-chat) - pobiera prowizję, a także przyjmuje darowizny w kryptowalutach. -- Monero: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt -- Bitcoin: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- BCH: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- USDT: - - BNB Smart Chain: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 - - Tron: TNnTrKLBmdy2Wn3cAQR98dAVvWhLskQGfW -- Ethereum: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 -- Solana: 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L +- Monero: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Bitcoin: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u +- BCH: bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg +- ETH/USDT (Ethereum, Arbitrum One): 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) +- Solana: 7JCf5m3TiHmYKZVr6jCu1KeZVtb9Y1jRMQDU69p5ARnu Dziękuję, From 7276bb944de39551f73ce927412e96da63e61515 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 29 Dec 2025 21:52:11 +0000 Subject: [PATCH 22/25] docs: update XMR donations address --- README.md | 4 ++-- blog/20220808-simplex-chat-v3.1-chat-groups.md | 2 +- blog/20220901-simplex-chat-v3.2-incognito-mode.md | 2 +- blog/20220928-simplex-chat-v4-encrypted-database.md | 4 ++-- ...-simplex-chat-v4.2-security-audit-new-website.md | 4 ++-- blog/20221206-simplex-chat-v4.3-voice-messages.md | 6 +++--- ...30103-simplex-chat-v4.4-disappearing-messages.md | 7 ++++--- docs/lang/cs/README.md | 10 +++++----- docs/lang/fr/README.md | 10 +++++----- docs/lang/pl/README.md | 13 +++++-------- 10 files changed, 30 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 2f0f92f5f7..3092cfc02f 100644 --- a/README.md +++ b/README.md @@ -163,9 +163,9 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat) (commission-free) or [OpenCollective](https://opencollective.com/simplex-chat) (~10% commission). - BTC: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u -- XMR: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt +- XMR: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c - BCH: bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg -- ETH/USDT (Ethereum, Arbitrum One): 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a +- ETH/USDT (Ethereum, Arbitrum One): 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) - ZEC: t1fwjQW5gpFhDqXNhxqDWyF9j9WeKvVS5Jg - ZEC shielded: u16rnvkflumf5uw9frngc2lymvmzgdr2mmc9unyu0l44unwfmdcpfm0axujd2w34ct3ye709azxsqge45705lpvvqu264ltzvfay55ygyq - DOGE: D99pV4n9TrPxBPCkQGx4w4SMSa6QjRBxPf diff --git a/blog/20220808-simplex-chat-v3.1-chat-groups.md b/blog/20220808-simplex-chat-v3.1-chat-groups.md index cce22393fb..7a93cecbc8 100644 --- a/blog/20220808-simplex-chat-v3.1-chat-groups.md +++ b/blog/20220808-simplex-chat-v3.1-chat-groups.md @@ -113,7 +113,7 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat): it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat): it also accepts donations in crypto-currencies, but charges a commission. -- Monero wallet: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt +- Monero wallet: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c Thank you, diff --git a/blog/20220901-simplex-chat-v3.2-incognito-mode.md b/blog/20220901-simplex-chat-v3.2-incognito-mode.md index 73df29c8d7..ff6ef5bc30 100644 --- a/blog/20220901-simplex-chat-v3.2-incognito-mode.md +++ b/blog/20220901-simplex-chat-v3.2-incognito-mode.md @@ -99,7 +99,7 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat): it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat): it also accepts donations in crypto-currencies, but charges a commission. -- Monero wallet: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt +- Monero wallet: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c Thank you, diff --git a/blog/20220928-simplex-chat-v4-encrypted-database.md b/blog/20220928-simplex-chat-v4-encrypted-database.md index 6f8064454f..b1ae048a51 100644 --- a/blog/20220928-simplex-chat-v4-encrypted-database.md +++ b/blog/20220928-simplex-chat-v4-encrypted-database.md @@ -132,8 +132,8 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat): it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat): it also accepts donations in crypto-currencies, but charges a commission. -- Monero wallet: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt -- Bitcoin wallet: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG +- Monero wallet: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Bitcoin wallet: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u Thank you, diff --git a/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md b/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md index 51dba8818c..e77a625b8f 100644 --- a/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md +++ b/blog/20221108-simplex-chat-v4.2-security-audit-new-website.md @@ -177,8 +177,8 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat) - it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat) - it charges a commission, and also accepts donations in many crypto-currencies. -- Monero wallet: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt -- Bitcoin wallet: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG +- Monero wallet: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Bitcoin wallet: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u - please let us know, via GitHub issue or chat, if you want to make a donation in some other cryptocurrency - we will add the address to the list. Thank you, diff --git a/blog/20221206-simplex-chat-v4.3-voice-messages.md b/blog/20221206-simplex-chat-v4.3-voice-messages.md index 07a6e227f0..a5af09d374 100644 --- a/blog/20221206-simplex-chat-v4.3-voice-messages.md +++ b/blog/20221206-simplex-chat-v4.3-voice-messages.md @@ -127,9 +127,9 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat) - it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat) - it charges a commission, and also accepts donations in many crypto-currencies. -- Monero address: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt -- Bitcoin address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- Ethereum address: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 +- Monero address: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Bitcoin address: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u +- Ethereum address: 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) - please let us know, via GitHub issue or chat, if you want to make a donation in some other cryptocurrency - we will add the address to the list. Thank you, diff --git a/blog/20230103-simplex-chat-v4.4-disappearing-messages.md b/blog/20230103-simplex-chat-v4.4-disappearing-messages.md index ab9010535f..b9b84c7ed9 100644 --- a/blog/20230103-simplex-chat-v4.4-disappearing-messages.md +++ b/blog/20230103-simplex-chat-v4.4-disappearing-messages.md @@ -91,9 +91,10 @@ It is possible to donate via: - [GitHub](https://github.com/sponsors/simplex-chat) - it is commission-free for us. - [OpenCollective](https://opencollective.com/simplex-chat) - it charges a commission, and also accepts donations in crypto-currencies. -- Monero address: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt - Bitcoin address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- BCH address: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- Ethereum address: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 +- Monero address: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Bitcoin address: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u +- BCH address: bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg +- Ethereum address: 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) - please let us know, via GitHub issue or chat, if you want to create a donation in some other cryptocurrency - we will add the address to the list. Thank you, diff --git a/docs/lang/cs/README.md b/docs/lang/cs/README.md index 7eab61395e..35a37be73b 100644 --- a/docs/lang/cs/README.md +++ b/docs/lang/cs/README.md @@ -277,11 +277,11 @@ Přispět je možné prostřednictvím: - [GitHub](https://github.com/sponsors/simplex-chat) - je to pro nás bez provize. - OpenCollective](https://opencollective.com/simplex-chat) - účtuje si provizi a přijímá také dary v kryptoměnách. -- Adresa Monero: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt. -- Bitcoinová adresa: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- BCH adresa: BCH: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- Ethereum adresa: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 -- Adresa Solana: 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L +- Adresa Monero: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c. +- Bitcoinová adresa: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u +- BCH adresa: bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg +- ETH/USDT (Ethereum, Arbitrum One) adresa: 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) +- Adresa Solana: 7JCf5m3TiHmYKZVr6jCu1KeZVtb9Y1jRMQDU69p5ARnu - dejte nám prosím vědět prostřednictvím GitHub issue nebo chatu, pokud chcete vytvořit příspěvek v nějaké jiné kryptoměně - přidáme adresu do seznamu. Děkujeme, diff --git a/docs/lang/fr/README.md b/docs/lang/fr/README.md index 69401da5a2..eaabfebe7d 100644 --- a/docs/lang/fr/README.md +++ b/docs/lang/fr/README.md @@ -112,11 +112,11 @@ Il est possible de faire un don via : - [GitHub](https://github.com/sponsors/simplex-chat) - il n'y a pas de commission à payer. - [OpenCollective](https://opencollective.com/simplex-chat) - ils prélèvent une commission et acceptent également les dons en crypto-monnaies. -- Adresse Monero : 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt -- Adresse Bitcoin : 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- Adresse BCH : 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- Adresse Ethereum : 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 -- Adresse Solana : 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L +- Adresse Monero : 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Adresse Bitcoin : bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u +- Adresse BCH : bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg +- Adresse ETH/USDT (Ethereum, Arbitrum One) : 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) +- Adresse Solana : 7JCf5m3TiHmYKZVr6jCu1KeZVtb9Y1jRMQDU69p5ARnu Nous vous remercions, diff --git a/docs/lang/pl/README.md b/docs/lang/pl/README.md index 23ca00c3e6..9081b644f0 100644 --- a/docs/lang/pl/README.md +++ b/docs/lang/pl/README.md @@ -164,14 +164,11 @@ Możesz nas wesprzeć za pomocą: - [GitHuba](https://github.com/sponsors/simplex-chat) - jest to dla nas wolne od prowizji. - [OpenCollective](https://opencollective.com/simplex-chat) - pobiera prowizję, a także przyjmuje darowizny w kryptowalutach. -- Monero: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt -- Bitcoin: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- BCH: 1bpefFkzuRoMY3ZuBbZNZxycbg7NYPYTG -- USDT: - - BNB Smart Chain: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 - - Tron: TNnTrKLBmdy2Wn3cAQR98dAVvWhLskQGfW -- Ethereum: 0x83fd788f7241a2be61780ea9dc72d2151e6843e2 -- Solana: 43tWFWDczgAcn4Rzwkpqg2mqwnQETSiTwznmCgA2tf1L +- Monero: 8A3ZWAXrrQddvnT1fPrtbK86ZAoM4nai3Gjg1LEow3JWcryJtovMnHYZnxTJpCLmAbfWbnPMeTzPmMBjAhyd4xoM89hYq1c +- Bitcoin: bc1q2gy6f02nn6vvcxs0pnu29tpnpyz0qf66505d4u +- BCH: bitcoincash:qq6c8vfvxqrk6rhdysgvkhqc24sggkfsx5nqvdlqcg +- ETH/USDT (Ethereum, Arbitrum One): 0xD7047Fe3Eecb2f2FF78d839dD927Be27Bc12c86a (donate.simplexchat.eth) +- Solana: 7JCf5m3TiHmYKZVr6jCu1KeZVtb9Y1jRMQDU69p5ARnu Dziękuję, From 59b31e263112bf8057b147504fb71a096b178199 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 30 Dec 2025 07:49:17 +0000 Subject: [PATCH 23/25] core: compress commands in remote connection (#5776) * core: compress commands in remote connection * backwards compatible compression * show in CLI if compression is enabled * use aroundWith for ghc 8.10.7 compatibility * skip bot API tests in GHC 8.10.7 --- src/Simplex/Chat.hs | 1 + src/Simplex/Chat/Controller.hs | 16 ++-- src/Simplex/Chat/Library/Commands.hs | 2 +- src/Simplex/Chat/Remote.hs | 45 ++++++--- src/Simplex/Chat/Remote/Protocol.hs | 43 ++++++--- src/Simplex/Chat/Remote/Types.hs | 11 ++- src/Simplex/Chat/View.hs | 10 +- tests/ChatTests/Utils.hs | 6 +- tests/RemoteTests.hs | 132 ++++++++++++++++----------- tests/Test.hs | 2 + 10 files changed, 171 insertions(+), 97 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 93132fb413..35234e1136 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -110,6 +110,7 @@ defaultChatConfig = deliveryWorkerDelay = 0, deliveryBucketSize = 10000, deviceNameForRemote = "", + remoteCompression = True, chatHooks = defaultChatHooks } diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 8003f66324..34ad95b800 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -159,6 +159,7 @@ data ChatConfig = ChatConfig deliveryBucketSize :: Int, highlyAvailable :: Bool, deviceNameForRemote :: Text, + remoteCompression :: Bool, chatHooks :: ChatHooks } @@ -754,7 +755,7 @@ data ChatResponse | CRRemoteFileStored {remoteHostId :: RemoteHostId, remoteFileSource :: CryptoFile} | CRRemoteCtrlList {remoteCtrls :: [RemoteCtrlInfo]} | CRRemoteCtrlConnecting {remoteCtrl_ :: Maybe RemoteCtrlInfo, ctrlAppInfo :: CtrlAppInfo, appVersion :: AppVersion} - | CRRemoteCtrlConnected {remoteCtrl :: RemoteCtrlInfo} + | CRRemoteCtrlConnected {remoteCtrl :: RemoteCtrlInfo, compression :: Bool} | CRSQLResult {rows :: [Text]} #if !defined(dbPostgres) | CRArchiveExported {archiveErrors :: [ArchiveError]} @@ -857,7 +858,7 @@ data ChatEvent | CEvtNtfMessage {user :: User, connEntity :: ConnectionEntity, ntfMessage :: NtfMsgAckInfo} | CEvtRemoteHostSessionCode {remoteHost_ :: Maybe RemoteHostInfo, sessionCode :: Text} | CEvtNewRemoteHost {remoteHost :: RemoteHostInfo} - | CEvtRemoteHostConnected {remoteHost :: RemoteHostInfo} + | CEvtRemoteHostConnected {remoteHost :: RemoteHostInfo, compression :: Bool} | CEvtRemoteHostStopped {remoteHostId_ :: Maybe RemoteHostId, rhsState :: RemoteHostSessionState, rhStopReason :: RemoteHostStopReason} | CEvtRemoteCtrlFound {remoteCtrl :: RemoteCtrlInfo, ctrlAppInfo_ :: Maybe CtrlAppInfo, appVersion :: AppVersion, compatible :: Bool} | CEvtRemoteCtrlSessionCode {remoteCtrl_ :: Maybe RemoteCtrlInfo, sessionCode :: Text} @@ -897,7 +898,7 @@ allowRemoteEvent = \case CEvtChatSuspended -> False CEvtRemoteHostSessionCode {} -> False CEvtNewRemoteHost _ -> False - CEvtRemoteHostConnected _ -> False + CEvtRemoteHostConnected {} -> False CEvtRemoteHostStopped {} -> False CEvtRemoteCtrlFound {} -> False CEvtRemoteCtrlSessionCode {} -> False @@ -1397,7 +1398,8 @@ data RemoteCtrlSession | RCSessionConnecting { remoteCtrlId_ :: Maybe RemoteCtrlId, rcsClient :: RCCtrlClient, - rcsWaitSession :: Async () + rcsWaitSession :: Async (), + ctrlAppInfo :: CtrlAppInfo } | RCSessionPendingConfirmation { remoteCtrlId_ :: Maybe RemoteCtrlId, @@ -1406,7 +1408,8 @@ data RemoteCtrlSession tls :: TLS 'TClient, sessionCode :: Text, rcsWaitSession :: Async (), - rcsWaitConfirmation :: TMVar (Either RCErrorType (RCCtrlSession, RCCtrlPairing)) + rcsWaitConfirmation :: TMVar (Either RCErrorType (RCCtrlSession, RCCtrlPairing)), + ctrlAppInfo :: CtrlAppInfo } | RCSessionConnected { remoteCtrlId :: RemoteCtrlId, @@ -1414,7 +1417,8 @@ data RemoteCtrlSession tls :: TLS 'TClient, rcsSession :: RCCtrlSession, http2Server :: Async (), - remoteOutputQ :: TBQueue (Either ChatError ChatEvent) + remoteOutputQ :: TBQueue (Either ChatError ChatEvent), + ctrlAppInfo :: CtrlAppInfo } data RemoteCtrlSessionState diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 57f4244fab..fc6a0ea782 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -2959,7 +2959,7 @@ processChatCommand vr nm = \case ConfirmRemoteCtrl rcId -> withUser_ $ do (rc, ctrlAppInfo) <- confirmRemoteCtrl rcId pure CRRemoteCtrlConnecting {remoteCtrl_ = Just rc, ctrlAppInfo, appVersion = currentAppVersion} - VerifyRemoteCtrlSession sessId -> withUser_ $ CRRemoteCtrlConnected <$> verifyRemoteCtrlSession (execChatCommand Nothing) sessId + VerifyRemoteCtrlSession sessId -> withUser_ $ verifyRemoteCtrlSession (execChatCommand Nothing) sessId StopRemoteCtrl -> withUser_ $ stopRemoteCtrl >> ok_ ListRemoteCtrls -> withUser_ $ CRRemoteCtrlList <$> listRemoteCtrls DeleteRemoteCtrl rc -> withUser_ $ deleteRemoteCtrl rc >> ok_ diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 255af5f318..b89d8a11f0 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -165,7 +165,8 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do where mkCtrlAppInfo = do deviceName <- chatReadVar localDeviceName - pure CtrlAppInfo {appVersionRange = ctrlAppVersionRange, deviceName} + useCompression <- asks $ remoteCompression . config + pure CtrlAppInfo {appVersionRange = ctrlAppVersionRange, deviceName, compression = BoolDef useCompression} parseHostAppInfo :: RCHostHello -> ExceptT RemoteHostError IO HostAppInfo parseHostAppInfo RCHostHello {app = hostAppInfo} = do hostInfo@HostAppInfo {appVersion, encoding} <- @@ -213,7 +214,9 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do RHSessionConfirmed _ RHPendingSession {rchClient} -> Right ((), RHSessionConnected {rchClient, tls, rhClient, pollAction, storePath}) _ -> Left $ ChatErrorRemoteHost rhKey RHEBadState chatWriteVar currentRemoteHost $ Just remoteHostId -- this is required for commands to be passed to remote host - toView $ CEvtRemoteHostConnected rhi {sessionState = Just RHSConnected {sessionCode}} + let RemoteHostClient {encryption = RemoteCrypto {compression}} = rhClient + remoteHost = rhi {sessionState = Just RHSConnected {sessionCode}} :: RemoteHostInfo + toView $ CEvtRemoteHostConnected {remoteHost, compression} upsertRemoteHost :: RCHostPairing -> Maybe RemoteHostInfo -> Maybe RCCtrlAddress -> Text -> SessionSeq -> RemoteHostSessionState -> CM RemoteHostInfo upsertRemoteHost pairing'@RCHostPairing {knownHost = kh_} rhi_ rcAddr_ hostDeviceName sseq state = do KnownHostPairing {hostDhPubKey = hostDhPubKey'} <- maybe (throwError . ChatError $ CEInternalError "KnownHost is known after verification") pure kh_ @@ -459,7 +462,7 @@ startRemoteCtrlSession = do connectRemoteCtrl :: RCVerifiedInvitation -> SessionSeq -> CM (Maybe RemoteCtrlInfo, CtrlAppInfo) connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) sseq = handleCtrlError sseq RCSRConnectionFailed "connectRemoteCtrl" $ do - ctrlInfo@CtrlAppInfo {deviceName = ctrlDeviceName} <- parseCtrlAppInfo app + ctrlInfo <- parseCtrlAppInfo app v <- checkAppVersion ctrlInfo rc_ <- withStore' $ \db -> getRemoteCtrlByFingerprint db ca mapM_ (validateRemoteCtrl inv) rc_ @@ -469,23 +472,23 @@ connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) cmdOk <- newEmptyTMVarIO rcsWaitSession <- async $ do atomically $ takeTMVar cmdOk - handleCtrlError sseq RCSRConnectionFailed "waitForCtrlSession" $ waitForCtrlSession rc_ ctrlDeviceName rcsClient vars + handleCtrlError sseq RCSRConnectionFailed "waitForCtrlSession" $ waitForCtrlSession rc_ ctrlInfo rcsClient vars updateRemoteCtrlSession sseq $ \case - RCSessionStarting -> Right RCSessionConnecting {remoteCtrlId_ = remoteCtrlId' <$> rc_, rcsClient, rcsWaitSession} + RCSessionStarting -> Right RCSessionConnecting {remoteCtrlId_ = remoteCtrlId' <$> rc_, rcsClient, rcsWaitSession, ctrlAppInfo = ctrlInfo} _ -> Left $ ChatErrorRemoteCtrl RCEBadState atomically $ putTMVar cmdOk () pure ((`remoteCtrlInfo` Just RCSConnecting) <$> rc_, ctrlInfo) where validateRemoteCtrl RCInvitation {idkey} RemoteCtrl {ctrlPairing = RCCtrlPairing {idPubKey}} = unless (idkey == idPubKey) $ throwError $ ChatErrorRemoteCtrl $ RCEProtocolError $ PRERemoteControl RCEIdentity - waitForCtrlSession :: Maybe RemoteCtrl -> Text -> RCCtrlClient -> RCStepTMVar (ByteString, TLS 'TClient, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> CM () - waitForCtrlSession rc_ ctrlName rcsClient vars = do + waitForCtrlSession :: Maybe RemoteCtrl -> CtrlAppInfo -> RCCtrlClient -> RCStepTMVar (ByteString, TLS 'TClient, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> CM () + waitForCtrlSession rc_ ctrlAppInfo@CtrlAppInfo {deviceName = ctrlName} rcsClient vars = do (uniq, tls, rcsWaitConfirmation) <- timeoutThrow (ChatErrorRemoteCtrl RCETimeout) networkIOTimeout $ takeRCStep vars let sessionCode = verificationCode uniq updateRemoteCtrlSession sseq $ \case RCSessionConnecting {rcsWaitSession} -> let remoteCtrlId_ = remoteCtrlId' <$> rc_ - in Right RCSessionPendingConfirmation {remoteCtrlId_, ctrlDeviceName = ctrlName, rcsClient, tls, sessionCode, rcsWaitSession, rcsWaitConfirmation} + in Right RCSessionPendingConfirmation {remoteCtrlId_, ctrlDeviceName = ctrlName, rcsClient, tls, sessionCode, rcsWaitSession, rcsWaitConfirmation, ctrlAppInfo} _ -> Left $ ChatErrorRemoteCtrl RCEBadState toView CEvtRemoteCtrlSessionCode {remoteCtrl_ = (`remoteCtrlInfo` Just RCSPendingConfirmation {sessionCode}) <$> rc_, sessionCode} checkAppVersion CtrlAppInfo {appVersionRange} = @@ -495,7 +498,8 @@ connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) getHostAppInfo appVersion = do hostDeviceName <- chatReadVar localDeviceName encryptFiles <- chatReadVar encryptLocalFiles - pure HostAppInfo {appVersion, deviceName = hostDeviceName, encoding = localEncoding, encryptFiles} + useCompression <- asks $ remoteCompression . config + pure HostAppInfo {appVersion, deviceName = hostDeviceName, encoding = localEncoding, encryptFiles, compression = BoolDef useCompression} parseCtrlAppInfo :: JT.Value -> CM CtrlAppInfo parseCtrlAppInfo ctrlAppInfo = do @@ -514,7 +518,7 @@ handleRemoteCommand execCC encryption remoteOutputQ HTTP2Request {request, reqBo parseRequest :: ExceptT RemoteProtocolError IO (C.SbKeyNonce, GetChunk, RemoteCommand) parseRequest = do (rfKN, header, getNext) <- parseDecryptHTTP2Body encryption request reqBody - (rfKN,getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecode header) + (rfKN,getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecodeStrict header) replyError = reply . RRChatResponse . RRError processCommand :: User -> C.SbKeyNonce -> GetChunk -> RemoteCommand -> CM () processCommand user rfKN getNext = \case @@ -611,7 +615,7 @@ remoteCtrlInfo RemoteCtrl {remoteCtrlId, ctrlDeviceName} sessionState = RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName, sessionState} -- | Take a look at emoji of tlsunique, commit pairing, and start session server -verifyRemoteCtrlSession :: (ByteString -> Int -> CM' (Either ChatError ChatResponse)) -> Text -> CM RemoteCtrlInfo +verifyRemoteCtrlSession :: (ByteString -> Int -> CM' (Either ChatError ChatResponse)) -> Text -> CM ChatResponse verifyRemoteCtrlSession execCC sessCode' = do (sseq, client, ctrlName, sessionCode, vars) <- chatReadVar remoteCtrlSession >>= \case @@ -625,14 +629,15 @@ verifyRemoteCtrlSession execCC sessCode' = do (rcsSession@RCCtrlSession {tls, sessionKeys}, rcCtrlPairing) <- timeoutThrow (ChatErrorRemoteCtrl RCETimeout) networkIOTimeout $ takeRCStep vars rc@RemoteCtrl {remoteCtrlId} <- upsertRemoteCtrl ctrlName rcCtrlPairing remoteOutputQ <- asks (tbqSize . config) >>= newTBQueueIO - encryption <- mkCtrlRemoteCrypto sessionKeys $ tlsUniq tls + encryption@RemoteCrypto {compression} <- mkCtrlRemoteCrypto sessionKeys (tlsUniq tls) =<< getRemoteCtrlAppInfo sseq cc <- ask http2Server <- liftIO . async $ attachHTTP2Server tls $ \req -> handleRemoteCommand execCC encryption remoteOutputQ req `runReaderT` cc void . forkIO $ monitor sseq http2Server updateRemoteCtrlSession sseq $ \case - RCSessionPendingConfirmation {} -> Right RCSessionConnected {remoteCtrlId, rcsClient = client, rcsSession, tls, http2Server, remoteOutputQ} + RCSessionPendingConfirmation {ctrlAppInfo} -> Right RCSessionConnected {remoteCtrlId, rcsClient = client, rcsSession, tls, http2Server, remoteOutputQ, ctrlAppInfo} _ -> Left $ ChatErrorRemoteCtrl RCEBadState - pure $ remoteCtrlInfo rc $ Just RCSConnected {sessionCode = tlsSessionCode tls} + let remoteCtrl = remoteCtrlInfo rc $ Just RCSConnected {sessionCode = tlsSessionCode tls} + pure CRRemoteCtrlConnected {remoteCtrl, compression} where upsertRemoteCtrl :: Text -> RCCtrlPairing -> CM RemoteCtrl upsertRemoteCtrl ctrlName rcCtrlPairing = withStore $ \db -> do @@ -717,6 +722,18 @@ updateRemoteCtrlSession sseq state = do Right st' -> Right () <$ writeTVar session (Just (sseq, st')) liftEither r +getRemoteCtrlAppInfo :: SessionSeq -> CM (Maybe CtrlAppInfo) +getRemoteCtrlAppInfo sseq = chatReadVar remoteCtrlSession $>>= pure . appInfo + where + appInfo (currSseq, sess) + | sseq == currSseq = case sess of + RCSessionStarting -> Nothing + RCSessionSearching {} -> Nothing + RCSessionConnecting {ctrlAppInfo} -> Just ctrlAppInfo + RCSessionPendingConfirmation {ctrlAppInfo} -> Just ctrlAppInfo + RCSessionConnected {ctrlAppInfo} -> Just ctrlAppInfo + | otherwise = Nothing + utf8String :: [Char] -> ByteString utf8String = encodeUtf8 . T.pack {-# INLINE utf8String #-} diff --git a/src/Simplex/Chat/Remote/Protocol.hs b/src/Simplex/Chat/Remote/Protocol.hs index d8155db7d6..b71c6baed8 100644 --- a/src/Simplex/Chat/Remote/Protocol.hs +++ b/src/Simplex/Chat/Remote/Protocol.hs @@ -11,6 +11,7 @@ module Simplex.Chat.Remote.Protocol where +import qualified Codec.Compression.Zstd as Z1 import Control.Monad import Control.Monad.Except import Control.Monad.Reader @@ -27,6 +28,7 @@ import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.ByteString.Builder (Builder, byteString, lazyByteString) import qualified Data.ByteString.Lazy as LB +import qualified Data.ByteString.Lazy.Internal as LB import Data.String (fromString) import Data.Text (Text) import Data.Text.Encoding (decodeUtf8) @@ -37,6 +39,7 @@ import Network.Transport.Internal (decodeWord32, encodeWord32) import Simplex.Chat.Controller import Simplex.Chat.Remote.Transport import Simplex.Chat.Remote.Types +import Simplex.Chat.Types (BoolDef (..)) import Simplex.FileTransfer.Description (FileDigest (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..)) @@ -102,10 +105,10 @@ $(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "RR") ''RemoteResponse) -- * Client side / desktop mkRemoteHostClient :: HTTP2Client -> HostSessKeys -> SessionCode -> FilePath -> HostAppInfo -> CM RemoteHostClient -mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {encoding, deviceName, encryptFiles} = do +mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {encoding, deviceName, encryptFiles, compression} = do let HostSessKeys {chainKeys, idPrivKey, sessPrivKey} = sessionKeys signatures = RSSign {idPrivKey, sessPrivKey} - encryption <- liftIO $ mkRemoteCrypto sessionCode chainKeys signatures + encryption <- mkRemoteCrypto sessionCode chainKeys signatures $ isTrue compression pure RemoteHostClient { hostEncoding = encoding, @@ -116,17 +119,19 @@ mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {enc storePath } -mkCtrlRemoteCrypto :: CtrlSessKeys -> SessionCode -> CM RemoteCrypto -mkCtrlRemoteCrypto CtrlSessKeys {chainKeys, idPubKey, sessPubKey} sessionCode = +mkCtrlRemoteCrypto :: CtrlSessKeys -> SessionCode -> Maybe CtrlAppInfo -> CM RemoteCrypto +mkCtrlRemoteCrypto CtrlSessKeys {chainKeys, idPubKey, sessPubKey} sessionCode ctrlAppInfo_ = do let signatures = RSVerify {idPubKey, sessPubKey} - in liftIO $ mkRemoteCrypto sessionCode chainKeys signatures + peerCompression = maybe False (\CtrlAppInfo {compression} -> isTrue compression) ctrlAppInfo_ + mkRemoteCrypto sessionCode chainKeys signatures peerCompression -mkRemoteCrypto :: SessionCode -> TSbChainKeys -> RemoteSignatures -> IO RemoteCrypto -mkRemoteCrypto sessionCode chainKeys signatures = do +mkRemoteCrypto :: SessionCode -> TSbChainKeys -> RemoteSignatures -> Bool -> CM RemoteCrypto +mkRemoteCrypto sessionCode chainKeys signatures peerCompression = do sndCounter <- newTVarIO 0 rcvCounter <- newTVarIO 0 skippedKeys <- liftIO TM.emptyIO - pure RemoteCrypto {sessionCode, sndCounter, rcvCounter, chainKeys, skippedKeys, signatures} + useCompression <- asks $ remoteCompression . config + pure RemoteCrypto {sessionCode, sndCounter, rcvCounter, chainKeys, skippedKeys, signatures, compression = peerCompression && useCompression} closeRemoteHostClient :: RemoteHostClient -> IO () closeRemoteHostClient RemoteHostClient {httpClient} = closeHTTP2Client httpClient @@ -176,7 +181,7 @@ sendRemoteCommand RemoteHostClient {httpClient, hostEncoding, encryption} file_ let req = httpRequest encFile_ encCmd HTTP2Response {response, respBody} <- liftError' (RPEHTTP2 . tshow) $ sendRequestDirect httpClient req Nothing (rfKN, header, getNext) <- parseDecryptHTTP2Body encryption response respBody - rr <- liftEitherWith (RPEInvalidJSON . fromString) $ J.eitherDecode header >>= JT.parseEither J.parseJSON . convertJSON hostEncoding localEncoding + rr <- liftEitherWith (RPEInvalidJSON . fromString) $ J.eitherDecodeStrict header >>= JT.parseEither J.parseJSON . convertJSON hostEncoding localEncoding pure (rfKN, getNext, rr) where httpRequest encFile_ cmdBld = H.requestStreaming N.methodPost "/" mempty $ \send flush -> do @@ -247,8 +252,11 @@ pattern OwsfTag = (SingleFieldJSONTag, J.Bool True) -- See https://github.com/simplex-chat/simplexmq/blob/master/rfcs/2023-10-25-remote-control.md for encoding encryptEncodeHTTP2Body :: Word32 -> C.SbKeyNonce -> RemoteCrypto -> LazyByteString -> ExceptT RemoteProtocolError IO Builder -encryptEncodeHTTP2Body corrId cmdKN RemoteCrypto {sessionCode, signatures} s = do - ct <- liftError PRERemoteControl $ RC.rcEncryptBody cmdKN $ LB.fromStrict (smpEncode sessionCode) <> s +encryptEncodeHTTP2Body corrId cmdKN RemoteCrypto {sessionCode, signatures, compression} s = do + let s' + | compression = LB.fromStrict $ Z1.compress 3 $ LB.toStrict s + | otherwise = s + ct <- liftError PRERemoteControl $ RC.rcEncryptBody cmdKN $ LB.Chunk (smpEncode sessionCode) s' let ctLen = encodeWord32 (fromIntegral $ LB.length ct) signed = LB.fromStrict (encodeWord32 corrId <> ctLen) <> ct sigs <- bodySignatures signed @@ -266,12 +274,12 @@ encryptEncodeHTTP2Body corrId cmdKN RemoteCrypto {sessionCode, signatures} s = d sign k = C.signatureBytes . C.sign' k . BA.convert . CH.hashFinalize -- | Parse and decrypt HTTP2 request/response -parseDecryptHTTP2Body :: HTTP2BodyChunk a => RemoteCrypto -> a -> HTTP2Body -> ExceptT RemoteProtocolError IO (C.SbKeyNonce, LazyByteString, Int -> IO ByteString) -parseDecryptHTTP2Body rc@RemoteCrypto {sessionCode, signatures} hr HTTP2Body {bodyBuffer} = do +parseDecryptHTTP2Body :: HTTP2BodyChunk a => RemoteCrypto -> a -> HTTP2Body -> ExceptT RemoteProtocolError IO (C.SbKeyNonce, ByteString, Int -> IO ByteString) +parseDecryptHTTP2Body rc@RemoteCrypto {sessionCode, signatures, compression} hr HTTP2Body {bodyBuffer} = do (corrId, ct) <- getBody (cmdKN, rfKN) <- ExceptT $ atomically $ getRemoteRcvKeys rc corrId s <- liftError PRERemoteControl $ RC.rcDecryptBody cmdKN ct - s' <- parseBody s + s' <- decompress =<< parseBody s pure (rfKN, s', getNext) where getBody :: ExceptT RemoteProtocolError IO (Word32, LazyByteString) @@ -320,3 +328,10 @@ parseDecryptHTTP2Body rc@RemoteCrypto {sessionCode, signatures} hr HTTP2Body {bo unless (LB.length bs == n) $ throwError PRESessionCode pure (LB.toStrict bs, rest) getNext sz = getBuffered bodyBuffer sz Nothing $ getBodyChunk hr + decompress :: LazyByteString -> ExceptT RemoteProtocolError IO ByteString + decompress s + | compression = case Z1.decompress $ LB.toStrict s of + Z1.Error e -> throwError $ RPEInvalidBody e + Z1.Skip -> pure B.empty + Z1.Decompress s' -> pure s' + | otherwise = pure $ LB.toStrict s diff --git a/src/Simplex/Chat/Remote/Types.hs b/src/Simplex/Chat/Remote/Types.hs index b7af624e9e..746aa7f566 100644 --- a/src/Simplex/Chat/Remote/Types.hs +++ b/src/Simplex/Chat/Remote/Types.hs @@ -21,7 +21,7 @@ import Data.Int (Int64) import Data.Text (Text) import Data.Word (Word16, Word32) import Simplex.Chat.Remote.AppVersion -import Simplex.Chat.Types (verificationCode) +import Simplex.Chat.Types (BoolDef, verificationCode) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, sumTypeJSON) @@ -47,7 +47,8 @@ data RemoteCrypto = RemoteCrypto rcvCounter :: TVar Word32, chainKeys :: TSbChainKeys, skippedKeys :: TM.TMap Word32 (C.SbKeyNonce, C.SbKeyNonce), - signatures :: RemoteSignatures + signatures :: RemoteSignatures, + compression :: Bool } getRemoteSndKeys :: RemoteCrypto -> STM (Word32, C.SbKeyNonce, C.SbKeyNonce) @@ -220,7 +221,8 @@ data RemoteFile = RemoteFile data CtrlAppInfo = CtrlAppInfo { appVersionRange :: AppVersionRange, - deviceName :: Text + deviceName :: Text, + compression :: BoolDef } deriving (Show) @@ -228,7 +230,8 @@ data HostAppInfo = HostAppInfo { appVersion :: AppVersion, deviceName :: Text, encoding :: PlatformEncoding, - encryptFiles :: Bool -- if the host encrypts files in app storage + encryptFiles :: Bool, -- if the host encrypts files in app storage + compression :: BoolDef } $(J.deriveJSON defaultJSON ''RemoteFile) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 4e1106c0d8..a8526e357f 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -273,8 +273,10 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte [ (maybe "connecting new remote controller" (\RemoteCtrlInfo {remoteCtrlId} -> "connecting remote controller " <> sShow remoteCtrlId) remoteCtrl_ <> ": ") <> viewRemoteCtrl ctrlAppInfo appVersion True ] - CRRemoteCtrlConnected RemoteCtrlInfo {remoteCtrlId = rcId, ctrlDeviceName} -> - ["remote controller " <> sShow rcId <> " session started with " <> plain ctrlDeviceName] + CRRemoteCtrlConnected RemoteCtrlInfo {remoteCtrlId = rcId, ctrlDeviceName} compression -> + ["remote controller " <> sShow rcId <> " session started with " <> plain ctrlDeviceName <> " (" <> compressStr <> " compression)"] + where + compressStr = if compression then "with" else "no" CRSQLResult rows -> map plain rows #if !defined(dbPostgres) CRArchiveExported archiveErrs -> if null archiveErrs then ["ok"] else ["archive export errors: " <> plain (show archiveErrs)] @@ -486,7 +488,9 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView} plain sessionCode ] CEvtNewRemoteHost RemoteHostInfo {remoteHostId = rhId, hostDeviceName} -> ["new remote host " <> sShow rhId <> " added: " <> plain hostDeviceName] - CEvtRemoteHostConnected RemoteHostInfo {remoteHostId = rhId} -> ["remote host " <> sShow rhId <> " connected"] + CEvtRemoteHostConnected RemoteHostInfo {remoteHostId = rhId} compression -> ["remote host " <> sShow rhId <> " connected (" <> compressStr <> " compression)"] + where + compressStr = if compression then "with" else "no" CEvtRemoteHostStopped {remoteHostId_} -> [ maybe "new remote host" (mappend "remote host " . sShow) remoteHostId_ <> " stopped" ] diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 87be15afe5..0e99b26bc5 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -81,19 +81,19 @@ businessProfile = mkProfile "biz" "Biz Inc" Nothing mkProfile :: T.Text -> T.Text -> Maybe ImageData -> Profile mkProfile displayName descr image = Profile {displayName, fullName = "", shortDescr = Just descr, image, contactLink = Nothing, peerType = Nothing, preferences = defaultPrefs} -it :: HasCallStack => String -> (TestParams -> Expectation) -> SpecWith (Arg (TestParams -> Expectation)) +it :: HasCallStack => String -> (ps -> Expectation) -> SpecWith (Arg (ps -> Expectation)) it name test = Hspec.it name $ \tmp -> timeout t (test tmp) >>= maybe (error "test timed out") pure where t = 90 * 1000000 -xit' :: HasCallStack => String -> (TestParams -> Expectation) -> SpecWith (Arg (TestParams -> Expectation)) +xit' :: HasCallStack => String -> (ps -> Expectation) -> SpecWith (Arg (ps -> Expectation)) xit' = if os == "linux" then xit else it xit'' :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a) xit'' = ifCI xit Hspec.it -xitMacCI :: HasCallStack => String -> (TestParams -> Expectation) -> SpecWith (Arg (TestParams -> Expectation)) +xitMacCI :: HasCallStack => String -> (ps -> Expectation) -> SpecWith (Arg (ps -> Expectation)) xitMacCI = ifCI (if os == "darwin" then xit else it) it xdescribe'' :: HasCallStack => String -> SpecWith a -> SpecWith a diff --git a/tests/RemoteTests.hs b/tests/RemoteTests.hs index 6f215a5dc8..51f4324bf0 100644 --- a/tests/RemoteTests.hs +++ b/tests/RemoteTests.hs @@ -2,6 +2,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TupleSections #-} module RemoteTests where @@ -15,7 +16,7 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Lazy.Char8 as LB import Data.List (find, isPrefixOf) import qualified Data.Map.Strict as M -import Simplex.Chat.Controller (versionNumber) +import Simplex.Chat.Controller (ChatConfig (..), versionNumber) import qualified Simplex.Chat.Controller as Controller import Simplex.Chat.Mobile.File import Simplex.Chat.Remote (remoteFilesFolder) @@ -31,6 +32,13 @@ import UnliftIO.Directory remoteTests :: SpecWith TestParams remoteTests = describe "Remote" $ do + xdescribe "No compression" $ aroundWith (. ((False, False),)) runRemoteTests + xdescribe "Mobile offers compression" $ aroundWith (. ((True, False),)) runRemoteTests + xdescribe "Desktop offers compression" $ aroundWith (. ((False, True),)) runRemoteTests + describe "With compression" $ aroundWith (. ((True, True),)) runRemoteTests + +runRemoteTests :: SpecWith ((Bool, Bool), TestParams) +runRemoteTests = do describe "protocol handshake" $ do it "connects with new pairing (stops mobile)" $ remoteHandshakeTest False it "connects with new pairing (stops desktop)" $ remoteHandshakeTest True @@ -48,14 +56,14 @@ remoteTests = describe "Remote" $ do -- * Chat commands -remoteHandshakeTest :: HasCallStack => Bool -> TestParams -> IO () -remoteHandshakeTest viaDesktop = testChat2 aliceProfile aliceDesktopProfile $ \mobile desktop -> do +remoteHandshakeTest :: HasCallStack => Bool -> ((Bool, Bool), TestParams) -> IO () +remoteHandshakeTest viaDesktop = testRemote $ \compress mobile desktop -> do desktop ##> "/list remote hosts" desktop <## "No remote hosts" mobile ##> "/list remote ctrls" mobile <## "No remote controllers" - startRemote mobile desktop + startRemote compress mobile desktop desktop ##> "/list remote hosts" desktop <## "Remote hosts:" @@ -77,14 +85,14 @@ remoteHandshakeTest viaDesktop = testChat2 aliceProfile aliceDesktopProfile $ \m mobile ##> "/list remote ctrls" mobile <## "No remote controllers" -remoteHandshakeStoredTest :: HasCallStack => TestParams -> IO () -remoteHandshakeStoredTest = testChat2 aliceProfile aliceDesktopProfile $ \mobile desktop -> do +remoteHandshakeStoredTest :: HasCallStack => ((Bool, Bool), TestParams) -> IO () +remoteHandshakeStoredTest = testRemote $ \compress mobile desktop -> do logNote "Starting new session" - startRemote mobile desktop + startRemote compress mobile desktop stopMobile mobile desktop `catchAny` (logError . tshow) logNote "Starting stored session" - startRemoteStored mobile desktop + startRemoteStored compress mobile desktop stopDesktop mobile desktop `catchAny` (logError . tshow) desktop ##> "/list remote hosts" @@ -95,23 +103,23 @@ remoteHandshakeStoredTest = testChat2 aliceProfile aliceDesktopProfile $ \mobile mobile <## "1. My desktop" logNote "Starting stored session again" - startRemoteStored mobile desktop + startRemoteStored compress mobile desktop stopMobile mobile desktop `catchAny` (logError . tshow) -remoteHandshakeDiscoverTest :: HasCallStack => TestParams -> IO () -remoteHandshakeDiscoverTest = testChat2 aliceProfile aliceDesktopProfile $ \mobile desktop -> do +remoteHandshakeDiscoverTest :: HasCallStack => ((Bool, Bool), TestParams) -> IO () +remoteHandshakeDiscoverTest = testRemote $ \compress mobile desktop -> do logNote "Preparing new session" - startRemote mobile desktop + startRemote compress mobile desktop stopMobile mobile desktop `catchAny` (logError . tshow) logNote "Starting stored session with multicast" - startRemoteDiscover mobile desktop + startRemoteDiscover compress mobile desktop stopMobile mobile desktop `catchAny` (logError . tshow) -remoteHandshakeRejectTest :: HasCallStack => TestParams -> IO () -remoteHandshakeRejectTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mobile desktop mobileBob -> do +remoteHandshakeRejectTest :: HasCallStack => ((Bool, Bool), TestParams) -> IO () +remoteHandshakeRejectTest = testRemote3 $ \compress mobile desktop mobileBob -> do logNote "Starting new session" - startRemote mobile desktop + startRemote compress mobile desktop stopMobile mobile desktop mobileBob ##> "/set device name MobileBob" @@ -135,12 +143,12 @@ remoteHandshakeRejectTest = testChat3 aliceProfile aliceDesktopProfile bobProfil mobile <## "Compare session code with controller and use:" mobile <## ("/verify remote ctrl " <> sessId) mobile ##> ("/verify remote ctrl " <> sessId) - mobile <## "remote controller 1 session started with My desktop" - desktop <## "remote host 1 connected" + mobile <## ("remote controller 1 session started with My desktop" <> compress) + desktop <## ("remote host 1 connected" <> compress) stopMobile mobile desktop -storedBindingsTest :: HasCallStack => TestParams -> IO () -storedBindingsTest = testChat2 aliceProfile aliceDesktopProfile $ \mobile desktop -> do +storedBindingsTest :: HasCallStack => ((Bool, Bool), TestParams) -> IO () +storedBindingsTest = testRemote $ \compress mobile desktop -> do desktop ##> "/set device name My desktop" desktop <## "ok" mobile ##> "/set device name Mobile" @@ -166,9 +174,9 @@ storedBindingsTest = testChat2 aliceProfile aliceDesktopProfile $ \mobile deskto desktop <## "new remote host connecting" mobile <## "new remote controller connected" verifyRemoteCtrl mobile desktop - mobile <## "remote controller 1 session started with My desktop" + mobile <## ("remote controller 1 session started with My desktop" <> compress) desktop <## "new remote host 1 added: Mobile" - desktop <## "remote host 1 connected" + desktop <## ("remote host 1 connected" <> compress) desktop ##> "/list remote hosts" desktop <## "Remote hosts:" @@ -180,9 +188,9 @@ storedBindingsTest = testChat2 aliceProfile aliceDesktopProfile $ \mobile deskto -- TODO: more parser tests -remoteMessageTest :: HasCallStack => TestParams -> IO () -remoteMessageTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mobile desktop bob -> do - startRemote mobile desktop +remoteMessageTest :: HasCallStack => ((Bool, Bool), TestParams) -> IO () +remoteMessageTest = testRemote3 $ \compress mobile desktop bob -> do + startRemote compress mobile desktop contactBob desktop bob logNote "sending messages" @@ -206,9 +214,9 @@ remoteMessageTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mob threadDelay 1000000 logNote "done" -remoteStoreFileTest :: HasCallStack => TestParams -> IO () +remoteStoreFileTest :: HasCallStack => ((Bool, Bool), TestParams) -> IO () remoteStoreFileTest = - testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mobile desktop bob -> + testRemote3 $ \compress mobile desktop bob -> withXFTPServer $ do let mobileFiles = "./tests/tmp/mobile_files" mobile ##> ("/_files_folder " <> mobileFiles) @@ -223,7 +231,7 @@ remoteStoreFileTest = bob ##> ("/_files_folder " <> bobFiles) bob <## "ok" - startRemote mobile desktop + startRemote compress mobile desktop contactBob desktop bob rhs <- readTVarIO (Controller.remoteHostSessions $ chatController desktop) @@ -336,8 +344,8 @@ remoteStoreFileTest = r `shouldStartWith` "remote host 1 error" r `shouldContain` err -remoteCLIFileTest :: HasCallStack => TestParams -> IO () -remoteCLIFileTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mobile desktop bob -> withXFTPServer $ do +remoteCLIFileTest :: HasCallStack => ((Bool, Bool), TestParams) -> IO () +remoteCLIFileTest = testRemote3 $ \compress mobile desktop bob -> withXFTPServer $ do let mobileFiles = "./tests/tmp/mobile_files" mobile ##> ("/_files_folder " <> mobileFiles) mobile <## "ok" @@ -347,7 +355,7 @@ remoteCLIFileTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mob desktop ##> ("/remote_hosts_folder " <> desktopHostFiles) desktop <## "ok" - startRemote mobile desktop + startRemote compress mobile desktop contactBob desktop bob rhs <- readTVarIO (Controller.remoteHostSessions $ chatController desktop) @@ -405,9 +413,9 @@ remoteCLIFileTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mob stopMobile mobile desktop -switchRemoteHostTest :: TestParams -> IO () -switchRemoteHostTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mobile desktop bob -> do - startRemote mobile desktop +switchRemoteHostTest :: HasCallStack => ((Bool, Bool), TestParams) -> IO () +switchRemoteHostTest = testRemote3 $ \compress mobile desktop bob -> do + startRemote compress mobile desktop contactBob desktop bob desktop ##> "/contacts" @@ -431,10 +439,10 @@ switchRemoteHostTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \ desktop <## "remote host 1 error: RHEInactive" desktop ##> "/contacts" -indicateRemoteHostTest :: TestParams -> IO () -indicateRemoteHostTest = testChat4 aliceProfile aliceDesktopProfile bobProfile cathProfile $ \mobile desktop bob cath -> do +indicateRemoteHostTest :: HasCallStack => ((Bool, Bool), TestParams) -> IO () +indicateRemoteHostTest = testRemote4 $ \compress mobile desktop bob cath -> do connectUsers desktop cath - startRemote mobile desktop + startRemote compress mobile desktop contactBob desktop bob -- remote contact -> remote host bob #> "@alice hi" @@ -455,8 +463,8 @@ indicateRemoteHostTest = testChat4 aliceProfile aliceDesktopProfile bobProfile c desktop <##> cath cath <##> desktop -multipleProfilesTest :: TestParams -> IO () -multipleProfilesTest = testChat4 aliceProfile aliceDesktopProfile bobProfile cathProfile $ \mobile desktop bob cath -> do +multipleProfilesTest :: HasCallStack => ((Bool, Bool), TestParams) -> IO () +multipleProfilesTest = testRemote4 $ \compress mobile desktop bob cath -> do connectUsers desktop cath desktop ##> "/create user desk_bottom" @@ -466,7 +474,7 @@ multipleProfilesTest = testChat4 aliceProfile aliceDesktopProfile bobProfile cat desktop <## "alice_desktop (Alice Desktop)" desktop <## "desk_bottom (active)" - startRemote mobile desktop + startRemote compress mobile desktop contactBob desktop bob desktop ##> "/users" desktop <## "alice (Alice) (active)" @@ -502,8 +510,28 @@ multipleProfilesTest = testChat4 aliceProfile aliceDesktopProfile bobProfile cat -- * Utils -startRemote :: TestCC -> TestCC -> IO () -startRemote mobile desktop = do +testRemote :: HasCallStack => (String -> TestCC -> TestCC -> IO()) -> ((Bool, Bool), TestParams) -> IO () +testRemote test ((mobileCompression, desktopCompression), ps) = + withNewTestChatCfg ps testCfg {remoteCompression = mobileCompression} "mobile" aliceProfile $ \mobile -> + withNewTestChatCfg ps testCfg {remoteCompression = desktopCompression} "desktop" aliceDesktopProfile $ \desktop -> + let compress = " (" <> (if mobileCompression && desktopCompression then "with" else "no") <> " compression)" + in test compress mobile desktop + + +testRemote3 :: HasCallStack => (String -> TestCC -> TestCC -> TestCC -> IO()) -> ((Bool, Bool), TestParams) -> IO () +testRemote3 test ps = + testRemote + (\compress mobile desktop -> withNewTestChat (snd ps) "bob" bobProfile $ test compress mobile desktop) + ps + +testRemote4 :: HasCallStack => (String -> TestCC -> TestCC -> TestCC -> TestCC -> IO()) -> ((Bool, Bool), TestParams) -> IO () +testRemote4 test ps = + testRemote3 + (\compress mobile desktop bob -> withNewTestChat (snd ps) "cath" cathProfile $ test compress mobile desktop bob) + ps + +startRemote :: String -> TestCC -> TestCC -> IO () +startRemote compress mobile desktop = do desktop ##> "/set device name My desktop" desktop <## "ok" mobile ##> "/set device name Mobile" @@ -518,12 +546,12 @@ startRemote mobile desktop = do desktop <## "new remote host connecting" mobile <## "new remote controller connected" verifyRemoteCtrl mobile desktop - mobile <## "remote controller 1 session started with My desktop" + mobile <## ("remote controller 1 session started with My desktop" <> compress) desktop <## "new remote host 1 added: Mobile" - desktop <## "remote host 1 connected" + desktop <## ("remote host 1 connected" <> compress) -startRemoteStored :: TestCC -> TestCC -> IO () -startRemoteStored mobile desktop = do +startRemoteStored :: String -> TestCC -> TestCC -> IO () +startRemoteStored compress mobile desktop = do desktop ##> "/start remote host 1" desktop <##. "remote host 1 started on " desktop <##. "other addresses: " @@ -534,11 +562,11 @@ startRemoteStored mobile desktop = do desktop <## "remote host 1 connecting" mobile <## "remote controller 1 connected" verifyRemoteCtrl mobile desktop - mobile <## "remote controller 1 session started with My desktop" - desktop <## "remote host 1 connected" + mobile <## ("remote controller 1 session started with My desktop" <> compress) + desktop <## ("remote host 1 connected" <> compress) -startRemoteDiscover :: TestCC -> TestCC -> IO () -startRemoteDiscover mobile desktop = do +startRemoteDiscover :: String -> TestCC -> TestCC -> IO () +startRemoteDiscover compress mobile desktop = do desktop ##> "/start remote host 1 multicast=on" desktop <##. "remote host 1 started on " desktop <##. "other addresses: " @@ -554,8 +582,8 @@ startRemoteDiscover mobile desktop = do desktop <## "remote host 1 connecting" mobile <## "remote controller 1 connected" verifyRemoteCtrl mobile desktop - mobile <## "remote controller 1 session started with My desktop" - desktop <## "remote host 1 connected" + mobile <## ("remote controller 1 session started with My desktop" <> compress) + desktop <## ("remote host 1 connected" <> compress) verifyRemoteCtrl :: TestCC -> TestCC -> IO () verifyRemoteCtrl mobile desktop = do diff --git a/tests/Test.hs b/tests/Test.hs index e1a5a58c7d..639708441e 100644 --- a/tests/Test.hs +++ b/tests/Test.hs @@ -55,7 +55,9 @@ main = do "src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql" #else describe "Schema dump" schemaDumpTest +#if MIN_VERSION_base(4,18,0) describe "Bot API docs" apiDocsTest +#endif around tmpBracket $ describe "WebRTC encryption" webRTCTests #endif describe "SimpleX chat markdown" markdownTests From ed3be9c228cd005ee7055c70d20c1b6de0df0ab3 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Tue, 30 Dec 2025 09:03:49 +0000 Subject: [PATCH 24/25] desktop: add fixed copyright (#6533) * desktop: add fixed copyright Also fixes reproducible builds. * update --------- Co-authored-by: Evgeny --- apps/multiplatform/desktop/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/multiplatform/desktop/build.gradle.kts b/apps/multiplatform/desktop/build.gradle.kts index 60ff535e88..1e7bda37c4 100644 --- a/apps/multiplatform/desktop/build.gradle.kts +++ b/apps/multiplatform/desktop/build.gradle.kts @@ -40,6 +40,7 @@ compose { } mainClass = "chat.simplex.desktop.MainKt" nativeDistributions { + copyright = "(c) 2020-2026 SimpleX Chat" // For debugging via VisualVM if (debugJava) { modules("jdk.zipfs", "jdk.unsupported", "jdk.management.agent") From f0467aee0010f4e2ccd81b0c540e5e324edb1ceb Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 4 Jan 2026 19:04:32 +0000 Subject: [PATCH 25/25] directory service: fix queries (#6539) * fix directory service queries * fix * reduce postgres pool size to 1 * stabilize postgres client tests, remove slow handshake tests * update simplexmq * fix test * test delay --- .../src/Directory/Store.hs | 12 +-- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Controller.hs | 32 +++--- src/Simplex/Chat/Library/Commands.hs | 4 +- src/Simplex/Chat/Options/Postgres.hs | 4 +- tests/ChatClient.hs | 20 ++-- tests/ChatTests/Direct.hs | 99 ++++++------------- tests/ChatTests/Files.hs | 4 +- tests/ChatTests/Groups.hs | 98 +++++------------- tests/ChatTests/Profiles.hs | 97 +++++------------- 11 files changed, 117 insertions(+), 257 deletions(-) diff --git a/apps/simplex-directory-service/src/Directory/Store.hs b/apps/simplex-directory-service/src/Directory/Store.hs index b78b446821..b5f7220724 100644 --- a/apps/simplex-directory-service/src/Directory/Store.hs +++ b/apps/simplex-directory-service/src/Directory/Store.hs @@ -351,11 +351,11 @@ searchListedGroups cc user@User {userId, userContactId} searchType lastGroup_ pa pure (gs, n) Just gId -> do gs <- groups $ DB.query db (listedGroupQuery <> " AND r.group_id > ? " <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, gId, pageSize) - n <- count $ DB.query db (countQuery' <> " AND r.group_id > ? " <> orderBy) (GRSActive, gId) + n <- count $ DB.query db (countQuery' <> " AND r.group_id > ?") (GRSActive, gId) pure (gs, n) where countQuery' = countQuery <> " WHERE r.group_reg_status = ? " - orderBy = " ORDER BY g.summary_current_members_count DESC " + orderBy = " ORDER BY g.summary_current_members_count DESC, r.group_reg_id ASC " STRecent -> case lastGroup_ of Nothing -> do gs <- groups $ DB.query db (listedGroupQuery <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, pageSize) @@ -363,11 +363,11 @@ searchListedGroups cc user@User {userId, userContactId} searchType lastGroup_ pa pure (gs, n) Just gId -> do gs <- groups $ DB.query db (listedGroupQuery <> " AND r.group_id > ? " <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, gId, pageSize) - n <- count $ DB.query db (countQuery' <> " AND r.group_id > ? " <> orderBy) (GRSActive, gId) + n <- count $ DB.query db (countQuery' <> " AND r.group_id > ?") (GRSActive, gId) pure (gs, n) where countQuery' = countQuery <> " WHERE r.group_reg_status = ? " - orderBy = " ORDER BY r.created_at DESC " + orderBy = " ORDER BY r.created_at DESC, r.group_reg_id ASC " STSearch search -> case lastGroup_ of Nothing -> do gs <- groups $ DB.query db (listedGroupQuery <> searchCond <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, s, s, s, s, pageSize) @@ -375,12 +375,12 @@ searchListedGroups cc user@User {userId, userContactId} searchType lastGroup_ pa pure (gs, n) Just gId -> do gs <- groups $ DB.query db (listedGroupQuery <> " AND r.group_id > ? " <> searchCond <> orderBy <> " LIMIT ?") (userId, userContactId, GRSActive, gId, s, s, s, s, pageSize) - n <- count $ DB.query db (countQuery' <> " AND r.group_id > ? " <> searchCond <> orderBy) (GRSActive, gId, s, s, s, s) + n <- count $ DB.query db (countQuery' <> " AND r.group_id > ? " <> searchCond) (GRSActive, gId, s, s, s, s) pure (gs, n) where s = T.toLower search countQuery' = countQuery <> " JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id WHERE r.group_reg_status = ? " - orderBy = " ORDER BY g.summary_current_members_count DESC " + orderBy = " ORDER BY g.summary_current_members_count DESC, r.group_reg_id ASC " where groups = (map (toGroupInfoReg (vr cc) user) <$>) count = maybeFirstRow' 0 fromOnly diff --git a/cabal.project b/cabal.project index e9842b1138..e5d7464ece 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 5f73d1e629a8807f1b9d94f8b411d6480a0a59fb + tag: a7b43b1a3e204759d4b7ad60928fa897b1600654 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 3cfc2a05af..16454f63d1 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."5f73d1e629a8807f1b9d94f8b411d6480a0a59fb" = "1w5mxw9rwiiiqphbg2rdyp4cvv9hz2l64f7fpfhncw6gncfx7ggw"; + "https://github.com/simplex-chat/simplexmq.git"."a7b43b1a3e204759d4b7ad60928fa897b1600654" = "169vjn5gyw42cmak6kwyl27zm57il43khnlj40zjwjw7cldkzdzi"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 34ad95b800..5754419933 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -96,7 +96,12 @@ import Simplex.RemoteControl.Types import System.IO (Handle) import System.Mem.Weak (Weak) import UnliftIO.STM -#if !defined(dbPostgres) + +#if defined(dbPostgres) +import qualified Database.PostgreSQL.Simple as PSQL + +type SQLError = PSQL.SqlError +#else import Database.SQLite.Simple (SQLError) import qualified Database.SQLite.Simple as SQL import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) @@ -1542,25 +1547,24 @@ withFastStore = withStorePriority True withStorePriority :: Bool -> (DB.Connection -> ExceptT StoreError IO a) -> CM a withStorePriority priority action = do ChatController {chatStore} <- ask - liftIOEither $ withTransactionPriority chatStore priority (runExceptT . withExceptT ChatErrorStore . action) `E.catches` handleDBErrors + liftIOEither $ withTransactionPriority chatStore priority (runExceptT . withExceptT ChatErrorStore . action) `E.catch` handleDBErrors withStoreBatch :: Traversable t => (DB.Connection -> t (IO (Either ChatError a))) -> CM' (t (Either ChatError a)) withStoreBatch actions = do ChatController {chatStore} <- ask - liftIO $ withTransaction chatStore $ mapM (`E.catches` handleDBErrors) . actions + liftIO $ withTransaction chatStore $ mapM (`E.catch` handleDBErrors) . actions --- TODO [postgres] postgres specific error handling -handleDBErrors :: [E.Handler (Either ChatError a)] -handleDBErrors = -#if !defined(dbPostgres) - ( E.Handler $ \(e :: SQLError) -> - let se = SQL.sqlError e - busy = se == SQL.ErrorBusy || se == SQL.ErrorLocked - in pure . Left . ChatErrorStore $ if busy then SEDBBusyError $ show se else SEDBException $ show e - ) : +handleDBErrors :: E.SomeException -> IO (Either ChatError a) +handleDBErrors e = pure $ Left $ ChatErrorStore $ case E.fromException e of + Just (e' :: SQLError) -> +#if defined(dbPostgres) + SEDBException $ show e' +#else + let se = SQL.sqlError e' + busy = se == SQL.ErrorBusy || se == SQL.ErrorLocked + in (if busy then SEDBBusyError else SEDBException) $ show e' #endif - [ E.Handler $ \(E.SomeException e) -> pure . Left . ChatErrorStore . SEDBException $ show e - ] + Nothing -> SEDBException $ show e withStoreBatch' :: Traversable t => (DB.Connection -> t (IO a)) -> CM' (t (Either ChatError a)) withStoreBatch' actions = withStoreBatch $ fmap (fmap Right) . actions diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index fc6a0ea782..675ca03a8a 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -262,7 +262,7 @@ stopChatController ChatController {smpAgent, agentAsync = s, sndFiles, rcvFiles, readTVarIO remoteHostSessions >>= mapM_ (cancelRemoteHost False . snd) atomically (stateTVar remoteCtrlSession (,Nothing)) >>= mapM_ (cancelRemoteCtrl False . snd) disconnectAgentClient smpAgent - readTVarIO s >>= mapM_ (\(a1, a2) -> uninterruptibleCancel a1 >> mapM_ uninterruptibleCancel a2) + readTVarIO s >>= mapM_ (\(a1, a2) -> forkIO $ uninterruptibleCancel a1 >> mapM_ uninterruptibleCancel a2) closeFiles sndFiles closeFiles rcvFiles atomically $ do @@ -1805,7 +1805,7 @@ processChatCommand vr nm = \case conn <- withFastStore $ \db -> getPendingContactConnection db userId connId let PendingContactConnection {pccConnStatus, connLinkInv} = conn case (pccConnStatus, connLinkInv) of - (ConnNew, Just _ссLink) -> do + (ConnNew, Just _ccLink) -> do newUser <- privateGetUser newUserId conn' <- recreateConn user conn newUser pure $ CRConnectionUserChanged user conn conn' newUser diff --git a/src/Simplex/Chat/Options/Postgres.hs b/src/Simplex/Chat/Options/Postgres.hs index c74ae37750..ab7414566c 100644 --- a/src/Simplex/Chat/Options/Postgres.hs +++ b/src/Simplex/Chat/Options/Postgres.hs @@ -42,7 +42,7 @@ chatDbOptsP _appDir defaultDbName = do ( long "pool-size" <> metavar "DB_POOL_SIZE" <> help "Database connection pool size" - <> value 10 + <> value 1 <> showDefault ) dbCreateSchema <- @@ -84,7 +84,7 @@ mobileDbOpts schemaPrefix connstr = do ChatDbOpts { dbConnstr, dbSchemaPrefix, - dbPoolSize = 10, + dbPoolSize = 1, dbCreateSchema = True } diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index d0b186dd94..e258f3dccc 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -131,7 +131,7 @@ testCoreOpts = -- dbSchemaPrefix is not used in tests (except bot tests where it's redefined), -- instead different schema prefix is passed per client so that single test database is used dbSchemaPrefix = "", - dbPoolSize = 3, + dbPoolSize = 1, dbCreateSchema = True #else { dbFilePrefix = "./simplex_v1", -- dbFilePrefix is not used in tests (except bot tests where it's redefined) @@ -184,16 +184,11 @@ aCfg = (agentConfig defaultChatConfig) {tbqSize = 16} testAgentCfg :: AgentConfig testAgentCfg = aCfg - { reconnectInterval = (reconnectInterval aCfg) {initialInterval = 50000} - } - -testAgentCfgSlow :: AgentConfig -testAgentCfgSlow = - testAgentCfg - { smpClientVRange = mkVersionRange (Version 1) srvHostnamesSMPClientVersion, -- v2 - smpAgentVRange = mkVersionRange duplexHandshakeSMPAgentVersion pqdrSMPAgentVersion, -- v5 - smpCfg = (smpCfg testAgentCfg) {serverVRange = mkVersionRange minClientSMPRelayVersion sendingProxySMPVersion} -- v8 + { reconnectInterval = (reconnectInterval aCfg) {initialInterval = 50000}, + messageRetryInterval = RetryInterval2 {riFast = riFast {initialInterval = 50000}, riSlow = riSlow {initialInterval = 50000}} } + where + RetryInterval2 {riFast, riSlow} = messageRetryInterval aCfg testAgentCfgNoShortLinks :: AgentConfig testAgentCfgNoShortLinks = @@ -213,9 +208,6 @@ testCfg = confirmMigrations = MCYesUp } -testCfgSlow :: ChatConfig -testCfgSlow = testCfg {agentConfig = testAgentCfgSlow} - testCfgNoShortLinks :: ChatConfig testCfgNoShortLinks = testCfg {agentConfig = testAgentCfgNoShortLinks} @@ -522,7 +514,7 @@ smpServerCfg :: ServerConfig STMMsgStore smpServerCfg = ServerConfig { transports = [(serverPort, transport @TLS, False)], - tbqSize = 1, + tbqSize = 4, msgQueueQuota = 16, maxJournalMsgCount = 24, maxJournalStateLines = 4, diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 1b93013258..a56ad6d4e3 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -94,22 +94,9 @@ chatDirectTests = do describe "operators and usage conditions" $ do it "get and enable operators, accept conditions" testOperators describe "async connection handshake" $ do - describe "connect when initiating client goes offline" $ do - it "curr" $ testAsyncInitiatingOffline True testCfg testCfg - it "v5" $ testAsyncInitiatingOffline False testCfgSlow testCfgSlow - it "v5/curr" $ testAsyncInitiatingOffline False testCfgSlow testCfg - it "curr/v5" $ testAsyncInitiatingOffline True testCfg testCfgSlow - describe "connect when accepting client goes offline" $ do - it "curr" $ testAsyncAcceptingOffline True testCfg testCfg - it "v5" $ testAsyncAcceptingOffline False testCfgSlow testCfgSlow - it "v5/curr" $ testAsyncAcceptingOffline False testCfgSlow testCfg - it "curr/v5" $ testAsyncAcceptingOffline True testCfg testCfgSlow - describe "connect, fully asynchronous (when clients are never simultaneously online)" $ do - it "curr" testFullAsyncFast - -- fails in CI - xit'' "v5" $ testFullAsyncSlow False testCfgSlow testCfgSlow - xit'' "v5/curr" $ testFullAsyncSlow False testCfgSlow testCfg - xit'' "curr/v5" $ testFullAsyncSlow True testCfg testCfgSlow + it "connect when initiating client goes offline" $ testAsyncInitiatingOffline True + it "connect when accepting client goes offline" $ testAsyncAcceptingOffline True + it "connect, fully asynchronous (when clients are never simultaneously online)" $ testFullAsyncFast describe "webrtc calls api" $ do it "negotiate call" testNegotiateCall #if !defined(dbPostgres) @@ -1241,33 +1228,33 @@ testOperators = where opts' = testOpts {coreOptions = testCoreOpts {smpServers = [], xftpServers = []}} -testAsyncInitiatingOffline :: HasCallStack => Bool -> ChatConfig -> ChatConfig -> TestParams -> IO () -testAsyncInitiatingOffline withShortLink aliceCfg bobCfg ps = do - inv <- withNewTestChatCfg ps aliceCfg "alice" aliceProfile $ \alice -> do +testAsyncInitiatingOffline :: HasCallStack => Bool -> TestParams -> IO () +testAsyncInitiatingOffline withShortLink ps = do + inv <- withNewTestChat ps "alice" aliceProfile $ \alice -> do threadDelay 250000 alice ##> "/c" (if withShortLink then getInvitation else getInvitationNoShortLink) alice - withNewTestChatCfg ps bobCfg "bob" bobProfile $ \bob -> do + withNewTestChat ps "bob" bobProfile $ \bob -> do threadDelay 250000 bob ##> ("/c " <> inv) bob <## "confirmation sent!" - withTestChatCfg ps aliceCfg "alice" $ \alice -> do + withTestChat ps "alice" $ \alice -> do alice <## "subscribed 1 connections on server localhost" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") -testAsyncAcceptingOffline :: HasCallStack => Bool -> ChatConfig -> ChatConfig -> TestParams -> IO () -testAsyncAcceptingOffline withShortLink aliceCfg bobCfg ps = do - inv <- withNewTestChatCfg ps aliceCfg "alice" aliceProfile $ \alice -> do +testAsyncAcceptingOffline :: HasCallStack => Bool -> TestParams -> IO () +testAsyncAcceptingOffline withShortLink ps = do + inv <- withNewTestChat ps "alice" aliceProfile $ \alice -> do alice ##> "/c" (if withShortLink then getInvitation else getInvitationNoShortLink) alice - withNewTestChatCfg ps bobCfg "bob" bobProfile $ \bob -> do + withNewTestChat ps "bob" bobProfile $ \bob -> do threadDelay 250000 bob ##> ("/c " <> inv) bob <## "confirmation sent!" - withTestChatCfg ps aliceCfg "alice" $ \alice -> do - withTestChatCfg ps bobCfg "bob" $ \bob -> do + withTestChat ps "alice" $ \alice -> do + withTestChat ps "bob" $ \bob -> do alice <## "subscribed 1 connections on server localhost" bob <## "subscribed 1 connections on server localhost" concurrently_ @@ -1292,30 +1279,6 @@ testFullAsyncFast ps = do bob <## "subscribed 1 connections on server localhost" bob <## "alice (Alice): contact is connected" -testFullAsyncSlow :: HasCallStack => Bool -> ChatConfig -> ChatConfig -> TestParams -> IO () -testFullAsyncSlow withShortLink aliceCfg bobCfg ps = do - inv <- withNewTestChatCfg ps aliceCfg "alice" aliceProfile $ \alice -> do - threadDelay 250000 - alice ##> "/c" - (if withShortLink then getInvitation else getInvitationNoShortLink) alice - withNewTestChatCfg ps bobCfg "bob" bobProfile $ \bob -> do - threadDelay 250000 - bob ##> ("/c " <> inv) - bob <## "confirmation sent!" - withAlice $ \alice -> - alice <## "subscribed 1 connections on server localhost" - withBob $ \bob -> - bob <## "subscribed 1 connections on server localhost" - withAlice $ \alice -> do - alice <## "subscribed 1 connections on server localhost" - alice <## "bob (Bob): contact is connected" - withBob $ \bob -> do - bob <## "subscribed 1 connections on server localhost" - bob <## "alice (Alice): contact is connected" - where - withAlice = withTestChatCfg ps aliceCfg "alice" - withBob = withTestChatCfg ps aliceCfg "bob" - testCallType :: CallType testCallType = CallType {media = CMVideo, capabilities = CallCapabilities {encryption = True}} @@ -1341,7 +1304,7 @@ repeatM_ n a = forM_ [1 .. n] $ const a testNegotiateCall :: HasCallStack => TestParams -> IO () testNegotiateCall = - testChat2 aliceProfile bobProfile $ \alice bob -> do + withTestOutput $ testChat2 aliceProfile bobProfile $ \alice bob -> do connectUsers alice bob -- just for testing db query alice ##> "/_call get" @@ -2200,7 +2163,7 @@ testUsersDifferentCIExpirationTTL ps = do showActiveUser alice "alisa" alice #$> ("/_get chat @6 count=100", chat, chatFeatures <> [(1, "alisa 1"), (0, "alisa 2"), (1, "alisa 3"), (0, "alisa 4")]) - threadDelay 2000000 + threadDelay 2100000 alice #$> ("/_get chat @6 count=100", chat, [(1,"chat banner")]) where @@ -2419,7 +2382,7 @@ testDisableCIExpirationOnlyForOneUser ps = do cfg = testCfg {initialCleanupManagerDelay = 0, cleanupManagerStepDelay = 0, ciExpirationInterval = 500000} testUsersTimedMessages :: HasCallStack => TestParams -> IO () -testUsersTimedMessages ps = do +testUsersTimedMessages ps' = do withNewTestChat ps "bob" bobProfile $ \bob -> do withNewTestChat ps "alice" aliceProfile $ \alice -> do connectUsers alice bob @@ -2462,10 +2425,8 @@ testUsersTimedMessages ps = do threadDelay 1000000 - alice <## "[user: alice] timed message deleted: alice 1" - alice <## "[user: alice] timed message deleted: alice 2" - bob <## "timed message deleted: alice 1" - bob <## "timed message deleted: alice 2" + alice <### ["[user: alice] timed message deleted: alice 1", "[user: alice] timed message deleted: alice 2"] + bob <### ["timed message deleted: alice 1", "timed message deleted: alice 2"] alice ##> "/user alice" showActiveUser alice "alice (Alice)" @@ -2477,10 +2438,8 @@ testUsersTimedMessages ps = do threadDelay 1000000 - alice <## "timed message deleted: alisa 1" - alice <## "timed message deleted: alisa 2" - bob <## "timed message deleted: alisa 1" - bob <## "timed message deleted: alisa 2" + alice <### ["timed message deleted: alisa 1", "timed message deleted: alisa 2"] + bob <### ["timed message deleted: alisa 1", "timed message deleted: alisa 2"] alice ##> "/user" showActiveUser alice "alisa" @@ -2519,10 +2478,8 @@ testUsersTimedMessages ps = do -- messages are deleted after restart threadDelay 1000000 - alice <## "[user: alice] timed message deleted: alice 3" - alice <## "[user: alice] timed message deleted: alice 4" - bob <## "timed message deleted: alice 3" - bob <## "timed message deleted: alice 4" + alice <### ["[user: alice] timed message deleted: alice 3", "[user: alice] timed message deleted: alice 4"] + bob <### ["timed message deleted: alice 3", "timed message deleted: alice 4"] alice ##> "/user alice" showActiveUser alice "alice (Alice)" @@ -2534,15 +2491,14 @@ testUsersTimedMessages ps = do threadDelay 1000000 - alice <## "timed message deleted: alisa 3" - alice <## "timed message deleted: alisa 4" - bob <## "timed message deleted: alisa 3" - bob <## "timed message deleted: alisa 4" + alice <### ["timed message deleted: alisa 3", "timed message deleted: alisa 4"] + bob <### ["timed message deleted: alisa 3", "timed message deleted: alisa 4"] alice ##> "/user" showActiveUser alice "alisa" alice #$> ("/_get chat @6 count=100", chat, [(1,"chat banner")]) where + ps = ps' {printOutput = True} :: TestParams configureTimedMessages :: HasCallStack => TestCC -> TestCC -> String -> String -> IO () configureTimedMessages alice bob bobId ttl = do aliceName <- userName alice @@ -2699,7 +2655,7 @@ testUserPrivacy = testSetChatItemTTL :: HasCallStack => TestParams -> IO () testSetChatItemTTL = testChat2 aliceProfile bobProfile $ - \alice bob -> do + \alice bob -> withXFTPServer $ do connectUsers alice bob alice #> "@bob 1" bob <# "alice> 1" @@ -2713,6 +2669,7 @@ testSetChatItemTTL = alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" bob <## "use /fr 1 [/ | ] to receive it" + alice <## "completed uploading file 1 (test.jpg) for bob" -- above items should be deleted after we set ttl threadDelay 3000000 alice #> "@bob 3" diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 2de9d7ffe5..a71e7ae173 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -761,7 +761,9 @@ testXFTPDeleteUploadedFileGroup = alice ##> "/fc 1" concurrentlyN_ - [ alice <## "cancelled sending file 1 (test.pdf) to bob, cath", + [ do + recipients <- dropStrPrefix "cancelled sending file 1 (test.pdf) to " <$> getTermLine alice + recipients == "bob, cath" || recipients == "cath, bob" `shouldBe` True, cath <## "alice cancelled sending file 1 (test.pdf)" ] diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index aa9a48f279..166528e69c 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -94,10 +94,7 @@ chatGroupTests = do describe "batch send messages" $ do it "send multiple messages api" testSendMulti it "send multiple timed messages" testSendMultiTimed -#if !defined(dbPostgres) - -- TODO [postgres] this test hangs with PostgreSQL it "send multiple messages (many chat batches)" testSendMultiManyBatches -#endif it "shared message body is reused" testSharedMessageBody it "shared batch body is reused" testSharedBatchBody describe "async group connections" $ do @@ -124,7 +121,6 @@ chatGroupTests = do it "ok to connect; known group" testPlanGroupLinkKnown it "own group link" testPlanGroupLinkOwn it "group link without contact - connecting" testPlanGroupLinkConnecting - it "group link without contact - connecting (slow handshake)" testPlanGroupLinkConnectingSlow it "re-join existing group after leaving" testPlanGroupLinkLeaveRejoin #if !defined(dbPostgres) -- TODO [postgres] restore from outdated db backup (same as in agent) @@ -2044,20 +2040,17 @@ testSendMultiManyBatches = (bob <# ("#team alice> message " <> show i)) (cath <# ("#team alice> message " <> show i)) - aliceItemsCount <- withCCTransaction alice $ \db -> - DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdAlice) :: IO [[Int]] - aliceItemsCount `shouldBe` [[300]] - - bobItemsCount <- withCCTransaction bob $ \db -> - DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdBob) :: IO [[Int]] - bobItemsCount `shouldBe` [[300]] - - cathItemsCount <- withCCTransaction cath $ \db -> - DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdCath) :: IO [[Int]] - cathItemsCount `shouldBe` [[300]] + checkItemCount alice msgIdAlice 300 + checkItemCount bob msgIdBob 300 + checkItemCount cath msgIdCath 300 + where + checkItemCount c msgId n = do + itemsCount <- withCCTransaction c $ \db -> + DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgId) :: IO [[Int]] + itemsCount `shouldBe` [[n]] testSharedMessageBody :: HasCallStack => TestParams -> IO () -testSharedMessageBody ps = +testSharedMessageBody ps' = withNewTestChatOpts ps opts' "alice" aliceProfile $ \alice -> do withSmpServer' serverCfg' $ withNewTestChatOpts ps opts' "bob" bobProfile $ \bob -> @@ -2066,9 +2059,7 @@ testSharedMessageBody ps = alice <## "disconnected 4 connections on server localhost" alice #> "#team hello" - bodiesCount1 <- withCCAgentTransaction alice $ \db -> - DB.query_ db "SELECT count(1) FROM snd_message_bodies" :: IO [[Int]] - bodiesCount1 `shouldBe` [[1]] + checkMsgBodyCount alice 1 withSmpServer' serverCfg' $ withTestChatOpts ps opts' "bob" $ \bob -> @@ -2080,12 +2071,15 @@ testSharedMessageBody ps = ] bob <# "#team alice> hello" cath <# "#team alice> hello" - bodiesCount2 <- withCCAgentTransaction alice $ \db -> - DB.query_ db "SELECT count(1) FROM snd_message_bodies" :: IO [[Int]] - bodiesCount2 `shouldBe` [[0]] +-- because of PostgreSQL concurrency deleteSndMsgDelivery fails to delete message body +#if !defined(dbPostgres) + threadDelay 500000 + checkMsgBodyCount alice 0 +#endif alice <## "disconnected 4 connections on server localhost" where + ps = ps' {printOutput = True} :: TestParams tmp = tmpPath ps serverCfg' = smpServerCfg @@ -2100,6 +2094,12 @@ testSharedMessageBody ps = } } +checkMsgBodyCount :: TestCC -> Int -> IO () +checkMsgBodyCount c n = do + bodiesCount <- withCCAgentTransaction c $ \db -> + DB.query_ db "SELECT count(1) FROM snd_message_bodies" + bodiesCount `shouldBe` [[n]] + testSharedBatchBody :: HasCallStack => TestParams -> IO () testSharedBatchBody ps = withNewTestChatOpts ps opts' "alice" aliceProfile $ \alice -> do @@ -2116,9 +2116,7 @@ testSharedBatchBody ps = _ <- getTermLine alice alice <## "300 messages sent" - bodiesCount1 <- withCCAgentTransaction alice $ \db -> - DB.query_ db "SELECT count(1) FROM snd_message_bodies" :: IO [[Int]] - bodiesCount1 `shouldBe` [[3]] + checkMsgBodyCount alice 3 withSmpServer' serverCfg' $ withTestChatOpts ps opts' "bob" $ \bob -> @@ -2132,9 +2130,10 @@ testSharedBatchBody ps = concurrently_ (bob <# ("#team alice> message " <> show i)) (cath <# ("#team alice> message " <> show i)) - bodiesCount2 <- withCCAgentTransaction alice $ \db -> - DB.query_ db "SELECT count(1) FROM snd_message_bodies" :: IO [[Int]] - bodiesCount2 `shouldBe` [[0]] +-- because of PostgreSQL concurrency deleteSndMsgDelivery fails to delete message body +#if !defined(dbPostgres) + checkMsgBodyCount alice 0 +#endif alice <## "disconnected 4 connections on server localhost" where @@ -3611,49 +3610,6 @@ testPlanGroupLinkConnecting ps = do bob <## "group link: known group #team" bob <## "use #team to send messages" -testPlanGroupLinkConnectingSlow :: HasCallStack => TestParams -> IO () -testPlanGroupLinkConnectingSlow ps = do - gLink <- withNewTestChatCfg ps testCfgSlow "alice" aliceProfile $ \alice -> do - threadDelay 100000 - alice ##> "/g team" - alice <## "group #team is created" - alice <## "to add members use /a team or /create link #team" - alice ##> "/create link #team" - getGroupLinkNoShortLink alice "team" GRMember True - withNewTestChatCfg ps testCfgSlow "bob" bobProfile $ \bob -> do - threadDelay 100000 - - bob ##> ("/c " <> gLink) - bob <## "connection request sent!" - - bob ##> ("/_connect plan 1 " <> gLink) - bob <## "group link: connecting, allowed to reconnect" - - let gLinkSchema2 = linkAnotherSchema gLink - bob ##> ("/_connect plan 1 " <> gLinkSchema2) - bob <## "group link: connecting, allowed to reconnect" - - threadDelay 100000 - withTestChatCfg ps testCfgSlow "alice" $ \alice -> do - alice - <### [ "subscribed 1 connections on server localhost", - "bob (Bob): accepting request to join group #team..." - ] - withTestChatCfg ps testCfgSlow "bob" $ \bob -> do - threadDelay 500000 - bob <## "subscribed 1 connections on server localhost" - bob <## "#team: joining the group..." - - bob ##> ("/_connect plan 1 " <> gLink) - bob <## "group link: connecting to group #team" - - let gLinkSchema2 = linkAnotherSchema gLink - bob ##> ("/_connect plan 1 " <> gLinkSchema2) - bob <## "group link: connecting to group #team" - - bob ##> ("/c " <> gLink) - bob <## "group link: connecting to group #team" - #if !defined(dbPostgres) testGroupMsgDecryptError :: HasCallStack => TestParams -> IO () testGroupMsgDecryptError ps = diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index a1ab8548ed..3fdadc3b64 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -61,7 +61,6 @@ chatProfileTests = do it "contact address ok to connect; known contact" testPlanAddressOkKnown it "own contact address" testPlanAddressOwn it "connecting via contact address" testPlanAddressConnecting - it "connecting via contact address (slow handshake)" testPlanAddressConnectingSlow it "re-connect with deleted contact" testPlanAddressContactDeletedReconnected it "contact via address" testPlanAddressContactViaAddress it "contact via short address" testPlanAddressContactViaShortAddress @@ -72,7 +71,6 @@ chatProfileTests = do it "set connection incognito" testSetConnectionIncognito it "reset connection incognito" testResetConnectionIncognito it "set connection incognito prohibited during negotiation" testSetConnectionIncognitoProhibitedDuringNegotiation - it "set connection incognito prohibited during negotiation (slow handshake)" testSetConnectionIncognitoProhibitedDuringNegotiationSlow it "connection incognito unchanged errors" testConnectionIncognitoUnchangedErrors it "set, reset, set connection incognito" testSetResetSetConnectionIncognito it "join group incognito" testJoinGroupIncognito @@ -1110,46 +1108,6 @@ testPlanAddressConnecting ps = do bob <## "contact address: known contact alice" bob <## "use @alice to send messages" -testPlanAddressConnectingSlow :: HasCallStack => TestParams -> IO () -testPlanAddressConnectingSlow ps = do - cLink <- withNewTestChatCfg ps testCfgSlow "alice" aliceProfile $ \alice -> do - alice ##> "/ad" - getContactLinkNoShortLink alice True - withNewTestChatCfg ps testCfgSlow "bob" bobProfile $ \bob -> do - threadDelay 100000 - - bob ##> ("/c " <> cLink) - bob <## "connection request sent!" - - bob ##> ("/_connect plan 1 " <> cLink) - bob <## "contact address: connecting, allowed to reconnect" - - let cLinkSchema2 = linkAnotherSchema cLink - bob ##> ("/_connect plan 1 " <> cLinkSchema2) - bob <## "contact address: connecting, allowed to reconnect" - - threadDelay 100000 - withTestChatCfg ps testCfgSlow "alice" $ \alice -> do - alice <## "subscribed 1 connections on server localhost" - alice <## "bob (Bob) wants to connect to you!" - alice <## "to accept: /ac bob" - alice <## "to reject: /rc bob (the sender will NOT be notified)" - alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." - withTestChatCfg ps testCfgSlow "bob" $ \bob -> do - threadDelay 500000 - bob <## "subscribed 1 connections on server localhost" - bob @@@ [("@alice", "")] - bob ##> ("/_connect plan 1 " <> cLink) - bob <## "contact address: connecting to contact alice" - - let cLinkSchema2 = linkAnotherSchema cLink - bob ##> ("/_connect plan 1 " <> cLinkSchema2) - bob <## "contact address: connecting to contact alice" - - bob ##> ("/c " <> cLink) - bob <## "contact address: connecting to contact alice" - testPlanAddressContactDeletedReconnected :: HasCallStack => TestParams -> IO () testPlanAddressContactDeletedReconnected = testChat2 aliceProfile bobProfile $ @@ -1559,30 +1517,6 @@ testSetConnectionIncognitoProhibitedDuringNegotiation ps = do alice `hasContactProfiles` ["alice", "bob"] bob `hasContactProfiles` ["alice", "bob"] -testSetConnectionIncognitoProhibitedDuringNegotiationSlow :: HasCallStack => TestParams -> IO () -testSetConnectionIncognitoProhibitedDuringNegotiationSlow ps = do - inv <- withNewTestChatCfg ps testCfgSlow "alice" aliceProfile $ \alice -> do - threadDelay 250000 - alice ##> "/connect" - getInvitationNoShortLink alice - withNewTestChatCfg ps testCfgSlow "bob" bobProfile $ \bob -> do - threadDelay 250000 - bob ##> ("/c " <> inv) - bob <## "confirmation sent!" - withTestChatCfg ps testCfgSlow "alice" $ \alice -> do - threadDelay 250000 - alice <## "subscribed 1 connections on server localhost" - alice ##> "/_set incognito :1 on" - alice <## "chat db error: SEPendingConnectionNotFound {connId = 1}" - withTestChatCfg ps testCfgSlow "bob" $ \bob -> do - bob <## "subscribed 1 connections on server localhost" - concurrently_ - (bob <## "alice (Alice): contact is connected") - (alice <## "bob (Bob): contact is connected") - alice <##> bob - alice `hasContactProfiles` ["alice", "bob"] - bob `hasContactProfiles` ["alice", "bob"] - testConnectionIncognitoUnchangedErrors :: HasCallStack => TestParams -> IO () testConnectionIncognitoUnchangedErrors = testChat2 aliceProfile bobProfile $ \alice bob -> do @@ -2022,8 +1956,14 @@ testChangePCCUser = testChat2 aliceProfile bobProfile $ alice ##> "/user alisa" showActiveUser alice "alisa" -- Change connection back to other user +#if defined(dbPostgres) + alice ##> "/_set conn user :2 3" + alice <## "connection 2 changed from user alisa to user alisa2, new link:" +#else + -- connection ID does not change in SQLite because table has no auto-increment alice ##> "/_set conn user :1 3" alice <## "connection 1 changed from user alisa to user alisa2, new link:" +#endif alice <## "" _shortInv <- getTermLine alice alice <## "" @@ -2065,8 +2005,14 @@ testChangePCCUserFromIncognito = testChat2 aliceProfile bobProfile $ alice ##> "/user alisa" showActiveUser alice "alisa" -- Change connection back to initial user +#if defined(dbPostgres) + alice ##> "/_set conn user :2 1" + alice <## "connection 2 changed from user alisa to user alice, new link:" +#else + -- connection ID does not change in SQLite because table has no auto-increment alice ##> "/_set conn user :1 1" alice <## "connection 1 changed from user alisa to user alice, new link:" +#endif alice <## "" _shortInv <- getTermLine alice alice <## "" @@ -2104,9 +2050,16 @@ testChangePCCUserAndThenIncognito = testChat2 aliceProfile bobProfile $ alice ##> "/user alisa" showActiveUser alice "alisa" -- Change connection to incognito and make sure it's attached to the newly created user profile +#if defined(dbPostgres) + alice ##> "/_set incognito :2 on" + _ <- getTermLine alice + alice <## "connection 2 changed to incognito" +#else + -- connection ID does not change in SQLite because table has no auto-increment alice ##> "/_set incognito :1 on" _ <- getTermLine alice alice <## "connection 1 changed to incognito" +#endif bob ##> ("/connect " <> inv) bob <## "confirmation sent!" alisaIncognito <- getTermLine alice @@ -2485,10 +2438,8 @@ testEnableTimedMessagesContact = alice #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(1, "Disappearing messages: enabled (1 sec)"), (1, "hi"), (0, "hey")]) bob #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(0, "Disappearing messages: enabled (1 sec)"), (0, "hi"), (1, "hey")]) threadDelay 1000000 - alice <## "timed message deleted: hi" - alice <## "timed message deleted: hey" - bob <## "timed message deleted: hi" - bob <## "timed message deleted: hey" + alice <### ["timed message deleted: hi", "timed message deleted: hey"] + bob <### ["timed message deleted: hi", "timed message deleted: hey"] alice #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(1, "Disappearing messages: enabled (1 sec)")]) bob #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(0, "Disappearing messages: enabled (1 sec)")]) -- turn off, messages are not disappearing @@ -2580,10 +2531,8 @@ testTimedMessagesEnabledGlobally = alice #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(0, "Disappearing messages: enabled (1 sec)"), (1, "hi"), (0, "hey")]) bob #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(1, "Disappearing messages: enabled (1 sec)"), (0, "hi"), (1, "hey")]) threadDelay 1000000 - alice <## "timed message deleted: hi" - bob <## "timed message deleted: hi" - alice <## "timed message deleted: hey" - bob <## "timed message deleted: hey" + alice <### ["timed message deleted: hi", "timed message deleted: hey"] + bob <### ["timed message deleted: hi", "timed message deleted: hey"] alice #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(0, "Disappearing messages: enabled (1 sec)")]) bob #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(1, "Disappearing messages: enabled (1 sec)")])