Compare commits

..
Author SHA1 Message Date
spaced4ndy cb46906aae Merge branch 'master' into ep/test-coverage 2023-06-02 18:05:07 +04:00
Evgeny Poberezkin 1d87f8e420 test coverage 2023-05-29 09:36:37 +01:00
37 changed files with 302 additions and 1182 deletions
+1 -1
View File
@@ -150,7 +150,7 @@ You can install and setup servers automatically using our script:
```sh
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/simplex-chat/simplexmq/stable/install.sh -o simplex-server-install.sh \
&& if echo 'b8cf2be103f21f9461d9a500bcd3db06ab7d01d68871b07f4bd245195cbead1d simplex-server-install.sh' | sha256sum -c; then chmod +x ./simplex-server-install.sh && ./simplex-server-install.sh; rm ./simplex-server-install.sh; else echo "SHA-256 checksum is incorrect!" && rm ./simplex-server-install.sh; fi
&& if echo '1268d605e90bca1a8c7ef476038a8bd9aa9b1a28f79ea0a1485669ccf8fc23cd simplex-server-install.sh' | sha256sum -c; then chmod +x ./simplex-server-install.sh && ./simplex-server-install.sh; rm /simplex-server-install.sh; else echo "SHA-256 checksum is incorrect!" && rm ./simplex-server-install.sh; fi
```
### Build from source
+8
View File
@@ -3,6 +3,14 @@ packages: .
-- packages: . ../hs-socks
-- packages: . ../http2
package *
coverage: True
library-coverage: True
package attoparsec
coverage: False
library-coverage: False
source-repository-package
type: git
location: https://github.com/simplex-chat/aeson.git
+5 -8
View File
@@ -11,7 +11,6 @@ scripts_systemd_smp="$scripts/smp-server.service"
scripts_systemd_xftp="$scripts/xftp-server.service"
scripts_update="$scripts/simplex-servers-update"
scripts_uninstall="$scripts/simplex-servers-uninstall"
scripts_stopscript="$scripts/simplex-servers-stopscript"
# Default installation paths
path_bin="/usr/local/bin"
@@ -19,7 +18,6 @@ path_bin_smp="$path_bin/smp-server"
path_bin_xftp="$path_bin/xftp-server"
path_bin_update="$path_bin/simplex-servers-update"
path_bin_uninstall="$path_bin/simplex-servers-uninstall"
path_bin_stopscript="$path_bin/simplex-servers-stopscript"
path_conf_etc="/etc/opt"
path_conf_var="/var/opt"
@@ -62,8 +60,8 @@ ${GRN}3.${NC} Setup user for each server:
${GRN}4.${NC} Create systemd services:
- smp: ${YLW}${path_systemd_smp}${NC}
- xftp: ${YLW}${path_systemd_xftp}${NC}
${GRN}5.${NC} Install stopscript (systemd), update and uninstallation script:
- all: ${YLW}${path_bin_update}${NC}, ${YLW}${path_bin_uninstall}${NC}, ${YLW}${path_bin_stopscript}${NC}
${GRN}5.${NC} Install update and uninstallation script:
- all: ${YLW}${path_bin_update}${NC}, ${YLW}${path_bin_uninstall}${NC}
Press ${GRN}ENTER${NC} to continue or ${RED}Ctrl+C${NC} to cancel installation"
@@ -89,9 +87,9 @@ setup_users() {
setup_dirs() {
# Unquoted varibles, so field splitting can occur
mkdir -p $path_conf_smp
chown "$user_smp":"$user_smp" $path_conf_smp
chown "$user_smp":"$user_smp" "$path_conf_smp"
mkdir -p $path_conf_xftp
chown "$user_xftp":"$user_xftp" $path_conf_xftp
chown "$user_xftp":"$user_xftp" "$path_conf_xftp"
}
setup_systemd() {
@@ -102,7 +100,6 @@ setup_systemd() {
setup_scripts() {
curl --proto '=https' --tlsv1.2 -sSf -L "$scripts_update" -o "$path_bin_update" && chmod +x "$path_bin_update"
curl --proto '=https' --tlsv1.2 -sSf -L "$scripts_uninstall" -o "$path_bin_uninstall" && chmod +x "$path_bin_uninstall"
curl --proto '=https' --tlsv1.2 -sSf -L "$scripts_stopscript" -o "$path_bin_stopscript" && chmod +x "$path_bin_stopscript"
}
checks() {
@@ -134,7 +131,7 @@ main() {
setup_systemd
printf "${GRN} Done!${NC}\n"
printf "Installing stopscript, update and uninstallation script..."
printf "Installing update and uninstallation script..."
setup_scripts
printf "${GRN} Done!${NC}\n"
+3 -1
View File
@@ -1,5 +1,5 @@
name: simplexmq
version: 5.1.3
version: 5.1.1
synopsis: SimpleXMQ message broker
description: |
This package includes <./docs/Simplex-Messaging-Server.html server>,
@@ -144,6 +144,8 @@ tests:
- silently == 1.2.*
- main-tester == 0.2.*
- timeit == 2.0.*
ghc-options:
- -fhpc
ghc-options:
# - -haddock
-30
View File
@@ -1,30 +0,0 @@
#!/usr/bin/env sh
set -eu
path_conf_var="/var/opt"
path_conf_smp="$path_conf_var/simplex"
path_conf_xftp="$path_conf_var/simplex-xftp"
path_conf_storelog_smp="$path_conf_smp/smp-server-store.log"
path_conf_storelog_xftp="$path_conf_xftp/file-server-store.log"
date="$(date -u '+%Y-%m-%dT%H:%M:%S')"
backup_smp() {
if [ -e "$path_conf_storelog_smp" ]; then
cp "$path_conf_storelog_smp" "${path_conf_storelog_smp}.${date:-date-failed}"
fi
}
backup_xftp() {
if [ -e "$path_conf_storelog_xftp" ]; then
cp "$path_conf_storelog_xftp" "${path_conf_storelog_xftp}.${date:-date-failed}"
fi
}
if [ "$1" = 'smp-server' ]; then
backup_smp
elif [ "$1" = 'xftp-server' ]; then
backup_xftp
else
backup_smp
backup_xftp
fi
+1 -6
View File
@@ -13,11 +13,6 @@ fi
printf "${RED}This action will permanently remove all configs, directories, binaries from Installation Script. Please backup any relevant configs if they are needed.${NC}\n\nPress ${GRN}ENTER${NC} to continue or ${RED}Ctrl+C${NC} to cancel installation"
read ans
systemctl stop smp-server
systemctl stop xftp-server
rm -rf /var/opt/simplex /etc/opt/simplex /var/opt/simplex-xftp /etc/opt/simplex-xftp /srv/xftp /etc/systemd/system/smp-server.service /etc/systemd/system/xftp-server.service /usr/local/bin/smp-server /usr/local/bin/xftp-server /usr/local/bin/simplex-servers-update /usr/local/bin/simplex-servers-uninstall
userdel smp && userdel xftp
rm -rf /var/opt/simplex /etc/opt/simplex /var/opt/simplex-xftp /etc/opt/simplex-xftp /srv/xftp /etc/systemd/system/smp-server.service /etc/systemd/system/xftp-server.service /usr/local/bin/smp-server /usr/local/bin/xftp-server /usr/local/bin/simplex-servers-update /usr/local/bin/simplex-servers-uninstall && userdel smp && userdel xftp
printf "Uninstallation is complete! Thanks for trying out SimpleX!\n"
+14 -28
View File
@@ -11,7 +11,6 @@ scripts_systemd_smp="$scripts/smp-server.service"
scripts_systemd_xftp="$scripts/xftp-server.service"
scripts_update="$scripts/simplex-servers-update"
scripts_uninstall="$scripts/simplex-servers-uninstall"
scripts_stopscript="$scripts/simplex-servers-stopscript"
# Default installation paths
path_bin="/usr/local/bin"
@@ -19,7 +18,6 @@ path_bin_smp="$path_bin/smp-server"
path_bin_xftp="$path_bin/xftp-server"
path_bin_update="$path_bin/simplex-servers-update"
path_bin_uninstall="$path_bin/simplex-servers-uninstall"
path_bin_stopscript="$path_bin/simplex-servers-stopscript"
path_systemd="/etc/systemd/system"
path_systemd_smp="$path_systemd/smp-server.service"
@@ -29,7 +27,6 @@ path_systemd_xftp="$path_systemd/xftp-server.service"
path_tmp_bin="$(mktemp -d)"
path_tmp_bin_update="$path_tmp_bin/simplex-servers-update"
path_tmp_bin_uninstall="$path_tmp_bin/simplex-servers-uninstall"
path_tmp_bin_stopscript="$path_tmp_bin/simplex-servers-stopscript"
path_tmp_systemd_smp="$path_tmp_bin/smp-server.service"
path_tmp_systemd_xftp="$path_tmp_bin/xftp-server.service"
@@ -46,9 +43,8 @@ remote_version="$(curl --proto '=https' --tlsv1.2 -sSf -L https://api.github.com
update_scripts() {
curl --proto '=https' --tlsv1.2 -sSf -L "$scripts_update" -o "$path_tmp_bin_update" && chmod +x "$path_tmp_bin_update"
curl --proto '=https' --tlsv1.2 -sSf -L "$scripts_uninstall" -o "$path_tmp_bin_uninstall" && chmod +x "$path_tmp_bin_uninstall"
curl --proto '=https' --tlsv1.2 -sSf -L "$scripts_stopscript" -o "$path_tmp_bin_stopscript" && chmod +x "$path_tmp_bin_stopscript"
if diff -q "$path_bin_uninstall" "$path_tmp_bin_uninstall" > /dev/null 2>&1; then
if diff -q "$path_bin_uninstall" "$path_tmp_bin_uninstall" > /dev/null; then
printf -- "- ${YLW}Uninstall script is up-to-date${NC}.\n"
rm "$path_tmp_bin_uninstall"
else
@@ -56,15 +52,7 @@ update_scripts() {
mv "$path_tmp_bin_uninstall" "$path_bin_uninstall"
printf "${GRN}Done!${NC}\n"
fi
if diff -q "$path_bin_stopscript" "$path_tmp_bin_stopscript" > /dev/null 2>&1; then
printf -- "- ${YLW}Stopscript script is up-to-date${NC}.\n"
rm "$path_tmp_bin_stopscript"
else
printf -- "- Updating stopscript script..."
mv "$path_tmp_bin_stopscript" "$path_bin_stopscript"
printf "${GRN}Done!${NC}\n"
fi
if diff -q "$path_bin_update" "$path_tmp_bin_update" > /dev/null 2>&1; then
if diff -q "$path_bin_update" "$path_tmp_bin_update" > /dev/null; then
printf -- "- ${YLW}Update script is up-to-date${NC}.\n"
rm "$path_tmp_bin_update"
else
@@ -80,7 +68,7 @@ update_systemd() {
curl --proto '=https' --tlsv1.2 -sSf -L "$scripts_systemd_smp" -o "$path_tmp_systemd_smp"
curl --proto '=https' --tlsv1.2 -sSf -L "$scripts_systemd_xftp" -o "$path_tmp_systemd_xftp"
if diff -q "$path_systemd_smp" "$path_tmp_systemd_smp" > /dev/null 2>&1; then
if diff -q "$path_systemd_smp" "$path_tmp_systemd_smp" > /dev/null; then
printf -- "- ${YLW}smp-server service is up-to-date${NC}.\n"
rm "$path_tmp_systemd_smp"
else
@@ -89,7 +77,7 @@ update_systemd() {
systemctl daemon-reload
printf "${GRN}Done!${NC}\n"
fi
if diff -q "$path_systemd_xftp" "$path_tmp_systemd_xftp" > /dev/null 2>&1; then
if diff -q "$path_systemd_xftp" "$path_tmp_systemd_xftp" > /dev/null; then
printf -- "- ${YLW}xftp-server service is up-to-date${NC}.\n"
rm "$path_tmp_systemd_xftp"
else
@@ -107,16 +95,16 @@ update_bins() {
systemctl stop smp-server
printf "${GRN}Done!${NC}\n"
printf -- "- Updating smp-server bin to %s..." "$remote_version"
curl --proto '=https' --tlsv1.2 -sSf -L "$bin_smp" -o "$path_bin_smp" && chmod +x "$path_bin_smp"
print -- "- Updating smp-server bin to %s..." "$remote_version"
curl --proto '=https' --tlsv1.2 -sSf -L "$bin_smp" -o "$bin_path_smp" && chmod +x "$bin_path_smp"
printf "${GRN}Done!${NC}\n"
printf -- "- Starting smp-server service..."
systemctl start smp-server
systemctl stop smp-server
printf "${GRN}Done!${NC}\n"
else
printf -- "- Updating smp-server bin..."
curl --proto '=https' --tlsv1.2 -sSf -L "$bin_smp" -o "$path_bin_smp" && chmod +x "$path_bin_smp"
print -- "- Updating smp-server bin..."
curl --proto '=https' --tlsv1.2 -sSf -L "$bin_smp" -o "$bin_path_smp" && chmod +x "$bin_path_smp"
printf "${GRN}Done!${NC}\n"
fi
@@ -125,16 +113,16 @@ update_bins() {
systemctl stop xftp-server
printf "${GRN}Done!${NC}\n"
printf -- "- Updating xftp-server bin to %s..." "$remote_version"
curl --proto '=https' --tlsv1.2 -sSf -L "$bin_xftp" -o "$path_bin_xftp" && chmod +x "$path_bin_xftp"
print -- "- Updating xftp-server bin to %s..." "$remote_version"
curl --proto '=https' --tlsv1.2 -sSf -L "$bin_xftp" -o "$bin_path_xftp" && chmod +x "$bin_path_xftp"
printf "${GRN}Done!${NC}\n"
printf -- "- Starting xftp-server service..."
systemctl start xftp-server
systemctl stop xftp-server
printf "${GRN}Done!${NC}\n"
else
printf -- "- Updating xftp-server bin..."
curl --proto '=https' --tlsv1.2 -sSf -L "$bin_smp" -o "$path_bin_xftp" && chmod +x "$path_bin_xftp"
print -- "- Updating xftp-server bin..."
curl --proto '=https' --tlsv1.2 -sSf -L "$bin_smp" -o "$bin_path_xftp" && chmod +x "$bin_path_xftp"
printf "${GRN}Done!${NC}\n"
fi
else
@@ -167,8 +155,6 @@ main() {
printf "Updating simplex server binaries...\n"
update_bins
rm -rf "$path_tmp_bin"
}
main "$@"
+1 -1
View File
@@ -6,7 +6,7 @@ User=smp
Group=smp
Type=simple
ExecStart=/usr/local/bin/smp-server start +RTS -N -RTS
ExecStopPost=/usr/local/bin/simplex-servers-stopscript smp-server
ExecStopPost=/usr/bin/env sh -c '[ -e "/var/opt/simplex/smp-server-store.log" ] && cp "/var/opt/simplex/smp-server-store.log" "/var/opt/simplex/smp-server-store.log.$(date +%FT%T)'
LimitNOFILE=65535
KillSignal=SIGINT
TimeoutStopSec=infinity
+1 -1
View File
@@ -6,7 +6,7 @@ User=xftp
Group=xftp
Type=simple
ExecStart=/usr/local/bin/xftp-server start +RTS -N -RTS
ExecStopPost=/usr/local/bin/simplex-servers-stopscript xftp-server
ExecStopPost=/usr/bin/env sh -c '[ -e "/var/opt/simplex-xftp/file-server-store.log" ] && cp "/var/opt/simplex-xftp/file-server-store.log" "/var/opt/simplex-xftp/file-server-store.log.$(date +%FT%T)'
LimitNOFILE=65535
KillSignal=SIGINT
TimeoutStopSec=infinity
+2 -3
View File
@@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack
name: simplexmq
version: 5.1.3
version: 5.1.1
synopsis: SimpleXMQ message broker
description: This package includes <./docs/Simplex-Messaging-Server.html server>,
<./docs/Simplex-Messaging-Client.html client> and
@@ -82,7 +82,6 @@ library
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230401_snd_files
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230510_files_pending_replicas_indexes
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230516_encrypted_rcv_message_hashes
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230531_switch_status
Simplex.Messaging.Agent.TAsyncs
Simplex.Messaging.Agent.TRcvQueues
Simplex.Messaging.Client
@@ -546,7 +545,7 @@ test-suite simplexmq-test
Paths_simplexmq
hs-source-dirs:
tests
ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns
ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -fhpc
build-depends:
HUnit ==1.6.*
, QuickCheck ==2.14.*
+5 -4
View File
@@ -38,8 +38,9 @@ import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as LB
import Data.Char (toLower)
import Data.Function (on)
import Data.Int (Int64)
import Data.List (foldl', sortOn)
import Data.List (foldl', groupBy, sortOn)
import Data.List.NonEmpty (NonEmpty (..), nonEmpty)
import qualified Data.List.NonEmpty as L
import Data.Map (Map)
@@ -65,7 +66,7 @@ import Simplex.Messaging.Encoding.String (StrEncoding (..))
import Simplex.Messaging.Parsers (parseAll)
import Simplex.Messaging.Protocol (ProtoServerWithAuth (..), SenderId, SndPrivateSignKey, XFTPServer, XFTPServerWithAuth)
import Simplex.Messaging.Server.CLI (getCliCommand')
import Simplex.Messaging.Util (groupAllOn, ifM, tshow, whenM)
import Simplex.Messaging.Util (ifM, tshow, whenM)
import System.Exit (exitFailure)
import System.FilePath (splitFileName, (</>))
import System.IO.Temp (getCanonicalTemporaryDirectory)
@@ -315,7 +316,7 @@ cliSendFileOpts SendOptions {filePath, outputDir, numRecipients, xftpServers, re
let xftpSrvs = fromMaybe defaultXFTPServers (nonEmpty xftpServers)
srvs <- liftIO $ replicateM (length chunks) $ getXFTPServer gen xftpSrvs
let thd3 (_, _, x) = x
chunks' = groupAllOn thd3 $ zip3 [1 ..] chunks srvs
chunks' = groupBy ((==) `on` thd3) $ sortOn thd3 $ zip3 [1 ..] chunks srvs
-- TODO shuffle/unshuffle chunks
-- the reason we don't do pooled downloads here within one server is that http2 library doesn't handle cleint concurrency, even though
-- upload doesn't allow other requests within the same client until complete (but download does allow).
@@ -427,7 +428,7 @@ cliReceiveFile ReceiveOptions {fileDescription, filePath, retryCount, tempPath,
liftIO $ printNoNewLine "Downloading file..."
downloadedChunks <- newTVarIO []
let srv FileChunk {replicas} = server (head replicas :: FileChunkReplica)
srvChunks = groupAllOn srv chunks
srvChunks = groupBy ((==) `on` srv) $ sortOn srv chunks
chunkPaths <- map snd . sortOn fst . concat <$> pooledForConcurrentlyN 16 srvChunks (mapM $ downloadFileChunk a encPath size downloadedChunks)
encDigest <- liftIO $ LC.sha512Hash <$> readChunks chunkPaths
when (encDigest /= unFileDigest digest) $ throwError $ CLIError "File digest mismatch"
+7 -4
View File
@@ -42,8 +42,9 @@ import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.Bifunctor (first)
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Function (on)
import Data.Int (Int64)
import Data.List (foldl', sortOn)
import Data.List (foldl', groupBy, sortOn)
import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe (fromMaybe)
@@ -58,7 +59,7 @@ import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (parseAll)
import Simplex.Messaging.Protocol (XFTPServer)
import Simplex.Messaging.Util (bshow, groupAllOn, (<$?>))
import Simplex.Messaging.Util (bshow, (<$?>))
data FileDescription (p :: FileParty) = FileDescription
{ party :: SFileParty p,
@@ -257,7 +258,9 @@ instance (ToField a) => ToField (FileSize a) where toField (FileSize s) = toFiel
groupReplicasByServer :: FileSize Word32 -> [FileChunk] -> [[FileServerReplica]]
groupReplicasByServer defChunkSize =
groupAllOn replicaServer . unfoldChunksToReplicas defChunkSize
groupBy ((==) `on` replicaServer)
. sortOn replicaServer
. unfoldChunksToReplicas defChunkSize
encodeFileReplicas :: FileSize Word32 -> [FileChunk] -> [YAMLServerReplicas]
encodeFileReplicas defChunkSize =
@@ -265,7 +268,7 @@ encodeFileReplicas defChunkSize =
where
encodeServerReplicas fs =
YAMLServerReplicas
{ server = replicaServer $ head fs, -- groupAllOn guarantees that fs is not empty
{ server = replicaServer $ head fs, -- groupBy guarantees that fs is not empty
chunks = map (B.unpack . encodeServerReplica) fs
}
+65 -171
View File
@@ -63,7 +63,6 @@ module Simplex.Messaging.Agent
sendMessage,
ackMessage,
switchConnection,
abortConnectionSwitch,
suspendConnection,
deleteConnection,
deleteConnections,
@@ -111,7 +110,7 @@ import Data.Composition ((.:), (.:.), (.::))
import Data.Foldable (foldl')
import Data.Functor (($>))
import Data.List (find)
import Data.List.NonEmpty (NonEmpty (..))
import Data.List.NonEmpty (NonEmpty (..), (<|))
import qualified Data.List.NonEmpty as L
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
@@ -205,7 +204,7 @@ ackMessageAsync :: forall m. AgentErrorMonad m => AgentClient -> ACorrId -> Conn
ackMessageAsync c = withAgentEnv c .:. ackMessageAsync' c
-- | Switch connection to the new receive queue
switchConnectionAsync :: AgentErrorMonad m => AgentClient -> ACorrId -> ConnId -> m ConnectionStats
switchConnectionAsync :: AgentErrorMonad m => AgentClient -> ACorrId -> ConnId -> m ()
switchConnectionAsync c = withAgentEnv c .: switchConnectionAsync' c
-- | Delete SMP agent connection (DEL command) asynchronously, no synchronous response
@@ -269,10 +268,6 @@ ackMessage c = withAgentEnv c .: ackMessage' c
switchConnection :: AgentErrorMonad m => AgentClient -> ConnId -> m ConnectionStats
switchConnection c = withAgentEnv c . switchConnection' c
-- | Abort switching connection to the new receive queue
abortConnectionSwitch :: AgentErrorMonad m => AgentClient -> ConnId -> m ConnectionStats
abortConnectionSwitch c = withAgentEnv c . abortConnectionSwitch' c
-- | Suspend SMP agent connection (OFF command)
suspendConnection :: AgentErrorMonad m => AgentClient -> ConnId -> m ()
suspendConnection c = withAgentEnv c . suspendConnection' c
@@ -529,18 +524,11 @@ deleteConnectionsAsync_ onSuccess c connIds = case connIds of
deleteConnQueues c True rqs >> onSuccess
-- | Add connection to the new receive queue
switchConnectionAsync' :: AgentMonad m => AgentClient -> ACorrId -> ConnId -> m ConnectionStats
switchConnectionAsync' :: AgentMonad m => AgentClient -> ACorrId -> ConnId -> m ()
switchConnectionAsync' c corrId connId =
withConnLock c connId "switchConnectionAsync" $
withStore c (`getConn` connId) >>= \case
SomeConn _ (DuplexConnection cData rqs@(rq :| _rqs) sqs)
| isJust (switchingRQ rqs) -> throwError $ CMD PROHIBITED
| otherwise -> do
rq1 <- withStore' c $ \db -> setRcvSwitchStatus db rq $ Just RSSwitchStarted
enqueueCommand c corrId connId Nothing $ AClientCommand $ APC SAEConn SWCH
let rqs' = updatedQs rq1 rqs
pure . connectionStats $ DuplexConnection cData rqs' sqs
_ -> throwError $ CMD PROHIBITED
withStore c (`getConn` connId) >>= \case
SomeConn _ DuplexConnection {} -> enqueueCommand c corrId connId Nothing $ AClientCommand $ APC SAEConn SWCH
_ -> throwError $ CMD PROHIBITED
newConn :: AgentMonad m => AgentClient -> UserId -> ConnId -> Bool -> SConnectionMode c -> Maybe CRClientData -> m (ConnId, ConnectionRequestUri c)
newConn c userId connId enableNtfs cMode clientData =
@@ -879,12 +867,7 @@ runCommandProcessing c@AgentClient {subQ} server_ = do
notify OK
LET confId ownCInfo -> withServer' . tryCommand $ allowConnection' c connId confId ownCInfo >> notify OK
ACK msgId -> withServer' . tryCommand $ ackMessage' c connId msgId >> notify OK
SWCH ->
noServer . tryCommand . withConnLock c connId "switchConnection" $
withStore c (`getConn` connId) >>= \case
SomeConn _ conn@(DuplexConnection _ (replaced :| _rqs) _) ->
switchDuplexConnection c conn replaced >>= notify . SWITCH QDRcv SPStarted
_ -> throwError $ CMD PROHIBITED
SWCH -> noServer $ tryCommand $ switchConnection' c connId >>= notify . SWITCH QDRcv SPStarted
DEL -> withServer' . tryCommand $ deleteConnection' c connId >> notify OK
_ -> notify $ ERR $ INTERNAL $ "unsupported async command " <> show (aCommandTag cmd)
AInternalCommand cmd -> case cmd of
@@ -904,24 +887,13 @@ runCommandProcessing c@AgentClient {subQ} server_ = do
enqueueMessage c cData sq SMP.MsgFlags {notification = True} HELLO
-- ICDeleteConn is no longer used, but it can be present in old client databases
ICDeleteConn -> withStore' c (`deleteCommand` cmdId)
ICDeleteRcvQueue rId -> withServer $ \srv -> tryWithLock "ICDeleteRcvQueue" $ do
rq <- withStore c (\db -> getDeletedRcvQueue db connId srv rId)
deleteQueue c rq
withStore' c (`deleteConnRcvQueue` rq)
ICQSecure rId senderKey ->
withServer $ \srv -> tryWithLock "ICQSecure" . withDuplexConn $ \(DuplexConnection cData rqs sqs) ->
case find (sameQueue (srv, rId)) rqs of
Just rq'@RcvQueue {server, sndId, status, dbReplaceQueueId = Just replaceQId} ->
case find ((replaceQId ==) . dbQId) rqs of
Just rq1 -> when (status == Confirmed) $ do
secureQueue c rq' senderKey
withStore' c $ \db -> setRcvQueueStatus db rq' Secured
void . enqueueMessages c cData sqs SMP.noMsgFlags $ QUSE [((server, sndId), True)]
rq1' <- withStore' c $ \db -> setRcvSwitchStatus db rq1 $ Just RSSendingQUSE
let rqs' = updatedQs rq1' rqs
conn' = DuplexConnection cData rqs' sqs
notify . SWITCH QDRcv SPSecured $ connectionStats conn'
_ -> internalErr "ICQSecure: no switching queue found"
Just rq'@RcvQueue {server, sndId, status} -> when (status == Confirmed) $ do
secureQueue c rq' senderKey
withStore' c $ \db -> setRcvQueueStatus db rq' Secured
void . enqueueMessages c cData sqs SMP.noMsgFlags $ QUSE [((server, sndId), True)]
_ -> internalErr "ICQSecure: queue address not found in connection"
ICQDelete rId -> do
withServer $ \srv -> tryWithLock "ICQDelete" . withDuplexConn $ \(DuplexConnection cData rqs sqs) -> do
@@ -930,20 +902,13 @@ runCommandProcessing c@AgentClient {subQ} server_ = do
Just (rq'@RcvQueue {primary}, rq'' : rqs')
| primary -> internalErr "ICQDelete: cannot delete primary rcv queue"
| otherwise -> do
checkRQSwchStatus rq' RSReceivedMessage
tryError (deleteQueue c rq') >>= \case
Right () -> finalizeSwitch
Left e
| temporaryOrHostError e -> throwError e
| otherwise -> finalizeSwitch >> throwError e
where
finalizeSwitch = do
withStore' c $ \db -> deleteConnRcvQueue db rq'
when (enableNtfs cData) $ do
ns <- asks ntfSupervisor
atomically $ sendNtfSubCommand ns (connId, NSCCreate)
let conn' = DuplexConnection cData (rq'' :| rqs') sqs
notify $ SWITCH QDRcv SPCompleted $ connectionStats conn'
deleteQueue c rq'
withStore' c $ \db -> deleteConnRcvQueue db rq'
when (enableNtfs cData) $ do
ns <- asks ntfSupervisor
atomically $ sendNtfSubCommand ns (connId, NSCCreate)
let conn' = DuplexConnection cData (rq'' :| rqs') sqs
notify $ SWITCH QDRcv SPCompleted $ connectionStats conn'
_ -> internalErr "ICQDelete: cannot delete the only queue in connection"
where
ack srv rId srvMsgId = do
@@ -1147,11 +1112,9 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} cData@ConnData {userId, connId, dupl
AM_A_MSG_ -> notify $ SENT mId
AM_QCONT_ -> pure ()
AM_QADD_ -> pure ()
AM_QKEY_ -> do
SomeConn _ conn <- withStore c (`getConn` connId)
notify . SWITCH QDSnd SPConfirmed $ connectionStats conn
AM_QKEY_ -> pure ()
AM_QUSE_ -> pure ()
AM_QTEST_ -> withConnLock c connId "runSmpQueueMsgDelivery AM_QTEST_" $ do
AM_QTEST_ -> do
withStore' c $ \db -> setSndQueueStatus db sq Active
SomeConn _ conn <- withStore c (`getConn` connId)
case conn of
@@ -1162,10 +1125,9 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} cData@ConnData {userId, connId, dupl
-- this is the same queue where this loop delivers messages to but with updated state
Just SndQueue {dbReplaceQueueId = Just replacedId, primary} ->
-- second part of this condition is a sanity check because dbReplaceQueueId cannot point to the same queue, see switchConnection'
case removeQP (\sq' -> dbQId sq' == replacedId && not (sameQueue addr sq')) sqs of
case removeQP (\sq'@SndQueue {dbQueueId} -> dbQueueId == replacedId && not (sameQueue addr sq')) sqs of
Nothing -> internalErr msgId "sent QTEST: queue not found in connection"
Just (sq', sq'' : sqs') -> do
checkSQSwchStatus sq' SSSendingQTEST
-- remove the delivery from the map to stop the thread when the delivery loop is complete
atomically $ TM.delete (qAddress sq') $ smpQueueMsgQueues c
withStore' c $ \db -> do
@@ -1216,54 +1178,21 @@ ackMessage' c connId msgId = withConnLock c connId "ackMessage" $ do
withStore' c $ \db -> deleteMsg db connId mId
switchConnection' :: AgentMonad m => AgentClient -> ConnId -> m ConnectionStats
switchConnection' c connId =
withConnLock c connId "switchConnection" $
withStore c (`getConn` connId) >>= \case
SomeConn _ conn@(DuplexConnection _ rqs@(rq :| _rqs) _)
| isJust (switchingRQ rqs) -> throwError $ CMD PROHIBITED
| otherwise -> do
rq' <- withStore' c $ \db -> setRcvSwitchStatus db rq $ Just RSSwitchStarted
switchDuplexConnection c conn rq'
_ -> throwError $ CMD PROHIBITED
switchDuplexConnection :: AgentMonad m => AgentClient -> Connection 'CDuplex -> RcvQueue -> m ConnectionStats
switchDuplexConnection c (DuplexConnection cData@ConnData {connId, userId} rqs sqs) rq@RcvQueue {server, dbQueueId, sndId} = do
checkRQSwchStatus rq RSSwitchStarted
clientVRange <- asks $ smpClientVRange . config
-- try to get the server that is different from all queues, or at least from the primary rcv queue
srvAuth@(ProtoServerWithAuth srv _) <- getNextServer c userId $ map qServer (L.toList rqs) <> map qServer (L.toList sqs)
srv' <- if srv == server then getNextServer c userId [server] else pure srvAuth
(q, qUri) <- newRcvQueue c userId connId srv' clientVRange
let rq' = (q :: RcvQueue) {primary = True, dbReplaceQueueId = Just dbQueueId}
void . withStore c $ \db -> addConnRcvQueue db connId rq'
addSubscription c rq'
void . enqueueMessages c cData sqs SMP.noMsgFlags $ QADD [(qUri, Just (server, sndId))]
rq1 <- withStore' c $ \db -> setRcvSwitchStatus db rq $ Just RSSendingQADD
let rqs' = updatedQs rq1 rqs <> [rq']
pure . connectionStats $ DuplexConnection cData rqs' sqs
abortConnectionSwitch' :: AgentMonad m => AgentClient -> ConnId -> m ConnectionStats
abortConnectionSwitch' c connId =
withConnLock c connId "abortConnectionSwitch" $
withStore c (`getConn` connId) >>= \case
SomeConn _ (DuplexConnection cData rqs sqs) -> case switchingRQ rqs of
Just rq
| canAbortRcvSwitch rq -> do
-- multiple queues to which the connections switches were possible when repeating switch was allowed
let (delRqs, keepRqs) = L.partition ((Just (dbQId rq) ==) . dbReplaceQId) rqs
case L.nonEmpty keepRqs of
Just rqs' -> do
rq' <- withStore' c $ \db -> do
mapM_ (setRcvQueueDeleted db) delRqs
setRcvSwitchStatus db rq Nothing
forM_ delRqs $ \RcvQueue {server, rcvId} -> enqueueCommand c "" connId (Just server) $ AInternalCommand $ ICDeleteRcvQueue rcvId
let rqs'' = updatedQs rq' rqs'
conn' = DuplexConnection cData rqs'' sqs
pure $ connectionStats conn'
_ -> throwError $ INTERNAL "won't delete all rcv queues in connection"
| otherwise -> throwError $ CMD PROHIBITED
_ -> throwError $ CMD PROHIBITED
_ -> throwError $ CMD PROHIBITED
switchConnection' c connId = withConnLock c connId "switchConnection" $ do
SomeConn _ conn <- withStore c (`getConn` connId)
case conn of
DuplexConnection cData@ConnData {userId} rqs@(rq@RcvQueue {server, dbQueueId, sndId} :| rqs_) sqs -> do
clientVRange <- asks $ smpClientVRange . config
-- try to get the server that is different from all queues, or at least from the primary rcv queue
srvAuth@(ProtoServerWithAuth srv _) <- getNextServer c userId $ map qServer (L.toList rqs) <> map qServer (L.toList sqs)
srv' <- if srv == server then getNextServer c userId [server] else pure srvAuth
(q, qUri) <- newRcvQueue c userId connId srv' clientVRange
let rq' = (q :: RcvQueue) {primary = True, dbReplaceQueueId = Just dbQueueId}
void . withStore c $ \db -> addConnRcvQueue db connId rq'
addSubscription c rq'
void . enqueueMessages c cData sqs SMP.noMsgFlags $ QADD [(qUri, Just (server, sndId))]
pure . connectionStats $ DuplexConnection cData (rq <| rq' :| rqs_) sqs
_ -> throwError $ CMD PROHIBITED
ackQueueMessage :: AgentMonad m => AgentClient -> RcvQueue -> SMP.MsgId -> m ()
ackQueueMessage c rq srvMsgId =
@@ -1402,11 +1331,11 @@ getConnectionRatchetAdHash' c connId = do
connectionStats :: Connection c -> ConnectionStats
connectionStats = \case
RcvConnection _ rq -> ConnectionStats {rcvQueuesInfo = [rcvQueueInfo rq], sndQueuesInfo = []}
SndConnection _ sq -> ConnectionStats {rcvQueuesInfo = [], sndQueuesInfo = [sndQueueInfo sq]}
DuplexConnection _ rqs sqs -> ConnectionStats {rcvQueuesInfo = map rcvQueueInfo $ L.toList rqs, sndQueuesInfo = map sndQueueInfo $ L.toList sqs}
ContactConnection _ rq -> ConnectionStats {rcvQueuesInfo = [rcvQueueInfo rq], sndQueuesInfo = []}
NewConnection _ -> ConnectionStats {rcvQueuesInfo = [], sndQueuesInfo = []}
RcvConnection _ rq -> ConnectionStats {rcvServers = [qServer rq], sndServers = []}
SndConnection _ sq -> ConnectionStats {rcvServers = [], sndServers = [qServer sq]}
DuplexConnection _ rqs sqs -> ConnectionStats {rcvServers = map qServer $ L.toList rqs, sndServers = map qServer $ L.toList sqs}
ContactConnection _ rq -> ConnectionStats {rcvServers = [qServer rq], sndServers = []}
NewConnection _ -> ConnectionStats {rcvServers = [], sndServers = []}
-- | Change servers to be used for creating new queues, in Reader monad
setProtocolServers' :: forall p m. (ProtocolTypeI p, UserProtocol p, AgentMonad m) => AgentClient -> UserId -> NonEmpty (ProtoServerWithAuth p) -> m ()
@@ -1769,10 +1698,8 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), v, s
case (conn, dbReplaceQueueId) of
(DuplexConnection _ rqs _, Just replacedId) -> do
when primary . withStore' c $ \db -> setRcvQueuePrimary db connId rq
case find ((replacedId ==) . dbQId) rqs of
Just rq'@RcvQueue {server, rcvId} -> do
checkRQSwchStatus rq' RSSendingQUSE
void $ withStore' c $ \db -> setRcvSwitchStatus db rq' $ Just RSReceivedMessage
case find (\RcvQueue {dbQueueId} -> dbQueueId == replacedId) rqs of
Just RcvQueue {server, rcvId} -> do
enqueueCommand c "" connId (Just server) $ AInternalCommand $ ICQDelete rcvId
_ -> notify . ERR . AGENT $ A_QUEUE "replaced RcvQueue not found in connection"
_ -> pure ()
@@ -1974,33 +1901,24 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), v, s
-- processed by queue sender
qAddMsg :: NonEmpty (SMPQueueUri, Maybe SndQAddr) -> Connection 'CDuplex -> m ()
qAddMsg ((_, Nothing) :| _) _ = qError "adding queue without switching is not supported"
qAddMsg ((qUri, Just addr) :| _) (DuplexConnection _ rqs sqs) = do
qAddMsg ((qUri, Just addr) :| _) (DuplexConnection _ rqs sqs@(sq :| sqs_)) = do
clientVRange <- asks $ smpClientVRange . config
case qUri `compatibleVersion` clientVRange of
Just qInfo@(Compatible sqInfo@SMPQueueInfo {queueAddress}) ->
case (findQ (qAddress sqInfo) sqs, findQ addr sqs) of
(Just _, _) -> qError "QADD: queue address is already used in connection"
(_, Just sq@SndQueue {dbQueueId}) -> do
let (delSqs, keepSqs) = L.partition ((Just dbQueueId ==) . dbReplaceQId) sqs
case L.nonEmpty keepSqs of
Just sqs' -> do
-- move inside case?
withStore' c $ \db -> mapM_ (deleteConnSndQueue db connId) delSqs
sq_@SndQueue {sndPublicKey, e2ePubKey} <- newSndQueue userId connId qInfo
let sq'' = (sq_ :: SndQueue) {primary = True, dbQueueId, dbReplaceQueueId = Just dbQueueId}
dbId <- withStore c $ \db -> addConnSndQueue db connId sq''
let sq2 = (sq'' :: SndQueue) {dbQueueId = dbId}
case (sndPublicKey, e2ePubKey) of
(Just sndPubKey, Just dhPublicKey) -> do
logServer "<--" c srv rId $ "MSG <QADD> " <> logSecret (senderId queueAddress)
let sqInfo' = (sqInfo :: SMPQueueInfo) {queueAddress = queueAddress {dhPublicKey}}
void . enqueueMessages c cData sqs SMP.noMsgFlags $ QKEY [(sqInfo', sndPubKey)]
sq1 <- withStore' c $ \db -> setSndSwitchStatus db sq $ Just SSSendingQKEY
let sqs'' = updatedQs sq1 sqs' <> [sq2]
conn' = DuplexConnection cData rqs sqs''
notify . SWITCH QDSnd SPStarted $ connectionStats conn'
_ -> qError "absent sender keys"
_ -> qError "QADD: won't delete all snd queues in connection"
(_, Just _replaced@SndQueue {dbQueueId}) -> do
sq_@SndQueue {sndPublicKey, e2ePubKey} <- newSndQueue userId connId qInfo
let sq' = (sq_ :: SndQueue) {primary = True, dbReplaceQueueId = Just dbQueueId}
void . withStore c $ \db -> addConnSndQueue db connId sq'
case (sndPublicKey, e2ePubKey) of
(Just sndPubKey, Just dhPublicKey) -> do
logServer "<--" c srv rId $ "MSG <QADD> " <> logSecret (senderId queueAddress)
let sqInfo' = (sqInfo :: SMPQueueInfo) {queueAddress = queueAddress {dhPublicKey}}
void . enqueueMessages c cData sqs SMP.noMsgFlags $ QKEY [(sqInfo', sndPubKey)]
let conn' = DuplexConnection cData rqs (sq <| sq' :| sqs_)
notify . SWITCH QDSnd SPStarted $ connectionStats conn'
_ -> qError "absent sender keys"
_ -> qError "QADD: replaced queue address is not found in connection"
_ -> throwError $ AGENT A_VERSION
@@ -2012,7 +1930,6 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), v, s
case findRQ (smpServer, senderId) rqs of
Just rq'@RcvQueue {rcvId, e2ePrivKey = dhPrivKey, smpClientVersion = cVer, status = status'}
| status' == New || status' == Confirmed -> do
checkRQSwchStatus rq RSSendingQADD
logServer "<--" c srv rId $ "MSG <QKEY> " <> logSecret senderId
let dhSecret = C.dh' dhPublicKey dhPrivKey
withStore' c $ \db -> setRcvQueueConfirmedE2E db rq' dhSecret $ min cVer cVer'
@@ -2027,23 +1944,16 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), v, s
-- mark queue as Secured and to start sending messages to it
qUseMsg :: NonEmpty ((SMPServer, SMP.SenderId), Bool) -> Connection 'CDuplex -> m ()
-- NOTE: does not yet support the change of the primary status during the rotation
qUseMsg ((addr, _primary) :| _) (DuplexConnection _ rqs sqs) =
qUseMsg ((addr, _primary) :| _) (DuplexConnection _ _ sqs) =
case findQ addr sqs of
Just sq'@SndQueue {dbReplaceQueueId = Just replaceQId} -> do
case find ((replaceQId ==) . dbQId) sqs of
Just sq1 -> do
checkSQSwchStatus sq1 SSSendingQKEY
logServer "<--" c srv rId $ "MSG <QUSE> " <> logSecret (snd addr)
withStore' c $ \db -> setSndQueueStatus db sq' Secured
let sq'' = (sq' :: SndQueue) {status = Secured}
-- sending QTEST to the new queue only, the old one will be removed if sent successfully
void $ enqueueMessages c cData [sq''] SMP.noMsgFlags $ QTEST [addr]
sq1' <- withStore' c $ \db -> setSndSwitchStatus db sq1 $ Just SSSendingQTEST
let sqs' = updatedQs sq1' sqs
conn' = DuplexConnection cData rqs sqs'
notify . SWITCH QDSnd SPSecured $ connectionStats conn'
_ -> qError "QUSE: switching SndQueue not found in connection"
_ -> qError "QUSE: switched queue address not found in connection"
Just sq' -> do
logServer "<--" c srv rId $ "MSG <QUSE> " <> logSecret (snd addr)
withStore' c $ \db -> setSndQueueStatus db sq' Secured
let sq'' = (sq' :: SndQueue) {status = Secured}
-- sending QTEST to the new queue only, the old one will be removed if sent successfully
void $ enqueueMessages c cData [sq''] SMP.noMsgFlags $ QTEST [addr]
notify . SWITCH QDSnd SPConfirmed $ connectionStats conn
_ -> qError "QUSE: queue address not found in connection"
qError :: String -> m ()
qError = throwError . AGENT . A_QUEUE
@@ -2069,21 +1979,6 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), v, s
| internalPrevMsgHash /= receivedPrevMsgHash = MsgError MsgBadHash
| otherwise = MsgError MsgDuplicate -- this case is not possible
checkRQSwchStatus :: AgentMonad m => RcvQueue -> RcvSwitchStatus -> m ()
checkRQSwchStatus rq@RcvQueue {rcvSwchStatus} expected =
unless (rcvSwchStatus == Just expected) $ switchStatusError rq expected rcvSwchStatus
checkSQSwchStatus :: AgentMonad m => SndQueue -> SndSwitchStatus -> m ()
checkSQSwchStatus sq@SndQueue {sndSwchStatus} expected =
unless (sndSwchStatus == Just expected) $ switchStatusError sq expected sndSwchStatus
switchStatusError :: (SMPQueueRec q, AgentMonad m, Show a) => q -> a -> Maybe a -> m ()
switchStatusError q expected actual =
throwError . INTERNAL $
("unexpected switch status, queueId=" <> show (queueId q))
<> (", expected=" <> show expected)
<> (", actual=" <> show actual)
connectReplyQueues :: AgentMonad m => AgentClient -> ConnData -> ConnInfo -> NonEmpty SMPQueueInfo -> m ()
connectReplyQueues c cData@ConnData {userId, connId} ownConnInfo (qInfo :| _) = do
clientVRange <- asks $ smpClientVRange . config
@@ -2168,6 +2063,5 @@ newSndQueue userId connId (Compatible (SMPQueueInfo smpClientVersion SMPQueueAdd
dbQueueId = 0,
primary = True,
dbReplaceQueueId = Nothing,
sndSwchStatus = Nothing,
smpClientVersion
}
-1
View File
@@ -841,7 +841,6 @@ newRcvQueue c userId connId (ProtoServerWithAuth srv auth) vRange = do
dbQueueId = 0,
primary = True,
dbReplaceQueueId = Nothing,
rcvSwchStatus = Nothing,
smpClientVersion = maxVersion vRange,
clientNtfCreds = Nothing,
deleteErrors = 0
+9 -109
View File
@@ -55,12 +55,8 @@ module Simplex.Messaging.Agent.Protocol
AEntityI (..),
MsgHash,
MsgMeta (..),
RcvQueueInfo (..),
SndQueueInfo (..),
ConnectionStats (..),
SwitchPhase (..),
RcvSwitchStatus (..),
SndSwitchStatus (..),
QueueDirection (..),
SMPConfirmation (..),
AgentMsgEnvelope (..),
@@ -159,7 +155,7 @@ import qualified Data.Map as M
import Data.Maybe (isJust)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (decodeLatin1, encodeUtf8)
import Data.Text.Encoding (encodeUtf8)
import Data.Time.Clock (UTCTime)
import Data.Time.Clock.System (SystemTime)
import Data.Time.ISO8601
@@ -477,20 +473,18 @@ instance ToJSON QueueDirection where
instance FromJSON QueueDirection where
parseJSON = strParseJSON "QueueDirection"
data SwitchPhase = SPStarted | SPConfirmed | SPSecured | SPCompleted
data SwitchPhase = SPStarted | SPConfirmed | SPCompleted
deriving (Eq, Show)
instance StrEncoding SwitchPhase where
strEncode = \case
SPStarted -> "started"
SPConfirmed -> "confirmed"
SPSecured -> "secured"
SPCompleted -> "completed"
strP =
A.takeTill (== ' ') >>= \case
"started" -> pure SPStarted
"confirmed" -> pure SPConfirmed
"secured" -> pure SPSecured
"completed" -> pure SPCompleted
_ -> fail "bad SwitchPhase"
@@ -501,113 +495,19 @@ instance ToJSON SwitchPhase where
instance FromJSON SwitchPhase where
parseJSON = strParseJSON "SwitchPhase"
data RcvSwitchStatus
= RSSwitchStarted
| RSSendingQADD
| RSSendingQUSE
| RSReceivedMessage
deriving (Eq, Show)
instance StrEncoding RcvSwitchStatus where
strEncode = \case
RSSwitchStarted -> "switch_started"
RSSendingQADD -> "sending_qadd"
RSSendingQUSE -> "sending_quse"
RSReceivedMessage -> "received_message"
strP =
A.takeTill (== ' ') >>= \case
"switch_started" -> pure RSSwitchStarted
"sending_qadd" -> pure RSSendingQADD
"sending_quse" -> pure RSSendingQUSE
"received_message" -> pure RSReceivedMessage
_ -> fail "bad RcvSwitchStatus"
instance ToField RcvSwitchStatus where toField = toField . decodeLatin1 . strEncode
instance FromField RcvSwitchStatus where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8
instance ToJSON RcvSwitchStatus where
toEncoding = strToJEncoding
toJSON = strToJSON
instance FromJSON RcvSwitchStatus where
parseJSON = strParseJSON "RcvSwitchStatus"
data SndSwitchStatus
= SSSendingQKEY
| SSSendingQTEST
deriving (Eq, Show)
instance StrEncoding SndSwitchStatus where
strEncode = \case
SSSendingQKEY -> "sending_qkey"
SSSendingQTEST -> "sending_qtest"
strP =
A.takeTill (== ' ') >>= \case
"sending_qkey" -> pure SSSendingQKEY
"sending_qtest" -> pure SSSendingQTEST
_ -> fail "bad SndSwitchStatus"
instance ToField SndSwitchStatus where toField = toField . decodeLatin1 . strEncode
instance FromField SndSwitchStatus where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8
instance ToJSON SndSwitchStatus where
toEncoding = strToJEncoding
toJSON = strToJSON
instance FromJSON SndSwitchStatus where
parseJSON = strParseJSON "SndSwitchStatus"
data RcvQueueInfo = RcvQueueInfo
{ rcvServer :: SMPServer,
rcvSwitchStatus :: Maybe RcvSwitchStatus,
canAbortSwitch :: Bool
}
deriving (Eq, Show, Generic)
instance ToJSON RcvQueueInfo where toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
instance StrEncoding RcvQueueInfo where
strEncode RcvQueueInfo {rcvServer, rcvSwitchStatus, canAbortSwitch} =
"srv=" <> strEncode rcvServer
<> maybe "" (\switch -> ";switch=" <> strEncode switch) rcvSwitchStatus
<> (";can_abort_switch=" <> strEncode canAbortSwitch)
strP = do
rcvServer <- "srv=" *> strP
rcvSwitchStatus <- optional $ ";switch=" *> strP
canAbortSwitch <- ";can_abort_switch=" *> strP
pure RcvQueueInfo {rcvServer, rcvSwitchStatus, canAbortSwitch}
data SndQueueInfo = SndQueueInfo
{ sndServer :: SMPServer,
sndSwitchStatus :: Maybe SndSwitchStatus
}
deriving (Eq, Show, Generic)
instance ToJSON SndQueueInfo where toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
instance StrEncoding SndQueueInfo where
strEncode SndQueueInfo {sndServer, sndSwitchStatus} =
"srv=" <> strEncode sndServer <> maybe "" (\switch -> ";switch=" <> strEncode switch) sndSwitchStatus
strP = do
sndServer <- "srv=" *> strP
sndSwitchStatus <- optional $ ";switch=" *> strP
pure SndQueueInfo {sndServer, sndSwitchStatus}
data ConnectionStats = ConnectionStats
{ rcvQueuesInfo :: [RcvQueueInfo],
sndQueuesInfo :: [SndQueueInfo]
{ rcvServers :: [SMPServer],
sndServers :: [SMPServer]
}
deriving (Eq, Show, Generic)
instance StrEncoding ConnectionStats where
strEncode ConnectionStats {rcvQueuesInfo, sndQueuesInfo} =
"rcv=" <> strEncodeList rcvQueuesInfo <> " snd=" <> strEncodeList sndQueuesInfo
strEncode ConnectionStats {rcvServers, sndServers} =
"rcv=" <> strEncodeList rcvServers <> " snd=" <> strEncodeList sndServers
strP = do
rcvQueuesInfo <- "rcv=" *> strListP
sndQueuesInfo <- " snd=" *> strListP
pure ConnectionStats {rcvQueuesInfo, sndQueuesInfo}
rcvServers <- "rcv=" *> strListP
sndServers <- " snd=" *> strListP
pure ConnectionStats {rcvServers, sndServers}
instance ToJSON ConnectionStats where toEncoding = J.genericToEncoding J.defaultOptions
+2 -50
View File
@@ -21,7 +21,6 @@ import Data.Kind (Type)
import Data.List (find)
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as L
import Data.Maybe (isJust)
import Data.Time (UTCTime)
import Data.Type.Equality
import Simplex.Messaging.Agent.Protocol
@@ -67,13 +66,12 @@ data RcvQueue = RcvQueue
sndId :: SMP.SenderId,
-- | queue status
status :: QueueStatus,
-- | database queue ID (within connection)
-- | database queue ID (within connection), can be Nothing for old queues
dbQueueId :: Int64,
-- | True for a primary or a next primary queue of the connection (next if dbReplaceQueueId is set)
primary :: Bool,
-- | database queue ID to replace, Nothing if this queue is not replacing another, `Just Nothing` is used for replacing old queues
dbReplaceQueueId :: Maybe Int64,
rcvSwchStatus :: Maybe RcvSwitchStatus,
-- | SMP client version
smpClientVersion :: Version,
-- | credentials used in context of notifications
@@ -82,22 +80,6 @@ data RcvQueue = RcvQueue
}
deriving (Eq, Show)
rcvQueueInfo :: RcvQueue -> RcvQueueInfo
rcvQueueInfo rq@RcvQueue {server, rcvSwchStatus} =
RcvQueueInfo {rcvServer = server, rcvSwitchStatus = rcvSwchStatus, canAbortSwitch = canAbortRcvSwitch rq}
canAbortRcvSwitch :: RcvQueue -> Bool
canAbortRcvSwitch = maybe False canAbort . rcvSwchStatus
where
canAbort = \case
RSSwitchStarted -> True
RSSendingQADD -> True
-- if switch is in RSSendingQUSE, a race condition with sender deleting the original queue is possible
RSSendingQUSE -> False
-- if switch is in RSReceivedMessage status, aborting switch (deleting new queue)
-- will break the connection because the sender would have original queue deleted
RSReceivedMessage -> False
data ClientNtfCreds = ClientNtfCreds
{ -- | key pair to be used by the notification server to sign transmissions
ntfPublicKey :: NtfPublicVerifyKey,
@@ -125,22 +107,17 @@ data SndQueue = SndQueue
e2eDhSecret :: C.DhSecretX25519,
-- | queue status
status :: QueueStatus,
-- | database queue ID (within connection)
-- | database queue ID (within connection), can be Nothing for old queues
dbQueueId :: Int64,
-- | True for a primary or a next primary queue of the connection (next if dbReplaceQueueId is set)
primary :: Bool,
-- | ID of the queue this one is replacing
dbReplaceQueueId :: Maybe Int64,
sndSwchStatus :: Maybe SndSwitchStatus,
-- | SMP client version
smpClientVersion :: Version
}
deriving (Eq, Show)
sndQueueInfo :: SndQueue -> SndQueueInfo
sndQueueInfo SndQueue {server, sndSwchStatus} =
SndQueueInfo {sndServer = server, sndSwitchStatus = sndSwchStatus}
instance SMPQueue RcvQueue where
qServer RcvQueue {server} = server
{-# INLINE qServer #-}
@@ -178,20 +155,10 @@ findRQ :: (SMPServer, SMP.SenderId) -> NonEmpty RcvQueue -> Maybe RcvQueue
findRQ sAddr = find $ sameQAddress sAddr . sndAddress
{-# INLINE findRQ #-}
switchingRQ :: NonEmpty RcvQueue -> Maybe RcvQueue
switchingRQ = find $ isJust . rcvSwchStatus
{-# INLINE switchingRQ #-}
updatedQs :: SMPQueueRec q => q -> NonEmpty q -> NonEmpty q
updatedQs q = L.map $ \q' -> if dbQId q == dbQId q' then q else q'
{-# INLINE updatedQs #-}
class SMPQueue q => SMPQueueRec q where
qUserId :: q -> UserId
qConnId :: q -> ConnId
queueId :: q -> QueueId
dbQId :: q -> Int64
dbReplaceQId :: q -> Maybe Int64
instance SMPQueueRec RcvQueue where
qUserId = userId
@@ -200,10 +167,6 @@ instance SMPQueueRec RcvQueue where
{-# INLINE qConnId #-}
queueId = rcvId
{-# INLINE queueId #-}
dbQId = dbQueueId
{-# INLINE dbQId #-}
dbReplaceQId = dbReplaceQueueId
{-# INLINE dbReplaceQId #-}
instance SMPQueueRec SndQueue where
qUserId = userId
@@ -212,10 +175,6 @@ instance SMPQueueRec SndQueue where
{-# INLINE qConnId #-}
queueId = sndId
{-# INLINE queueId #-}
dbQId = dbQueueId
{-# INLINE dbQId #-}
dbReplaceQId = dbReplaceQueueId
{-# INLINE dbReplaceQId #-}
-- * Connection types
@@ -349,7 +308,6 @@ data InternalCommand
| ICAllowSecure SMP.RecipientId SMP.SndPublicVerifyKey
| ICDuplexSecure SMP.RecipientId SMP.SndPublicVerifyKey
| ICDeleteConn
| ICDeleteRcvQueue SMP.RecipientId
| ICQSecure SMP.RecipientId SMP.SndPublicVerifyKey
| ICQDelete SMP.RecipientId
@@ -359,7 +317,6 @@ data InternalCommandTag
| ICAllowSecure_
| ICDuplexSecure_
| ICDeleteConn_
| ICDeleteRcvQueue_
| ICQSecure_
| ICQDelete_
deriving (Show)
@@ -371,7 +328,6 @@ instance StrEncoding InternalCommand where
ICAllowSecure rId sndKey -> strEncode (ICAllowSecure_, rId, sndKey)
ICDuplexSecure rId sndKey -> strEncode (ICDuplexSecure_, rId, sndKey)
ICDeleteConn -> strEncode ICDeleteConn_
ICDeleteRcvQueue rId -> strEncode (ICDeleteRcvQueue_, rId)
ICQSecure rId senderKey -> strEncode (ICQSecure_, rId, senderKey)
ICQDelete rId -> strEncode (ICQDelete_, rId)
strP =
@@ -381,7 +337,6 @@ instance StrEncoding InternalCommand where
ICAllowSecure_ -> ICAllowSecure <$> _strP <*> _strP
ICDuplexSecure_ -> ICDuplexSecure <$> _strP <*> _strP
ICDeleteConn_ -> pure ICDeleteConn
ICDeleteRcvQueue_ -> ICDeleteRcvQueue <$> _strP
ICQSecure_ -> ICQSecure <$> _strP <*> _strP
ICQDelete_ -> ICQDelete <$> _strP
@@ -392,7 +347,6 @@ instance StrEncoding InternalCommandTag where
ICAllowSecure_ -> "ALLOW_SECURE"
ICDuplexSecure_ -> "DUPLEX_SECURE"
ICDeleteConn_ -> "DELETE_CONN"
ICDeleteRcvQueue_ -> "DELETE_RCV_QUEUE"
ICQSecure_ -> "QSECURE"
ICQDelete_ -> "QDELETE"
strP =
@@ -402,7 +356,6 @@ instance StrEncoding InternalCommandTag where
"ALLOW_SECURE" -> pure ICAllowSecure_
"DUPLEX_SECURE" -> pure ICDuplexSecure_
"DELETE_CONN" -> pure ICDeleteConn_
"DELETE_RCV_QUEUE" -> pure ICDeleteRcvQueue_
"QSECURE" -> pure ICQSecure_
"QDELETE" -> pure ICQDelete_
_ -> fail "bad InternalCommandTag"
@@ -419,7 +372,6 @@ internalCmdTag = \case
ICAllowSecure {} -> ICAllowSecure_
ICDuplexSecure {} -> ICDuplexSecure_
ICDeleteConn -> ICDeleteConn_
ICDeleteRcvQueue {} -> ICDeleteRcvQueue_
ICQSecure {} -> ICQSecure_
ICQDelete _ -> ICQDelete_
+30 -95
View File
@@ -54,19 +54,14 @@ module Simplex.Messaging.Agent.Store.SQLite
setConnDeleted,
getDeletedConnIds,
getRcvConn,
getRcvQueueById,
getSndQueueById,
deleteConn,
upgradeRcvConnToDuplex,
upgradeSndConnToDuplex,
addConnRcvQueue,
addConnSndQueue,
setRcvQueueStatus,
setRcvSwitchStatus,
setRcvQueueDeleted,
setRcvQueueConfirmedE2E,
setSndQueueStatus,
setSndSwitchStatus,
setRcvQueuePrimary,
setSndQueuePrimary,
deleteConnRcvQueue,
@@ -74,7 +69,6 @@ module Simplex.Messaging.Agent.Store.SQLite
deleteConnSndQueue,
getPrimaryRcvQueue,
getRcvQueue,
getDeletedRcvQueue,
setRcvQueueNtfCreds,
-- Confirmations
createConfirmation,
@@ -208,10 +202,11 @@ import Data.Bifunctor (second)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Base64.URL as U
import Data.Char (toLower)
import Data.Function (on)
import Data.Functor (($>))
import Data.IORef
import Data.Int (Int64)
import Data.List (foldl', intercalate, sortBy)
import Data.List (foldl', groupBy, intercalate, sortBy)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as L
import qualified Data.Map.Strict as M
@@ -249,7 +244,7 @@ import Simplex.Messaging.Parsers (blobFieldParser, dropPrefix, fromTextField_, s
import Simplex.Messaging.Protocol
import qualified Simplex.Messaging.Protocol as SMP
import Simplex.Messaging.Transport.Client (TransportHost)
import Simplex.Messaging.Util (bshow, diffToMilliseconds, eitherToMaybe, groupOn, ($>>=), (<$$>))
import Simplex.Messaging.Util (bshow, diffToMilliseconds, eitherToMaybe, ($>>=), (<$$>))
import Simplex.Messaging.Version
import System.Directory (copyFile, createDirectoryIfMissing, doesFileExist)
import System.Exit (exitFailure)
@@ -566,7 +561,7 @@ getRcvConn :: DB.Connection -> SMPServer -> SMP.RecipientId -> IO (Either StoreE
getRcvConn db ProtocolServer {host, port} rcvId = runExceptT $ do
rq@RcvQueue {connId} <-
ExceptT . firstRow toRcvQueue SEConnNotFound $
DB.query db (rcvQueueQuery <> " WHERE q.host = ? AND q.port = ? AND q.rcv_id = ? AND q.deleted = 0") (host, port, rcvId)
DB.query db (rcvQueueQuery <> " WHERE q.host = ? AND q.port = ? AND q.rcv_id = ?") (host, port, rcvId)
(rq,) <$> ExceptT (getConn db connId)
deleteConn :: DB.Connection -> ConnId -> IO ()
@@ -625,29 +620,6 @@ setRcvQueueStatus db RcvQueue {rcvId, server = ProtocolServer {host, port}} stat
|]
[":status" := status, ":host" := host, ":port" := port, ":rcv_id" := rcvId]
setRcvSwitchStatus :: DB.Connection -> RcvQueue -> Maybe RcvSwitchStatus -> IO RcvQueue
setRcvSwitchStatus db rq@RcvQueue {rcvId, server = ProtocolServer {host, port}} rcvSwchStatus = do
DB.execute
db
[sql|
UPDATE rcv_queues
SET switch_status = ?
WHERE host = ? AND port = ? AND rcv_id = ?
|]
(rcvSwchStatus, host, port, rcvId)
pure rq {rcvSwchStatus}
setRcvQueueDeleted :: DB.Connection -> RcvQueue -> IO ()
setRcvQueueDeleted db RcvQueue {rcvId, server = ProtocolServer {host, port}} = do
DB.execute
db
[sql|
UPDATE rcv_queues
SET deleted = 1
WHERE host = ? AND port = ? AND rcv_id = ?
|]
(host, port, rcvId)
setRcvQueueConfirmedE2E :: DB.Connection -> RcvQueue -> C.DhSecretX25519 -> Version -> IO ()
setRcvQueueConfirmedE2E db RcvQueue {rcvId, server = ProtocolServer {host, port}} e2eDhSecret smpClientVersion =
DB.executeNamed
@@ -679,18 +651,6 @@ setSndQueueStatus db SndQueue {sndId, server = ProtocolServer {host, port}} stat
|]
[":status" := status, ":host" := host, ":port" := port, ":snd_id" := sndId]
setSndSwitchStatus :: DB.Connection -> SndQueue -> Maybe SndSwitchStatus -> IO SndQueue
setSndSwitchStatus db sq@SndQueue {sndId, server = ProtocolServer {host, port}} sndSwchStatus = do
DB.execute
db
[sql|
UPDATE snd_queues
SET switch_status = ?
WHERE host = ? AND port = ? AND snd_id = ?
|]
(sndSwchStatus, host, port, sndId)
pure sq {sndSwchStatus}
setRcvQueuePrimary :: DB.Connection -> ConnId -> RcvQueue -> IO ()
setRcvQueuePrimary db connId RcvQueue {dbQueueId} = do
DB.execute db "UPDATE rcv_queues SET rcv_primary = ? WHERE conn_id = ?" (False, connId)
@@ -727,12 +687,7 @@ getPrimaryRcvQueue db connId =
getRcvQueue :: DB.Connection -> ConnId -> SMPServer -> SMP.RecipientId -> IO (Either StoreError RcvQueue)
getRcvQueue db connId (SMPServer host port _) rcvId =
firstRow toRcvQueue SEConnNotFound $
DB.query db (rcvQueueQuery <> "WHERE q.conn_id = ? AND q.host = ? AND q.port = ? AND q.rcv_id = ? AND q.deleted = 0") (connId, host, port, rcvId)
getDeletedRcvQueue :: DB.Connection -> ConnId -> SMPServer -> SMP.RecipientId -> IO (Either StoreError RcvQueue)
getDeletedRcvQueue db connId (SMPServer host port _) rcvId =
firstRow toRcvQueue SEConnNotFound $
DB.query db (rcvQueueQuery <> "WHERE q.conn_id = ? AND q.host = ? AND q.port = ? AND q.rcv_id = ? AND q.deleted = 1") (connId, host, port, rcvId)
DB.query db (rcvQueueQuery <> "WHERE q.conn_id = ? AND q.host = ? AND q.port = ? AND q.rcv_id = ?") (connId, host, port, rcvId)
setRcvQueueNtfCreds :: DB.Connection -> ConnId -> Maybe ClientNtfCreds -> IO ()
setRcvQueueNtfCreds db connId clientNtfCreds =
@@ -963,7 +918,7 @@ setMsgUserAck db connId agentMsgId = runExceptT $ do
(dbRcvId, srvMsgId) <-
ExceptT . firstRow id SEMsgNotFound $
DB.query db "SELECT rcv_queue_id, broker_id FROM rcv_messages WHERE conn_id = ? AND internal_id = ?" (connId, agentMsgId)
rq <- ExceptT $ getRcvQueueById db connId dbRcvId
rq <- ExceptT $ getRcvQueueById_ db connId dbRcvId
pure (rq, srvMsgId)
getLastMsg :: DB.Connection -> ConnId -> SMP.MsgId -> IO (Maybe RcvMsg)
@@ -1091,9 +1046,7 @@ insertedRowId db = fromOnly . head <$> DB.query_ db "SELECT last_insert_rowid()"
getPendingCommands :: DB.Connection -> ConnId -> IO [(Maybe SMPServer, [AsyncCmdId])]
getPendingCommands db connId = do
-- `groupOn` is used instead of `groupAllOn` to avoid extra sorting by `server + cmdId`, as the query already sorts by them.
-- TODO review whether this can break if, e.g., the server has another key hash.
map (\ids -> (fst $ head ids, map snd ids)) . groupOn fst . map srvCmdId
map (\ids -> (fst $ head ids, map snd ids)) . groupBy ((==) `on` fst) . map srvCmdId
<$> DB.query
db
[sql|
@@ -1422,7 +1375,7 @@ getNtfRcvQueue db SMPQueueNtf {smpServer = (SMPServer host port _), notifierId}
[sql|
SELECT conn_id, rcv_ntf_dh_secret
FROM rcv_queues
WHERE host = ? AND port = ? AND ntf_id = ? AND deleted = 0
WHERE host = ? AND port = ? AND ntf_id = ?
|]
(host, port, notifierId)
where
@@ -1658,7 +1611,7 @@ getDeletedConnIds db = map fromOnly <$> DB.query db "SELECT conn_id FROM connect
getRcvQueuesByConnId_ :: DB.Connection -> ConnId -> IO (Maybe (NonEmpty RcvQueue))
getRcvQueuesByConnId_ db connId =
L.nonEmpty . sortBy primaryFirst . map toRcvQueue
<$> DB.query db (rcvQueueQuery <> "WHERE q.conn_id = ? AND q.deleted = 0") (Only connId)
<$> DB.query db (rcvQueueQuery <> "WHERE q.conn_id = ?") (Only connId)
where
primaryFirst RcvQueue {primary = p, dbReplaceQueueId = i} RcvQueue {primary = p', dbReplaceQueueId = i'} =
-- the current primary queue is ordered first, the next primary - second
@@ -1669,7 +1622,7 @@ rcvQueueQuery =
[sql|
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret,
q.e2e_priv_key, q.e2e_dh_secret, q.snd_id, q.status,
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.switch_status, q.smp_client_version, q.delete_errors,
q.rcv_queue_id, q.rcv_primary, q.replace_rcv_queue_id, q.smp_client_version, q.delete_errors,
q.ntf_public_key, q.ntf_private_key, q.ntf_id, q.rcv_ntf_dh_secret
FROM rcv_queues q
JOIN servers s ON q.host = s.host AND q.port = s.port
@@ -1678,62 +1631,44 @@ rcvQueueQuery =
toRcvQueue ::
(UserId, C.KeyHash, ConnId, NonEmpty TransportHost, ServiceName, SMP.RecipientId, SMP.RcvPrivateSignKey, SMP.RcvDhSecret, C.PrivateKeyX25519, Maybe C.DhSecretX25519, SMP.SenderId, QueueStatus)
:. (Int64, Bool, Maybe Int64, Maybe RcvSwitchStatus, Maybe Version, Int)
:. (Int64, Bool, Maybe Int64, Maybe Version, Int)
:. (Maybe SMP.NtfPublicVerifyKey, Maybe SMP.NtfPrivateSignKey, Maybe SMP.NotifierId, Maybe RcvNtfDhSecret) ->
RcvQueue
toRcvQueue ((userId, keyHash, connId, host, port, rcvId, rcvPrivateKey, rcvDhSecret, e2ePrivKey, e2eDhSecret, sndId, status) :. (dbQueueId, primary, dbReplaceQueueId, rcvSwchStatus, smpClientVersion_, deleteErrors) :. (ntfPublicKey_, ntfPrivateKey_, notifierId_, rcvNtfDhSecret_)) =
toRcvQueue ((userId, keyHash, connId, host, port, rcvId, rcvPrivateKey, rcvDhSecret, e2ePrivKey, e2eDhSecret, sndId, status) :. (dbQueueId, primary, dbReplaceQueueId, smpClientVersion_, deleteErrors) :. (ntfPublicKey_, ntfPrivateKey_, notifierId_, rcvNtfDhSecret_)) =
let server = SMPServer host port keyHash
smpClientVersion = fromMaybe 1 smpClientVersion_
clientNtfCreds = case (ntfPublicKey_, ntfPrivateKey_, notifierId_, rcvNtfDhSecret_) of
(Just ntfPublicKey, Just ntfPrivateKey, Just notifierId, Just rcvNtfDhSecret) -> Just $ ClientNtfCreds {ntfPublicKey, ntfPrivateKey, notifierId, rcvNtfDhSecret}
_ -> Nothing
in RcvQueue {userId, connId, server, rcvId, rcvPrivateKey, rcvDhSecret, e2ePrivKey, e2eDhSecret, sndId, status, dbQueueId, primary, dbReplaceQueueId, rcvSwchStatus, smpClientVersion, clientNtfCreds, deleteErrors}
in RcvQueue {userId, connId, server, rcvId, rcvPrivateKey, rcvDhSecret, e2ePrivKey, e2eDhSecret, sndId, status, dbQueueId, primary, dbReplaceQueueId, smpClientVersion, clientNtfCreds, deleteErrors}
getRcvQueueById :: DB.Connection -> ConnId -> Int64 -> IO (Either StoreError RcvQueue)
getRcvQueueById db connId dbRcvId =
getRcvQueueById_ :: DB.Connection -> ConnId -> Int64 -> IO (Either StoreError RcvQueue)
getRcvQueueById_ db connId dbRcvId =
firstRow toRcvQueue SEConnNotFound $
DB.query db (rcvQueueQuery <> " WHERE q.conn_id = ? AND q.rcv_queue_id = ? AND q.deleted = 0") (connId, dbRcvId)
DB.query db (rcvQueueQuery <> " WHERE q.conn_id = ? AND q.rcv_queue_id = ?") (connId, dbRcvId)
-- | returns all connection queues, the first queue is the primary one
getSndQueuesByConnId_ :: DB.Connection -> ConnId -> IO (Maybe (NonEmpty SndQueue))
getSndQueuesByConnId_ dbConn connId =
L.nonEmpty . sortBy primaryFirst . map toSndQueue
<$> DB.query dbConn (sndQueueQuery <> "WHERE q.conn_id = ?") (Only connId)
L.nonEmpty . sortBy primaryFirst . map sndQueue
<$> DB.query
dbConn
[sql|
SELECT c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.host, q.port, q.snd_id, q.snd_public_key, q.snd_private_key, q.e2e_pub_key, q.e2e_dh_secret, q.status, q.snd_queue_id, q.snd_primary, q.replace_snd_queue_id, q.smp_client_version
FROM snd_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.conn_id = ?;
|]
(Only connId)
where
sndQueue ((userId, keyHash, host, port, sndId, sndPublicKey, sndPrivateKey, e2ePubKey, e2eDhSecret, status) :. (dbQueueId, primary, dbReplaceQueueId, smpClientVersion)) =
let server = SMPServer host port keyHash
in SndQueue {userId, connId, server, sndId, sndPublicKey, sndPrivateKey, e2ePubKey, e2eDhSecret, status, dbQueueId, primary, dbReplaceQueueId, smpClientVersion}
primaryFirst SndQueue {primary = p, dbReplaceQueueId = i} SndQueue {primary = p', dbReplaceQueueId = i'} =
-- the current primary queue is ordered first, the next primary - second
compare (Down p) (Down p') <> compare i i'
sndQueueQuery :: Query
sndQueueQuery =
[sql|
SELECT
c.user_id, COALESCE(q.server_key_hash, s.key_hash), q.conn_id, q.host, q.port, q.snd_id,
q.snd_public_key, q.snd_private_key, q.e2e_pub_key, q.e2e_dh_secret, q.status,
q.snd_queue_id, q.snd_primary, q.replace_snd_queue_id, q.switch_status, q.smp_client_version
FROM snd_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
|]
toSndQueue ::
(UserId, C.KeyHash, ConnId, NonEmpty TransportHost, ServiceName, SenderId)
:. (Maybe C.APublicVerifyKey, SndPrivateSignKey, Maybe C.PublicKeyX25519, C.DhSecretX25519, QueueStatus)
:. (Int64, Bool, Maybe Int64, Maybe SndSwitchStatus, Version) ->
SndQueue
toSndQueue
( (userId, keyHash, connId, host, port, sndId)
:. (sndPublicKey, sndPrivateKey, e2ePubKey, e2eDhSecret, status)
:. (dbQueueId, primary, dbReplaceQueueId, sndSwchStatus, smpClientVersion)
) =
let server = SMPServer host port keyHash
in SndQueue {userId, connId, server, sndId, sndPublicKey, sndPrivateKey, e2ePubKey, e2eDhSecret, status, dbQueueId, primary, dbReplaceQueueId, sndSwchStatus, smpClientVersion}
getSndQueueById :: DB.Connection -> ConnId -> Int64 -> IO (Either StoreError SndQueue)
getSndQueueById db connId dbSndId =
firstRow toSndQueue SEConnNotFound $
DB.query db (sndQueueQuery <> " WHERE q.conn_id = ? AND q.snd_queue_id = ?") (connId, dbSndId)
-- * updateRcvIds helpers
retrieveLastIdsAndHashRcv_ :: DB.Connection -> ConnId -> IO (InternalId, InternalRcvId, PrevExternalSndId, PrevRcvMsgHash)
@@ -60,7 +60,6 @@ import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230320_retry_state
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230401_snd_files
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230510_files_pending_replicas_indexes
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230516_encrypted_rcv_message_hashes
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230531_switch_status
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
import Simplex.Messaging.Transport.Client (TransportHost)
@@ -87,8 +86,7 @@ schemaMigrations =
("m20230320_retry_state", m20230320_retry_state, Just down_m20230320_retry_state),
("m20230401_snd_files", m20230401_snd_files, Just down_m20230401_snd_files),
("m20230510_files_pending_replicas_indexes", m20230510_files_pending_replicas_indexes, Just down_m20230510_files_pending_replicas_indexes),
("m20230516_encrypted_rcv_message_hashes", m20230516_encrypted_rcv_message_hashes, Just down_m20230516_encrypted_rcv_message_hashes),
("m20230531_switch_status", m20230531_switch_status, Just down_m20230531_switch_status)
("m20230516_encrypted_rcv_message_hashes", m20230516_encrypted_rcv_message_hashes, Just down_m20230516_encrypted_rcv_message_hashes)
]
-- | The list of migrations in ascending order by date
@@ -1,22 +0,0 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Messaging.Agent.Store.SQLite.Migrations.M20230531_switch_status where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20230531_switch_status :: Query
m20230531_switch_status =
[sql|
ALTER TABLE rcv_queues ADD COLUMN switch_status TEXT;
ALTER TABLE rcv_queues ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0;
ALTER TABLE snd_queues ADD COLUMN switch_status TEXT;
|]
down_m20230531_switch_status :: Query
down_m20230531_switch_status =
[sql|
ALTER TABLE snd_queues DROP COLUMN switch_status;
ALTER TABLE rcv_queues DROP COLUMN deleted;
ALTER TABLE rcv_queues DROP COLUMN switch_status;
|]
@@ -50,8 +50,6 @@ CREATE TABLE rcv_queues(
replace_rcv_queue_id INTEGER NULL,
delete_errors INTEGER DEFAULT 0 CHECK(delete_errors NOT NULL),
server_key_hash BLOB,
switch_status TEXT,
deleted INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY(host, port, rcv_id),
FOREIGN KEY(host, port) REFERENCES servers
ON DELETE RESTRICT ON UPDATE CASCADE,
@@ -73,7 +71,6 @@ CREATE TABLE snd_queues(
snd_primary INTEGER CHECK(snd_primary NOT NULL),
replace_snd_queue_id INTEGER NULL,
server_key_hash BLOB,
switch_status TEXT,
PRIMARY KEY(host, port, snd_id),
FOREIGN KEY(host, port) REFERENCES servers
ON DELETE RESTRICT ON UPDATE CASCADE
+6 -20
View File
@@ -41,7 +41,6 @@ module Simplex.Messaging.Client
subscribeSMPQueues,
getSMPMessage,
subscribeSMPQueueNotifications,
subscribeSMPQueuesNtfs,
secureSMPQueue,
enableSMPQueueNotifications,
disableSMPQueueNotifications,
@@ -219,10 +218,8 @@ data ProtocolClientConfig = ProtocolClientConfig
defaultTransport :: (ServiceName, ATransport),
-- | network configuration
networkConfig :: NetworkConfig,
-- | client-server protocol version range
serverVRange :: VersionRange,
-- | delay between sending batches of commands (microseconds)
batchDelay :: Maybe Int
-- | SMP client-server protocol version range
smpServerVRange :: VersionRange
}
-- | Default protocol client configuration.
@@ -232,8 +229,7 @@ defaultClientConfig =
{ qSize = 64,
defaultTransport = ("443", transport @TLS),
networkConfig = defaultNetworkConfig,
serverVRange = supportedSMPServerVRange,
batchDelay = Nothing
smpServerVRange = supportedSMPServerVRange
}
data Request err msg = Request
@@ -279,7 +275,7 @@ type TransportSession msg = (UserId, ProtoServer msg, Maybe EntityId)
-- A single queue can be used for multiple 'SMPClient' instances,
-- as 'SMPServerTransmission' includes server information.
getProtocolClient :: forall err msg. Protocol err msg => TransportSession msg -> ProtocolClientConfig -> Maybe (TBQueue (ServerTransmission msg)) -> (ProtocolClient err msg -> IO ()) -> IO (Either (ProtocolClientError err) (ProtocolClient err msg))
getProtocolClient transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize, networkConfig, serverVRange, batchDelay} msgQ disconnected = do
getProtocolClient transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize, networkConfig, smpServerVRange} msgQ disconnected = do
case chooseTransportHost networkConfig (host srv) of
Right useHost ->
(atomically (mkProtocolClient useHost) >>= runClient useTransport useHost)
@@ -332,7 +328,7 @@ getProtocolClient transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize,
client :: forall c. Transport c => TProxy c -> PClient err msg -> TMVar (Either (ProtocolClientError err) (ProtocolClient err msg)) -> c -> IO ()
client _ c cVar h =
runExceptT (protocolClientHandshake @err @msg h (keyHash srv) serverVRange) >>= \case
runExceptT (protocolClientHandshake @err @msg h (keyHash srv) smpServerVRange) >>= \case
Left e -> atomically . putTMVar cVar . Left $ PCETransportError e
Right th@THandle {sessionId, thVersion} -> do
sessionTs <- getCurrentTime
@@ -344,7 +340,7 @@ getProtocolClient transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize,
`finally` disconnected c'
send :: Transport c => ProtocolClient err msg -> THandle c -> IO ()
send ProtocolClient {client_ = PClient {sndQ}} h = forever $ atomically (readTBQueue sndQ) >>= tPut h batchDelay
send ProtocolClient {client_ = PClient {sndQ}} h = forever $ atomically (readTBQueue sndQ) >>= tPut h
receive :: Transport c => ProtocolClient err msg -> THandle c -> IO ()
receive ProtocolClient {client_ = PClient {rcvQ}} h = forever $ tGet h >>= atomically . writeTBQueue rcvQ
@@ -491,16 +487,6 @@ getSMPMessage c rpKey rId =
subscribeSMPQueueNotifications :: SMPClient -> NtfPrivateSignKey -> NotifierId -> ExceptT SMPClientError IO ()
subscribeSMPQueueNotifications = okSMPCommand NSUB
-- | Subscribe to multiple SMP queues notifications batching commands if supported.
subscribeSMPQueuesNtfs :: SMPClient -> NonEmpty (NtfPrivateSignKey, NotifierId) -> IO (NonEmpty (Either SMPClientError ()))
subscribeSMPQueuesNtfs c qs = sendProtocolCommands c cs >>= mapM response
where
cs = L.map (\(npKey, nId) -> (Just npKey, nId, Cmd SNotifier NSUB)) qs
response r = pure $ case r of
Right OK -> Right ()
Right r' -> Left . PCEUnexpectedResponse $ bshow r'
Left e -> Left e
-- | Secure the SMP queue by adding a sender public key.
--
-- https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#secure-queue-command
+25 -73
View File
@@ -5,7 +5,6 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
@@ -17,31 +16,24 @@ import Control.Logger.Simple
import Control.Monad.Except
import Control.Monad.IO.Unlift
import Control.Monad.Trans.Except
import Data.Bifunctor (first, bimap)
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Either (partitionEithers)
import Data.List (partition)
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as L
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Maybe (listToMaybe)
import Data.Set (Set)
import Data.Text.Encoding
import Data.Tuple (swap)
import Numeric.Natural
import Simplex.Messaging.Agent.RetryInterval
import Simplex.Messaging.Client
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol (BrokerMsg, ProtocolServer (..), QueueId, SMPServer, NtfPrivateSignKey, NotifierId, RcvPrivateSignKey, RecipientId)
import Simplex.Messaging.Protocol (BrokerMsg, ProtocolServer (..), QueueId, SMPServer)
import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Transport
import Simplex.Messaging.Util (catchAll_, tryE, ($>>=))
import Simplex.Messaging.Util (catchAll_, tryE, unlessM, ($>>=))
import System.Timeout (timeout)
import UnliftIO (async)
import UnliftIO (async, forConcurrently_)
import UnliftIO.Exception (Exception)
import qualified UnliftIO.Exception as E
import UnliftIO.STM
@@ -52,8 +44,8 @@ data SMPClientAgentEvent
= CAConnected SMPServer
| CADisconnected SMPServer (Set SMPSub)
| CAReconnected SMPServer
| CAResubscribed SMPServer (NonEmpty SMPSub)
| CASubError SMPServer (NonEmpty (SMPSub, SMPClientError))
| CAResubscribed SMPServer SMPSub
| CASubError SMPServer SMPSub SMPClientError
data SMPSubParty = SPRecipient | SPNotifier
deriving (Eq, Ord, Show)
@@ -192,9 +184,9 @@ getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, msgQ} srv =
_ -> TM.insert srv sVar ps
serverDown :: Map SMPSub C.APrivateSignKey -> IO ()
serverDown ss = unless (M.null ss) $ do
serverDown ss = unless (M.null ss) . void . runExceptT $ do
notify . CADisconnected srv $ M.keysSet ss
void $ runExceptT reconnectServer
reconnectServer
reconnectServer :: ExceptT SMPClientError IO ()
reconnectServer = do
@@ -209,38 +201,27 @@ getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, msgQ} srv =
reconnectClient :: ExceptT SMPClientError IO ()
reconnectClient = do
withSMP ca srv $ \smp -> do
liftIO $ notify $ CAReconnected srv
cs_ <- atomically $ mapM readTVar =<< TM.lookup srv (pendingSrvSubs ca)
forM_ cs_ $ \cs -> do
subs' <- filterM (fmap not . atomically . hasSub (srvSubs ca) srv . fst) $ M.assocs cs
let (nSubs, rSubs) = partition (isNotifier . fst . fst) subs'
subscribe_ smp SPNotifier nSubs
subscribe_ smp SPRecipient rSubs
notify $ CAReconnected srv
cs <- atomically $ mapM readTVar =<< TM.lookup srv (pendingSrvSubs ca)
forConcurrently_ (maybe [] M.assocs cs) $ \sub@(s, _) ->
unlessM (atomically $ hasSub (srvSubs ca) srv s) $
subscribe_ smp sub `catchE` handleError s
where
isNotifier = \case
SPNotifier -> True
SPRecipient -> False
subscribe_ :: SMPClient -> (SMPSub, C.APrivateSignKey) -> ExceptT SMPClientError IO ()
subscribe_ smp sub@(s, _) = do
smpSubscribe smp sub
atomically $ addSubscription ca srv sub
notify $ CAResubscribed srv s
subscribe_ :: SMPClient -> SMPSubParty -> [(SMPSub, C.APrivateSignKey)] -> ExceptT SMPClientError IO ()
subscribe_ smp party subs =
case L.nonEmpty subs of
Just subs' -> do
let subs'' :: (NonEmpty (QueueId, C.APrivateSignKey)) = L.map (first snd) subs'
rs <- liftIO $ smpSubscribeQueues party ca smp srv subs''
let rs' :: (NonEmpty ((SMPSub, C.APrivateSignKey), Either SMPClientError ())) =
L.zipWith (first . const) subs' rs
rs'' :: [Either (SMPSub, SMPClientError) (SMPSub, C.APrivateSignKey)] =
map (\(sub, r) -> bimap (fst sub,) (const sub) r) $ L.toList rs'
(errs, oks) = partitionEithers rs''
(tempErrs, finalErrs) = partition (temporaryClientError . snd) errs
mapM_ (atomically . addSubscription ca srv) oks
mapM_ (liftIO . notify . CAResubscribed srv) $ L.nonEmpty $ map fst oks
mapM_ (atomically . removePendingSubscription ca srv . fst) finalErrs
mapM_ (liftIO . notify . CASubError srv) $ L.nonEmpty finalErrs
mapM_ (throwE . snd) $ listToMaybe tempErrs
Nothing -> pure ()
handleError :: SMPSub -> SMPClientError -> ExceptT SMPClientError IO ()
handleError s = \case
e@PCEResponseTimeout -> throwE e
e@PCENetworkError -> throwE e
e -> do
notify $ CASubError srv s e
atomically $ removePendingSubscription ca srv s
notify :: SMPClientAgentEvent -> IO ()
notify :: SMPClientAgentEvent -> ExceptT SMPClientError IO ()
notify evt = atomically $ writeTBQueue (agentQ ca) evt
closeSMPClientAgent :: MonadUnliftIO m => SMPClientAgent -> m ()
@@ -282,35 +263,6 @@ subscribeQueue ca srv sub = do
removePendingSubscription ca srv $ fst sub
throwE e
subscribeQueuesSMP :: SMPClientAgent -> SMPServer -> NonEmpty (RecipientId, RcvPrivateSignKey) -> IO (NonEmpty (RecipientId, Either SMPClientError ()))
subscribeQueuesSMP = subscribeQueues_ SPRecipient
subscribeQueuesNtfs :: SMPClientAgent -> SMPServer -> NonEmpty (NotifierId, NtfPrivateSignKey) -> IO (NonEmpty (NotifierId, Either SMPClientError ()))
subscribeQueuesNtfs = subscribeQueues_ SPNotifier
subscribeQueues_ :: SMPSubParty -> SMPClientAgent -> SMPServer -> NonEmpty (QueueId, C.APrivateSignKey) -> IO (NonEmpty (QueueId, Either SMPClientError ()))
subscribeQueues_ party ca srv subs = do
atomically $ forM_ subs $ addPendingSubscription ca srv . first (party,)
runExceptT (getSMPServerClient' ca srv) >>= \case
Left e -> pure $ L.map ((,Left e) . fst) subs
Right smp -> smpSubscribeQueues party ca smp srv subs
smpSubscribeQueues :: SMPSubParty -> SMPClientAgent -> SMPClient -> SMPServer -> NonEmpty (QueueId, C.APrivateSignKey) -> IO (NonEmpty (QueueId, Either SMPClientError ()))
smpSubscribeQueues party ca smp srv subs = do
rs <- L.zip subs <$> subscribe smp (L.map swap subs)
atomically $ forM rs $ \(sub, r) -> (fst sub,) <$> case r of
Right () -> do
addSubscription ca srv $ first (party,) sub
pure $ Right ()
Left e -> do
when (e /= PCENetworkError && e /= PCEResponseTimeout) $
removePendingSubscription ca srv $ (party,) $ fst sub
pure $ Left e
where
subscribe = case party of
SPRecipient -> subscribeSMPQueues
SPNotifier -> subscribeSMPQueuesNtfs
showServer :: SMPServer -> ByteString
showServer ProtocolServer {host, port} =
strEncode host <> B.pack (if null port then "" else ':' : port)
@@ -429,7 +429,7 @@ data NtfSubStatus
NSAuth
| -- | SMP error other than AUTH
NSErr ByteString
deriving (Eq, Ord, Show)
deriving (Eq, Show)
ntfShouldSubscribe :: NtfSubStatus -> Bool
ntfShouldSubscribe = \case
+49 -83
View File
@@ -16,17 +16,12 @@ import Control.Concurrent.STM (stateTVar)
import Control.Logger.Simple
import Control.Monad.Except
import Control.Monad.Reader
import Data.Bifunctor (second)
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Functor (($>))
import Data.Int (Int64)
import Data.List (intercalate, sort)
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as L
import Data.List (intercalate)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Maybe (catMaybes)
import qualified Data.Text as T
import Data.Text.Encoding (decodeLatin1)
import Data.Time.Clock (UTCTime (..), diffTimeToPicoseconds, getCurrentTime)
@@ -56,7 +51,7 @@ import System.Exit (exitFailure)
import System.IO (BufferMode (..), hPutStrLn, hSetBuffering)
import System.Mem.Weak (deRefWeak)
import UnliftIO (IOMode (..), async, uninterruptibleCancel, withFile)
import UnliftIO.Concurrent (forkIO, killThread, mkWeakThreadId)
import UnliftIO.Concurrent (forkIO, killThread, mkWeakThreadId, threadDelay)
import UnliftIO.Directory (doesFileExist, renameFile)
import UnliftIO.Exception
import UnliftIO.STM
@@ -146,32 +141,24 @@ ntfServer cfg@NtfServerConfig {transports, logTLSErrors} started = do
resubscribe :: NtfSubscriber -> Map NtfSubscriptionId NtfSubData -> M ()
resubscribe NtfSubscriber {newSubQ} subs = do
subs' <- atomically $ filterM (fmap ntfShouldSubscribe . readTVar . subStatus) $ M.elems subs
atomically . writeTBQueue newSubQ $ map NtfSub subs'
liftIO $ logInfo $ "SMP resubscriptions queued (" <> tshow (length subs') <> " subscriptions)"
d <- asks $ resubscribeDelay . config
forM_ subs $ \sub@NtfSubData {} ->
whenM (ntfShouldSubscribe <$> readTVarIO (subStatus sub)) $ do
atomically $ writeTBQueue newSubQ $ NtfSub sub
threadDelay d
liftIO $ logInfo "SMP connections resubscribed"
ntfSubscriber :: NtfSubscriber -> M ()
ntfSubscriber NtfSubscriber {smpSubscribers, newSubQ, smpAgent = ca@SMPClientAgent {msgQ, agentQ}} = do
raceAny_ [subscribe, receiveSMP, receiveAgent]
where
subscribe :: M ()
subscribe = forever $ do
subs <- atomically (readTBQueue newSubQ)
let ss = L.groupAllWith server subs
forM_ ss $ \serverSubs -> do
let srv = server $ L.head serverSubs
batches = toChunks 900 $ L.toList serverSubs
SMPSubscriber {newSubQ = subscriberSubQ} <- getSMPSubscriber srv
mapM_ (atomically . writeTQueue subscriberSubQ) batches
toChunks :: Int -> [a] -> [NonEmpty a]
toChunks _ [] = []
toChunks n xs =
let (ys, xs') = splitAt n xs
in maybe id (:) (L.nonEmpty ys) (toChunks n xs')
server :: NtfEntityRec 'Subscription -> SMPServer
server (NtfSub sub) = ntfSubServer sub
subscribe =
forever $
atomically (readTBQueue newSubQ) >>= \case
sub@(NtfSub NtfSubData {smpQueue = SMPQueueNtf {smpServer}}) -> do
SMPSubscriber {newSubQ = subscriberSubQ} <- getSMPSubscriber smpServer
atomically $ writeTQueue subscriberSubQ sub
getSMPSubscriber :: SMPServer -> M SMPSubscriber
getSMPSubscriber smpServer =
@@ -186,36 +173,21 @@ ntfSubscriber NtfSubscriber {smpSubscribers, newSubQ, smpAgent = ca@SMPClientAge
runSMPSubscriber :: SMPSubscriber -> M ()
runSMPSubscriber SMPSubscriber {newSubQ = subscriberSubQ} =
forever $ do
subs <- atomically (peekTQueue subscriberSubQ)
let subs' = L.map (\(NtfSub sub) -> sub) subs
srv = server $ L.head subs
logSubStatus srv "subscribing" $ length subs
mapM_ (\NtfSubData {smpQueue} -> updateSubStatus smpQueue NSPending) subs'
rs <- liftIO $ subscribeQueues srv subs'
(subs'', oks, errs) <- foldM process ([], 0, []) rs
atomically $ do
void $ readTQueue subscriberSubQ
mapM_ (writeTQueue subscriberSubQ . L.map NtfSub) $ L.nonEmpty subs''
logSubStatus srv "retrying" $ length subs''
logSubStatus srv "subscribed" oks
logSubErrors srv errs
where
process :: ([NtfSubData], Int, [NtfSubStatus]) -> (NtfSubData, Either SMPClientError ()) -> M ([NtfSubData], Int, [NtfSubStatus])
process (subs, oks, errs) (sub@NtfSubData {smpQueue}, r) = case r of
Right _ -> updateSubStatus smpQueue NSActive $> (subs, oks + 1, errs)
Left e -> update <$> handleSubError smpQueue e
where
update = \case
Just err -> (subs, oks, err : errs) -- permanent error, log and don't retry subscription
Nothing -> (sub : subs, oks, errs) -- temporary error, retry subscription
-- | Subscribe to queues. The list of results can have a different order.
subscribeQueues :: SMPServer -> NonEmpty NtfSubData -> IO (NonEmpty (NtfSubData, Either SMPClientError ()))
subscribeQueues srv subs =
L.map (second snd) . L.zip subs <$> subscribeQueuesNtfs ca srv (L.map sub subs)
where
sub NtfSubData {smpQueue = SMPQueueNtf {notifierId}, notifierKey} = (notifierId, notifierKey)
forever $
atomically (peekTQueue subscriberSubQ)
>>= \(NtfSub NtfSubData {smpQueue, notifierKey}) -> do
updateSubStatus smpQueue NSPending
let SMPQueueNtf {smpServer, notifierId} = smpQueue
liftIO (runExceptT $ subscribeQueue ca smpServer ((SPNotifier, notifierId), notifierKey)) >>= \case
Right _ -> do
updateSubStatus smpQueue NSActive
void . atomically $ readTQueue subscriberSubQ
Left err -> do
handleSubError smpQueue err
case err of
PCEResponseTimeout -> pure ()
PCENetworkError -> pure ()
_ -> void . atomically $ readTQueue subscriberSubQ
receiveSMP :: M ()
receiveSMP = forever $ do
@@ -240,43 +212,37 @@ ntfSubscriber NtfSubscriber {smpSubscribers, newSubQ, smpAgent = ca@SMPClientAge
atomically (readTBQueue agentQ) >>= \case
CAConnected _ -> pure ()
CADisconnected srv subs -> do
logSubStatus srv "disconnected" $ length subs
logInfo $ "SMP server disconnected " <> showServer' srv <> " (" <> tshow (length subs) <> ") subscriptions"
forM_ subs $ \(_, ntfId) -> do
let smpQueue = SMPQueueNtf srv ntfId
updateSubStatus smpQueue NSInactive
CAReconnected srv ->
logInfo $ "SMP server reconnected " <> showServer' srv
CAResubscribed srv subs -> do
forM_ subs $ \(_, ntfId) -> updateSubStatus (SMPQueueNtf srv ntfId) NSActive
logSubStatus srv "resubscribed" $ length subs
CASubError srv errs ->
forM errs (\((_, ntfId), err) -> handleSubError (SMPQueueNtf srv ntfId) err)
>>= logSubErrors srv . catMaybes . L.toList
CAResubscribed srv sub -> do
let ntfId = snd sub
smpQueue = SMPQueueNtf srv ntfId
updateSubStatus smpQueue NSActive
CASubError srv (_, ntfId) err -> do
logError $ "SMP subscription error on server " <> showServer' srv <> ": " <> tshow err
handleSubError (SMPQueueNtf srv ntfId) err
where
showServer' = decodeLatin1 . strEncode . host
logSubStatus srv event n = when (n > 0) $
logInfo $ "SMP server " <> event <> " " <> showServer' srv <> " (" <> tshow n <> " subscriptions)"
logSubErrors :: SMPServer -> [NtfSubStatus] -> M ()
logSubErrors srv errs = forM_ (L.group $ sort errs) $ \errs' -> do
logError $ "SMP subscription errors on server " <> showServer' srv <> ": " <> tshow (L.head errs') <> " (" <> tshow (length errs') <> " errors)"
showServer' = decodeLatin1 . strEncode . host
handleSubError :: SMPQueueNtf -> SMPClientError -> M (Maybe NtfSubStatus)
handleSubError :: SMPQueueNtf -> SMPClientError -> M ()
handleSubError smpQueue = \case
PCEProtocolError AUTH -> updateSubStatus smpQueue NSAuth $> Just NSAuth
PCEProtocolError AUTH -> updateSubStatus smpQueue NSAuth
PCEProtocolError e -> updateErr "SMP error " e
PCEIOError e -> updateErr "IOError " e
PCEResponseError e -> updateErr "ResponseError " e
PCEUnexpectedResponse r -> updateErr "UnexpectedResponse " r
PCETransportError e -> updateErr "TransportError " e
PCECryptoError e -> updateErr "CryptoError " e
PCEIncompatibleHost -> let e = NSErr "IncompatibleHost" in updateSubStatus smpQueue e $> Just e
PCEResponseTimeout -> pure Nothing
PCENetworkError -> pure Nothing
PCEIOError _ -> pure Nothing
PCEIncompatibleHost -> updateSubStatus smpQueue $ NSErr "IncompatibleHost"
PCEResponseTimeout -> pure ()
PCENetworkError -> pure ()
where
updateErr :: Show e => ByteString -> e -> M (Maybe NtfSubStatus)
updateErr errType e = updateSubStatus smpQueue (NSErr $ errType <> bshow e) $> Just (NSErr errType)
updateErr :: Show e => ByteString -> e -> M ()
updateErr errType e = updateSubStatus smpQueue . NSErr $ errType <> bshow e
updateSubStatus smpQueue status = do
st <- asks store
@@ -370,7 +336,7 @@ receive th NtfServerClient {rcvQ, sndQ, activeAt} = forever $ do
send :: Transport c => THandle c -> NtfServerClient -> IO ()
send h@THandle {thVersion = v} NtfServerClient {sndQ, sessionId, activeAt} = forever $ do
t <- atomically $ readTBQueue sndQ
void . liftIO $ tPut h Nothing [(Nothing, encodeTransmission v sessionId t)]
void . liftIO $ tPut h [(Nothing, encodeTransmission v sessionId t)]
atomically . writeTVar activeAt =<< liftIO getSystemTime
-- instance Show a => Show (TVar a) where
@@ -538,7 +504,7 @@ client NtfServerClient {rcvQ, sndQ} NtfSubscriber {newSubQ, smpAgent = ca} NtfPu
sub <- atomically $ mkNtfSubData subId newSub
resp <-
atomically (addNtfSubscription st subId sub) >>= \case
Just _ -> atomically (writeTBQueue newSubQ [NtfSub sub]) $> NRSubId subId
Just _ -> atomically (writeTBQueue newSubQ $ NtfSub sub) $> NRSubId subId
_ -> pure $ NRErr AUTH
withNtfLog (`logCreateSubscription` sub)
incNtfStat subCreated
@@ -12,7 +12,6 @@ import Control.Monad.IO.Unlift
import Crypto.Random
import Data.ByteString.Char8 (ByteString)
import Data.Int (Int64)
import Data.List.NonEmpty (NonEmpty)
import Data.Time.Clock (getCurrentTime)
import Data.Time.Clock.System (SystemTime)
import Data.Word (Word16)
@@ -48,6 +47,7 @@ data NtfServerConfig = NtfServerConfig
apnsConfig :: APNSPushClientConfig,
inactiveClientExpiration :: Maybe ExpirationConfig,
storeLogFile :: Maybe FilePath,
resubscribeDelay :: Int, -- microseconds
-- CA certificate private key is not needed for initialization
caCertificateFile :: FilePath,
privateKeyFile :: FilePath,
@@ -93,7 +93,7 @@ newNtfServerEnv config@NtfServerConfig {subQSize, pushQSize, smpAgentCfg, apnsCo
data NtfSubscriber = NtfSubscriber
{ smpSubscribers :: TMap SMPServer SMPSubscriber,
newSubQ :: TBQueue [NtfEntityRec 'Subscription],
newSubQ :: TBQueue (NtfEntityRec 'Subscription),
smpAgent :: SMPClientAgent
}
@@ -105,7 +105,7 @@ newNtfSubscriber qSize smpAgentCfg = do
pure NtfSubscriber {smpSubscribers, newSubQ, smpAgent}
data SMPSubscriber = SMPSubscriber
{ newSubQ :: TQueue (NonEmpty (NtfEntityRec 'Subscription)),
{ newSubQ :: TQueue (NtfEntityRec 'Subscription),
subThreadId :: TVar (Maybe (Weak ThreadId))
}
@@ -14,8 +14,7 @@ import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import Network.Socket (HostName)
import Options.Applicative
import Simplex.Messaging.Client (ProtocolClientConfig (..))
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
import Simplex.Messaging.Client.Agent (defaultSMPClientAgentConfig)
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Notifications.Server (runNtfServer)
import Simplex.Messaging.Notifications.Server.Env (NtfServerConfig (..))
@@ -29,10 +28,7 @@ import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
import Text.Read (readMaybe)
ntfServerVersion :: String
ntfServerVersion = "1.4.2"
defaultSMPBatchDelay :: Int
defaultSMPBatchDelay = 10000
ntfServerVersion = "1.4.0"
ntfServerCLI :: FilePath -> FilePath -> IO ()
ntfServerCLI cfgPath logPath =
@@ -84,9 +80,7 @@ ntfServerCLI cfgPath logPath =
<> ("host: " <> host <> "\n")
<> ("port: " <> defaultServerPort <> "\n")
<> "log_tls_errors: off\n\
\# delay between command batches sent to SMP relays (microseconds), 0 to disable\n"
<> ("smp_batch_delay: " <> show defaultSMPBatchDelay <> "\n")
<> "websockets: off\n"
\websockets: off\n"
runServer ini = do
hSetBuffering stdout LineBuffering
hSetBuffering stderr LineBuffering
@@ -102,20 +96,19 @@ ntfServerCLI cfgPath logPath =
enableStoreLog = settingIsOn "STORE_LOG" "enable" ini
logStats = settingIsOn "STORE_LOG" "log_stats" ini
c = combine cfgPath . ($ defaultX509Config)
smpBatchDelay = readIniDefault defaultSMPBatchDelay "TRANSPORT" "smp_batch_delay" ini
batchDelay = if smpBatchDelay <= 0 then Nothing else Just smpBatchDelay
serverConfig =
NtfServerConfig
{ transports = iniTransports ini,
subIdBytes = 24,
regCodeBytes = 32,
clientQSize = 64,
subQSize = 512,
pushQSize = 1048,
smpAgentCfg = defaultSMPClientAgentConfig {smpCfg = (smpCfg defaultSMPClientAgentConfig) {batchDelay}},
clientQSize = 16,
subQSize = 64,
pushQSize = 128,
smpAgentCfg = defaultSMPClientAgentConfig,
apnsConfig = defaultAPNSPushClientConfig,
inactiveClientExpiration = Nothing,
storeLogFile = enableStoreLog $> storeLogFilePath,
resubscribeDelay = 50000, -- 50ms
caCertificateFile = c caCrtFile,
privateKeyFile = c serverKeyFile,
certificateFile = c serverCrtFile,
@@ -20,7 +20,7 @@ import qualified Data.Set as S
import Data.Word (Word16)
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Notifications.Protocol
import Simplex.Messaging.Protocol (NtfPrivateSignKey, SMPServer)
import Simplex.Messaging.Protocol (NtfPrivateSignKey)
import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Util (whenM, ($>>=))
@@ -68,9 +68,6 @@ data NtfSubData = NtfSubData
subStatus :: TVar NtfSubStatus
}
ntfSubServer :: NtfSubData -> SMPServer
ntfSubServer NtfSubData {smpQueue = SMPQueueNtf {smpServer}} = smpServer
data NtfEntityRec (e :: NtfEntity) where
NtfTkn :: NtfTknData -> NtfEntityRec 'Token
NtfSub :: NtfSubData -> NtfEntityRec 'Subscription
+3 -4
View File
@@ -146,7 +146,6 @@ module Simplex.Messaging.Protocol
where
import Control.Applicative (optional, (<|>))
import Control.Concurrent (threadDelay)
import Control.Monad.Except
import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Aeson as J
@@ -1245,8 +1244,8 @@ instance Encoding CommandError where
_ -> fail "bad command error type"
-- | Send signed SMP transmission to TCP transport.
tPut :: Transport c => THandle c -> Maybe Int -> NonEmpty SentRawTransmission -> IO [Either TransportError ()]
tPut th delay_ trs
tPut :: Transport c => THandle c -> NonEmpty SentRawTransmission -> IO [Either TransportError ()]
tPut th trs
| batch th = tPutBatch [] $ L.map tEncode trs
| otherwise = forM (L.toList trs) $ tPutLog . tEncode
where
@@ -1256,7 +1255,7 @@ tPut th delay_ trs
r <- if n == 0 then largeMsg else replicate n <$> tPutLog (tEncodeBatch n s)
let rs' = rs <> r
case ts_ of
Just ts' -> mapM_ threadDelay delay_ >> tPutBatch rs' ts'
Just ts' -> tPutBatch rs' ts'
_ -> pure rs'
largeMsg = putStrLn "tPut error: large message" >> pure [Left TELargeMsg]
tPutLog s = do
+1 -1
View File
@@ -281,7 +281,7 @@ receive th Client {rcvQ, sndQ, activeAt} = forever $ do
send :: Transport c => THandle c -> Client -> IO ()
send h@THandle {thVersion = v} Client {sndQ, sessionId, activeAt} = forever $ do
ts <- atomically $ L.sortWith tOrder <$> readTBQueue sndQ
void . liftIO . tPut h Nothing $ L.map ((Nothing,) . encodeTransmission v sessionId) ts
void . liftIO . tPut h $ L.map ((Nothing,) . encodeTransmission v sessionId) ts
atomically . writeTVar activeAt =<< liftIO getSystemTime
where
tOrder :: Transmission BrokerMsg -> Int
+1 -1
View File
@@ -164,7 +164,7 @@ smpServerCLI cfgPath logPath =
serverConfig =
ServerConfig
{ transports = iniTransports ini,
tbqSize = 64,
tbqSize = 32,
serverTbqSize = 1024,
msgQueueQuota = 128,
queueIdBytes = 24,
+1 -13
View File
@@ -16,9 +16,8 @@ import Data.Int (Int64)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8With)
import Data.Time (NominalDiffTime)
import Data.Time (NominalDiffTime, nominalDiffTimeToSeconds)
import UnliftIO.Async
import Data.List (groupBy, sortOn)
raceAny_ :: MonadUnliftIO m => [m a] -> m ()
raceAny_ = r []
@@ -103,17 +102,6 @@ eitherToMaybe :: Either a b -> Maybe b
eitherToMaybe = either (const Nothing) Just
{-# INLINE eitherToMaybe #-}
groupOn :: Eq k => (a -> k) -> [a] -> [[a]]
groupOn = groupBy . eqOn
-- it is equivalent to groupBy ((==) `on` f),
-- but it redefines `on` to avoid duplicate computation for most values.
-- source: https://hackage.haskell.org/package/extra-1.7.13/docs/src/Data.List.Extra.html#groupOn
-- the on2 in this package is specialized to only use `==` as the function, `eqOn f` is equivalent to `(==) `on` f`
where eqOn f = \x -> let fx = f x in \y -> fx == f y
groupAllOn :: Ord k => (a -> k) -> [a] -> [[a]]
groupAllOn f = groupOn f . sortOn f
safeDecodeUtf8 :: ByteString -> Text
safeDecodeUtf8 = decodeUtf8With onError
where
+41 -362
View File
@@ -105,7 +105,7 @@ pattern Msg :: MsgBody -> ACommand 'Agent e
pattern Msg msgBody <- MSG MsgMeta {integrity = MsgOk} _ msgBody
smpCfgV1 :: ProtocolClientConfig
smpCfgV1 = (smpCfg agentCfg) {serverVRange = vr11}
smpCfgV1 = (smpCfg agentCfg) {smpServerVRange = vr11}
agentCfgV1 :: AgentConfig
agentCfgV1 = agentCfg {smpAgentVRange = vr11, smpClientVRange = vr11, e2eEncryptVRange = vr11, smpCfg = smpCfgV1}
@@ -125,18 +125,6 @@ runRight action =
Right x -> pure x
Left e -> error $ "Unexpected error: " <> show e
getInAnyOrder :: HasCallStack => AgentClient -> [AEntityTransmission 'AEConn -> Bool] -> Expectation
getInAnyOrder _ [] = pure ()
getInAnyOrder c rs = do
r <- get c
let rest = filter (not . expected r) rs
if length rest < length rs
then getInAnyOrder c rest
else error $ "unexpected event: " <> show r
where
expected :: AEntityTransmission 'AEConn -> (AEntityTransmission 'AEConn -> Bool) -> Bool
expected r rp = rp r
functionalAPITests :: ATransport -> Spec
functionalAPITests t = do
describe "Establishing duplex connection" $ do
@@ -180,10 +168,9 @@ functionalAPITests t = do
it "should suspend agent on timeout, even if pending messages not sent" $
testSuspendingAgentTimeout t
describe "Batching SMP commands" $ do
it "should subscribe to multiple (200) subscriptions with batching" $
xit "should subscribe to multiple (200) subscriptions with batching" $
testBatchedSubscriptions 200 10 t
-- 200 subscriptions gets very slow with test coverage, use below test instead
xit "should subscribe to multiple (6) subscriptions with batching" $
it "should subscribe to multiple (6) subscriptions with batching" $
testBatchedSubscriptions 6 3 t
describe "Async agent commands" $ do
it "should connect using async agent commands" $
@@ -203,23 +190,13 @@ functionalAPITests t = do
testUsersNoServer t
it "should connect two users and switch session mode" $
withSmpServer t testTwoUsers
describe "Connection switch" $ do
describe "Queue rotation" $ do
describe "should switch delivery to the new queue" $
testServerMatrix2 t testSwitchConnection
describe "should switch to new queue asynchronously" $
testServerMatrix2 t testSwitchAsync
describe "should delete connection during switch" $
describe "should delete connection during rotation" $
testServerMatrix2 t testSwitchDelete
describe "should abort switch in Started phase" $
testServerMatrix2 t testAbortSwitchStarted
describe "should abort switch in Started phase, reinitiate immediately" $
testServerMatrix2 t testAbortSwitchStartedReinitiate
describe "should prohibit to abort switch in Secured phase" $
testServerMatrix2 t testCannotAbortSwitchSecured
describe "should switch two connections simultaneously" $
testServerMatrix2 t testSwitch2Connections
describe "should switch two connections simultaneously, abort one" $
testServerMatrix2 t testSwitch2ConnectionsAbort1
describe "SMP basic auth" $ do
describe "with server auth" $ do
-- allow NEW | server auth, v | clnt1 auth, v | clnt2 auth, v | 2 - success, 1 - JOIN fail, 0 - NEW fail
@@ -983,49 +960,28 @@ testSwitchConnection servers = do
runRight_ $ do
(aId, bId) <- makeConnection a b
exchangeGreetingsMsgId 4 a bId b aId
testFullSwitch a bId b aId 10
testFullSwitch a bId b aId 16
testFullSwitch :: AgentClient -> ByteString -> AgentClient -> ByteString -> Int64 -> ExceptT AgentErrorType IO ()
testFullSwitch a bId b aId msgId = do
stats <- switchConnectionAsync a "" bId
liftIO $ rcvSwchStatuses' stats `shouldMatchList` [Just RSSwitchStarted]
switchComplete a bId b aId
exchangeGreetingsMsgId msgId a bId b aId
switchConnectionAsync a "" bId
switchComplete a bId b aId
exchangeGreetingsMsgId 10 a bId b aId
switchComplete :: AgentClient -> ByteString -> AgentClient -> ByteString -> ExceptT AgentErrorType IO ()
switchComplete a bId b aId = do
phaseRcv a bId SPStarted [Just RSSendingQADD, Nothing]
phaseSnd b aId SPStarted [Just SSSendingQKEY, Nothing]
phaseSnd b aId SPConfirmed [Just SSSendingQKEY, Nothing]
phaseRcv a bId SPConfirmed [Just RSSendingQADD, Nothing]
phaseRcv a bId SPSecured [Just RSSendingQUSE, Nothing]
phaseSnd b aId SPSecured [Just SSSendingQTEST, Nothing]
phaseSnd b aId SPCompleted [Nothing]
phaseRcv a bId SPCompleted [Nothing]
phase a bId QDRcv SPStarted
phase b aId QDSnd SPStarted
phase a bId QDRcv SPConfirmed
phase b aId QDSnd SPConfirmed
phase b aId QDSnd SPCompleted
phase a bId QDRcv SPCompleted
phaseRcv :: AgentClient -> ByteString -> SwitchPhase -> [Maybe RcvSwitchStatus] -> ExceptT AgentErrorType IO ()
phaseRcv c connId p swchStatuses = phase c connId QDRcv p (\stats -> rcvSwchStatuses' stats `shouldMatchList` swchStatuses)
rcvSwchStatuses' :: ConnectionStats -> [Maybe RcvSwitchStatus]
rcvSwchStatuses' ConnectionStats {rcvQueuesInfo} = map (\RcvQueueInfo {rcvSwitchStatus} -> rcvSwitchStatus) rcvQueuesInfo
phaseSnd :: AgentClient -> ByteString -> SwitchPhase -> [Maybe SndSwitchStatus] -> ExceptT AgentErrorType IO ()
phaseSnd c connId p swchStatuses = phase c connId QDSnd p (\stats -> sndSwchStatuses' stats `shouldMatchList` swchStatuses)
sndSwchStatuses' :: ConnectionStats -> [Maybe SndSwitchStatus]
sndSwchStatuses' ConnectionStats {sndQueuesInfo} = map (\SndQueueInfo {sndSwitchStatus} -> sndSwitchStatus) sndQueuesInfo
phase :: AgentClient -> ByteString -> QueueDirection -> SwitchPhase -> (ConnectionStats -> Expectation) -> ExceptT AgentErrorType IO ()
phase c connId d p statsExpectation =
phase :: AgentClient -> ByteString -> QueueDirection -> SwitchPhase -> ExceptT AgentErrorType IO ()
phase c connId d p =
get c >>= \(_, connId', msg) -> do
liftIO $ connId `shouldBe` connId'
case msg of
SWITCH d' p' stats -> liftIO $ do
SWITCH d' p' _ -> liftIO $ do
d `shouldBe` d'
p `shouldBe` p'
statsExpectation stats
ERR (AGENT A_DUPLICATE) -> phase c connId d p statsExpectation
ERR (AGENT A_DUPLICATE) -> phase c connId d p
r -> do
liftIO . putStrLn $ "expected: " <> show p <> ", received: " <> show r
SWITCH {} <- pure r
@@ -1037,44 +993,33 @@ testSwitchAsync servers = do
(aId, bId) <- makeConnection a b
exchangeGreetingsMsgId 4 a bId b aId
pure (aId, bId)
let withA' = sessionSubscribe withA [bId]
withB' = sessionSubscribe withB [aId]
let withA' = session withA bId
withB' = session withB aId
withA' $ \a -> do
stats <- switchConnectionAsync a "" bId
liftIO $ rcvSwchStatuses' stats `shouldMatchList` [Just RSSwitchStarted]
phaseRcv a bId SPStarted [Just RSSendingQADD, Nothing]
switchConnectionAsync a "" bId
phase a bId QDRcv SPStarted
withB' $ \b -> phase b aId QDSnd SPStarted
withA' $ \a -> phase a bId QDRcv SPConfirmed
withB' $ \b -> do
phaseSnd b aId SPStarted [Just SSSendingQKEY, Nothing]
phaseSnd b aId SPConfirmed [Just SSSendingQKEY, Nothing]
withA' $ \a -> do
phaseRcv a bId SPConfirmed [Just RSSendingQADD, Nothing]
phaseRcv a bId SPSecured [Just RSSendingQUSE, Nothing]
withB' $ \b -> do
phaseSnd b aId SPSecured [Just SSSendingQTEST, Nothing]
phaseSnd b aId SPCompleted [Nothing]
withA' $ \a -> phaseRcv a bId SPCompleted [Nothing]
phase b aId QDSnd SPConfirmed
phase b aId QDSnd SPCompleted
withA' $ \a -> phase a bId QDRcv SPCompleted
withA $ \a -> withB $ \b -> runRight_ $ do
subscribeConnection a bId
subscribeConnection b aId
exchangeGreetingsMsgId 10 a bId b aId
testFullSwitch a bId b aId 16
where
withA :: (AgentClient -> IO a) -> IO a
withA = withAgent agentCfg servers testDB
withB :: (AgentClient -> IO a) -> IO a
withB = withAgent agentCfg {initialClientId = 1} servers testDB2
withAgent :: AgentConfig -> InitialAgentServers -> FilePath -> (AgentClient -> IO a) -> IO a
withAgent cfg' servers dbPath = bracket (getSMPAgentClient' cfg' servers dbPath) disconnectAgentClient
sessionSubscribe :: (forall a. (AgentClient -> IO a) -> IO a) -> [ConnId] -> (AgentClient -> ExceptT AgentErrorType IO ()) -> IO ()
sessionSubscribe withC connIds a =
withC $ \c -> runRight_ $ do
void $ subscribeConnections c connIds
r <- a c
liftIO $ threadDelay 500000
liftIO $ noMessages c "nothing else should be delivered"
pure r
withAgent :: AgentConfig -> FilePath -> (AgentClient -> IO a) -> IO a
withAgent cfg' dbPath = bracket (getSMPAgentClient' cfg' servers dbPath) disconnectAgentClient
session :: (forall a. (AgentClient -> IO a) -> IO a) -> ConnId -> (AgentClient -> ExceptT AgentErrorType IO ()) -> IO ()
session withC connId a =
withC $ \c -> runRight_ $ do
subscribeConnection c connId
r <- a c
liftIO $ threadDelay 500000
pure r
withA = withAgent agentCfg testDB
withB = withAgent agentCfg {initialClientId = 1} testDB2
testSwitchDelete :: InitialAgentServers -> IO ()
testSwitchDelete servers = do
@@ -1084,280 +1029,14 @@ testSwitchDelete servers = do
(aId, bId) <- makeConnection a b
exchangeGreetingsMsgId 4 a bId b aId
disconnectAgentClient b
stats <- switchConnectionAsync a "" bId
liftIO $ rcvSwchStatuses' stats `shouldMatchList` [Just RSSwitchStarted]
phaseRcv a bId SPStarted [Just RSSendingQADD, Nothing]
switchConnectionAsync a "" bId
phase a bId QDRcv SPStarted
deleteConnectionAsync a bId
get a =##> \case ("", c, DEL_RCVQ _ _ Nothing) -> c == bId; _ -> False
get a =##> \case ("", c, DEL_RCVQ _ _ Nothing) -> c == bId; _ -> False
get a =##> \case ("", c, DEL_CONN) -> c == bId; _ -> False
liftIO $ noMessages a "nothing else should be delivered to alice"
testAbortSwitchStarted :: HasCallStack => InitialAgentServers -> IO ()
testAbortSwitchStarted servers = do
(aId, bId) <- withA $ \a -> withB $ \b -> runRight $ do
(aId, bId) <- makeConnection a b
exchangeGreetingsMsgId 4 a bId b aId
pure (aId, bId)
let withA' = sessionSubscribe withA [bId]
withB' = sessionSubscribe withB [aId]
withA' $ \a -> do
stats <- switchConnectionAsync a "" bId
liftIO $ rcvSwchStatuses' stats `shouldMatchList` [Just RSSwitchStarted]
phaseRcv a bId SPStarted [Just RSSendingQADD, Nothing]
-- repeat switch is prohibited
Left Agent.CMD {cmdErr = PROHIBITED} <- runExceptT $ switchConnectionAsync a "" bId
-- abort current switch
stats' <- abortConnectionSwitch a bId
liftIO $ rcvSwchStatuses' stats' `shouldMatchList` [Nothing]
withB' $ \b -> do
phaseSnd b aId SPStarted [Just SSSendingQKEY, Nothing]
phaseSnd b aId SPConfirmed [Just SSSendingQKEY, Nothing]
withA' $ \a -> do
get a ##> ("", bId, ERR (AGENT {agentErr = A_QUEUE {queueErr = "QKEY: queue address not found in connection"}}))
-- repeat switch
stats <- switchConnectionAsync a "" bId
liftIO $ rcvSwchStatuses' stats `shouldMatchList` [Just RSSwitchStarted]
phaseRcv a bId SPStarted [Just RSSendingQADD, Nothing]
withA $ \a -> withB $ \b -> runRight_ $ do
subscribeConnection a bId
subscribeConnection b aId
phaseSnd b aId SPStarted [Just SSSendingQKEY, Nothing]
phaseSnd b aId SPConfirmed [Just SSSendingQKEY, Nothing]
phaseRcv a bId SPConfirmed [Just RSSendingQADD, Nothing]
phaseRcv a bId SPSecured [Just RSSendingQUSE, Nothing]
phaseSnd b aId SPSecured [Just SSSendingQTEST, Nothing]
phaseSnd b aId SPCompleted [Nothing]
phaseRcv a bId SPCompleted [Nothing]
exchangeGreetingsMsgId 12 a bId b aId
testFullSwitch a bId b aId 18
where
withA :: (AgentClient -> IO a) -> IO a
withA = withAgent agentCfg servers testDB
withB :: (AgentClient -> IO a) -> IO a
withB = withAgent agentCfg {initialClientId = 1} servers testDB2
testAbortSwitchStartedReinitiate :: HasCallStack => InitialAgentServers -> IO ()
testAbortSwitchStartedReinitiate servers = do
(aId, bId) <- withA $ \a -> withB $ \b -> runRight $ do
(aId, bId) <- makeConnection a b
exchangeGreetingsMsgId 4 a bId b aId
pure (aId, bId)
let withA' = sessionSubscribe withA [bId]
withB' = sessionSubscribe withB [aId]
withA' $ \a -> do
stats <- switchConnectionAsync a "" bId
liftIO $ rcvSwchStatuses' stats `shouldMatchList` [Just RSSwitchStarted]
phaseRcv a bId SPStarted [Just RSSendingQADD, Nothing]
-- abort current switch
stats' <- abortConnectionSwitch a bId
liftIO $ rcvSwchStatuses' stats' `shouldMatchList` [Nothing]
-- repeat switch
stats'' <- switchConnectionAsync a "" bId
liftIO $ rcvSwchStatuses' stats'' `shouldMatchList` [Just RSSwitchStarted]
phaseRcv a bId SPStarted [Just RSSendingQADD, Nothing]
withB' $ \b -> do
phaseSnd b aId SPStarted [Just SSSendingQKEY, Nothing]
liftIO . getInAnyOrder b $
[ switchPhaseSndP aId SPStarted [Just SSSendingQKEY, Nothing],
switchPhaseSndP aId SPConfirmed [Just SSSendingQKEY, Nothing]
]
phaseSnd b aId SPConfirmed [Just SSSendingQKEY, Nothing]
withA $ \a -> withB $ \b -> runRight_ $ do
subscribeConnection a bId
subscribeConnection b aId
liftIO . getInAnyOrder a $
[ errQueueNotFoundP bId,
switchPhaseRcvP bId SPConfirmed [Just RSSendingQADD, Nothing]
]
phaseRcv a bId SPSecured [Just RSSendingQUSE, Nothing]
phaseSnd b aId SPSecured [Just SSSendingQTEST, Nothing]
phaseSnd b aId SPCompleted [Nothing]
phaseRcv a bId SPCompleted [Nothing]
exchangeGreetingsMsgId 12 a bId b aId
testFullSwitch a bId b aId 18
where
withA :: (AgentClient -> IO a) -> IO a
withA = withAgent agentCfg servers testDB
withB :: (AgentClient -> IO a) -> IO a
withB = withAgent agentCfg {initialClientId = 1} servers testDB2
switchPhaseRcvP :: ConnId -> SwitchPhase -> [Maybe RcvSwitchStatus] -> AEntityTransmission 'AEConn -> Bool
switchPhaseRcvP cId sphase swchStatuses = switchPhaseP cId QDRcv sphase (\stats -> rcvSwchStatuses' stats == swchStatuses)
switchPhaseSndP :: ConnId -> SwitchPhase -> [Maybe SndSwitchStatus] -> AEntityTransmission 'AEConn -> Bool
switchPhaseSndP cId sphase swchStatuses = switchPhaseP cId QDSnd sphase (\stats -> sndSwchStatuses' stats == swchStatuses)
switchPhaseP :: ConnId -> QueueDirection -> SwitchPhase -> (ConnectionStats -> Bool) -> AEntityTransmission 'AEConn -> Bool
switchPhaseP cId qd sphase statsP = \case
(_, cId', SWITCH qd' sphase' stats) -> cId' == cId && qd' == qd && sphase' == sphase && statsP stats
_ -> False
errQueueNotFoundP :: ConnId -> AEntityTransmission 'AEConn -> Bool
errQueueNotFoundP cId = \case
(_, cId', ERR AGENT {agentErr = A_QUEUE {queueErr = "QKEY: queue address not found in connection"}}) -> cId' == cId
_ -> False
testCannotAbortSwitchSecured :: HasCallStack => InitialAgentServers -> IO ()
testCannotAbortSwitchSecured servers = do
(aId, bId) <- withA $ \a -> withB $ \b -> runRight $ do
(aId, bId) <- makeConnection a b
exchangeGreetingsMsgId 4 a bId b aId
pure (aId, bId)
let withA' = sessionSubscribe withA [bId]
withB' = sessionSubscribe withB [aId]
withA' $ \a -> do
stats <- switchConnectionAsync a "" bId
liftIO $ rcvSwchStatuses' stats `shouldMatchList` [Just RSSwitchStarted]
phaseRcv a bId SPStarted [Just RSSendingQADD, Nothing]
withB' $ \b -> do
phaseSnd b aId SPStarted [Just SSSendingQKEY, Nothing]
phaseSnd b aId SPConfirmed [Just SSSendingQKEY, Nothing]
withA' $ \a -> do
phaseRcv a bId SPConfirmed [Just RSSendingQADD, Nothing]
phaseRcv a bId SPSecured [Just RSSendingQUSE, Nothing]
Left Agent.CMD {cmdErr = PROHIBITED} <- runExceptT $ abortConnectionSwitch a bId
pure ()
withA $ \a -> withB $ \b -> runRight_ $ do
subscribeConnection a bId
subscribeConnection b aId
phaseSnd b aId SPSecured [Just SSSendingQTEST, Nothing]
phaseSnd b aId SPCompleted [Nothing]
phaseRcv a bId SPCompleted [Nothing]
exchangeGreetingsMsgId 10 a bId b aId
testFullSwitch a bId b aId 16
where
withA :: (AgentClient -> IO a) -> IO a
withA = withAgent agentCfg servers testDB
withB :: (AgentClient -> IO a) -> IO a
withB = withAgent agentCfg {initialClientId = 1} servers testDB2
testSwitch2Connections :: HasCallStack => InitialAgentServers -> IO ()
testSwitch2Connections servers = do
(aId1, bId1, aId2, bId2) <- withA $ \a -> withB $ \b -> runRight $ do
(aId1, bId1) <- makeConnection a b
exchangeGreetingsMsgId 4 a bId1 b aId1
(aId2, bId2) <- makeConnection a b
exchangeGreetingsMsgId 4 a bId2 b aId2
pure (aId1, bId1, aId2, bId2)
withA $ \a -> runRight_ $ do
void $ subscribeConnections a [bId1, bId2]
stats1 <- switchConnectionAsync a "" bId1
liftIO $ rcvSwchStatuses' stats1 `shouldMatchList` [Just RSSwitchStarted]
phaseRcv a bId1 SPStarted [Just RSSendingQADD, Nothing]
stats2 <- switchConnectionAsync a "" bId2
liftIO $ rcvSwchStatuses' stats2 `shouldMatchList` [Just RSSwitchStarted]
phaseRcv a bId2 SPStarted [Just RSSendingQADD, Nothing]
withA $ \a -> withB $ \b -> runRight_ $ do
void $ subscribeConnections a [bId1, bId2]
void $ subscribeConnections b [aId1, aId2]
liftIO . getInAnyOrder b $
[ switchPhaseSndP aId1 SPStarted [Just SSSendingQKEY, Nothing],
switchPhaseSndP aId1 SPConfirmed [Just SSSendingQKEY, Nothing],
switchPhaseSndP aId2 SPStarted [Just SSSendingQKEY, Nothing],
switchPhaseSndP aId2 SPConfirmed [Just SSSendingQKEY, Nothing]
]
liftIO . getInAnyOrder a $
[ switchPhaseRcvP bId1 SPConfirmed [Just RSSendingQADD, Nothing],
switchPhaseRcvP bId1 SPSecured [Just RSSendingQUSE, Nothing],
switchPhaseRcvP bId2 SPConfirmed [Just RSSendingQADD, Nothing],
switchPhaseRcvP bId2 SPSecured [Just RSSendingQUSE, Nothing]
]
liftIO . getInAnyOrder b $
[ switchPhaseSndP aId1 SPSecured [Just SSSendingQTEST, Nothing],
switchPhaseSndP aId1 SPCompleted [Nothing],
switchPhaseSndP aId2 SPSecured [Just SSSendingQTEST, Nothing],
switchPhaseSndP aId2 SPCompleted [Nothing]
]
liftIO . getInAnyOrder a $
[ switchPhaseRcvP bId1 SPCompleted [Nothing],
switchPhaseRcvP bId2 SPCompleted [Nothing]
]
exchangeGreetingsMsgId 10 a bId1 b aId1
exchangeGreetingsMsgId 10 a bId2 b aId2
testFullSwitch a bId1 b aId1 16
testFullSwitch a bId2 b aId2 16
where
withA :: (AgentClient -> IO a) -> IO a
withA = withAgent agentCfg servers testDB
withB :: (AgentClient -> IO a) -> IO a
withB = withAgent agentCfg {initialClientId = 1} servers testDB2
testSwitch2ConnectionsAbort1 :: HasCallStack => InitialAgentServers -> IO ()
testSwitch2ConnectionsAbort1 servers = do
(aId1, bId1, aId2, bId2) <- withA $ \a -> withB $ \b -> runRight $ do
(aId1, bId1) <- makeConnection a b
exchangeGreetingsMsgId 4 a bId1 b aId1
(aId2, bId2) <- makeConnection a b
exchangeGreetingsMsgId 4 a bId2 b aId2
pure (aId1, bId1, aId2, bId2)
let withA' = sessionSubscribe withA [bId1, bId2]
withB' = sessionSubscribe withB [aId1, aId2]
withA' $ \a -> do
stats1 <- switchConnectionAsync a "" bId1
liftIO $ rcvSwchStatuses' stats1 `shouldMatchList` [Just RSSwitchStarted]
phaseRcv a bId1 SPStarted [Just RSSendingQADD, Nothing]
stats2 <- switchConnectionAsync a "" bId2
liftIO $ rcvSwchStatuses' stats2 `shouldMatchList` [Just RSSwitchStarted]
phaseRcv a bId2 SPStarted [Just RSSendingQADD, Nothing]
-- abort switch of second connection
stats2' <- abortConnectionSwitch a bId2
liftIO $ rcvSwchStatuses' stats2' `shouldMatchList` [Nothing]
withB' $ \b -> do
liftIO . getInAnyOrder b $
[ switchPhaseSndP aId1 SPStarted [Just SSSendingQKEY, Nothing],
switchPhaseSndP aId1 SPConfirmed [Just SSSendingQKEY, Nothing],
switchPhaseSndP aId2 SPStarted [Just SSSendingQKEY, Nothing],
switchPhaseSndP aId2 SPConfirmed [Just SSSendingQKEY, Nothing]
]
withA' $ \a -> do
liftIO . getInAnyOrder a $
[ switchPhaseRcvP bId1 SPConfirmed [Just RSSendingQADD, Nothing],
switchPhaseRcvP bId1 SPSecured [Just RSSendingQUSE, Nothing],
errQueueNotFoundP bId2
]
withA $ \a -> withB $ \b -> runRight_ $ do
void $ subscribeConnections a [bId1, bId2]
void $ subscribeConnections b [aId1, aId2]
phaseSnd b aId1 SPSecured [Just SSSendingQTEST, Nothing]
phaseSnd b aId1 SPCompleted [Nothing]
phaseRcv a bId1 SPCompleted [Nothing]
exchangeGreetingsMsgId 10 a bId1 b aId1
exchangeGreetingsMsgId 8 a bId2 b aId2
testFullSwitch a bId1 b aId1 16
testFullSwitch a bId2 b aId2 14
where
withA :: (AgentClient -> IO a) -> IO a
withA = withAgent agentCfg servers testDB
withB :: (AgentClient -> IO a) -> IO a
withB = withAgent agentCfg {initialClientId = 1} servers testDB2
testCreateQueueAuth :: (Maybe BasicAuth, Version) -> (Maybe BasicAuth, Version) -> IO Int
testCreateQueueAuth clnt1 clnt2 = do
a <- getClient clnt1
@@ -1381,7 +1060,7 @@ testCreateQueueAuth clnt1 clnt2 = do
where
getClient (clntAuth, clntVersion) =
let servers = initAgentServers {smp = userServers [ProtoServerWithAuth testSMPServer clntAuth]}
smpCfg = (defaultClientConfig :: ProtocolClientConfig) {serverVRange = mkVersionRange 4 clntVersion}
smpCfg = (defaultClientConfig :: ProtocolClientConfig) {smpServerVRange = mkVersionRange 4 clntVersion}
in getSMPAgentClient' agentCfg {smpCfg} servers testDB
testSMPServerConnectionTest :: ATransport -> Maybe BasicAuth -> SMPServerWithAuth -> IO (Maybe ProtocolTestFailure)
+3 -54
View File
@@ -19,8 +19,8 @@ import qualified Data.ByteString.Base64.URL as U
import Data.ByteString.Char8 (ByteString)
import Data.Text.Encoding (encodeUtf8)
import NtfClient
import SMPAgentClient (agentCfg, initAgentServers, initAgentServers2, testDB, testDB2)
import SMPClient (cfg, testPort, testPort2, testStoreLogFile2, withSmpServer, withSmpServerConfigOn, withSmpServerStoreLogOn, xit')
import SMPAgentClient (agentCfg, initAgentServers, testDB, testDB2)
import SMPClient (testPort, withSmpServer, withSmpServerStoreLogOn, xit')
import Simplex.Messaging.Agent
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers)
import Simplex.Messaging.Agent.Protocol
@@ -31,7 +31,6 @@ import Simplex.Messaging.Notifications.Server.Push.APNS
import Simplex.Messaging.Notifications.Types (NtfToken (..))
import Simplex.Messaging.Protocol (ErrorType (AUTH), MsgFlags (MsgFlags), SMPMsgMeta (..))
import qualified Simplex.Messaging.Protocol as SMP
import Simplex.Messaging.Server.Env.STM (ServerConfig (..))
import Simplex.Messaging.Transport (ATransport)
import Simplex.Messaging.Util (tryE)
import System.Directory (doesFileExist, removeFile)
@@ -86,10 +85,6 @@ notificationTests t =
it "should resume subscriptions after SMP server is restarted" $ \_ ->
withAPNSMockServer $ \apns ->
withNtfServer t $ testNotificationsSMPRestart t apns
describe "Notifications after SMP server restart" $
it "should resume batched subscriptions after SMP server is restarted" $ \_ ->
withAPNSMockServer $ \apns ->
withNtfServer t $ testNotificationsSMPRestartBatch 100 t apns
describe "should switch notifications to the new queue" $
testServerMatrix2 t $ \servers ->
withAPNSMockServer $ \apns ->
@@ -485,52 +480,6 @@ testNotificationsSMPRestart t APNSMockServer {apnsQ} = do
get alice =##> \case ("", c, Msg "hello again") -> c == bobId; _ -> False
liftIO $ killThread threadId
testNotificationsSMPRestartBatch :: Int -> ATransport -> APNSMockServer -> IO ()
testNotificationsSMPRestartBatch n t APNSMockServer {apnsQ} = do
a <- getSMPAgentClient' agentCfg initAgentServers2 testDB
b <- getSMPAgentClient' agentCfg initAgentServers2 testDB2
conns <- runServers $ do
conns <- forM [1 .. n :: Int] . const $ makeConnection a b
_ <- registerTestToken a "abcd" NMInstant apnsQ
liftIO $ threadDelay 1500000
forM_ conns $ \(aliceId, bobId) -> do
msgId <- sendMessage b aliceId (SMP.MsgFlags True) "hello"
get b ##> ("", aliceId, SENT msgId)
void $ messageNotification apnsQ
get a =##> \case ("", c, Msg "hello") -> c == bobId; _ -> False
ackMessage a bobId msgId
pure conns
runRight_ @AgentErrorType $ do
("", "", DOWN _ bcs1) <- nGet a
("", "", DOWN _ bcs2) <- nGet a
liftIO $ length (bcs1 <> bcs2) `shouldBe` length conns
("", "", DOWN _ acs1) <- nGet b
("", "", DOWN _ acs2) <- nGet b
liftIO $ length (acs1 <> acs2) `shouldBe` length conns
runServers $ do
("", "", UP _ bcs1) <- nGet a
("", "", UP _ bcs2) <- nGet a
liftIO $ length (bcs1 <> bcs2) `shouldBe` length conns
("", "", UP _ acs1) <- nGet b
("", "", UP _ acs2) <- nGet b
liftIO $ length (acs1 <> acs2) `shouldBe` length conns
liftIO $ threadDelay 1500000
forM_ conns $ \(aliceId, bobId) -> do
msgId <- sendMessage b aliceId (SMP.MsgFlags True) "hello again"
get b ##> ("", aliceId, SENT msgId)
_ <- messageNotificationData a apnsQ
get a =##> \case ("", c, Msg "hello again") -> c == bobId; _ -> False
where
runServers :: ExceptT AgentErrorType IO a -> IO a
runServers a = do
withSmpServerStoreLogOn t testPort $ \t1 -> do
res <- withSmpServerConfigOn t cfg {storeLogFile = Just testStoreLogFile2} testPort2 $ \t2 ->
runRight a `finally` killThread t2
killThread t1
pure res
testSwitchNotifications :: InitialAgentServers -> APNSMockServer -> IO ()
testSwitchNotifications servers APNSMockServer {apnsQ} = do
a <- getSMPAgentClient' agentCfg servers testDB
@@ -547,7 +496,7 @@ testSwitchNotifications servers APNSMockServer {apnsQ} = do
get a =##> \case ("", c, Msg msg') -> c == bId && msg == msg'; _ -> False
ackMessage a bId msgId
testMessage "hello"
_ <- switchConnectionAsync a "" bId
switchConnectionAsync a "" bId
switchComplete a bId b aId
liftIO $ threadDelay 500000
testMessage "hello again"
-4
View File
@@ -167,7 +167,6 @@ rcvQueue1 =
dbQueueId = 1,
primary = True,
dbReplaceQueueId = Nothing,
rcvSwchStatus = Nothing,
smpClientVersion = 1,
clientNtfCreds = Nothing,
deleteErrors = 0
@@ -188,7 +187,6 @@ sndQueue1 =
dbQueueId = 1,
primary = True,
dbReplaceQueueId = Nothing,
sndSwchStatus = Nothing,
smpClientVersion = 1
}
@@ -326,7 +324,6 @@ testUpgradeRcvConnToDuplex =
e2eDhSecret = testDhSecret,
status = New,
dbQueueId = 1,
sndSwchStatus = Nothing,
primary = True,
dbReplaceQueueId = Nothing,
smpClientVersion = 1
@@ -355,7 +352,6 @@ testUpgradeSndConnToDuplex =
sndId = "4567",
status = New,
dbQueueId = 1,
rcvSwchStatus = Nothing,
primary = True,
dbReplaceQueueId = Nothing,
smpClientVersion = 1,
+2 -1
View File
@@ -90,6 +90,7 @@ ntfServerCfg =
},
inactiveClientExpiration = Just defaultInactiveClientExpiration,
storeLogFile = Nothing,
resubscribeDelay = 1000,
-- CA certificate private key is not needed for initialization
caCertificateFile = "tests/fixtures/ca.crt",
privateKeyFile = "tests/fixtures/server.key",
@@ -133,7 +134,7 @@ ntfServerTest _ t = runNtfTest $ \h -> tPut' h t >> tGet' h
where
tPut' h (sig, corrId, queueId, smp) = do
let t' = smpEncode (sessionId (h :: THandle c), corrId, queueId, smp)
[Right ()] <- tPut h Nothing [(sig, t')]
[Right ()] <- tPut h [(sig, t')]
pure ()
tGet' h = do
[(Nothing, _, (CorrId corrId, qId, Right cmd))] <- tGet h
+1 -1
View File
@@ -159,7 +159,7 @@ smpServerTest _ t = runSmpTest $ \h -> tPut' h t >> tGet' h
where
tPut' h (sig, corrId, queueId, smp) = do
let t' = smpEncode (sessionId (h :: THandle c), corrId, queueId, smp)
[Right ()] <- tPut h Nothing [(sig, t')]
[Right ()] <- tPut h [(sig, t')]
pure ()
tGet' h = do
[(Nothing, _, (CorrId corrId, qId, Right cmd))] <- tGet h
+1 -1
View File
@@ -88,7 +88,7 @@ signSendRecv h@THandle {thVersion, sessionId} pk (corrId, qId, cmd) = do
tPut1 :: Transport c => THandle c -> SentRawTransmission -> IO (Either TransportError ())
tPut1 h t = do
[r] <- tPut h Nothing [t]
[r] <- tPut h [t]
pure r
tGet1 :: (ProtocolEncoding err cmd, Transport c, MonadIO m, MonadFail m) => THandle c -> m (SignedTransmission err cmd)