mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-09-01 18:08:36 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4268b90763 | ||
|
|
fa772af6c6 | ||
|
|
bec4e5e038 | ||
|
|
24ded9e5a0 | ||
|
|
f5e666ae4f | ||
|
|
62133ceb24 | ||
|
|
3b50e1fb7d | ||
|
|
7c25b3b1e0 | ||
|
|
a70bd02c67 | ||
|
|
7000431249 |
@@ -1,3 +1,31 @@
|
|||||||
|
# 6.0.5
|
||||||
|
|
||||||
|
SMP agent:
|
||||||
|
- support generic SOCKS proxy (without isolate-by-auth).
|
||||||
|
- reduce max message sizes
|
||||||
|
|
||||||
|
# 6.0.4
|
||||||
|
|
||||||
|
SMP server:
|
||||||
|
- better performance/memory: fewer map updates on re-subscriptions (#1297), split and reduce STM transactions (#1294)
|
||||||
|
- send DELD when subscribed queue is deleted (#1312)
|
||||||
|
- add created/updated/used date to queues to manage expiration (#1306)
|
||||||
|
|
||||||
|
XFTP server: truncate file creation time to 1 hour (#1310)
|
||||||
|
|
||||||
|
Servers:
|
||||||
|
- bind control port only to 127.0.0.1 for better security in case of firewall misconfiguration (#1280)
|
||||||
|
- reduce memory used for period stats (#1298)
|
||||||
|
|
||||||
|
Agent: process last notification from list (#1307)
|
||||||
|
- report receive file error with redirected file ID, when redirect is present (#1304)
|
||||||
|
- special error when deleted user record is not in database (#1303)
|
||||||
|
- fix race when sending a message to the deleted connection (#1296)
|
||||||
|
- support for multiple messages in a single notification
|
||||||
|
|
||||||
|
Ntf server:
|
||||||
|
- only use SOCKS proxy for servers without public address (#1314)
|
||||||
|
|
||||||
# 6.0.3
|
# 6.0.3
|
||||||
|
|
||||||
Agent:
|
Agent:
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: simplexmq
|
name: simplexmq
|
||||||
version: 6.0.3.0
|
version: 6.0.5.0
|
||||||
synopsis: SimpleXMQ message broker
|
synopsis: SimpleXMQ message broker
|
||||||
description: |
|
description: |
|
||||||
This package includes <./docs/Simplex-Messaging-Server.html server>,
|
This package includes <./docs/Simplex-Messaging-Server.html server>,
|
||||||
|
|||||||
+2
-2
@@ -250,7 +250,7 @@ In pseudo-code:
|
|||||||
```
|
```
|
||||||
// session 1
|
// session 1
|
||||||
hostHelloSecret(1) = dhSecret(1)
|
hostHelloSecret(1) = dhSecret(1)
|
||||||
sessionSecret(1) = sha3-256(dhSecret(1) || kemSecret(1)) // to encrypt session 1 data, incl. controller hello
|
sessionSecret(1) = sha256(dhSecret(1) || kemSecret(1)) // to encrypt session 1 data, incl. controller hello
|
||||||
dhSecret(1) = dh(hostHelloDhKey(1), controllerInvitationDhKey(1))
|
dhSecret(1) = dh(hostHelloDhKey(1), controllerInvitationDhKey(1))
|
||||||
kemCiphertext(1) = enc(kemSecret(1), kemEncKey(1))
|
kemCiphertext(1) = enc(kemSecret(1), kemEncKey(1))
|
||||||
// kemEncKey is included in host HELLO, kemCiphertext - in controller HELLO
|
// kemEncKey is included in host HELLO, kemCiphertext - in controller HELLO
|
||||||
@@ -262,7 +262,7 @@ dhSecret(n') = dh(hostHelloDhKey(n - 1), controllerDhKey(n))
|
|||||||
|
|
||||||
// session n
|
// session n
|
||||||
hostHelloSecret(n) = dhSecret(n)
|
hostHelloSecret(n) = dhSecret(n)
|
||||||
sessionSecret(n) = sha3-256(dhSecret(n) || kemSecret(n)) // to encrypt session n data, incl. controller hello
|
sessionSecret(n) = sha256(dhSecret(n) || kemSecret(n)) // to encrypt session n data, incl. controller hello
|
||||||
dhSecret(n) = dh(hostHelloDhKey(n), controllerDhKey(n))
|
dhSecret(n) = dh(hostHelloDhKey(n), controllerDhKey(n))
|
||||||
// controllerDhKey(n) is either from invitation or from multicast announcement
|
// controllerDhKey(n) is either from invitation or from multicast announcement
|
||||||
kemCiphertext(n) = enc(kemSecret(n), kemEncKey(n))
|
kemCiphertext(n) = enc(kemSecret(n), kemEncKey(n))
|
||||||
|
|||||||
@@ -1,30 +1,174 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
path_conf_var="/var/opt"
|
# Common
|
||||||
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')"
|
date="$(date -u '+%Y-%m-%dT%H:%M:%S')"
|
||||||
|
|
||||||
backup_smp() {
|
GRN='\033[0;32m'
|
||||||
if [ -e "$path_conf_storelog_smp" ]; then
|
YLW='\033[1;33m'
|
||||||
cp "$path_conf_storelog_smp" "${path_conf_storelog_smp}.${date:-date-failed}"
|
BLU='\033[1;34m'
|
||||||
fi
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
path_conf_var="/var/opt"
|
||||||
|
|
||||||
|
smp_variables() {
|
||||||
|
path_conf_smp="$path_conf_var/simplex"
|
||||||
|
path_conf_smp_archive="$path_conf_smp/backups"
|
||||||
|
path_conf_smp_archive_storelog="$path_conf_smp_archive/queues"
|
||||||
|
path_conf_smp_archive_stats="$path_conf_smp_archive/stats"
|
||||||
|
path_conf_smp_archive_messages="$path_conf_smp_archive/messages"
|
||||||
|
path_conf_storelog_smp="$path_conf_smp/smp-server-store.log"
|
||||||
|
path_conf_storelog_smp_out="$path_conf_smp_archive_storelog/smp-server-store.log.${date:-date-failed}"
|
||||||
|
|
||||||
|
path_conf_stats_smp="$path_conf_smp/smp-server-stats.log"
|
||||||
|
path_conf_stats_smp_out="$path_conf_smp_archive_stats/smp-server-stats.log.${date:-date-failed}"
|
||||||
|
|
||||||
|
path_conf_messages_smp="$path_conf_smp/smp-server-messages.log"
|
||||||
|
path_conf_messages_smp_out="$path_conf_smp_archive_messages/smp-server-messages.log.${date:-date-failed}"
|
||||||
}
|
}
|
||||||
|
|
||||||
backup_xftp() {
|
xftp_variables() {
|
||||||
if [ -e "$path_conf_storelog_xftp" ]; then
|
path_conf_xftp="$path_conf_var/simplex-xftp"
|
||||||
cp "$path_conf_storelog_xftp" "${path_conf_storelog_xftp}.${date:-date-failed}"
|
path_conf_xftp_archive="$path_conf_xftp/backups"
|
||||||
fi
|
|
||||||
|
path_conf_xftp_archive_storelog="$path_conf_xftp_archive/queues"
|
||||||
|
path_conf_xftp_archive_stats="$path_conf_xftp_archive/stats"
|
||||||
|
|
||||||
|
path_conf_storelog_xftp="$path_conf_xftp/file-server-store.log"
|
||||||
|
path_conf_storelog_xftp_out="$path_conf_xftp_archive_storelog/file-server-store.log.${date:-date-failed}"
|
||||||
|
|
||||||
|
path_conf_stats_xftp="$path_conf_xftp/file-server-stats.log"
|
||||||
|
path_conf_stats_xftp_out="$path_conf_xftp_archive_stats/file-server-stats.log.${date:-date-failed}"
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ "$1" = 'smp-server' ]; then
|
checks() {
|
||||||
backup_smp
|
result=${SERVICE_RESULT:-exit-code}
|
||||||
elif [ "$1" = 'xftp-server' ]; then
|
status=${EXIT_STATUS:-TERM}
|
||||||
backup_xftp
|
|
||||||
else
|
case "$result" in
|
||||||
backup_smp
|
success)
|
||||||
backup_xftp
|
case "$status" in
|
||||||
fi
|
TERM)
|
||||||
|
printf "${RED}Refusing to backup files with failed service state${NC}\n"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
:
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf "${RED}Refusing to backup files with failed service state${NC}\n"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
smp_check() {
|
||||||
|
if [ ! -d "$path_conf_smp_archive_storelog" ]; then
|
||||||
|
mkdir -p "$path_conf_smp_archive_storelog"
|
||||||
|
fi
|
||||||
|
if [ ! -d "$path_conf_smp_archive_messages" ]; then
|
||||||
|
mkdir -p "$path_conf_smp_archive_messages"
|
||||||
|
fi
|
||||||
|
if [ ! -d "$path_conf_smp_archive_stats" ]; then
|
||||||
|
mkdir -p "$path_conf_smp_archive_stats"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
xftp_check() {
|
||||||
|
if [ ! -d "$path_conf_xftp_archive_storelog" ]; then
|
||||||
|
mkdir -p "$path_conf_xftp_archive_storelog"
|
||||||
|
fi
|
||||||
|
if [ ! -d "$path_conf_xftp_archive_stats" ]; then
|
||||||
|
mkdir -p "$path_conf_xftp_archive_stats"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
backup() {
|
||||||
|
file="$1"
|
||||||
|
out="$2"
|
||||||
|
file_type="$3"
|
||||||
|
|
||||||
|
if [ -e "$file" ]; then
|
||||||
|
if cp "$file" "$out"; then
|
||||||
|
printf "${YLW}${file_type}${NC} ${GRN}backup successful:${NC} ${BLU}%s${NC}\n" "${out}"
|
||||||
|
else
|
||||||
|
printf "${YLW}${file_type}${NC} ${RED}backup failed!${NC}\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset file out file_type
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
directory="$1"
|
||||||
|
|
||||||
|
file_type="$2"
|
||||||
|
|
||||||
|
files_date=$(find "$directory" -type f -exec stat --format="%y" {} + | awk '{print $1}' | sort -nr | uniq | awk 'NR==2')
|
||||||
|
|
||||||
|
if [ -n "$files_date" ]; then
|
||||||
|
files=$(find "$directory" -type f -not -newermt "$files_date" -printf "%T@ %Tc %p\n" | sort -n | awk '{print $NF}')
|
||||||
|
|
||||||
|
if [ -n "$files" ]; then
|
||||||
|
printf '%s' "$files" | xargs rm -f
|
||||||
|
printf "${YLW}Old ${file_type} files${NC}${GRN} has been deleted:${NC}\n"
|
||||||
|
files_colored=$(printf '%s' "$files" | awk '{print "\033[1;34m"$0"\033[0m"}')
|
||||||
|
printf "${files_colored}\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset directory file_type files_date files
|
||||||
|
}
|
||||||
|
|
||||||
|
smp_backup() {
|
||||||
|
backup "$path_conf_storelog_smp" "$path_conf_storelog_smp_out" 'Storelog'
|
||||||
|
backup "$path_conf_messages_smp" "$path_conf_messages_smp_out" 'Messages'
|
||||||
|
backup "$path_conf_stats_smp" "$path_conf_stats_smp_out" 'Stats'
|
||||||
|
}
|
||||||
|
|
||||||
|
smp_cleanup() {
|
||||||
|
cleanup "$path_conf_smp_archive_storelog" 'storelog'
|
||||||
|
cleanup "$path_conf_smp_archive_stats" 'stats'
|
||||||
|
cleanup "$path_conf_smp_archive_messages" 'messages'
|
||||||
|
}
|
||||||
|
|
||||||
|
xftp_backup() {
|
||||||
|
backup "$path_conf_storelog_xftp" "$path_conf_storelog_xftp_out" 'Storelog'
|
||||||
|
backup "$path_conf_stats_xftp" "$path_conf_stats_xftp_out" 'Stats'
|
||||||
|
}
|
||||||
|
|
||||||
|
xftp_cleanup() {
|
||||||
|
cleanup "$path_conf_xftp_archive_storelog" 'storelog'
|
||||||
|
cleanup "$path_conf_xftp_archive_stats" 'stats'
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
type="${1:-}"
|
||||||
|
|
||||||
|
checks
|
||||||
|
|
||||||
|
case "$type" in
|
||||||
|
smp-server)
|
||||||
|
smp_variables
|
||||||
|
smp_check
|
||||||
|
smp_backup
|
||||||
|
smp_cleanup
|
||||||
|
;;
|
||||||
|
xftp-server)
|
||||||
|
xftp_variables
|
||||||
|
xftp_check
|
||||||
|
xftp_backup
|
||||||
|
xftp_cleanup
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf "${YLW}Unknown server type.${NC}\n"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|||||||
+2
-1
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
|||||||
-- see: https://github.com/sol/hpack
|
-- see: https://github.com/sol/hpack
|
||||||
|
|
||||||
name: simplexmq
|
name: simplexmq
|
||||||
version: 6.0.3.0
|
version: 6.0.5.0
|
||||||
synopsis: SimpleXMQ message broker
|
synopsis: SimpleXMQ message broker
|
||||||
description: This package includes <./docs/Simplex-Messaging-Server.html server>,
|
description: This package includes <./docs/Simplex-Messaging-Server.html server>,
|
||||||
<./docs/Simplex-Messaging-Client.html client> and
|
<./docs/Simplex-Messaging-Client.html client> and
|
||||||
@@ -610,6 +610,7 @@ test-suite simplexmq-test
|
|||||||
CoreTests.CryptoTests
|
CoreTests.CryptoTests
|
||||||
CoreTests.EncodingTests
|
CoreTests.EncodingTests
|
||||||
CoreTests.RetryIntervalTests
|
CoreTests.RetryIntervalTests
|
||||||
|
CoreTests.SOCKSSettings
|
||||||
CoreTests.TRcvQueuesTests
|
CoreTests.TRcvQueuesTests
|
||||||
CoreTests.UtilTests
|
CoreTests.UtilTests
|
||||||
CoreTests.VersionRangeTests
|
CoreTests.VersionRangeTests
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import Simplex.Messaging.Client
|
|||||||
defaultNetworkConfig,
|
defaultNetworkConfig,
|
||||||
proxyUsername,
|
proxyUsername,
|
||||||
transportClientConfig,
|
transportClientConfig,
|
||||||
|
clientSocksCredentials,
|
||||||
unexpectedResponse,
|
unexpectedResponse,
|
||||||
)
|
)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
@@ -100,7 +101,7 @@ defaultXFTPClientConfig =
|
|||||||
|
|
||||||
getXFTPClient :: TransportSession FileResponse -> XFTPClientConfig -> (XFTPClient -> IO ()) -> IO (Either XFTPClientError XFTPClient)
|
getXFTPClient :: TransportSession FileResponse -> XFTPClientConfig -> (XFTPClient -> IO ()) -> IO (Either XFTPClientError XFTPClient)
|
||||||
getXFTPClient transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN, xftpNetworkConfig, serverVRange} disconnected = runExceptT $ do
|
getXFTPClient transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN, xftpNetworkConfig, serverVRange} disconnected = runExceptT $ do
|
||||||
let username = proxyUsername transportSession
|
let socksCreds = clientSocksCredentials xftpNetworkConfig $ proxyUsername transportSession
|
||||||
ProtocolServer _ host port keyHash = srv
|
ProtocolServer _ host port keyHash = srv
|
||||||
useHost <- liftEither $ chooseTransportHost xftpNetworkConfig host
|
useHost <- liftEither $ chooseTransportHost xftpNetworkConfig host
|
||||||
let tcConfig = (transportClientConfig xftpNetworkConfig useHost) {alpn = clientALPN}
|
let tcConfig = (transportClientConfig xftpNetworkConfig useHost) {alpn = clientALPN}
|
||||||
@@ -108,7 +109,7 @@ getXFTPClient transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN,
|
|||||||
clientVar <- newTVarIO Nothing
|
clientVar <- newTVarIO Nothing
|
||||||
let usePort = if null port then "443" else port
|
let usePort = if null port then "443" else port
|
||||||
clientDisconnected = readTVarIO clientVar >>= mapM_ disconnected
|
clientDisconnected = readTVarIO clientVar >>= mapM_ disconnected
|
||||||
http2Client <- liftError' xftpClientError $ getVerifiedHTTP2Client (Just username) useHost usePort (Just keyHash) Nothing http2Config clientDisconnected
|
http2Client <- liftError' xftpClientError $ getVerifiedHTTP2Client socksCreds useHost usePort (Just keyHash) Nothing http2Config clientDisconnected
|
||||||
let HTTP2Client {sessionId, sessionALPN} = http2Client
|
let HTTP2Client {sessionId, sessionALPN} = http2Client
|
||||||
v = VersionXFTP 1
|
v = VersionXFTP 1
|
||||||
thServerVRange = versionToRange v
|
thServerVRange = versionToRange v
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import qualified Data.Map.Strict as M
|
|||||||
import Data.Maybe (fromMaybe, isJust)
|
import Data.Maybe (fromMaybe, isJust)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Time.Clock (UTCTime (..), diffTimeToPicoseconds, getCurrentTime)
|
import Data.Time.Clock (UTCTime (..), diffTimeToPicoseconds, getCurrentTime)
|
||||||
import Data.Time.Clock.System (SystemTime (..), getSystemTime)
|
|
||||||
import Data.Time.Format.ISO8601 (iso8601Show)
|
import Data.Time.Format.ISO8601 (iso8601Show)
|
||||||
import Data.Word (Word32)
|
import Data.Word (Word32)
|
||||||
import qualified Data.X509 as X
|
import qualified Data.X509 as X
|
||||||
@@ -57,6 +56,7 @@ import Simplex.Messaging.Encoding.String
|
|||||||
import Simplex.Messaging.Protocol (CorrId (..), EntityId (..), RcvPublicAuthKey, RcvPublicDhKey, RecipientId, TransmissionAuth, pattern NoEntity)
|
import Simplex.Messaging.Protocol (CorrId (..), EntityId (..), RcvPublicAuthKey, RcvPublicDhKey, RecipientId, TransmissionAuth, pattern NoEntity)
|
||||||
import Simplex.Messaging.Server (dummyVerifyCmd, verifyCmdAuthorization)
|
import Simplex.Messaging.Server (dummyVerifyCmd, verifyCmdAuthorization)
|
||||||
import Simplex.Messaging.Server.Expiration
|
import Simplex.Messaging.Server.Expiration
|
||||||
|
import Simplex.Messaging.Server.QueueStore (RoundedSystemTime, getRoundedSystemTime)
|
||||||
import Simplex.Messaging.Server.Stats
|
import Simplex.Messaging.Server.Stats
|
||||||
import Simplex.Messaging.TMap (TMap)
|
import Simplex.Messaging.TMap (TMap)
|
||||||
import qualified Simplex.Messaging.TMap as TM
|
import qualified Simplex.Messaging.TMap as TM
|
||||||
@@ -399,7 +399,7 @@ processXFTPRequest HTTP2Body {bodyPart} = \case
|
|||||||
r <- runExceptT $ do
|
r <- runExceptT $ do
|
||||||
sizes <- asks $ allowedChunkSizes . config
|
sizes <- asks $ allowedChunkSizes . config
|
||||||
unless (size file `elem` sizes) $ throwE SIZE
|
unless (size file `elem` sizes) $ throwE SIZE
|
||||||
ts <- liftIO getSystemTime
|
ts <- liftIO getFileTime
|
||||||
-- TODO validate body empty
|
-- TODO validate body empty
|
||||||
sId <- ExceptT $ addFileRetry st file 3 ts
|
sId <- ExceptT $ addFileRetry st file 3 ts
|
||||||
rcps <- mapM (ExceptT . addRecipientRetry st 3 sId) rks
|
rcps <- mapM (ExceptT . addRecipientRetry st 3 sId) rks
|
||||||
@@ -412,7 +412,7 @@ processXFTPRequest HTTP2Body {bodyPart} = \case
|
|||||||
let rIds = L.map (\(FileRecipient rId _) -> rId) rcps
|
let rIds = L.map (\(FileRecipient rId _) -> rId) rcps
|
||||||
pure $ FRSndIds sId rIds
|
pure $ FRSndIds sId rIds
|
||||||
pure $ either FRErr id r
|
pure $ either FRErr id r
|
||||||
addFileRetry :: FileStore -> FileInfo -> Int -> SystemTime -> M (Either XFTPErrorType XFTPFileId)
|
addFileRetry :: FileStore -> FileInfo -> Int -> RoundedSystemTime -> M (Either XFTPErrorType XFTPFileId)
|
||||||
addFileRetry st file n ts =
|
addFileRetry st file n ts =
|
||||||
retryAdd n $ \sId -> runExceptT $ do
|
retryAdd n $ \sId -> runExceptT $ do
|
||||||
ExceptT $ addFile st sId file ts
|
ExceptT $ addFile st sId file ts
|
||||||
@@ -531,6 +531,9 @@ deleteServerFile_ FileRec {senderId, fileInfo, filePath} = do
|
|||||||
liftIO $ atomicModifyIORef'_ (filesCount stats) (subtract 1)
|
liftIO $ atomicModifyIORef'_ (filesCount stats) (subtract 1)
|
||||||
liftIO $ atomicModifyIORef'_ (filesSize stats) (subtract $ fromIntegral $ size fileInfo)
|
liftIO $ atomicModifyIORef'_ (filesSize stats) (subtract $ fromIntegral $ size fileInfo)
|
||||||
|
|
||||||
|
getFileTime :: IO RoundedSystemTime
|
||||||
|
getFileTime = getRoundedSystemTime fileTimePrecision
|
||||||
|
|
||||||
expireServerFiles :: Maybe Int -> ExpirationConfig -> M ()
|
expireServerFiles :: Maybe Int -> ExpirationConfig -> M ()
|
||||||
expireServerFiles itemDelay expCfg = do
|
expireServerFiles itemDelay expCfg = do
|
||||||
st <- asks store
|
st <- asks store
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ module Simplex.FileTransfer.Server.Store
|
|||||||
expiredFilePath,
|
expiredFilePath,
|
||||||
getFile,
|
getFile,
|
||||||
ackFile,
|
ackFile,
|
||||||
|
fileTimePrecision,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
@@ -25,12 +26,12 @@ import qualified Data.Attoparsec.ByteString.Char8 as A
|
|||||||
import Data.Int (Int64)
|
import Data.Int (Int64)
|
||||||
import Data.Set (Set)
|
import Data.Set (Set)
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import Data.Time.Clock.System (SystemTime (..))
|
|
||||||
import Simplex.FileTransfer.Protocol (FileInfo (..), SFileParty (..), XFTPFileId)
|
import Simplex.FileTransfer.Protocol (FileInfo (..), SFileParty (..), XFTPFileId)
|
||||||
import Simplex.FileTransfer.Transport (XFTPErrorType (..))
|
import Simplex.FileTransfer.Transport (XFTPErrorType (..))
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Protocol (RcvPublicAuthKey, RecipientId, SenderId)
|
import Simplex.Messaging.Protocol (RcvPublicAuthKey, RecipientId, SenderId)
|
||||||
|
import Simplex.Messaging.Server.QueueStore (RoundedSystemTime (..))
|
||||||
import Simplex.Messaging.TMap (TMap)
|
import Simplex.Messaging.TMap (TMap)
|
||||||
import qualified Simplex.Messaging.TMap as TM
|
import qualified Simplex.Messaging.TMap as TM
|
||||||
import Simplex.Messaging.Util (ifM, ($>>=))
|
import Simplex.Messaging.Util (ifM, ($>>=))
|
||||||
@@ -46,9 +47,12 @@ data FileRec = FileRec
|
|||||||
fileInfo :: FileInfo,
|
fileInfo :: FileInfo,
|
||||||
filePath :: TVar (Maybe FilePath),
|
filePath :: TVar (Maybe FilePath),
|
||||||
recipientIds :: TVar (Set RecipientId),
|
recipientIds :: TVar (Set RecipientId),
|
||||||
createdAt :: SystemTime
|
createdAt :: RoundedSystemTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileTimePrecision :: Int64
|
||||||
|
fileTimePrecision = 3600 -- truncate creation time to 1 hour
|
||||||
|
|
||||||
data FileRecipient = FileRecipient RecipientId RcvPublicAuthKey
|
data FileRecipient = FileRecipient RecipientId RcvPublicAuthKey
|
||||||
|
|
||||||
instance StrEncoding FileRecipient where
|
instance StrEncoding FileRecipient where
|
||||||
@@ -62,14 +66,14 @@ newFileStore = do
|
|||||||
usedStorage <- newTVarIO 0
|
usedStorage <- newTVarIO 0
|
||||||
pure FileStore {files, recipients, usedStorage}
|
pure FileStore {files, recipients, usedStorage}
|
||||||
|
|
||||||
addFile :: FileStore -> SenderId -> FileInfo -> SystemTime -> STM (Either XFTPErrorType ())
|
addFile :: FileStore -> SenderId -> FileInfo -> RoundedSystemTime -> STM (Either XFTPErrorType ())
|
||||||
addFile FileStore {files} sId fileInfo createdAt =
|
addFile FileStore {files} sId fileInfo createdAt =
|
||||||
ifM (TM.member sId files) (pure $ Left DUPLICATE_) $ do
|
ifM (TM.member sId files) (pure $ Left DUPLICATE_) $ do
|
||||||
f <- newFileRec sId fileInfo createdAt
|
f <- newFileRec sId fileInfo createdAt
|
||||||
TM.insert sId f files
|
TM.insert sId f files
|
||||||
pure $ Right ()
|
pure $ Right ()
|
||||||
|
|
||||||
newFileRec :: SenderId -> FileInfo -> SystemTime -> STM FileRec
|
newFileRec :: SenderId -> FileInfo -> RoundedSystemTime -> STM FileRec
|
||||||
newFileRec senderId fileInfo createdAt = do
|
newFileRec senderId fileInfo createdAt = do
|
||||||
recipientIds <- newTVar S.empty
|
recipientIds <- newTVar S.empty
|
||||||
filePath <- newTVar Nothing
|
filePath <- newTVar Nothing
|
||||||
@@ -120,8 +124,8 @@ getFile st party fId = case party of
|
|||||||
expiredFilePath :: FileStore -> XFTPFileId -> Int64 -> STM (Maybe (Maybe FilePath))
|
expiredFilePath :: FileStore -> XFTPFileId -> Int64 -> STM (Maybe (Maybe FilePath))
|
||||||
expiredFilePath FileStore {files} sId old =
|
expiredFilePath FileStore {files} sId old =
|
||||||
TM.lookup sId files
|
TM.lookup sId files
|
||||||
$>>= \FileRec {filePath, createdAt} ->
|
$>>= \FileRec {filePath, createdAt = RoundedSystemTime createdAt} ->
|
||||||
if systemSeconds createdAt < old
|
if createdAt + fileTimePrecision < old
|
||||||
then Just <$> readTVar filePath
|
then Just <$> readTVar filePath
|
||||||
else pure Nothing
|
else pure Nothing
|
||||||
|
|
||||||
|
|||||||
@@ -28,18 +28,18 @@ import Data.List.NonEmpty (NonEmpty)
|
|||||||
import qualified Data.List.NonEmpty as L
|
import qualified Data.List.NonEmpty as L
|
||||||
import Data.Map.Strict (Map)
|
import Data.Map.Strict (Map)
|
||||||
import qualified Data.Map.Strict as M
|
import qualified Data.Map.Strict as M
|
||||||
import Data.Time.Clock.System (SystemTime)
|
|
||||||
import Simplex.FileTransfer.Protocol (FileInfo (..))
|
import Simplex.FileTransfer.Protocol (FileInfo (..))
|
||||||
import Simplex.FileTransfer.Server.Store
|
import Simplex.FileTransfer.Server.Store
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Protocol (RcvPublicAuthKey, RecipientId, SenderId)
|
import Simplex.Messaging.Protocol (RcvPublicAuthKey, RecipientId, SenderId)
|
||||||
|
import Simplex.Messaging.Server.QueueStore (RoundedSystemTime)
|
||||||
import Simplex.Messaging.Server.StoreLog
|
import Simplex.Messaging.Server.StoreLog
|
||||||
import Simplex.Messaging.Util (bshow, whenM)
|
import Simplex.Messaging.Util (bshow, whenM)
|
||||||
import System.Directory (doesFileExist, renameFile)
|
import System.Directory (doesFileExist, renameFile)
|
||||||
import System.IO
|
import System.IO
|
||||||
|
|
||||||
data FileStoreLogRecord
|
data FileStoreLogRecord
|
||||||
= AddFile SenderId FileInfo SystemTime
|
= AddFile SenderId FileInfo RoundedSystemTime
|
||||||
| PutFile SenderId FilePath
|
| PutFile SenderId FilePath
|
||||||
| AddRecipients SenderId (NonEmpty FileRecipient)
|
| AddRecipients SenderId (NonEmpty FileRecipient)
|
||||||
| DeleteFile SenderId
|
| DeleteFile SenderId
|
||||||
@@ -64,7 +64,7 @@ instance StrEncoding FileStoreLogRecord where
|
|||||||
logFileStoreRecord :: StoreLog 'WriteMode -> FileStoreLogRecord -> IO ()
|
logFileStoreRecord :: StoreLog 'WriteMode -> FileStoreLogRecord -> IO ()
|
||||||
logFileStoreRecord = writeStoreLogRecord
|
logFileStoreRecord = writeStoreLogRecord
|
||||||
|
|
||||||
logAddFile :: StoreLog 'WriteMode -> SenderId -> FileInfo -> SystemTime -> IO ()
|
logAddFile :: StoreLog 'WriteMode -> SenderId -> FileInfo -> RoundedSystemTime -> IO ()
|
||||||
logAddFile s = logFileStoreRecord s .:. AddFile
|
logAddFile s = logFileStoreRecord s .:. AddFile
|
||||||
|
|
||||||
logPutFile :: StoreLog 'WriteMode -> SenderId -> FilePath -> IO ()
|
logPutFile :: StoreLog 'WriteMode -> SenderId -> FilePath -> IO ()
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ import Simplex.Messaging.Agent.Store
|
|||||||
import Simplex.Messaging.Agent.Store.SQLite
|
import Simplex.Messaging.Agent.Store.SQLite
|
||||||
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
||||||
import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations
|
import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations
|
||||||
import Simplex.Messaging.Client (ProtocolClient (..), SMPClientError, ServerTransmission (..), ServerTransmissionBatch, temporaryClientError, unexpectedResponse)
|
import Simplex.Messaging.Client (SMPClientError, ServerTransmission (..), ServerTransmissionBatch, temporaryClientError, unexpectedResponse)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Crypto.File (CryptoFile, CryptoFileArgs)
|
import Simplex.Messaging.Crypto.File (CryptoFile, CryptoFileArgs)
|
||||||
import Simplex.Messaging.Crypto.Ratchet (PQEncryption, PQSupport (..), pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff, pattern PQSupportOn)
|
import Simplex.Messaging.Crypto.Ratchet (PQEncryption, PQSupport (..), pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff, pattern PQSupportOn)
|
||||||
@@ -181,7 +181,7 @@ import Simplex.Messaging.Protocol (BrokerMsg, Cmd (..), ErrorType (AUTH), MsgBod
|
|||||||
import qualified Simplex.Messaging.Protocol as SMP
|
import qualified Simplex.Messaging.Protocol as SMP
|
||||||
import Simplex.Messaging.ServiceScheme (ServiceScheme (..))
|
import Simplex.Messaging.ServiceScheme (ServiceScheme (..))
|
||||||
import qualified Simplex.Messaging.TMap as TM
|
import qualified Simplex.Messaging.TMap as TM
|
||||||
import Simplex.Messaging.Transport (SMPVersion, THandleParams (sessionId))
|
import Simplex.Messaging.Transport (SMPVersion)
|
||||||
import Simplex.Messaging.Util
|
import Simplex.Messaging.Util
|
||||||
import Simplex.Messaging.Version
|
import Simplex.Messaging.Version
|
||||||
import Simplex.RemoteControl.Client
|
import Simplex.RemoteControl.Client
|
||||||
@@ -2450,17 +2450,14 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), _v, sessId
|
|||||||
handleNotifyAck :: AM ACKd -> AM ACKd
|
handleNotifyAck :: AM ACKd -> AM ACKd
|
||||||
handleNotifyAck m = m `catchAgentError` \e -> notify (ERR e) >> ack
|
handleNotifyAck m = m `catchAgentError` \e -> notify (ERR e) >> ack
|
||||||
SMP.END ->
|
SMP.END ->
|
||||||
atomically (TM.lookup tSess (smpClients c) $>>= (tryReadTMVar . sessionVar) >>= processEND)
|
atomically (ifM (activeClientSession c tSess sessId) (removeSubscription c connId $> True) (pure False))
|
||||||
>>= notifyEnd
|
>>= notifyEnd
|
||||||
where
|
where
|
||||||
processEND = \case
|
|
||||||
Just (Right clnt)
|
|
||||||
| sessId == sessionId (thParams $ connectedClient clnt) ->
|
|
||||||
removeSubscription c connId $> True
|
|
||||||
_ -> pure False
|
|
||||||
notifyEnd removed
|
notifyEnd removed
|
||||||
| removed = notify END >> logServer "<--" c srv rId "END"
|
| removed = notify END >> logServer "<--" c srv rId "END"
|
||||||
| otherwise = logServer "<--" c srv rId "END from disconnected client - ignored"
|
| otherwise = logServer "<--" c srv rId "END from disconnected client - ignored"
|
||||||
|
-- Possibly, we need to add some flag to connection that it was deleted
|
||||||
|
SMP.DELD -> atomically (removeSubscription c connId) >> notify DELD
|
||||||
SMP.ERR e -> notify $ ERR $ SMP (B.unpack $ strEncode srv) e
|
SMP.ERR e -> notify $ ERR $ SMP (B.unpack $ strEncode srv) e
|
||||||
r -> unexpected r
|
r -> unexpected r
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -277,14 +277,14 @@ supportedSMPAgentVRange = mkVersionRange minSupportedSMPAgentVersion currentSMPA
|
|||||||
e2eEncConnInfoLength :: VersionSMPA -> PQSupport -> Int
|
e2eEncConnInfoLength :: VersionSMPA -> PQSupport -> Int
|
||||||
e2eEncConnInfoLength v = \case
|
e2eEncConnInfoLength v = \case
|
||||||
-- reduced by 3726 (roughly the increase of message ratchet header size + key and ciphertext in reply link)
|
-- reduced by 3726 (roughly the increase of message ratchet header size + key and ciphertext in reply link)
|
||||||
PQSupportOn | v >= pqdrSMPAgentVersion -> 11122
|
PQSupportOn | v >= pqdrSMPAgentVersion -> 11106
|
||||||
_ -> 14848
|
_ -> 14832
|
||||||
|
|
||||||
e2eEncAgentMsgLength :: VersionSMPA -> PQSupport -> Int
|
e2eEncAgentMsgLength :: VersionSMPA -> PQSupport -> Int
|
||||||
e2eEncAgentMsgLength v = \case
|
e2eEncAgentMsgLength v = \case
|
||||||
-- reduced by 2222 (the increase of message ratchet header size)
|
-- reduced by 2222 (the increase of message ratchet header size)
|
||||||
PQSupportOn | v >= pqdrSMPAgentVersion -> 13634
|
PQSupportOn | v >= pqdrSMPAgentVersion -> 13618
|
||||||
_ -> 15856
|
_ -> 15840
|
||||||
|
|
||||||
-- | SMP agent event
|
-- | SMP agent event
|
||||||
type ATransmission = (ACorrId, AEntityId, AEvt)
|
type ATransmission = (ACorrId, AEntityId, AEvt)
|
||||||
@@ -344,6 +344,7 @@ data AEvent (e :: AEntity) where
|
|||||||
INFO :: PQSupport -> ConnInfo -> AEvent AEConn
|
INFO :: PQSupport -> ConnInfo -> AEvent AEConn
|
||||||
CON :: PQEncryption -> AEvent AEConn -- notification that connection is established
|
CON :: PQEncryption -> AEvent AEConn -- notification that connection is established
|
||||||
END :: AEvent AEConn
|
END :: AEvent AEConn
|
||||||
|
DELD :: AEvent AEConn
|
||||||
CONNECT :: AProtocolType -> TransportHost -> AEvent AENone
|
CONNECT :: AProtocolType -> TransportHost -> AEvent AENone
|
||||||
DISCONNECT :: AProtocolType -> TransportHost -> AEvent AENone
|
DISCONNECT :: AProtocolType -> TransportHost -> AEvent AENone
|
||||||
DOWN :: SMPServer -> [ConnId] -> AEvent AENone
|
DOWN :: SMPServer -> [ConnId] -> AEvent AENone
|
||||||
@@ -413,6 +414,7 @@ data AEventTag (e :: AEntity) where
|
|||||||
INFO_ :: AEventTag AEConn
|
INFO_ :: AEventTag AEConn
|
||||||
CON_ :: AEventTag AEConn
|
CON_ :: AEventTag AEConn
|
||||||
END_ :: AEventTag AEConn
|
END_ :: AEventTag AEConn
|
||||||
|
DELD_ :: AEventTag AEConn
|
||||||
CONNECT_ :: AEventTag AENone
|
CONNECT_ :: AEventTag AENone
|
||||||
DISCONNECT_ :: AEventTag AENone
|
DISCONNECT_ :: AEventTag AENone
|
||||||
DOWN_ :: AEventTag AENone
|
DOWN_ :: AEventTag AENone
|
||||||
@@ -466,6 +468,7 @@ aEventTag = \case
|
|||||||
INFO {} -> INFO_
|
INFO {} -> INFO_
|
||||||
CON _ -> CON_
|
CON _ -> CON_
|
||||||
END -> END_
|
END -> END_
|
||||||
|
DELD -> DELD_
|
||||||
CONNECT {} -> CONNECT_
|
CONNECT {} -> CONNECT_
|
||||||
DISCONNECT {} -> DISCONNECT_
|
DISCONNECT {} -> DISCONNECT_
|
||||||
DOWN {} -> DOWN_
|
DOWN {} -> DOWN_
|
||||||
|
|||||||
@@ -80,10 +80,12 @@ module Simplex.Messaging.Client
|
|||||||
defaultSMPClientConfig,
|
defaultSMPClientConfig,
|
||||||
defaultNetworkConfig,
|
defaultNetworkConfig,
|
||||||
transportClientConfig,
|
transportClientConfig,
|
||||||
|
clientSocksCredentials,
|
||||||
chooseTransportHost,
|
chooseTransportHost,
|
||||||
proxyUsername,
|
proxyUsername,
|
||||||
temporaryClientError,
|
temporaryClientError,
|
||||||
smpProxyError,
|
smpProxyError,
|
||||||
|
textToHostMode,
|
||||||
ServerTransmissionBatch,
|
ServerTransmissionBatch,
|
||||||
ServerTransmission (..),
|
ServerTransmission (..),
|
||||||
ClientCommand,
|
ClientCommand,
|
||||||
@@ -122,10 +124,13 @@ import Data.List (find)
|
|||||||
import Data.List.NonEmpty (NonEmpty (..))
|
import Data.List.NonEmpty (NonEmpty (..))
|
||||||
import qualified Data.List.NonEmpty as L
|
import qualified Data.List.NonEmpty as L
|
||||||
import Data.Maybe (catMaybes, fromMaybe)
|
import Data.Maybe (catMaybes, fromMaybe)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
import Data.Time.Clock (UTCTime (..), diffUTCTime, getCurrentTime)
|
import Data.Time.Clock (UTCTime (..), diffUTCTime, getCurrentTime)
|
||||||
import qualified Data.X509 as X
|
import qualified Data.X509 as X
|
||||||
import qualified Data.X509.Validation as XV
|
import qualified Data.X509.Validation as XV
|
||||||
import Network.Socket (ServiceName)
|
import Network.Socket (ServiceName)
|
||||||
|
import Network.Socks5 (SocksCredentials (..))
|
||||||
import Numeric.Natural
|
import Numeric.Natural
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding
|
import Simplex.Messaging.Encoding
|
||||||
@@ -136,7 +141,7 @@ import Simplex.Messaging.Server.QueueStore.QueueInfo
|
|||||||
import Simplex.Messaging.TMap (TMap)
|
import Simplex.Messaging.TMap (TMap)
|
||||||
import qualified Simplex.Messaging.TMap as TM
|
import qualified Simplex.Messaging.TMap as TM
|
||||||
import Simplex.Messaging.Transport
|
import Simplex.Messaging.Transport
|
||||||
import Simplex.Messaging.Transport.Client (SocksProxy, TransportClientConfig (..), TransportHost (..), defaultTcpConnectTimeout, runTransportClient)
|
import Simplex.Messaging.Transport.Client (SocksAuth (..), SocksProxyWithAuth (..), TransportClientConfig (..), TransportHost (..), defaultTcpConnectTimeout, runTransportClient)
|
||||||
import Simplex.Messaging.Transport.KeepAlive
|
import Simplex.Messaging.Transport.KeepAlive
|
||||||
import Simplex.Messaging.Transport.WebSockets (WS)
|
import Simplex.Messaging.Transport.WebSockets (WS)
|
||||||
import Simplex.Messaging.Util (bshow, diffToMicroseconds, ifM, liftEitherWith, raceAny_, threadDelay', tshow, whenM)
|
import Simplex.Messaging.Util (bshow, diffToMicroseconds, ifM, liftEitherWith, raceAny_, threadDelay', tshow, whenM)
|
||||||
@@ -236,6 +241,12 @@ data HostMode
|
|||||||
HMPublic
|
HMPublic
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
textToHostMode :: Text -> Either String HostMode
|
||||||
|
textToHostMode = \case
|
||||||
|
"public" -> Right HMPublic
|
||||||
|
"onion" -> Right HMOnionViaSocks
|
||||||
|
s -> Left $ T.unpack $ "Invalid host_mode: " <> s
|
||||||
|
|
||||||
data SocksMode
|
data SocksMode
|
||||||
= -- | always use SOCKS proxy when enabled
|
= -- | always use SOCKS proxy when enabled
|
||||||
SMAlways
|
SMAlways
|
||||||
@@ -257,7 +268,7 @@ instance StrEncoding SocksMode where
|
|||||||
-- | network configuration for the client
|
-- | network configuration for the client
|
||||||
data NetworkConfig = NetworkConfig
|
data NetworkConfig = NetworkConfig
|
||||||
{ -- | use SOCKS5 proxy
|
{ -- | use SOCKS5 proxy
|
||||||
socksProxy :: Maybe SocksProxy,
|
socksProxy :: Maybe SocksProxyWithAuth,
|
||||||
-- | when to use SOCKS proxy
|
-- | when to use SOCKS proxy
|
||||||
socksMode :: SocksMode,
|
socksMode :: SocksMode,
|
||||||
-- | determines critera which host is chosen from the list
|
-- | determines critera which host is chosen from the list
|
||||||
@@ -355,12 +366,22 @@ transportClientConfig :: NetworkConfig -> TransportHost -> TransportClientConfig
|
|||||||
transportClientConfig NetworkConfig {socksProxy, socksMode, tcpConnectTimeout, tcpKeepAlive, logTLSErrors} host =
|
transportClientConfig NetworkConfig {socksProxy, socksMode, tcpConnectTimeout, tcpKeepAlive, logTLSErrors} host =
|
||||||
TransportClientConfig {socksProxy = useSocksProxy socksMode, tcpConnectTimeout, tcpKeepAlive, logTLSErrors, clientCredentials = Nothing, alpn = Nothing}
|
TransportClientConfig {socksProxy = useSocksProxy socksMode, tcpConnectTimeout, tcpKeepAlive, logTLSErrors, clientCredentials = Nothing, alpn = Nothing}
|
||||||
where
|
where
|
||||||
useSocksProxy SMAlways = socksProxy
|
socksProxy' = (\(SocksProxyWithAuth _ proxy) -> proxy) <$> socksProxy
|
||||||
|
useSocksProxy SMAlways = socksProxy'
|
||||||
useSocksProxy SMOnion = case host of
|
useSocksProxy SMOnion = case host of
|
||||||
THOnionHost _ -> socksProxy
|
THOnionHost _ -> socksProxy'
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
{-# INLINE transportClientConfig #-}
|
{-# INLINE transportClientConfig #-}
|
||||||
|
|
||||||
|
clientSocksCredentials :: NetworkConfig -> ByteString -> Maybe SocksCredentials
|
||||||
|
clientSocksCredentials NetworkConfig {socksProxy} sessionUsername = case socksProxy of
|
||||||
|
Just (SocksProxyWithAuth auth _) -> case auth of
|
||||||
|
SocksAuthUsername {username, password} -> Just $ SocksCredentials username password
|
||||||
|
SocksAuthNull -> Nothing
|
||||||
|
SocksIsolateByAuth -> Just $ SocksCredentials sessionUsername ""
|
||||||
|
Nothing -> Nothing
|
||||||
|
{-# INLINE clientSocksCredentials #-}
|
||||||
|
|
||||||
-- | protocol client configuration.
|
-- | protocol client configuration.
|
||||||
data ProtocolClientConfig v = ProtocolClientConfig
|
data ProtocolClientConfig v = ProtocolClientConfig
|
||||||
{ -- | size of TBQueue to use for server commands and responses
|
{ -- | size of TBQueue to use for server commands and responses
|
||||||
@@ -489,9 +510,9 @@ getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize
|
|||||||
runClient (port', ATransport t) useHost c = do
|
runClient (port', ATransport t) useHost c = do
|
||||||
cVar <- newEmptyTMVarIO
|
cVar <- newEmptyTMVarIO
|
||||||
let tcConfig = (transportClientConfig networkConfig useHost) {alpn = clientALPN}
|
let tcConfig = (transportClientConfig networkConfig useHost) {alpn = clientALPN}
|
||||||
username = proxyUsername transportSession
|
socksCreds = clientSocksCredentials networkConfig $ proxyUsername transportSession
|
||||||
tId <-
|
tId <-
|
||||||
runTransportClient tcConfig (Just username) useHost port' (Just $ keyHash srv) (client t c cVar)
|
runTransportClient tcConfig socksCreds useHost port' (Just $ keyHash srv) (client t c cVar)
|
||||||
`forkFinally` \_ -> void (atomically . tryPutTMVar cVar $ Left PCENetworkError)
|
`forkFinally` \_ -> void (atomically . tryPutTMVar cVar $ Left PCENetworkError)
|
||||||
c_ <- tcpConnectTimeout `timeout` atomically (takeTMVar cVar)
|
c_ <- tcpConnectTimeout `timeout` atomically (takeTMVar cVar)
|
||||||
case c_ of
|
case c_ of
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
module Simplex.Messaging.Crypto.SNTRUP761 where
|
module Simplex.Messaging.Crypto.SNTRUP761 where
|
||||||
|
|
||||||
import Crypto.Hash (Digest, SHA3_256, hash)
|
import Crypto.Hash (Digest, SHA256, hash)
|
||||||
import Data.ByteArray (ScrubbedBytes)
|
import Data.ByteArray (ScrubbedBytes)
|
||||||
import qualified Data.ByteArray as BA
|
import qualified Data.ByteArray as BA
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
@@ -28,4 +28,4 @@ kcbEncrypt (KEMHybridSecret k) = sbEncrypt_ k
|
|||||||
kemHybridSecret :: PublicKeyX25519 -> PrivateKeyX25519 -> KEMSharedKey -> KEMHybridSecret
|
kemHybridSecret :: PublicKeyX25519 -> PrivateKeyX25519 -> KEMSharedKey -> KEMHybridSecret
|
||||||
kemHybridSecret k pk (KEMSharedKey kem) =
|
kemHybridSecret k pk (KEMSharedKey kem) =
|
||||||
let DhSecretX25519 dh = C.dh' k pk
|
let DhSecretX25519 dh = C.dh' k pk
|
||||||
in KEMHybridSecret $ BA.convert (hash $ BA.convert dh <> kem :: Digest SHA3_256)
|
in KEMHybridSecret $ BA.convert (hash $ BA.convert dh <> kem :: Digest SHA256)
|
||||||
|
|||||||
@@ -443,6 +443,8 @@ data NtfSubStatus
|
|||||||
NSInactive
|
NSInactive
|
||||||
| -- | END received
|
| -- | END received
|
||||||
NSEnd
|
NSEnd
|
||||||
|
| -- | DELD received (connection was deleted)
|
||||||
|
NSDeleted
|
||||||
| -- | SMP AUTH error
|
| -- | SMP AUTH error
|
||||||
NSAuth
|
NSAuth
|
||||||
| -- | SMP error other than AUTH
|
| -- | SMP error other than AUTH
|
||||||
@@ -456,6 +458,7 @@ ntfShouldSubscribe = \case
|
|||||||
NSActive -> True
|
NSActive -> True
|
||||||
NSInactive -> True
|
NSInactive -> True
|
||||||
NSEnd -> False
|
NSEnd -> False
|
||||||
|
NSDeleted -> False
|
||||||
NSAuth -> False
|
NSAuth -> False
|
||||||
NSErr _ -> False
|
NSErr _ -> False
|
||||||
|
|
||||||
@@ -466,6 +469,7 @@ instance Encoding NtfSubStatus where
|
|||||||
NSActive -> "ACTIVE"
|
NSActive -> "ACTIVE"
|
||||||
NSInactive -> "INACTIVE"
|
NSInactive -> "INACTIVE"
|
||||||
NSEnd -> "END"
|
NSEnd -> "END"
|
||||||
|
NSDeleted -> "DELETED"
|
||||||
NSAuth -> "AUTH"
|
NSAuth -> "AUTH"
|
||||||
NSErr err -> "ERR " <> err
|
NSErr err -> "ERR " <> err
|
||||||
smpP =
|
smpP =
|
||||||
@@ -475,6 +479,7 @@ instance Encoding NtfSubStatus where
|
|||||||
"ACTIVE" -> pure NSActive
|
"ACTIVE" -> pure NSActive
|
||||||
"INACTIVE" -> pure NSInactive
|
"INACTIVE" -> pure NSInactive
|
||||||
"END" -> pure NSEnd
|
"END" -> pure NSEnd
|
||||||
|
"DELETED" -> pure NSDeleted
|
||||||
"AUTH" -> pure NSAuth
|
"AUTH" -> pure NSAuth
|
||||||
"ERR" -> NSErr <$> (A.space *> A.takeByteString)
|
"ERR" -> NSErr <$> (A.space *> A.takeByteString)
|
||||||
_ -> fail "bad NtfSubStatus"
|
_ -> fail "bad NtfSubStatus"
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ ntfSubscriber NtfSubscriber {smpSubscribers, newSubQ, smpAgent = ca@SMPClientAge
|
|||||||
Right SMP.END ->
|
Right SMP.END ->
|
||||||
whenM (atomically $ activeClientSession' ca sessionId srv) $
|
whenM (atomically $ activeClientSession' ca sessionId srv) $
|
||||||
updateSubStatus smpQueue NSEnd
|
updateSubStatus smpQueue NSEnd
|
||||||
|
Right SMP.DELD -> updateSubStatus smpQueue NSDeleted
|
||||||
Right (SMP.ERR e) -> logError $ "SMP server error: " <> tshow e
|
Right (SMP.ERR e) -> logError $ "SMP server error: " <> tshow e
|
||||||
Right _ -> logError "SMP server unexpected response"
|
Right _ -> logError "SMP server unexpected response"
|
||||||
Left e -> logError $ "SMP client error: " <> tshow e
|
Left e -> logError $ "SMP client error: " <> tshow e
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import qualified Data.Text as T
|
|||||||
import qualified Data.Text.IO as T
|
import qualified Data.Text.IO as T
|
||||||
import Network.Socket (HostName)
|
import Network.Socket (HostName)
|
||||||
import Options.Applicative
|
import Options.Applicative
|
||||||
import Simplex.Messaging.Client (NetworkConfig (..), ProtocolClientConfig (..), SocksMode (..), defaultNetworkConfig)
|
import Simplex.Messaging.Client (HostMode (..), NetworkConfig (..), ProtocolClientConfig (..), SocksMode (..), defaultNetworkConfig, textToHostMode)
|
||||||
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
|
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Notifications.Server (runNtfServer)
|
import Simplex.Messaging.Notifications.Server (runNtfServer)
|
||||||
@@ -92,6 +92,10 @@ ntfServerCLI cfgPath logPath =
|
|||||||
<> "websockets: off\n\n\
|
<> "websockets: off\n\n\
|
||||||
\[SUBSCRIBER]\n\
|
\[SUBSCRIBER]\n\
|
||||||
\# Network configuration for notification server client.\n\
|
\# Network configuration for notification server client.\n\
|
||||||
|
\# `host_mode` can be 'public' (default) or 'onion'.\n\
|
||||||
|
\# It defines prefferred hostname for destination servers with multiple hostnames.\n\
|
||||||
|
\# host_mode: public\n\
|
||||||
|
\# required_host_mode: off\n\n\
|
||||||
\# SOCKS proxy port for subscribing to SMP servers.\n\
|
\# SOCKS proxy port for subscribing to SMP servers.\n\
|
||||||
\# You may need a separate instance of SOCKS proxy for incoming single-hop requests.\n\
|
\# You may need a separate instance of SOCKS proxy for incoming single-hop requests.\n\
|
||||||
\# socks_proxy: localhost:9050\n\n\
|
\# socks_proxy: localhost:9050\n\n\
|
||||||
@@ -134,6 +138,8 @@ ntfServerCLI cfgPath logPath =
|
|||||||
defaultNetworkConfig
|
defaultNetworkConfig
|
||||||
{ socksProxy = either error id <$!> strDecodeIni "SUBSCRIBER" "socks_proxy" ini,
|
{ socksProxy = either error id <$!> strDecodeIni "SUBSCRIBER" "socks_proxy" ini,
|
||||||
socksMode = maybe SMOnion (either error id) $! strDecodeIni "SUBSCRIBER" "socks_mode" ini,
|
socksMode = maybe SMOnion (either error id) $! strDecodeIni "SUBSCRIBER" "socks_mode" ini,
|
||||||
|
hostMode = either (const HMPublic) (either error id . textToHostMode) $ lookupValue "SUBSCRIBER" "host_mode" ini,
|
||||||
|
requiredHostMode = fromMaybe False $ iniOnOff "SUBSCRIBER" "required_host_mode" ini,
|
||||||
smpPingInterval = 60_000_000 -- 1 minutes
|
smpPingInterval = 60_000_000 -- 1 minutes
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -484,6 +484,7 @@ data BrokerMsg where
|
|||||||
RRES :: EncFwdResponse -> BrokerMsg -- relay to proxy
|
RRES :: EncFwdResponse -> BrokerMsg -- relay to proxy
|
||||||
PRES :: EncResponse -> BrokerMsg -- proxy to client
|
PRES :: EncResponse -> BrokerMsg -- proxy to client
|
||||||
END :: BrokerMsg
|
END :: BrokerMsg
|
||||||
|
DELD :: BrokerMsg
|
||||||
INFO :: QueueInfo -> BrokerMsg
|
INFO :: QueueInfo -> BrokerMsg
|
||||||
OK :: BrokerMsg
|
OK :: BrokerMsg
|
||||||
ERR :: ErrorType -> BrokerMsg
|
ERR :: ErrorType -> BrokerMsg
|
||||||
@@ -705,6 +706,7 @@ data BrokerMsgTag
|
|||||||
| RRES_
|
| RRES_
|
||||||
| PRES_
|
| PRES_
|
||||||
| END_
|
| END_
|
||||||
|
| DELD_
|
||||||
| INFO_
|
| INFO_
|
||||||
| OK_
|
| OK_
|
||||||
| ERR_
|
| ERR_
|
||||||
@@ -778,6 +780,7 @@ instance Encoding BrokerMsgTag where
|
|||||||
RRES_ -> "RRES"
|
RRES_ -> "RRES"
|
||||||
PRES_ -> "PRES"
|
PRES_ -> "PRES"
|
||||||
END_ -> "END"
|
END_ -> "END"
|
||||||
|
DELD_ -> "DELD"
|
||||||
INFO_ -> "INFO"
|
INFO_ -> "INFO"
|
||||||
OK_ -> "OK"
|
OK_ -> "OK"
|
||||||
ERR_ -> "ERR"
|
ERR_ -> "ERR"
|
||||||
@@ -794,6 +797,7 @@ instance ProtocolMsgTag BrokerMsgTag where
|
|||||||
"RRES" -> Just RRES_
|
"RRES" -> Just RRES_
|
||||||
"PRES" -> Just PRES_
|
"PRES" -> Just PRES_
|
||||||
"END" -> Just END_
|
"END" -> Just END_
|
||||||
|
"DELD" -> Just DELD_
|
||||||
"INFO" -> Just INFO_
|
"INFO" -> Just INFO_
|
||||||
"OK" -> Just OK_
|
"OK" -> Just OK_
|
||||||
"ERR" -> Just ERR_
|
"ERR" -> Just ERR_
|
||||||
@@ -1423,6 +1427,9 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
|
|||||||
RRES (EncFwdResponse encBlock) -> e (RRES_, ' ', Tail encBlock)
|
RRES (EncFwdResponse encBlock) -> e (RRES_, ' ', Tail encBlock)
|
||||||
PRES (EncResponse encBlock) -> e (PRES_, ' ', Tail encBlock)
|
PRES (EncResponse encBlock) -> e (PRES_, ' ', Tail encBlock)
|
||||||
END -> e END_
|
END -> e END_
|
||||||
|
DELD
|
||||||
|
| v >= deletedEventSMPVersion -> e DELD_
|
||||||
|
| otherwise -> e END_
|
||||||
INFO info -> e (INFO_, ' ', info)
|
INFO info -> e (INFO_, ' ', info)
|
||||||
OK -> e OK_
|
OK -> e OK_
|
||||||
ERR err -> e (ERR_, ' ', err)
|
ERR err -> e (ERR_, ' ', err)
|
||||||
@@ -1448,6 +1455,7 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
|
|||||||
RRES_ -> RRES <$> (EncFwdResponse . unTail <$> _smpP)
|
RRES_ -> RRES <$> (EncFwdResponse . unTail <$> _smpP)
|
||||||
PRES_ -> PRES <$> (EncResponse . unTail <$> _smpP)
|
PRES_ -> PRES <$> (EncResponse . unTail <$> _smpP)
|
||||||
END_ -> pure END
|
END_ -> pure END
|
||||||
|
DELD_ -> pure DELD
|
||||||
INFO_ -> INFO <$> _smpP
|
INFO_ -> INFO <$> _smpP
|
||||||
OK_ -> pure OK
|
OK_ -> pure OK
|
||||||
ERR_ -> ERR <$> _smpP
|
ERR_ -> ERR <$> _smpP
|
||||||
|
|||||||
@@ -136,9 +136,11 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
|
|||||||
expired <- restoreServerMessages
|
expired <- restoreServerMessages
|
||||||
restoreServerStats expired
|
restoreServerStats expired
|
||||||
raceAny_
|
raceAny_
|
||||||
( serverThread s "server subscribedQ" subscribedQ subscribers pendingENDs subscriptions cancelSub
|
( serverThread s "server subscribedQ" True subscribedQ subscribers pendingENDs subscriptions cancelSub
|
||||||
: serverThread s "server ntfSubscribedQ" ntfSubscribedQ Env.notifiers pendingNtfENDs ntfSubscriptions (\_ -> pure ())
|
: serverThread s "server deletedQ" False deletedQ subscribers pendingDELDs subscriptions cancelSub
|
||||||
: sendPendingENDsThread s
|
: serverThread s "server ntfSubscribedQ" True ntfSubscribedQ Env.notifiers pendingNtfENDs ntfSubscriptions (\_ -> pure ())
|
||||||
|
: serverThread s "server ntfDeletedQ" False ntfDeletedQ Env.notifiers pendingNtfDELDs ntfSubscriptions (\_ -> pure ())
|
||||||
|
: sendPendingEvtsThread s
|
||||||
: receiveFromProxyAgent pa
|
: receiveFromProxyAgent pa
|
||||||
: map runServer transports <> expireMessagesThread_ cfg <> serverStatsThread_ cfg <> controlPortThread_ cfg
|
: map runServer transports <> expireMessagesThread_ cfg <> serverStatsThread_ cfg <> controlPortThread_ cfg
|
||||||
)
|
)
|
||||||
@@ -163,13 +165,14 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
|
|||||||
forall s.
|
forall s.
|
||||||
Server ->
|
Server ->
|
||||||
String ->
|
String ->
|
||||||
(Server -> TQueue (QueueId, ClientId, Subscribed)) ->
|
Subscribed ->
|
||||||
|
(Server -> TQueue (QueueId, ClientId)) ->
|
||||||
(Server -> TMap QueueId (TVar Client)) ->
|
(Server -> TMap QueueId (TVar Client)) ->
|
||||||
(Server -> TVar (IM.IntMap (NonEmpty RecipientId))) ->
|
(Server -> TVar (IM.IntMap (NonEmpty RecipientId))) ->
|
||||||
(Client -> TMap QueueId s) ->
|
(Client -> TMap QueueId s) ->
|
||||||
(s -> IO ()) ->
|
(s -> IO ()) ->
|
||||||
M ()
|
M ()
|
||||||
serverThread s label subQ subs ends clientSubs unsub = do
|
serverThread s label subscribed subQ subs pendingEvts clientSubs unsub = do
|
||||||
labelMyThread label
|
labelMyThread label
|
||||||
cls <- asks clients
|
cls <- asks clients
|
||||||
liftIO . forever $
|
liftIO . forever $
|
||||||
@@ -177,8 +180,8 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
|
|||||||
$>>= endPreviousSubscriptions
|
$>>= endPreviousSubscriptions
|
||||||
>>= mapM_ unsub
|
>>= mapM_ unsub
|
||||||
where
|
where
|
||||||
updateSubscribers :: TVar (IM.IntMap (Maybe Client)) -> (QueueId, ClientId, Bool) -> STM (Maybe (QueueId, Client))
|
updateSubscribers :: TVar (IM.IntMap (Maybe Client)) -> (QueueId, ClientId) -> STM (Maybe (QueueId, Client))
|
||||||
updateSubscribers cls (qId, clntId, subscribed) =
|
updateSubscribers cls (qId, clntId) =
|
||||||
-- Client lookup by ID is in the same STM transaction.
|
-- Client lookup by ID is in the same STM transaction.
|
||||||
-- In case client disconnects during the transaction,
|
-- In case client disconnects during the transaction,
|
||||||
-- it will be re-evaluated, and the client won't be stored as subscribed.
|
-- it will be re-evaluated, and the client won't be stored as subscribed.
|
||||||
@@ -201,37 +204,41 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
|
|||||||
| otherwise = (\yes -> if yes then Just (qId, c') else Nothing) <$> readTVar (connected c')
|
| otherwise = (\yes -> if yes then Just (qId, c') else Nothing) <$> readTVar (connected c')
|
||||||
endPreviousSubscriptions :: (QueueId, Client) -> IO (Maybe s)
|
endPreviousSubscriptions :: (QueueId, Client) -> IO (Maybe s)
|
||||||
endPreviousSubscriptions (qId, c) = do
|
endPreviousSubscriptions (qId, c) = do
|
||||||
atomically $ modifyTVar' (ends s) $ IM.alter (Just . maybe [qId] (qId <|)) (clientId c)
|
atomically $ modifyTVar' (pendingEvts s) $ IM.alter (Just . maybe [qId] (qId <|)) (clientId c)
|
||||||
atomically $ TM.lookupDelete qId (clientSubs c)
|
atomically $ TM.lookupDelete qId (clientSubs c)
|
||||||
|
|
||||||
sendPendingENDsThread :: Server -> M ()
|
sendPendingEvtsThread :: Server -> M ()
|
||||||
sendPendingENDsThread s = do
|
sendPendingEvtsThread s = do
|
||||||
endInt <- asks $ pendingENDInterval . config
|
endInt <- asks $ pendingENDInterval . config
|
||||||
cls <- asks clients
|
cls <- asks clients
|
||||||
forever $ do
|
forever $ do
|
||||||
threadDelay endInt
|
threadDelay endInt
|
||||||
sendPending cls $ pendingENDs s
|
sendPending cls END $ pendingENDs s
|
||||||
sendPending cls $ pendingNtfENDs s
|
sendPending cls DELD $ pendingDELDs s
|
||||||
|
sendPending cls END $ pendingNtfENDs s
|
||||||
|
sendPending cls DELD $ pendingNtfDELDs s
|
||||||
where
|
where
|
||||||
sendPending cls ref = do
|
sendPending cls evt ref = do
|
||||||
ends <- atomically $ swapTVar ref IM.empty
|
ends <- atomically $ swapTVar ref IM.empty
|
||||||
unless (null ends) $ forM_ (IM.assocs ends) $ \(cId, qIds) ->
|
unless (null ends) $ forM_ (IM.assocs ends) $ \(cId, qIds) ->
|
||||||
mapM_ (queueENDs qIds) . join . IM.lookup cId =<< readTVarIO cls
|
mapM_ (queueEvts qIds evt) . join . IM.lookup cId =<< readTVarIO cls
|
||||||
queueENDs qIds c@Client {connected, sndQ = q} =
|
queueEvts qIds evt c@Client {connected, sndQ = q} =
|
||||||
whenM (readTVarIO connected) $ do
|
whenM (readTVarIO connected) $ do
|
||||||
sent <- atomically $ ifM (isFullTBQueue q) (pure False) (writeTBQueue q ts $> True)
|
sent <- atomically $ ifM (isFullTBQueue q) (pure False) (writeTBQueue q ts $> True)
|
||||||
if sent
|
if sent
|
||||||
then updateEndStats
|
then updateEndStats
|
||||||
else -- if queue is full it can block
|
else -- if queue is full it can block
|
||||||
forkClient c ("sendPendingENDsThread.queueENDs") $
|
forkClient c ("sendPendingEvtsThread.queueEvts") $
|
||||||
atomically (writeTBQueue q ts) >> updateEndStats
|
atomically (writeTBQueue q ts) >> updateEndStats
|
||||||
where
|
where
|
||||||
ts = L.map (CorrId "",,END) qIds
|
ts = L.map (CorrId "",,evt) qIds
|
||||||
updateEndStats = do
|
updateEndStats = case evt of
|
||||||
stats <- asks serverStats
|
END -> do
|
||||||
let len = L.length qIds
|
stats <- asks serverStats
|
||||||
liftIO $ atomicModifyIORef'_ (qSubEnd stats) (+ len)
|
let len = L.length qIds
|
||||||
liftIO $ atomicModifyIORef'_ (qSubEndB stats) (+ (len `div` 255 + 1)) -- up to 255 ENDs in the batch
|
liftIO $ atomicModifyIORef'_ (qSubEnd stats) (+ len)
|
||||||
|
liftIO $ atomicModifyIORef'_ (qSubEndB stats) (+ (len `div` 255 + 1)) -- up to 255 ENDs in the batch
|
||||||
|
_ -> pure ()
|
||||||
|
|
||||||
receiveFromProxyAgent :: ProxyAgent -> M ()
|
receiveFromProxyAgent :: ProxyAgent -> M ()
|
||||||
receiveFromProxyAgent ProxyAgent {smpAgent = SMPClientAgent {agentQ}} =
|
receiveFromProxyAgent ProxyAgent {smpAgent = SMPClientAgent {agentQ}} =
|
||||||
@@ -892,7 +899,7 @@ forkClient Client {endThreads, endThreadSeq} label action = do
|
|||||||
mkWeakThreadId t >>= atomically . modifyTVar' endThreads . IM.insert tId
|
mkWeakThreadId t >>= atomically . modifyTVar' endThreads . IM.insert tId
|
||||||
|
|
||||||
client :: THandleParams SMPVersion 'TServer -> Client -> Server -> M ()
|
client :: THandleParams SMPVersion 'TServer -> Client -> Server -> M ()
|
||||||
client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId, procThreads} Server {subscribedQ, ntfSubscribedQ, subscribers, notifiers} = do
|
client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId, procThreads} Server {subscribedQ, deletedQ, ntfSubscribedQ, ntfDeletedQ, subscribers, notifiers} = do
|
||||||
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " commands"
|
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " commands"
|
||||||
forever $
|
forever $
|
||||||
atomically (readTBQueue rcvQ)
|
atomically (readTBQueue rcvQ)
|
||||||
@@ -985,11 +992,9 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
|
|||||||
processCommand (qr_, (corrId, entId, cmd)) = case cmd of
|
processCommand (qr_, (corrId, entId, cmd)) = case cmd of
|
||||||
Cmd SProxiedClient command -> processProxiedCmd (corrId, entId, command)
|
Cmd SProxiedClient command -> processProxiedCmd (corrId, entId, command)
|
||||||
Cmd SSender command -> Just <$> case command of
|
Cmd SSender command -> Just <$> case command of
|
||||||
SKEY sKey -> (corrId,entId,) <$> case qr_ of
|
SKEY sKey ->
|
||||||
Just qr@QueueRec {sndSecure}
|
withQueue $ \QueueRec {sndSecure, recipientId} ->
|
||||||
| sndSecure -> secureQueue_ "SKEY" qr sKey
|
(corrId,entId,) <$> if sndSecure then secureQueue_ "SKEY" recipientId sKey else pure $ ERR AUTH
|
||||||
| otherwise -> pure $ ERR AUTH
|
|
||||||
Nothing -> pure $ ERR INTERNAL
|
|
||||||
SEND flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody
|
SEND flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody
|
||||||
PING -> pure (corrId, NoEntity, PONG)
|
PING -> pure (corrId, NoEntity, PONG)
|
||||||
RFWD encBlock -> (corrId, NoEntity,) <$> processForwardedCommand encBlock
|
RFWD encBlock -> (corrId, NoEntity,) <$> processForwardedCommand encBlock
|
||||||
@@ -1009,9 +1014,9 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
|
|||||||
SUB -> withQueue (`subscribeQueue` entId)
|
SUB -> withQueue (`subscribeQueue` entId)
|
||||||
GET -> withQueue getMessage
|
GET -> withQueue getMessage
|
||||||
ACK msgId -> withQueue (`acknowledgeMsg` msgId)
|
ACK msgId -> withQueue (`acknowledgeMsg` msgId)
|
||||||
KEY sKey -> (corrId,entId,) <$> case qr_ of
|
KEY sKey ->
|
||||||
Just qr -> secureQueue_ "KEY" qr sKey
|
withQueue $ \QueueRec {recipientId} ->
|
||||||
Nothing -> pure $ ERR INTERNAL
|
(corrId,entId,) <$> secureQueue_ "KEY" recipientId sKey
|
||||||
NKEY nKey dhKey -> addQueueNotifier_ st nKey dhKey
|
NKEY nKey dhKey -> addQueueNotifier_ st nKey dhKey
|
||||||
NDEL -> deleteQueueNotifier_ st
|
NDEL -> deleteQueueNotifier_ st
|
||||||
OFF -> suspendQueue_ st
|
OFF -> suspendQueue_ st
|
||||||
@@ -1063,10 +1068,9 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
|
|||||||
n <- asks $ queueIdBytes . config
|
n <- asks $ queueIdBytes . config
|
||||||
liftM2 (,) (randomId n) (randomId n)
|
liftM2 (,) (randomId n) (randomId n)
|
||||||
|
|
||||||
secureQueue_ :: T.Text -> QueueRec -> SndPublicAuthKey -> M BrokerMsg
|
secureQueue_ :: T.Text -> RecipientId -> SndPublicAuthKey -> M BrokerMsg
|
||||||
secureQueue_ name qr@QueueRec {recipientId = rId} sKey = time name $ do
|
secureQueue_ name rId sKey = time name $ do
|
||||||
withLog $ \s -> logSecureQueue s rId sKey
|
withLog $ \s -> logSecureQueue s rId sKey
|
||||||
updateQueueDate qr
|
|
||||||
st <- asks queueStore
|
st <- asks queueStore
|
||||||
stats <- asks serverStats
|
stats <- asks serverStats
|
||||||
incStat $ qSecured stats
|
incStat $ qSecured stats
|
||||||
@@ -1086,20 +1090,22 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
|
|||||||
liftIO (addQueueNotifier st entId ntfCreds) >>= \case
|
liftIO (addQueueNotifier st entId ntfCreds) >>= \case
|
||||||
Left DUPLICATE_ -> addNotifierRetry (n - 1) rcvPublicDhKey rcvNtfDhSecret
|
Left DUPLICATE_ -> addNotifierRetry (n - 1) rcvPublicDhKey rcvNtfDhSecret
|
||||||
Left e -> pure $ ERR e
|
Left e -> pure $ ERR e
|
||||||
Right _ -> do
|
Right nId_ -> do
|
||||||
withLog $ \s -> logAddNotifier s entId ntfCreds
|
withLog $ \s -> logAddNotifier s entId ntfCreds
|
||||||
incStat . ntfCreated =<< asks serverStats
|
incStat . ntfCreated =<< asks serverStats
|
||||||
|
forM_ nId_ $ \nId -> atomically $ writeTQueue ntfDeletedQ (nId, clientId)
|
||||||
pure $ NID notifierId rcvPublicDhKey
|
pure $ NID notifierId rcvPublicDhKey
|
||||||
|
|
||||||
deleteQueueNotifier_ :: QueueStore -> M (Transmission BrokerMsg)
|
deleteQueueNotifier_ :: QueueStore -> M (Transmission BrokerMsg)
|
||||||
deleteQueueNotifier_ st = do
|
deleteQueueNotifier_ st = do
|
||||||
withLog (`logDeleteNotifier` entId)
|
withLog (`logDeleteNotifier` entId)
|
||||||
liftIO (deleteQueueNotifier st entId) >>= \case
|
liftIO (deleteQueueNotifier st entId) >>= \case
|
||||||
Right () -> do
|
Right (Just nId) -> do
|
||||||
-- Possibly, the same should be done if the queue is suspended, but currently we do not use it
|
-- Possibly, the same should be done if the queue is suspended, but currently we do not use it
|
||||||
atomically $ writeTQueue ntfSubscribedQ (entId, clientId, False)
|
atomically $ writeTQueue ntfDeletedQ (nId, clientId)
|
||||||
incStat . ntfDeleted =<< asks serverStats
|
incStat . ntfDeleted =<< asks serverStats
|
||||||
pure ok
|
pure ok
|
||||||
|
Right Nothing -> pure ok
|
||||||
Left e -> pure $ err e
|
Left e -> pure $ err e
|
||||||
|
|
||||||
suspendQueue_ :: QueueStore -> M (Transmission BrokerMsg)
|
suspendQueue_ :: QueueStore -> M (Transmission BrokerMsg)
|
||||||
@@ -1124,7 +1130,7 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
|
|||||||
where
|
where
|
||||||
newSub :: M Sub
|
newSub :: M Sub
|
||||||
newSub = time "SUB newSub" . atomically $ do
|
newSub = time "SUB newSub" . atomically $ do
|
||||||
writeTQueue subscribedQ (rId, clientId, True)
|
writeTQueue subscribedQ (rId, clientId)
|
||||||
sub <- newSubscription NoSub
|
sub <- newSubscription NoSub
|
||||||
TM.insert rId sub subscriptions
|
TM.insert rId sub subscriptions
|
||||||
pure sub
|
pure sub
|
||||||
@@ -1199,7 +1205,7 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
|
|||||||
pure ok
|
pure ok
|
||||||
where
|
where
|
||||||
newSub = do
|
newSub = do
|
||||||
writeTQueue ntfSubscribedQ (entId, clientId, True)
|
writeTQueue ntfSubscribedQ (entId, clientId)
|
||||||
TM.insert entId () ntfSubscriptions
|
TM.insert entId () ntfSubscriptions
|
||||||
|
|
||||||
acknowledgeMsg :: QueueRec -> MsgId -> M (Transmission BrokerMsg)
|
acknowledgeMsg :: QueueRec -> MsgId -> M (Transmission BrokerMsg)
|
||||||
@@ -1480,9 +1486,9 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
|
|||||||
liftIO (deleteQueue st entId $>>= \q -> delMsgQueue ms entId $> Right q) >>= \case
|
liftIO (deleteQueue st entId $>>= \q -> delMsgQueue ms entId $> Right q) >>= \case
|
||||||
Right q -> do
|
Right q -> do
|
||||||
-- Possibly, the same should be done if the queue is suspended, but currently we do not use it
|
-- Possibly, the same should be done if the queue is suspended, but currently we do not use it
|
||||||
atomically $ writeTQueue subscribedQ (entId, clientId, False)
|
atomically $ writeTQueue deletedQ (entId, clientId)
|
||||||
forM_ (notifierId <$> notifier q) $ \nId ->
|
forM_ (notifierId <$> notifier q) $ \nId ->
|
||||||
atomically $ writeTQueue ntfSubscribedQ (nId, clientId, False)
|
atomically $ writeTQueue ntfDeletedQ (nId, clientId)
|
||||||
updateDeletedStats q
|
updateDeletedStats q
|
||||||
pure ok
|
pure ok
|
||||||
Left e -> pure $ err e
|
Left e -> pure $ err e
|
||||||
|
|||||||
@@ -136,12 +136,16 @@ data Env = Env
|
|||||||
type Subscribed = Bool
|
type Subscribed = Bool
|
||||||
|
|
||||||
data Server = Server
|
data Server = Server
|
||||||
{ subscribedQ :: TQueue (RecipientId, ClientId, Subscribed),
|
{ subscribedQ :: TQueue (RecipientId, ClientId),
|
||||||
|
deletedQ :: TQueue (RecipientId, ClientId),
|
||||||
subscribers :: TMap RecipientId (TVar Client),
|
subscribers :: TMap RecipientId (TVar Client),
|
||||||
ntfSubscribedQ :: TQueue (NotifierId, ClientId, Subscribed),
|
ntfSubscribedQ :: TQueue (NotifierId, ClientId),
|
||||||
|
ntfDeletedQ :: TQueue (NotifierId, ClientId),
|
||||||
notifiers :: TMap NotifierId (TVar Client),
|
notifiers :: TMap NotifierId (TVar Client),
|
||||||
pendingENDs :: TVar (IntMap (NonEmpty RecipientId)),
|
pendingENDs :: TVar (IntMap (NonEmpty RecipientId)),
|
||||||
|
pendingDELDs :: TVar (IntMap (NonEmpty RecipientId)),
|
||||||
pendingNtfENDs :: TVar (IntMap (NonEmpty NotifierId)),
|
pendingNtfENDs :: TVar (IntMap (NonEmpty NotifierId)),
|
||||||
|
pendingNtfDELDs :: TVar (IntMap (NonEmpty NotifierId)),
|
||||||
savingLock :: Lock
|
savingLock :: Lock
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,13 +185,17 @@ data Sub = Sub
|
|||||||
newServer :: IO Server
|
newServer :: IO Server
|
||||||
newServer = do
|
newServer = do
|
||||||
subscribedQ <- newTQueueIO
|
subscribedQ <- newTQueueIO
|
||||||
|
deletedQ <- newTQueueIO
|
||||||
subscribers <- TM.emptyIO
|
subscribers <- TM.emptyIO
|
||||||
ntfSubscribedQ <- newTQueueIO
|
ntfSubscribedQ <- newTQueueIO
|
||||||
|
ntfDeletedQ <- newTQueueIO
|
||||||
notifiers <- TM.emptyIO
|
notifiers <- TM.emptyIO
|
||||||
pendingENDs <- newTVarIO IM.empty
|
pendingENDs <- newTVarIO IM.empty
|
||||||
|
pendingDELDs <- newTVarIO IM.empty
|
||||||
pendingNtfENDs <- newTVarIO IM.empty
|
pendingNtfENDs <- newTVarIO IM.empty
|
||||||
|
pendingNtfDELDs <- newTVarIO IM.empty
|
||||||
savingLock <- atomically createLock
|
savingLock <- atomically createLock
|
||||||
return Server {subscribedQ, subscribers, ntfSubscribedQ, notifiers, pendingENDs, pendingNtfENDs, savingLock}
|
return Server {subscribedQ, deletedQ, subscribers, ntfSubscribedQ, ntfDeletedQ, notifiers, pendingENDs, pendingDELDs, pendingNtfENDs, pendingNtfDELDs, savingLock}
|
||||||
|
|
||||||
newClient :: ClientId -> Natural -> VersionSMP -> ByteString -> SystemTime -> IO Client
|
newClient :: ClientId -> Natural -> VersionSMP -> ByteString -> SystemTime -> IO Client
|
||||||
newClient clientId qSize thVersion sessionId createdAt = do
|
newClient clientId qSize thVersion sessionId createdAt = do
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import qualified Data.Text.IO as T
|
|||||||
import Network.Socket (HostName)
|
import Network.Socket (HostName)
|
||||||
import Options.Applicative
|
import Options.Applicative
|
||||||
import Simplex.Messaging.Agent.Protocol (connReqUriP')
|
import Simplex.Messaging.Agent.Protocol (connReqUriP')
|
||||||
import Simplex.Messaging.Client (HostMode (..), NetworkConfig (..), ProtocolClientConfig (..), SocksMode (..), defaultNetworkConfig)
|
import Simplex.Messaging.Client (HostMode (..), NetworkConfig (..), ProtocolClientConfig (..), SocksMode (..), defaultNetworkConfig, textToHostMode)
|
||||||
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
|
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
@@ -307,7 +307,7 @@ smpServerCLI_ generateSite serveStaticFiles cfgPath logPath =
|
|||||||
defaultNetworkConfig
|
defaultNetworkConfig
|
||||||
{ socksProxy = either error id <$!> strDecodeIni "PROXY" "socks_proxy" ini,
|
{ socksProxy = either error id <$!> strDecodeIni "PROXY" "socks_proxy" ini,
|
||||||
socksMode = maybe SMOnion (either error id) $! strDecodeIni "PROXY" "socks_mode" ini,
|
socksMode = maybe SMOnion (either error id) $! strDecodeIni "PROXY" "socks_mode" ini,
|
||||||
hostMode = either (const HMPublic) textToHostMode $ lookupValue "PROXY" "host_mode" ini,
|
hostMode = either (const HMPublic) (either error id . textToHostMode) $ lookupValue "PROXY" "host_mode" ini,
|
||||||
requiredHostMode = fromMaybe False $ iniOnOff "PROXY" "required_host_mode" ini
|
requiredHostMode = fromMaybe False $ iniOnOff "PROXY" "required_host_mode" ini
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -318,11 +318,6 @@ smpServerCLI_ generateSite serveStaticFiles cfgPath logPath =
|
|||||||
serverClientConcurrency = readIniDefault defaultProxyClientConcurrency "PROXY" "client_concurrency" ini,
|
serverClientConcurrency = readIniDefault defaultProxyClientConcurrency "PROXY" "client_concurrency" ini,
|
||||||
information = serverPublicInfo ini
|
information = serverPublicInfo ini
|
||||||
}
|
}
|
||||||
textToHostMode :: Text -> HostMode
|
|
||||||
textToHostMode = \case
|
|
||||||
"public" -> HMPublic
|
|
||||||
"onion" -> HMOnionViaSocks
|
|
||||||
s -> error . T.unpack $ "Invalid host_mode: " <> s
|
|
||||||
textToOwnServers :: Text -> [ByteString]
|
textToOwnServers :: Text -> [ByteString]
|
||||||
textToOwnServers = map encodeUtf8 . T.words
|
textToOwnServers = map encodeUtf8 . T.words
|
||||||
|
|
||||||
|
|||||||
@@ -74,23 +74,26 @@ secureQueue QueueStore {queues} rId sKey = toResult <$> do
|
|||||||
let !q' = q {senderKey = Just sKey}
|
let !q' = q {senderKey = Just sKey}
|
||||||
in writeTVar qVar q' $> Just q'
|
in writeTVar qVar q' $> Just q'
|
||||||
|
|
||||||
addQueueNotifier :: QueueStore -> RecipientId -> NtfCreds -> IO (Either ErrorType QueueRec)
|
addQueueNotifier :: QueueStore -> RecipientId -> NtfCreds -> IO (Either ErrorType (Maybe NotifierId))
|
||||||
addQueueNotifier QueueStore {queues, notifiers} rId ntfCreds@NtfCreds {notifierId = nId} = do
|
addQueueNotifier QueueStore {queues, notifiers} rId ntfCreds@NtfCreds {notifierId = nId} = do
|
||||||
ifM (TM.memberIO nId notifiers) (pure $ Left DUPLICATE_) $
|
TM.lookupIO rId queues >>= \case
|
||||||
withQueue rId queues $ \qVar -> do
|
Just qVar -> atomically $ ifM (TM.member nId notifiers) (pure $ Left DUPLICATE_) $ do
|
||||||
q <- readTVar qVar
|
q <- readTVar qVar
|
||||||
forM_ (notifier q) $ (`TM.delete` notifiers) . notifierId
|
nId_ <- forM (notifier q) $ \NtfCreds {notifierId} -> TM.delete notifierId notifiers $> notifierId
|
||||||
let !q' = q {notifier = Just ntfCreds}
|
let !q' = q {notifier = Just ntfCreds}
|
||||||
writeTVar qVar q'
|
writeTVar qVar q'
|
||||||
TM.insert nId rId notifiers
|
TM.insert nId rId notifiers
|
||||||
pure q'
|
pure $ Right nId_
|
||||||
|
Nothing -> pure $ Left AUTH
|
||||||
|
|
||||||
deleteQueueNotifier :: QueueStore -> RecipientId -> IO (Either ErrorType ())
|
deleteQueueNotifier :: QueueStore -> RecipientId -> IO (Either ErrorType (Maybe NotifierId))
|
||||||
deleteQueueNotifier QueueStore {queues, notifiers} rId =
|
deleteQueueNotifier QueueStore {queues, notifiers} rId =
|
||||||
withQueue rId queues $ \qVar -> do
|
withQueue rId queues $ \qVar -> do
|
||||||
q <- readTVar qVar
|
q <- readTVar qVar
|
||||||
forM_ (notifier q) $ \NtfCreds {notifierId} -> TM.delete notifierId notifiers
|
forM (notifier q) $ \NtfCreds {notifierId} -> do
|
||||||
writeTVar qVar $! q {notifier = Nothing}
|
TM.delete notifierId notifiers
|
||||||
|
writeTVar qVar $! q {notifier = Nothing}
|
||||||
|
pure notifierId
|
||||||
|
|
||||||
suspendQueue :: QueueStore -> RecipientId -> IO (Either ErrorType ())
|
suspendQueue :: QueueStore -> RecipientId -> IO (Either ErrorType ())
|
||||||
suspendQueue QueueStore {queues} rId =
|
suspendQueue QueueStore {queues} rId =
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ module Simplex.Messaging.Transport
|
|||||||
authCmdsSMPVersion,
|
authCmdsSMPVersion,
|
||||||
sendingProxySMPVersion,
|
sendingProxySMPVersion,
|
||||||
sndAuthKeySMPVersion,
|
sndAuthKeySMPVersion,
|
||||||
|
deletedEventSMPVersion,
|
||||||
simplexMQVersion,
|
simplexMQVersion,
|
||||||
smpBlockSize,
|
smpBlockSize,
|
||||||
TransportConfig (..),
|
TransportConfig (..),
|
||||||
@@ -130,6 +131,9 @@ smpBlockSize = 16384
|
|||||||
-- 5 - basic auth for SMP servers (11/12/2022)
|
-- 5 - basic auth for SMP servers (11/12/2022)
|
||||||
-- 6 - allow creating queues without subscribing (9/10/2023)
|
-- 6 - allow creating queues without subscribing (9/10/2023)
|
||||||
-- 7 - support authenticated encryption to verify senders' commands, imply but do NOT send session ID in signed part (4/30/2024)
|
-- 7 - support authenticated encryption to verify senders' commands, imply but do NOT send session ID in signed part (4/30/2024)
|
||||||
|
-- 8 - SMP proxy for sender commands
|
||||||
|
-- 9 - faster handshake: SKEY command for sender to secure queue
|
||||||
|
-- 10 - DELD event to subscriber when queue is deleted via another connnection
|
||||||
|
|
||||||
data SMPVersion
|
data SMPVersion
|
||||||
|
|
||||||
@@ -160,14 +164,17 @@ sendingProxySMPVersion = VersionSMP 8
|
|||||||
sndAuthKeySMPVersion :: VersionSMP
|
sndAuthKeySMPVersion :: VersionSMP
|
||||||
sndAuthKeySMPVersion = VersionSMP 9
|
sndAuthKeySMPVersion = VersionSMP 9
|
||||||
|
|
||||||
|
deletedEventSMPVersion :: VersionSMP
|
||||||
|
deletedEventSMPVersion = VersionSMP 10
|
||||||
|
|
||||||
currentClientSMPRelayVersion :: VersionSMP
|
currentClientSMPRelayVersion :: VersionSMP
|
||||||
currentClientSMPRelayVersion = VersionSMP 9
|
currentClientSMPRelayVersion = VersionSMP 10
|
||||||
|
|
||||||
legacyServerSMPRelayVersion :: VersionSMP
|
legacyServerSMPRelayVersion :: VersionSMP
|
||||||
legacyServerSMPRelayVersion = VersionSMP 6
|
legacyServerSMPRelayVersion = VersionSMP 6
|
||||||
|
|
||||||
currentServerSMPRelayVersion :: VersionSMP
|
currentServerSMPRelayVersion :: VersionSMP
|
||||||
currentServerSMPRelayVersion = VersionSMP 9
|
currentServerSMPRelayVersion = VersionSMP 10
|
||||||
|
|
||||||
-- Max SMP protocol version to be used in e2e encrypted
|
-- Max SMP protocol version to be used in e2e encrypted
|
||||||
-- connection between client and server, as defined by SMP proxy.
|
-- connection between client and server, as defined by SMP proxy.
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ module Simplex.Messaging.Transport.Client
|
|||||||
defaultSMPPort,
|
defaultSMPPort,
|
||||||
defaultTcpConnectTimeout,
|
defaultTcpConnectTimeout,
|
||||||
defaultTransportClientConfig,
|
defaultTransportClientConfig,
|
||||||
|
defaultSocksProxyWithAuth,
|
||||||
defaultSocksProxy,
|
defaultSocksProxy,
|
||||||
|
defaultSocksHost,
|
||||||
TransportClientConfig (..),
|
TransportClientConfig (..),
|
||||||
SocksProxy,
|
SocksProxy (..),
|
||||||
|
SocksProxyWithAuth (..),
|
||||||
|
SocksAuth (..),
|
||||||
TransportHost (..),
|
TransportHost (..),
|
||||||
TransportHosts (..),
|
TransportHosts (..),
|
||||||
TransportHosts_ (..),
|
TransportHosts_ (..),
|
||||||
@@ -23,7 +27,7 @@ module Simplex.Messaging.Transport.Client
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Control.Applicative (optional)
|
import Control.Applicative (optional, (<|>))
|
||||||
import Control.Logger.Simple (logError)
|
import Control.Logger.Simple (logError)
|
||||||
import Control.Monad (when)
|
import Control.Monad (when)
|
||||||
import Data.Aeson (FromJSON (..), ToJSON (..))
|
import Data.Aeson (FromJSON (..), ToJSON (..))
|
||||||
@@ -79,7 +83,7 @@ instance StrEncoding TransportHost where
|
|||||||
strP =
|
strP =
|
||||||
A.choice
|
A.choice
|
||||||
[ THIPv4 <$> ((,,,) <$> ipNum <*> ipNum <*> ipNum <*> A.decimal),
|
[ THIPv4 <$> ((,,,) <$> ipNum <*> ipNum <*> ipNum <*> A.decimal),
|
||||||
maybe (Left "bad IPv6") (Right . THIPv6 . fromIPv6w) . readMaybe . B.unpack <$?> A.takeWhile1 (\c -> isHexDigit c || c == ':'),
|
maybe (Left "bad IPv6") (Right . THIPv6 . fromIPv6w) . readMaybe . B.unpack <$?> ipv6StrP,
|
||||||
THOnionHost <$> ((<>) <$> A.takeWhile (\c -> isAsciiLower c || isDigit c) <*> A.string ".onion"),
|
THOnionHost <$> ((<>) <$> A.takeWhile (\c -> isAsciiLower c || isDigit c) <*> A.string ".onion"),
|
||||||
THDomainName . B.unpack <$> (notOnion <$?> A.takeWhile1 (A.notInClass ":#,;/ \n\r\t"))
|
THDomainName . B.unpack <$> (notOnion <$?> A.takeWhile1 (A.notInClass ":#,;/ \n\r\t"))
|
||||||
]
|
]
|
||||||
@@ -87,6 +91,9 @@ instance StrEncoding TransportHost where
|
|||||||
ipNum = validIP <$?> (A.decimal <* A.char '.')
|
ipNum = validIP <$?> (A.decimal <* A.char '.')
|
||||||
validIP :: Int -> Either String Word8
|
validIP :: Int -> Either String Word8
|
||||||
validIP n = if 0 <= n && n <= 255 then Right $ fromIntegral n else Left "invalid IP address"
|
validIP n = if 0 <= n && n <= 255 then Right $ fromIntegral n else Left "invalid IP address"
|
||||||
|
ipv6StrP =
|
||||||
|
A.char '[' *> A.takeWhile1 (/= ']') <* A.char ']'
|
||||||
|
<|> A.takeWhile1 (\c -> isHexDigit c || c == ':')
|
||||||
notOnion s = if ".onion" `B.isSuffixOf` s then Left "invalid onion host" else Right s
|
notOnion s = if ".onion" `B.isSuffixOf` s then Left "invalid onion host" else Right s
|
||||||
|
|
||||||
instance ToJSON TransportHost where
|
instance ToJSON TransportHost where
|
||||||
@@ -134,16 +141,16 @@ clientTransportConfig TransportClientConfig {logTLSErrors} =
|
|||||||
TransportConfig {logTLSErrors, transportTimeout = Nothing}
|
TransportConfig {logTLSErrors, transportTimeout = Nothing}
|
||||||
|
|
||||||
-- | Connect to passed TCP host:port and pass handle to the client.
|
-- | Connect to passed TCP host:port and pass handle to the client.
|
||||||
runTransportClient :: Transport c => TransportClientConfig -> Maybe ByteString -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a
|
runTransportClient :: Transport c => TransportClientConfig -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a
|
||||||
runTransportClient = runTLSTransportClient supportedParameters Nothing
|
runTransportClient = runTLSTransportClient supportedParameters Nothing
|
||||||
|
|
||||||
runTLSTransportClient :: Transport c => T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> Maybe ByteString -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a
|
runTLSTransportClient :: Transport c => T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a
|
||||||
runTLSTransportClient tlsParams caStore_ cfg@TransportClientConfig {socksProxy, tcpKeepAlive, clientCredentials, alpn} proxyUsername host port keyHash client = do
|
runTLSTransportClient tlsParams caStore_ cfg@TransportClientConfig {socksProxy, tcpKeepAlive, clientCredentials, alpn} socksCreds host port keyHash client = do
|
||||||
serverCert <- newEmptyTMVarIO
|
serverCert <- newEmptyTMVarIO
|
||||||
let hostName = B.unpack $ strEncode host
|
let hostName = B.unpack $ strEncode host
|
||||||
clientParams = mkTLSClientParams tlsParams caStore_ hostName port keyHash clientCredentials alpn serverCert
|
clientParams = mkTLSClientParams tlsParams caStore_ hostName port keyHash clientCredentials alpn serverCert
|
||||||
connectTCP = case socksProxy of
|
connectTCP = case socksProxy of
|
||||||
Just proxy -> connectSocksClient proxy proxyUsername (hostAddr host)
|
Just proxy -> connectSocksClient proxy socksCreds (hostAddr host)
|
||||||
_ -> connectTCPClient hostName
|
_ -> connectTCPClient hostName
|
||||||
c <- do
|
c <- do
|
||||||
sock <- connectTCP port
|
sock <- connectTCP port
|
||||||
@@ -191,40 +198,70 @@ connectTCPClient host port = withSocketsDo $ resolve >>= tryOpen err
|
|||||||
defaultSMPPort :: PortNumber
|
defaultSMPPort :: PortNumber
|
||||||
defaultSMPPort = 5223
|
defaultSMPPort = 5223
|
||||||
|
|
||||||
connectSocksClient :: SocksProxy -> Maybe ByteString -> SocksHostAddress -> ServiceName -> IO Socket
|
connectSocksClient :: SocksProxy -> Maybe SocksCredentials -> SocksHostAddress -> ServiceName -> IO Socket
|
||||||
connectSocksClient (SocksProxy addr) proxyUsername hostAddr _port = do
|
connectSocksClient (SocksProxy addr) socksCreds hostAddr _port = do
|
||||||
let port = if null _port then defaultSMPPort else fromMaybe defaultSMPPort $ readMaybe _port
|
let port = if null _port then defaultSMPPort else fromMaybe defaultSMPPort $ readMaybe _port
|
||||||
fst <$> case proxyUsername of
|
fst <$> case socksCreds of
|
||||||
Just username -> socksConnectAuth (defaultSocksConf addr) (SocksAddress hostAddr port) (SocksCredentials username "")
|
Just creds -> socksConnectAuth (defaultSocksConf addr) (SocksAddress hostAddr port) creds
|
||||||
_ -> socksConnect (defaultSocksConf addr) (SocksAddress hostAddr port)
|
_ -> socksConnect (defaultSocksConf addr) (SocksAddress hostAddr port)
|
||||||
|
|
||||||
defaultSocksHost :: HostAddress
|
defaultSocksHost :: (Word8, Word8, Word8, Word8)
|
||||||
defaultSocksHost = tupleToHostAddress (127, 0, 0, 1)
|
defaultSocksHost = (127, 0, 0, 1)
|
||||||
|
|
||||||
|
defaultSocksProxyWithAuth :: SocksProxyWithAuth
|
||||||
|
defaultSocksProxyWithAuth = SocksProxyWithAuth SocksIsolateByAuth defaultSocksProxy
|
||||||
|
|
||||||
defaultSocksProxy :: SocksProxy
|
defaultSocksProxy :: SocksProxy
|
||||||
defaultSocksProxy = SocksProxy $ SockAddrInet 9050 defaultSocksHost
|
defaultSocksProxy = SocksProxy $ SockAddrInet 9050 $ tupleToHostAddress defaultSocksHost
|
||||||
|
|
||||||
newtype SocksProxy = SocksProxy SockAddr
|
newtype SocksProxy = SocksProxy SockAddr
|
||||||
deriving (Eq)
|
deriving (Eq)
|
||||||
|
|
||||||
|
data SocksProxyWithAuth = SocksProxyWithAuth SocksAuth SocksProxy
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
data SocksAuth
|
||||||
|
= SocksAuthUsername {username :: ByteString, password :: ByteString}
|
||||||
|
| SocksAuthNull
|
||||||
|
| SocksIsolateByAuth -- this is default
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
instance Show SocksProxy where show (SocksProxy addr) = show addr
|
instance Show SocksProxy where show (SocksProxy addr) = show addr
|
||||||
|
|
||||||
instance StrEncoding SocksProxy where
|
instance StrEncoding SocksProxy where
|
||||||
strEncode = B.pack . show
|
strEncode = B.pack . show
|
||||||
strP = do
|
strP = do
|
||||||
host <- maybe defaultSocksHost tupleToHostAddress <$> optional ipv4P
|
host <- fromMaybe (THIPv4 defaultSocksHost) <$> optional strP
|
||||||
port <- fromMaybe 9050 <$> optional (A.char ':' *> (fromInteger <$> A.decimal))
|
port <- fromMaybe 9050 <$> optional (A.char ':' *> (fromInteger <$> A.decimal))
|
||||||
pure . SocksProxy $ SockAddrInet port host
|
SocksProxy <$> socksAddr port host
|
||||||
where
|
where
|
||||||
ipv4P = (,,,) <$> ipNum <*> ipNum <*> ipNum <*> A.decimal
|
socksAddr port = \case
|
||||||
ipNum = A.decimal <* A.char '.'
|
THIPv4 addr -> pure $ SockAddrInet port $ tupleToHostAddress addr
|
||||||
|
THIPv6 addr -> pure $ SockAddrInet6 port 0 addr 0
|
||||||
|
_ -> fail "SOCKS5 host should be IPv4 or IPv6 address"
|
||||||
|
|
||||||
instance ToJSON SocksProxy where
|
instance StrEncoding SocksProxyWithAuth where
|
||||||
|
strEncode (SocksProxyWithAuth auth proxy) = strEncode auth <> strEncode proxy
|
||||||
|
strP = SocksProxyWithAuth <$> strP <*> strP
|
||||||
|
|
||||||
|
instance ToJSON SocksProxyWithAuth where
|
||||||
toJSON = strToJSON
|
toJSON = strToJSON
|
||||||
toEncoding = strToJEncoding
|
toEncoding = strToJEncoding
|
||||||
|
|
||||||
instance FromJSON SocksProxy where
|
instance FromJSON SocksProxyWithAuth where
|
||||||
parseJSON = strParseJSON "SocksProxy"
|
parseJSON = strParseJSON "SocksProxyWithAuth"
|
||||||
|
|
||||||
|
instance StrEncoding SocksAuth where
|
||||||
|
strEncode = \case
|
||||||
|
SocksAuthUsername {username, password} -> username <> ":" <> password <> "@"
|
||||||
|
SocksAuthNull -> "@"
|
||||||
|
SocksIsolateByAuth -> ""
|
||||||
|
strP = usernameP <|> (SocksAuthNull <$ A.char '@') <|> pure SocksIsolateByAuth
|
||||||
|
where
|
||||||
|
usernameP = do
|
||||||
|
username <- A.takeTill (== ':') <* A.char ':'
|
||||||
|
password <- A.takeTill (== '@') <* A.char '@'
|
||||||
|
pure SocksAuthUsername {username, password}
|
||||||
|
|
||||||
mkTLSClientParams :: T.Supported -> Maybe XS.CertificateStore -> HostName -> ServiceName -> Maybe C.KeyHash -> Maybe (X.CertificateChain, T.PrivKey) -> Maybe [ALPN] -> TMVar X.CertificateChain -> T.ClientParams
|
mkTLSClientParams :: T.Supported -> Maybe XS.CertificateStore -> HostName -> ServiceName -> Maybe C.KeyHash -> Maybe (X.CertificateChain, T.PrivKey) -> Maybe [ALPN] -> TMVar X.CertificateChain -> T.ClientParams
|
||||||
mkTLSClientParams supported caStore_ host port cafp_ clientCreds_ alpn_ serverCerts =
|
mkTLSClientParams supported caStore_ host port cafp_ clientCreds_ alpn_ serverCerts =
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import Control.Concurrent.Async
|
|||||||
import Control.Exception (IOException, try)
|
import Control.Exception (IOException, try)
|
||||||
import qualified Control.Exception as E
|
import qualified Control.Exception as E
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Data.ByteString.Char8 (ByteString)
|
|
||||||
import Data.Functor (($>))
|
import Data.Functor (($>))
|
||||||
import Data.Time (UTCTime, getCurrentTime)
|
import Data.Time (UTCTime, getCurrentTime)
|
||||||
import qualified Data.X509 as X
|
import qualified Data.X509 as X
|
||||||
@@ -20,6 +19,7 @@ import Network.HPACK (BufferSize)
|
|||||||
import Network.HTTP2.Client (ClientConfig (..), Request, Response)
|
import Network.HTTP2.Client (ClientConfig (..), Request, Response)
|
||||||
import qualified Network.HTTP2.Client as H
|
import qualified Network.HTTP2.Client as H
|
||||||
import Network.Socket (HostName, ServiceName)
|
import Network.Socket (HostName, ServiceName)
|
||||||
|
import Network.Socks5 (SocksCredentials)
|
||||||
import qualified Network.TLS as T
|
import qualified Network.TLS as T
|
||||||
import Numeric.Natural (Natural)
|
import Numeric.Natural (Natural)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
@@ -91,10 +91,10 @@ data HTTP2ClientError = HCResponseTimeout | HCNetworkError | HCIOError IOExcepti
|
|||||||
getHTTP2Client :: HostName -> ServiceName -> Maybe XS.CertificateStore -> HTTP2ClientConfig -> IO () -> IO (Either HTTP2ClientError HTTP2Client)
|
getHTTP2Client :: HostName -> ServiceName -> Maybe XS.CertificateStore -> HTTP2ClientConfig -> IO () -> IO (Either HTTP2ClientError HTTP2Client)
|
||||||
getHTTP2Client host port = getVerifiedHTTP2Client Nothing (THDomainName host) port Nothing
|
getHTTP2Client host port = getVerifiedHTTP2Client Nothing (THDomainName host) port Nothing
|
||||||
|
|
||||||
getVerifiedHTTP2Client :: Maybe ByteString -> TransportHost -> ServiceName -> Maybe C.KeyHash -> Maybe XS.CertificateStore -> HTTP2ClientConfig -> IO () -> IO (Either HTTP2ClientError HTTP2Client)
|
getVerifiedHTTP2Client :: Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> Maybe XS.CertificateStore -> HTTP2ClientConfig -> IO () -> IO (Either HTTP2ClientError HTTP2Client)
|
||||||
getVerifiedHTTP2Client proxyUsername host port keyHash caStore config disconnected = getVerifiedHTTP2ClientWith config host port disconnected setup
|
getVerifiedHTTP2Client socksCreds host port keyHash caStore config disconnected = getVerifiedHTTP2ClientWith config host port disconnected setup
|
||||||
where
|
where
|
||||||
setup = runHTTP2Client (suportedTLSParams config) caStore (transportConfig config) (bufferSize config) proxyUsername host port keyHash
|
setup = runHTTP2Client (suportedTLSParams config) caStore (transportConfig config) (bufferSize config) socksCreds host port keyHash
|
||||||
|
|
||||||
attachHTTP2Client :: HTTP2ClientConfig -> TransportHost -> ServiceName -> IO () -> Int -> TLS -> IO (Either HTTP2ClientError HTTP2Client)
|
attachHTTP2Client :: HTTP2ClientConfig -> TransportHost -> ServiceName -> IO () -> Int -> TLS -> IO (Either HTTP2ClientError HTTP2Client)
|
||||||
attachHTTP2Client config host port disconnected bufferSize tls = getVerifiedHTTP2ClientWith config host port disconnected setup
|
attachHTTP2Client config host port disconnected bufferSize tls = getVerifiedHTTP2ClientWith config host port disconnected setup
|
||||||
@@ -178,11 +178,11 @@ sendRequestDirect HTTP2Client {client_ = HClient {config, disconnected}, sendReq
|
|||||||
http2RequestTimeout :: HTTP2ClientConfig -> Maybe Int -> Int
|
http2RequestTimeout :: HTTP2ClientConfig -> Maybe Int -> Int
|
||||||
http2RequestTimeout HTTP2ClientConfig {connTimeout} = maybe connTimeout (connTimeout +)
|
http2RequestTimeout HTTP2ClientConfig {connTimeout} = maybe connTimeout (connTimeout +)
|
||||||
|
|
||||||
runHTTP2Client :: forall a. T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> BufferSize -> Maybe ByteString -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (TLS -> H.Client a) -> IO a
|
runHTTP2Client :: forall a. T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> BufferSize -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (TLS -> H.Client a) -> IO a
|
||||||
runHTTP2Client tlsParams caStore tcConfig bufferSize proxyUsername host port keyHash = runHTTP2ClientWith bufferSize host setup
|
runHTTP2Client tlsParams caStore tcConfig bufferSize socksCreds host port keyHash = runHTTP2ClientWith bufferSize host setup
|
||||||
where
|
where
|
||||||
setup :: (TLS -> IO a) -> IO a
|
setup :: (TLS -> IO a) -> IO a
|
||||||
setup = runTLSTransportClient tlsParams caStore tcConfig proxyUsername host port keyHash
|
setup = runTLSTransportClient tlsParams caStore tcConfig socksCreds host port keyHash
|
||||||
|
|
||||||
runHTTP2ClientWith :: forall a. BufferSize -> TransportHost -> ((TLS -> IO a) -> IO a) -> (TLS -> H.Client a) -> IO a
|
runHTTP2ClientWith :: forall a. BufferSize -> TransportHost -> ((TLS -> IO a) -> IO a) -> (TLS -> H.Client a) -> IO a
|
||||||
runHTTP2ClientWith bufferSize host setup client = setup $ \tls -> withHTTP2 bufferSize (run tls) (pure ()) tls
|
runHTTP2ClientWith bufferSize host setup client = setup $ \tls -> withHTTP2 bufferSize (run tls) (pure ()) tls
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ testNtfMatrix t runTest = do
|
|||||||
it "curr servers; curr clients" $ runNtfTestCfg t 1 cfg ntfServerCfg agentCfg agentCfg runTest
|
it "curr servers; curr clients" $ runNtfTestCfg t 1 cfg ntfServerCfg agentCfg agentCfg runTest
|
||||||
it "curr servers; prev clients" $ runNtfTestCfg t 3 cfg ntfServerCfg agentCfgVPrevPQ agentCfgVPrevPQ runTest
|
it "curr servers; prev clients" $ runNtfTestCfg t 3 cfg ntfServerCfg agentCfgVPrevPQ agentCfgVPrevPQ runTest
|
||||||
it "prev servers; prev clients" $ runNtfTestCfg t 3 cfgVPrev ntfServerCfgVPrev agentCfgVPrevPQ agentCfgVPrevPQ runTest
|
it "prev servers; prev clients" $ runNtfTestCfg t 3 cfgVPrev ntfServerCfgVPrev agentCfgVPrevPQ agentCfgVPrevPQ runTest
|
||||||
it "prev servers; curr clients" $ runNtfTestCfg t 3 cfgVPrev ntfServerCfgVPrev agentCfg agentCfg runTest
|
it "prev servers; curr clients" $ runNtfTestCfg t 1 cfgVPrev ntfServerCfgVPrev agentCfg agentCfg runTest
|
||||||
-- servers can be upgraded in any order
|
-- servers can be upgraded in any order
|
||||||
it "servers: curr SMP, prev NTF; prev clients" $ runNtfTestCfg t 3 cfg ntfServerCfgVPrev agentCfgVPrevPQ agentCfgVPrevPQ runTest
|
it "servers: curr SMP, prev NTF; prev clients" $ runNtfTestCfg t 3 cfg ntfServerCfgVPrev agentCfgVPrevPQ agentCfgVPrevPQ runTest
|
||||||
it "servers: prev SMP, curr NTF; prev clients" $ runNtfTestCfg t 3 cfgVPrev ntfServerCfg agentCfgVPrevPQ agentCfgVPrevPQ runTest
|
it "servers: prev SMP, curr NTF; prev clients" $ runNtfTestCfg t 3 cfgVPrev ntfServerCfg agentCfgVPrevPQ agentCfgVPrevPQ runTest
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE OverloadedLists #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
|
||||||
|
|
||||||
|
module CoreTests.SOCKSSettings where
|
||||||
|
|
||||||
|
import Network.Socket (SockAddr (..), tupleToHostAddress)
|
||||||
|
import Simplex.Messaging.Client
|
||||||
|
import Simplex.Messaging.Encoding.String
|
||||||
|
import Simplex.Messaging.Protocol (ErrorType)
|
||||||
|
import Simplex.Messaging.Transport.Client
|
||||||
|
import Test.Hspec
|
||||||
|
|
||||||
|
socksSettingsTests :: Spec
|
||||||
|
socksSettingsTests = do
|
||||||
|
describe "hostMode and requiredHostMode settings" testHostMode
|
||||||
|
describe "socksMode setting, independent of hostMode setting" testSocksMode
|
||||||
|
describe "socks proxy address encoding" testSocksProxyEncoding
|
||||||
|
|
||||||
|
testPublicHost :: TransportHost
|
||||||
|
testPublicHost = "smp.example.com"
|
||||||
|
|
||||||
|
testOnionHost :: TransportHost
|
||||||
|
testOnionHost = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrst.onion"
|
||||||
|
|
||||||
|
testHostMode :: Spec
|
||||||
|
testHostMode = do
|
||||||
|
describe "requiredHostMode = False (default)" $ do
|
||||||
|
it "without socks proxy, should choose onion host only with HMOnion" $ do
|
||||||
|
chooseTransportHost @ErrorType defaultNetworkConfig [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
|
||||||
|
chooseHost HMOnionViaSocks Nothing [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
|
||||||
|
chooseHost HMOnion Nothing [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
|
||||||
|
chooseHost HMPublic Nothing [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
|
||||||
|
it "with socks proxy, should choose onion host with HMOnionViaSocks (default) and HMOnion" $ do
|
||||||
|
chooseTransportHost @ErrorType defaultNetworkConfig {socksProxy = Just defaultSocksProxyWithAuth} [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
|
||||||
|
chooseHost HMOnionViaSocks (Just defaultSocksProxyWithAuth) [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
|
||||||
|
chooseHost HMOnion (Just defaultSocksProxyWithAuth) [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
|
||||||
|
chooseHost HMPublic (Just defaultSocksProxyWithAuth) [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
|
||||||
|
it "should choose any available host, if preferred not available" $ do
|
||||||
|
chooseHost HMOnionViaSocks Nothing [testOnionHost] `shouldBe` Right testOnionHost
|
||||||
|
chooseHost HMOnion Nothing [testPublicHost] `shouldBe` Right testPublicHost
|
||||||
|
chooseHost HMPublic Nothing [testOnionHost] `shouldBe` Right testOnionHost
|
||||||
|
chooseHost HMOnionViaSocks (Just defaultSocksProxyWithAuth) [testPublicHost] `shouldBe` Right testPublicHost
|
||||||
|
chooseHost HMOnion (Just defaultSocksProxyWithAuth) [testPublicHost] `shouldBe` Right testPublicHost
|
||||||
|
chooseHost HMPublic (Just defaultSocksProxyWithAuth) [testOnionHost] `shouldBe` Right testOnionHost
|
||||||
|
describe "requiredHostMode = True" $ do
|
||||||
|
it "should fail, if preferred host not available" $ do
|
||||||
|
testOnionHost `incompatible` (HMOnionViaSocks, Nothing)
|
||||||
|
testPublicHost `incompatible` (HMOnion, Nothing)
|
||||||
|
testOnionHost `incompatible` (HMPublic, Nothing)
|
||||||
|
testPublicHost `incompatible` (HMOnionViaSocks, Just defaultSocksProxyWithAuth)
|
||||||
|
testPublicHost `incompatible` (HMOnion, Just defaultSocksProxyWithAuth)
|
||||||
|
testOnionHost `incompatible` (HMPublic, Just defaultSocksProxyWithAuth)
|
||||||
|
it "should choose preferred host, if available" $ do
|
||||||
|
testPublicHost `compatible` (HMOnionViaSocks, Nothing)
|
||||||
|
testOnionHost `compatible` (HMOnion, Nothing)
|
||||||
|
testPublicHost `compatible` (HMPublic, Nothing)
|
||||||
|
testOnionHost `compatible` (HMOnionViaSocks, Just defaultSocksProxyWithAuth)
|
||||||
|
testOnionHost `compatible` (HMOnion, Just defaultSocksProxyWithAuth)
|
||||||
|
testPublicHost `compatible` (HMPublic, Just defaultSocksProxyWithAuth)
|
||||||
|
where
|
||||||
|
chooseHost = chooseHostCfg defaultNetworkConfig
|
||||||
|
host `incompatible` (hostMode, socksProxy) =
|
||||||
|
chooseHostCfg defaultNetworkConfig {requiredHostMode = True} hostMode socksProxy [host] `shouldBe` Left PCEIncompatibleHost
|
||||||
|
host `compatible` (hostMode, socksProxy) = do
|
||||||
|
chooseHostCfg defaultNetworkConfig {requiredHostMode = True} hostMode socksProxy [host] `shouldBe` Right host
|
||||||
|
chooseHostCfg defaultNetworkConfig {requiredHostMode = True} hostMode socksProxy [host, testPublicHost, testOnionHost] `shouldBe` Right host
|
||||||
|
chooseHostCfg cfg hostMode socksProxy =
|
||||||
|
chooseTransportHost @ErrorType cfg {hostMode, socksProxy}
|
||||||
|
|
||||||
|
testSocksMode :: Spec
|
||||||
|
testSocksMode = do
|
||||||
|
it "should not use SOCKS proxy if not specified" $ do
|
||||||
|
transportSocksCfg defaultNetworkConfig testPublicHost `shouldBe` Nothing
|
||||||
|
transportSocksCfg defaultNetworkConfig testOnionHost `shouldBe` Nothing
|
||||||
|
transportSocks Nothing SMAlways testPublicHost `shouldBe` Nothing
|
||||||
|
transportSocks Nothing SMAlways testOnionHost `shouldBe` Nothing
|
||||||
|
transportSocks Nothing SMOnion testPublicHost `shouldBe` Nothing
|
||||||
|
transportSocks Nothing SMOnion testOnionHost `shouldBe` Nothing
|
||||||
|
it "should always use SOCKS proxy if specified and (socksMode = SMAlways or (socksMode = SMOnion and onion host))" $ do
|
||||||
|
transportSocksCfg defaultNetworkConfig {socksProxy = Just defaultSocksProxyWithAuth} testPublicHost `shouldBe` Just defaultSocksProxy
|
||||||
|
transportSocksCfg defaultNetworkConfig {socksProxy = Just defaultSocksProxyWithAuth} testOnionHost `shouldBe` Just defaultSocksProxy
|
||||||
|
transportSocks (Just defaultSocksProxyWithAuth) SMAlways testPublicHost `shouldBe` Just defaultSocksProxy
|
||||||
|
transportSocks (Just defaultSocksProxyWithAuth) SMAlways testOnionHost `shouldBe` Just defaultSocksProxy
|
||||||
|
transportSocks (Just defaultSocksProxyWithAuth) SMOnion testPublicHost `shouldBe` Nothing
|
||||||
|
transportSocks (Just defaultSocksProxyWithAuth) SMOnion testOnionHost `shouldBe` Just defaultSocksProxy
|
||||||
|
where
|
||||||
|
transportSocks proxy socksMode = transportSocksCfg defaultNetworkConfig {socksProxy = proxy, socksMode}
|
||||||
|
transportSocksCfg cfg host =
|
||||||
|
let TransportClientConfig {socksProxy} = transportClientConfig cfg host
|
||||||
|
in socksProxy
|
||||||
|
|
||||||
|
testSocksProxyEncoding :: Spec
|
||||||
|
testSocksProxyEncoding = do
|
||||||
|
it "should decode SOCKS proxy with isolate-by-auth mode" $ do
|
||||||
|
let authIsolate proxy = Right $ SocksProxyWithAuth SocksIsolateByAuth proxy
|
||||||
|
strDecode "" `shouldBe` authIsolate defaultSocksProxy
|
||||||
|
strDecode ":9050" `shouldBe` authIsolate defaultSocksProxy
|
||||||
|
strDecode ":8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet 8080 $ tupleToHostAddress defaultSocksHost)
|
||||||
|
strDecode "127.0.0.1" `shouldBe` authIsolate defaultSocksProxy
|
||||||
|
strDecode "1.1.1.1" `shouldBe` authIsolate (SocksProxy $ SockAddrInet 9050 $ tupleToHostAddress (1, 1, 1, 1))
|
||||||
|
strDecode "::1" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 9050 0 (0, 0, 0, 1) 0)
|
||||||
|
strDecode "[fd12:3456:789a:1::1]" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 9050 0 (0xfd123456, 0x789a0001, 0, 1) 0)
|
||||||
|
strDecode "127.0.0.1:9050" `shouldBe` authIsolate defaultSocksProxy
|
||||||
|
strDecode "127.0.0.1:8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet 8080 $ tupleToHostAddress defaultSocksHost)
|
||||||
|
strDecode "[::1]:9050" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 9050 0 (0, 0, 0, 1) 0)
|
||||||
|
strDecode "[::1]:8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 8080 0 (0, 0, 0, 1) 0)
|
||||||
|
strDecode "[fd12:3456:789a:1::1]:8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)
|
||||||
|
it "should decode SOCKS proxy without credentials" $ do
|
||||||
|
let authNull proxy = Right $ SocksProxyWithAuth SocksAuthNull proxy
|
||||||
|
strDecode "@" `shouldBe` authNull defaultSocksProxy
|
||||||
|
strDecode "@:9050" `shouldBe` authNull defaultSocksProxy
|
||||||
|
strDecode "@127.0.0.1" `shouldBe` authNull defaultSocksProxy
|
||||||
|
strDecode "@1.1.1.1" `shouldBe` authNull (SocksProxy $ SockAddrInet 9050 $ tupleToHostAddress (1, 1, 1, 1))
|
||||||
|
strDecode "@127.0.0.1:9050" `shouldBe` authNull defaultSocksProxy
|
||||||
|
strDecode "@[fd12:3456:789a:1::1]:8080" `shouldBe` authNull (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)
|
||||||
|
it "should decode SOCKS proxy with credentials" $ do
|
||||||
|
let authUser proxy = Right $ SocksProxyWithAuth SocksAuthUsername {username = "user", password = "pass"} proxy
|
||||||
|
strDecode "user:pass@" `shouldBe` authUser defaultSocksProxy
|
||||||
|
strDecode "user:pass@:9050" `shouldBe` authUser defaultSocksProxy
|
||||||
|
strDecode "user:pass@127.0.0.1" `shouldBe` authUser defaultSocksProxy
|
||||||
|
strDecode "user:pass@127.0.0.1:9050" `shouldBe` authUser defaultSocksProxy
|
||||||
|
strDecode "user:pass@fd12:3456:789a:1::1" `shouldBe` authUser (SocksProxy $ SockAddrInet6 9050 0 (0xfd123456, 0x789a0001, 0, 1) 0)
|
||||||
|
strDecode "user:pass@[fd12:3456:789a:1::1]:8080" `shouldBe` authUser (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)
|
||||||
@@ -385,6 +385,10 @@ testSwitchSub (ATransport t) =
|
|||||||
Resp "bcda" _ ok3 <- signSendRecv rh2 rKey ("bcda", rId, ACK mId3)
|
Resp "bcda" _ ok3 <- signSendRecv rh2 rKey ("bcda", rId, ACK mId3)
|
||||||
(ok3, OK) #== "accepts ACK from the 2nd TCP connection"
|
(ok3, OK) #== "accepts ACK from the 2nd TCP connection"
|
||||||
|
|
||||||
|
Resp "cdab" _ OK <- signSendRecv rh1 rKey ("cdab", rId, DEL)
|
||||||
|
Resp "" rId' DELD <- tGet1 rh2
|
||||||
|
(rId', rId) #== "connection deleted event delivered to subscribed client"
|
||||||
|
|
||||||
1000 `timeout` tGet @SMPVersion @ErrorType @BrokerMsg rh1 >>= \case
|
1000 `timeout` tGet @SMPVersion @ErrorType @BrokerMsg rh1 >>= \case
|
||||||
Nothing -> return ()
|
Nothing -> return ()
|
||||||
Just _ -> error "nothing else is delivered to the 1st TCP connection"
|
Just _ -> error "nothing else is delivered to the 1st TCP connection"
|
||||||
@@ -839,7 +843,8 @@ testMessageNotifications (ATransport t) =
|
|||||||
Resp "3a" _ OK <- signSendRecv rh rKey ("3a", rId, ACK mId1)
|
Resp "3a" _ OK <- signSendRecv rh rKey ("3a", rId, ACK mId1)
|
||||||
Resp "" _ (NMSG _ _) <- tGet1 nh1
|
Resp "" _ (NMSG _ _) <- tGet1 nh1
|
||||||
Resp "4" _ OK <- signSendRecv nh2 nKey ("4", nId, NSUB)
|
Resp "4" _ OK <- signSendRecv nh2 nKey ("4", nId, NSUB)
|
||||||
Resp "" _ END <- tGet1 nh1
|
Resp "" nId2 END <- tGet1 nh1
|
||||||
|
nId2 `shouldBe` nId
|
||||||
Resp "5" _ OK <- signSendRecv sh sKey ("5", sId, _SEND' "hello again")
|
Resp "5" _ OK <- signSendRecv sh sKey ("5", sId, _SEND' "hello again")
|
||||||
Resp "" _ (Msg mId2 msg2) <- tGet1 rh
|
Resp "" _ (Msg mId2 msg2) <- tGet1 rh
|
||||||
Resp "5a" _ OK <- signSendRecv rh rKey ("5a", rId, ACK mId2)
|
Resp "5a" _ OK <- signSendRecv rh rKey ("5a", rId, ACK mId2)
|
||||||
@@ -849,6 +854,8 @@ testMessageNotifications (ATransport t) =
|
|||||||
Nothing -> pure ()
|
Nothing -> pure ()
|
||||||
Just _ -> error "nothing else should be delivered to the 1st notifier's TCP connection"
|
Just _ -> error "nothing else should be delivered to the 1st notifier's TCP connection"
|
||||||
Resp "6" _ OK <- signSendRecv rh rKey ("6", rId, NDEL)
|
Resp "6" _ OK <- signSendRecv rh rKey ("6", rId, NDEL)
|
||||||
|
Resp "" nId3 DELD <- tGet1 nh2
|
||||||
|
nId3 `shouldBe` nId
|
||||||
Resp "7" _ OK <- signSendRecv sh sKey ("7", sId, _SEND' "hello there")
|
Resp "7" _ OK <- signSendRecv sh sKey ("7", sId, _SEND' "hello there")
|
||||||
Resp "" _ (Msg mId3 msg3) <- tGet1 rh
|
Resp "" _ (Msg mId3 msg3) <- tGet1 rh
|
||||||
(dec mId3 msg3, Right "hello there") #== "delivered from queue again"
|
(dec mId3 msg3, Right "hello there") #== "delivered from queue again"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import CoreTests.CryptoFileTests
|
|||||||
import CoreTests.CryptoTests
|
import CoreTests.CryptoTests
|
||||||
import CoreTests.EncodingTests
|
import CoreTests.EncodingTests
|
||||||
import CoreTests.RetryIntervalTests
|
import CoreTests.RetryIntervalTests
|
||||||
|
import CoreTests.SOCKSSettings
|
||||||
import CoreTests.TRcvQueuesTests
|
import CoreTests.TRcvQueuesTests
|
||||||
import CoreTests.UtilTests
|
import CoreTests.UtilTests
|
||||||
import CoreTests.VersionRangeTests
|
import CoreTests.VersionRangeTests
|
||||||
@@ -52,6 +53,7 @@ main = do
|
|||||||
describe "Encryption tests" cryptoTests
|
describe "Encryption tests" cryptoTests
|
||||||
describe "Encrypted files tests" cryptoFileTests
|
describe "Encrypted files tests" cryptoFileTests
|
||||||
describe "Retry interval tests" retryIntervalTests
|
describe "Retry interval tests" retryIntervalTests
|
||||||
|
describe "SOCKS settings tests" socksSettingsTests
|
||||||
describe "TRcvQueues tests" tRcvQueuesTests
|
describe "TRcvQueues tests" tRcvQueuesTests
|
||||||
describe "Util tests" utilTests
|
describe "Util tests" utilTests
|
||||||
describe "SMP server via TLS" $ serverTests (transport @TLS)
|
describe "SMP server via TLS" $ serverTests (transport @TLS)
|
||||||
|
|||||||
+2
-2
@@ -429,7 +429,7 @@ testXFTPAgentSendRestore = withGlobalLogging logCfgNoLogs $ do
|
|||||||
("", sfId', SFPROG _ _) <- sfGet sndr'
|
("", sfId', SFPROG _ _) <- sfGet sndr'
|
||||||
liftIO $ sfId' `shouldBe` sfId
|
liftIO $ sfId' `shouldBe` sfId
|
||||||
|
|
||||||
threadDelay 100000
|
threadDelay 200000
|
||||||
|
|
||||||
withXFTPServerStoreLogOn $ \_ -> do
|
withXFTPServerStoreLogOn $ \_ -> do
|
||||||
-- send file - should continue uploading with server up
|
-- send file - should continue uploading with server up
|
||||||
@@ -443,7 +443,7 @@ testXFTPAgentSendRestore = withGlobalLogging logCfgNoLogs $ do
|
|||||||
pure rfd1
|
pure rfd1
|
||||||
|
|
||||||
-- prefix path should be removed after sending file
|
-- prefix path should be removed after sending file
|
||||||
threadDelay 100000
|
threadDelay 200000
|
||||||
doesDirectoryExist prefixPath `shouldReturn` False
|
doesDirectoryExist prefixPath `shouldReturn` False
|
||||||
doesFileExist encPath `shouldReturn` False
|
doesFileExist encPath `shouldReturn` False
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user