2023-10-11 11:45:05 +03:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
2023-09-27 11:41:02 +03:00
|
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
2023-10-13 20:53:04 +03:00
|
|
|
{-# LANGUAGE GADTs #-}
|
2023-09-27 11:41:02 +03:00
|
|
|
{-# LANGUAGE LambdaCase #-}
|
|
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
2023-11-08 22:13:52 +02:00
|
|
|
{-# LANGUAGE OverloadedLists #-}
|
2023-09-27 11:41:02 +03:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
2023-10-07 16:23:24 +03:00
|
|
|
{-# LANGUAGE TupleSections #-}
|
2023-10-13 17:52:27 +01:00
|
|
|
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
|
|
|
|
|
2023-09-27 11:41:02 +03:00
|
|
|
module Simplex.Chat.Remote where
|
|
|
|
|
2023-10-30 16:00:54 +02:00
|
|
|
import Control.Applicative ((<|>))
|
2023-10-07 16:23:24 +03:00
|
|
|
import Control.Logger.Simple
|
2023-10-04 18:36:10 +03:00
|
|
|
import Control.Monad
|
2023-09-27 11:41:02 +03:00
|
|
|
import Control.Monad.Except
|
|
|
|
import Control.Monad.IO.Class
|
2023-10-29 19:06:32 +00:00
|
|
|
import Control.Monad.Reader
|
2023-10-04 18:36:10 +03:00
|
|
|
import Crypto.Random (getRandomBytes)
|
2023-09-27 11:41:02 +03:00
|
|
|
import qualified Data.Aeson as J
|
2023-11-08 22:13:52 +02:00
|
|
|
import qualified Data.Aeson.Types as JT
|
2023-10-22 11:42:19 +03:00
|
|
|
import Data.ByteString (ByteString)
|
2024-04-03 10:47:38 +01:00
|
|
|
import qualified Data.ByteString.Base64.URL as B64U
|
2023-10-22 11:42:19 +03:00
|
|
|
import Data.ByteString.Builder (Builder)
|
2023-10-04 18:36:10 +03:00
|
|
|
import qualified Data.ByteString.Char8 as B
|
2023-10-30 16:00:54 +02:00
|
|
|
import Data.Functor (($>))
|
2023-11-28 18:32:33 +02:00
|
|
|
import Data.List.NonEmpty (NonEmpty, nonEmpty)
|
|
|
|
import qualified Data.List.NonEmpty as L
|
2023-09-27 11:41:02 +03:00
|
|
|
import qualified Data.Map.Strict as M
|
2023-11-20 12:19:00 +02:00
|
|
|
import Data.Maybe (fromMaybe, isJust)
|
2023-10-15 00:18:04 +01:00
|
|
|
import Data.Text (Text)
|
2023-10-07 16:23:24 +03:00
|
|
|
import qualified Data.Text as T
|
2023-11-13 20:16:34 +00:00
|
|
|
import Data.Text.Encoding (decodeLatin1, encodeUtf8)
|
2023-11-28 18:32:33 +02:00
|
|
|
import Data.Word (Word16, Word32)
|
2023-10-22 11:42:19 +03:00
|
|
|
import qualified Network.HTTP.Types as N
|
2023-10-30 16:00:54 +02:00
|
|
|
import Network.HTTP2.Server (responseStreaming)
|
2023-11-08 22:13:52 +02:00
|
|
|
import qualified Paths_simplex_chat as SC
|
2023-09-27 11:41:02 +03:00
|
|
|
import Simplex.Chat.Controller
|
2023-10-29 19:06:32 +00:00
|
|
|
import Simplex.Chat.Files
|
|
|
|
import Simplex.Chat.Messages (chatNameStr)
|
2023-11-08 22:13:52 +02:00
|
|
|
import Simplex.Chat.Remote.AppVersion
|
2023-10-22 11:42:19 +03:00
|
|
|
import Simplex.Chat.Remote.Protocol
|
2023-11-08 22:13:52 +02:00
|
|
|
import Simplex.Chat.Remote.RevHTTP (attachHTTP2Server, attachRevHTTP2Client)
|
2023-10-29 19:06:32 +00:00
|
|
|
import Simplex.Chat.Remote.Transport
|
2023-09-27 11:41:02 +03:00
|
|
|
import Simplex.Chat.Remote.Types
|
2023-10-29 19:06:32 +00:00
|
|
|
import Simplex.Chat.Store.Files
|
2023-09-29 14:56:56 +03:00
|
|
|
import Simplex.Chat.Store.Remote
|
2023-10-29 19:06:32 +00:00
|
|
|
import Simplex.Chat.Store.Shared
|
2023-11-08 22:13:52 +02:00
|
|
|
import Simplex.Chat.Types
|
2024-04-14 17:31:56 +01:00
|
|
|
import Simplex.Chat.Util (encryptFile, liftIOEither)
|
2023-10-29 19:06:32 +00:00
|
|
|
import Simplex.FileTransfer.Description (FileDigest (..))
|
2023-11-08 22:13:52 +02:00
|
|
|
import Simplex.Messaging.Agent
|
|
|
|
import Simplex.Messaging.Agent.Protocol (AgentErrorType (RCP))
|
2024-09-24 09:25:41 +01:00
|
|
|
import qualified Simplex.Messaging.Crypto as C
|
2023-10-29 19:06:32 +00:00
|
|
|
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
|
|
|
|
import qualified Simplex.Messaging.Crypto.File as CF
|
2023-09-29 14:56:56 +03:00
|
|
|
import Simplex.Messaging.Encoding.String (StrEncoding (..))
|
2023-10-30 16:00:54 +02:00
|
|
|
import qualified Simplex.Messaging.TMap as TM
|
2023-11-10 16:10:10 +00:00
|
|
|
import Simplex.Messaging.Transport (TLS, closeConnection, tlsUniq)
|
2023-11-08 22:13:52 +02:00
|
|
|
import Simplex.Messaging.Transport.HTTP2.Client (HTTP2ClientError, closeHTTP2Client)
|
2023-10-29 19:06:32 +00:00
|
|
|
import Simplex.Messaging.Transport.HTTP2.Server (HTTP2Request (..))
|
2023-11-08 22:13:52 +02:00
|
|
|
import Simplex.Messaging.Util
|
|
|
|
import Simplex.RemoteControl.Client
|
2023-11-17 20:50:38 +02:00
|
|
|
import Simplex.RemoteControl.Invitation (RCInvitation (..), RCSignedInvitation (..), RCVerifiedInvitation (..), verifySignedInvitation)
|
2023-11-01 12:48:58 +02:00
|
|
|
import Simplex.RemoteControl.Types
|
2023-10-30 16:00:54 +02:00
|
|
|
import System.FilePath (takeFileName, (</>))
|
2023-09-29 14:56:56 +03:00
|
|
|
import UnliftIO
|
2023-11-08 22:13:52 +02:00
|
|
|
import UnliftIO.Concurrent (forkIO)
|
2023-11-15 17:57:29 +02:00
|
|
|
import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExist, removeDirectoryRecursive, renameFile)
|
2023-10-22 11:42:19 +03:00
|
|
|
|
2025-01-10 15:27:29 +04:00
|
|
|
remoteFilesFolder :: String
|
|
|
|
remoteFilesFolder = "simplex_v1_files"
|
|
|
|
|
2023-11-08 22:13:52 +02:00
|
|
|
-- when acting as host
|
|
|
|
minRemoteCtrlVersion :: AppVersion
|
2025-04-23 13:27:58 +01:00
|
|
|
minRemoteCtrlVersion = AppVersion [6, 3, 3, 1]
|
2023-09-27 11:41:02 +03:00
|
|
|
|
2023-11-08 22:13:52 +02:00
|
|
|
-- when acting as controller
|
|
|
|
minRemoteHostVersion :: AppVersion
|
2025-04-23 13:27:58 +01:00
|
|
|
minRemoteHostVersion = AppVersion [6, 3, 3, 1]
|
2023-10-22 11:42:19 +03:00
|
|
|
|
2023-11-08 22:13:52 +02:00
|
|
|
currentAppVersion :: AppVersion
|
|
|
|
currentAppVersion = AppVersion SC.version
|
|
|
|
|
|
|
|
ctrlAppVersionRange :: AppVersionRange
|
|
|
|
ctrlAppVersionRange = mkAppVersionRange minRemoteHostVersion currentAppVersion
|
|
|
|
|
|
|
|
hostAppVersionRange :: AppVersionRange
|
|
|
|
hostAppVersionRange = mkAppVersionRange minRemoteCtrlVersion currentAppVersion
|
2023-10-15 14:17:36 +01:00
|
|
|
|
2023-11-13 20:39:41 +02:00
|
|
|
networkIOTimeout :: Int
|
|
|
|
networkIOTimeout = 15000000
|
|
|
|
|
2023-11-17 20:50:38 +02:00
|
|
|
discoveryTimeout :: Int
|
|
|
|
discoveryTimeout = 60000000
|
|
|
|
|
2023-11-08 22:13:52 +02:00
|
|
|
-- * Desktop side
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
getRemoteHostClient :: RemoteHostId -> CM RemoteHostClient
|
2023-11-20 12:19:00 +02:00
|
|
|
getRemoteHostClient rhId = do
|
|
|
|
sessions <- asks remoteHostSessions
|
2023-11-26 18:16:37 +00:00
|
|
|
liftIOEither . atomically $
|
|
|
|
TM.lookup rhKey sessions >>= \case
|
|
|
|
Just (_, RHSessionConnected {rhClient}) -> pure $ Right rhClient
|
|
|
|
Just _ -> pure . Left $ ChatErrorRemoteHost rhKey RHEBadState
|
|
|
|
Nothing -> pure . Left $ ChatErrorRemoteHost rhKey RHEMissing
|
2023-10-15 14:17:36 +01:00
|
|
|
where
|
2023-11-08 22:13:52 +02:00
|
|
|
rhKey = RHId rhId
|
2023-10-22 11:42:19 +03:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
withRemoteHostSession :: RHKey -> SessionSeq -> (RemoteHostSession -> Either ChatError (a, RemoteHostSession)) -> CM a
|
2023-11-20 12:19:00 +02:00
|
|
|
withRemoteHostSession rhKey sseq f = do
|
2023-11-08 22:13:52 +02:00
|
|
|
sessions <- asks remoteHostSessions
|
2023-11-26 18:16:37 +00:00
|
|
|
r <-
|
|
|
|
atomically $
|
|
|
|
TM.lookup rhKey sessions >>= \case
|
|
|
|
Nothing -> pure . Left $ ChatErrorRemoteHost rhKey RHEMissing
|
|
|
|
Just (stateSeq, state)
|
|
|
|
| stateSeq /= sseq -> pure . Left $ ChatErrorRemoteHost rhKey RHEBadState
|
|
|
|
| otherwise -> case f state of
|
|
|
|
Right (r, newState) -> Right r <$ TM.insert rhKey (sseq, newState) sessions
|
|
|
|
Left ce -> pure $ Left ce
|
2023-11-08 22:13:52 +02:00
|
|
|
liftEither r
|
|
|
|
|
2023-11-20 12:19:00 +02:00
|
|
|
-- | Transition session state with a 'RHNew' ID to an assigned 'RemoteHostId'
|
2024-04-01 13:34:45 +01:00
|
|
|
setNewRemoteHostId :: SessionSeq -> RemoteHostId -> CM ()
|
2023-11-20 12:19:00 +02:00
|
|
|
setNewRemoteHostId sseq rhId = do
|
2023-10-22 11:42:19 +03:00
|
|
|
sessions <- asks remoteHostSessions
|
2023-11-20 12:19:00 +02:00
|
|
|
liftIOEither . atomically $ do
|
|
|
|
TM.lookup RHNew sessions >>= \case
|
|
|
|
Nothing -> err RHEMissing
|
|
|
|
Just sess@(stateSeq, _)
|
|
|
|
| stateSeq /= sseq -> err RHEBadState
|
|
|
|
| otherwise -> do
|
|
|
|
TM.delete RHNew sessions
|
|
|
|
TM.insert (RHId rhId) sess sessions
|
|
|
|
pure $ Right ()
|
|
|
|
where
|
|
|
|
err = pure . Left . ChatErrorRemoteHost RHNew
|
2023-11-08 22:13:52 +02:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
startRemoteHost :: Maybe (RemoteHostId, Bool) -> Maybe RCCtrlAddress -> Maybe Word16 -> CM (NonEmpty RCCtrlAddress, Maybe RemoteHostInfo, RCSignedInvitation)
|
2023-11-28 18:32:33 +02:00
|
|
|
startRemoteHost rh_ rcAddrPrefs_ port_ = do
|
2023-11-08 22:13:52 +02:00
|
|
|
(rhKey, multicast, remoteHost_, pairing) <- case rh_ of
|
|
|
|
Just (rhId, multicast) -> do
|
|
|
|
rh@RemoteHost {hostPairing} <- withStore $ \db -> getRemoteHost db rhId
|
2023-11-13 20:16:34 +00:00
|
|
|
pure (RHId rhId, multicast, Just $ remoteHostInfo rh $ Just RHSStarting, hostPairing) -- get from the database, start multicast if requested
|
2024-04-01 13:34:45 +01:00
|
|
|
Nothing -> lift . withAgent' $ \a -> (RHNew,False,Nothing,) <$> rcNewHostPairing a
|
2023-11-20 12:19:00 +02:00
|
|
|
sseq <- startRemoteHostSession rhKey
|
2023-11-08 22:13:52 +02:00
|
|
|
ctrlAppInfo <- mkCtrlAppInfo
|
2023-11-28 18:32:33 +02:00
|
|
|
(localAddrs, invitation, rchClient, vars) <- handleConnectError rhKey sseq . withAgent $ \a -> rcConnectHost a pairing (J.toJSON ctrlAppInfo) multicast rcAddrPrefs_ port_
|
|
|
|
let rcAddr_ = L.head localAddrs <$ rcAddrPrefs_
|
2023-11-09 20:25:05 +02:00
|
|
|
cmdOk <- newEmptyTMVarIO
|
|
|
|
rhsWaitSession <- async $ do
|
2023-11-11 16:03:12 +00:00
|
|
|
rhKeyVar <- newTVarIO rhKey
|
2023-11-09 20:25:05 +02:00
|
|
|
atomically $ takeTMVar cmdOk
|
2023-11-28 18:32:33 +02:00
|
|
|
handleHostError sseq rhKeyVar $ waitForHostSession remoteHost_ rhKey sseq rcAddr_ rhKeyVar vars
|
2023-11-08 22:13:52 +02:00
|
|
|
let rhs = RHPendingSession {rhKey, rchClient, rhsWaitSession, remoteHost_}
|
2023-11-20 12:19:00 +02:00
|
|
|
withRemoteHostSession rhKey sseq $ \case
|
2023-11-13 20:16:34 +00:00
|
|
|
RHSessionStarting ->
|
|
|
|
let inv = decodeLatin1 $ strEncode invitation
|
|
|
|
in Right ((), RHSessionConnecting inv rhs)
|
2023-11-08 22:13:52 +02:00
|
|
|
_ -> Left $ ChatErrorRemoteHost rhKey RHEBadState
|
2023-11-28 18:32:33 +02:00
|
|
|
(localAddrs, remoteHost_, invitation) <$ atomically (putTMVar cmdOk ())
|
2023-10-07 16:23:24 +03:00
|
|
|
where
|
2023-11-08 22:13:52 +02:00
|
|
|
mkCtrlAppInfo = do
|
|
|
|
deviceName <- chatReadVar localDeviceName
|
|
|
|
pure CtrlAppInfo {appVersionRange = ctrlAppVersionRange, deviceName}
|
2023-11-09 09:37:56 +00:00
|
|
|
parseHostAppInfo :: RCHostHello -> ExceptT RemoteHostError IO HostAppInfo
|
|
|
|
parseHostAppInfo RCHostHello {app = hostAppInfo} = do
|
2023-11-09 20:25:05 +02:00
|
|
|
hostInfo@HostAppInfo {appVersion, encoding} <-
|
2023-11-09 09:37:56 +00:00
|
|
|
liftEitherWith (RHEProtocolError . RPEInvalidJSON) $ JT.parseEither J.parseJSON hostAppInfo
|
|
|
|
unless (isAppCompatible appVersion ctrlAppVersionRange) $ throwError $ RHEBadVersion appVersion
|
|
|
|
when (encoding == PEKotlin && localEncoding == PESwift) $ throwError $ RHEProtocolError RPEIncompatibleEncoding
|
|
|
|
pure hostInfo
|
2024-04-01 13:34:45 +01:00
|
|
|
handleConnectError :: RHKey -> SessionSeq -> CM a -> CM a
|
2023-11-26 18:16:37 +00:00
|
|
|
handleConnectError rhKey sessSeq action =
|
|
|
|
action `catchChatError` \err -> do
|
|
|
|
logError $ "startRemoteHost.rcConnectHost crashed: " <> tshow err
|
|
|
|
cancelRemoteHostSession (Just (sessSeq, RHSRConnectionFailed err)) rhKey
|
|
|
|
throwError err
|
2024-04-01 13:34:45 +01:00
|
|
|
handleHostError :: SessionSeq -> TVar RHKey -> CM () -> CM ()
|
2023-11-26 18:16:37 +00:00
|
|
|
handleHostError sessSeq rhKeyVar action =
|
|
|
|
action `catchChatError` \err -> do
|
|
|
|
logError $ "startRemoteHost.waitForHostSession crashed: " <> tshow err
|
|
|
|
readTVarIO rhKeyVar >>= cancelRemoteHostSession (Just (sessSeq, RHSRCrashed err))
|
2024-04-01 13:34:45 +01:00
|
|
|
waitForHostSession :: Maybe RemoteHostInfo -> RHKey -> SessionSeq -> Maybe RCCtrlAddress -> TVar RHKey -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> CM ()
|
2023-11-28 18:32:33 +02:00
|
|
|
waitForHostSession remoteHost_ rhKey sseq rcAddr_ rhKeyVar vars = do
|
2023-11-16 16:56:39 +02:00
|
|
|
(sessId, tls, vars') <- timeoutThrow (ChatErrorRemoteHost rhKey RHETimeout) 60000000 $ takeRCStep vars
|
2023-11-15 13:17:31 +00:00
|
|
|
let sessionCode = verificationCode sessId
|
2023-11-20 12:19:00 +02:00
|
|
|
withRemoteHostSession rhKey sseq $ \case
|
|
|
|
RHSessionConnecting _inv rhs' -> Right ((), RHSessionPendingConfirmation sessionCode tls rhs')
|
2023-11-13 20:16:34 +00:00
|
|
|
_ -> Left $ ChatErrorRemoteHost rhKey RHEBadState
|
2023-11-20 12:19:00 +02:00
|
|
|
let rh_' = (\rh -> (rh :: RemoteHostInfo) {sessionState = Just RHSPendingConfirmation {sessionCode}}) <$> remoteHost_
|
2025-05-04 22:14:36 +01:00
|
|
|
toView CEvtRemoteHostSessionCode {remoteHost_ = rh_', sessionCode}
|
2023-11-16 16:56:39 +02:00
|
|
|
(RCHostSession {sessionKeys}, rhHello, pairing') <- timeoutThrow (ChatErrorRemoteHost rhKey RHETimeout) 60000000 $ takeRCStep vars'
|
2023-11-09 09:37:56 +00:00
|
|
|
hostInfo@HostAppInfo {deviceName = hostDeviceName} <-
|
|
|
|
liftError (ChatErrorRemoteHost rhKey) $ parseHostAppInfo rhHello
|
2023-11-20 12:19:00 +02:00
|
|
|
withRemoteHostSession rhKey sseq $ \case
|
|
|
|
RHSessionPendingConfirmation _ tls' rhs' -> Right ((), RHSessionConfirmed tls' rhs')
|
2023-11-09 20:25:05 +02:00
|
|
|
_ -> Left $ ChatErrorRemoteHost rhKey RHEBadState
|
2023-11-28 18:32:33 +02:00
|
|
|
rhi@RemoteHostInfo {remoteHostId, storePath} <- upsertRemoteHost pairing' rh_' rcAddr_ hostDeviceName sseq RHSConfirmed {sessionCode}
|
2023-11-09 20:25:05 +02:00
|
|
|
let rhKey' = RHId remoteHostId -- rhKey may be invalid after upserting on RHNew
|
2023-11-12 21:43:43 +00:00
|
|
|
when (rhKey' /= rhKey) $ do
|
|
|
|
atomically $ writeTVar rhKeyVar rhKey'
|
2025-05-04 22:14:36 +01:00
|
|
|
toView $ CEvtNewRemoteHost rhi
|
2023-11-20 12:19:00 +02:00
|
|
|
-- set up HTTP transport and remote profile protocol
|
|
|
|
disconnected <- toIO $ onDisconnected rhKey' sseq
|
2024-04-01 13:34:45 +01:00
|
|
|
httpClient <- liftError' (httpError remoteHostId) $ attachRevHTTP2Client disconnected tls
|
2023-11-10 16:10:10 +00:00
|
|
|
rhClient <- mkRemoteHostClient httpClient sessionKeys sessId storePath hostInfo
|
2023-11-08 22:13:52 +02:00
|
|
|
pollAction <- async $ pollEvents remoteHostId rhClient
|
2023-11-20 12:19:00 +02:00
|
|
|
withRemoteHostSession rhKey' sseq $ \case
|
2023-11-13 20:39:41 +02:00
|
|
|
RHSessionConfirmed _ RHPendingSession {rchClient} -> Right ((), RHSessionConnected {rchClient, tls, rhClient, pollAction, storePath})
|
2023-11-20 12:19:00 +02:00
|
|
|
_ -> Left $ ChatErrorRemoteHost rhKey RHEBadState
|
2023-11-08 22:13:52 +02:00
|
|
|
chatWriteVar currentRemoteHost $ Just remoteHostId -- this is required for commands to be passed to remote host
|
2025-05-04 22:14:36 +01:00
|
|
|
toView $ CEvtRemoteHostConnected rhi {sessionState = Just RHSConnected {sessionCode}}
|
2024-04-01 13:34:45 +01:00
|
|
|
upsertRemoteHost :: RCHostPairing -> Maybe RemoteHostInfo -> Maybe RCCtrlAddress -> Text -> SessionSeq -> RemoteHostSessionState -> CM RemoteHostInfo
|
2023-11-28 18:32:33 +02:00
|
|
|
upsertRemoteHost pairing'@RCHostPairing {knownHost = kh_} rhi_ rcAddr_ hostDeviceName sseq state = do
|
2023-11-08 22:13:52 +02:00
|
|
|
KnownHostPairing {hostDhPubKey = hostDhPubKey'} <- maybe (throwError . ChatError $ CEInternalError "KnownHost is known after verification") pure kh_
|
2023-11-09 20:25:05 +02:00
|
|
|
case rhi_ of
|
2023-11-08 22:13:52 +02:00
|
|
|
Nothing -> do
|
|
|
|
storePath <- liftIO randomStorePath
|
2023-11-28 18:32:33 +02:00
|
|
|
rh@RemoteHost {remoteHostId} <- withStore $ \db -> insertRemoteHost db hostDeviceName storePath rcAddr_ port_ pairing' >>= getRemoteHost db
|
2023-11-20 12:19:00 +02:00
|
|
|
setNewRemoteHostId sseq remoteHostId
|
2023-11-13 20:16:34 +00:00
|
|
|
pure $ remoteHostInfo rh $ Just state
|
2023-11-08 22:13:52 +02:00
|
|
|
Just rhi@RemoteHostInfo {remoteHostId} -> do
|
2023-11-28 18:32:33 +02:00
|
|
|
withStore' $ \db -> updateHostPairing db remoteHostId hostDeviceName hostDhPubKey' rcAddr_ port_
|
2023-11-13 20:16:34 +00:00
|
|
|
pure (rhi :: RemoteHostInfo) {sessionState = Just state}
|
2024-04-01 13:34:45 +01:00
|
|
|
onDisconnected :: RHKey -> SessionSeq -> CM ()
|
2023-11-20 12:19:00 +02:00
|
|
|
onDisconnected rhKey sseq = do
|
|
|
|
logDebug $ "HTTP2 client disconnected: " <> tshow (rhKey, sseq)
|
2023-11-24 00:00:20 +02:00
|
|
|
cancelRemoteHostSession (Just (sseq, RHSRDisconnected)) rhKey
|
2024-04-01 13:34:45 +01:00
|
|
|
pollEvents :: RemoteHostId -> RemoteHostClient -> CM ()
|
2023-11-08 22:13:52 +02:00
|
|
|
pollEvents rhId rhClient = do
|
2023-10-15 14:17:36 +01:00
|
|
|
oq <- asks outputQ
|
2023-11-08 22:13:52 +02:00
|
|
|
forever $ do
|
|
|
|
r_ <- liftRH rhId $ remoteRecv rhClient 10000000
|
2025-05-04 22:14:36 +01:00
|
|
|
forM r_ $ \r -> atomically $ writeTBQueue oq (Just rhId, r)
|
2023-11-20 12:19:00 +02:00
|
|
|
httpError :: RemoteHostId -> HTTP2ClientError -> ChatError
|
|
|
|
httpError rhId = ChatErrorRemoteHost (RHId rhId) . RHEProtocolError . RPEHTTP2 . tshow
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
startRemoteHostSession :: RHKey -> CM SessionSeq
|
2023-11-20 12:19:00 +02:00
|
|
|
startRemoteHostSession rhKey = do
|
|
|
|
sessions <- asks remoteHostSessions
|
|
|
|
nextSessionSeq <- asks remoteSessionSeq
|
|
|
|
liftIOEither . atomically $
|
|
|
|
TM.lookup rhKey sessions >>= \case
|
|
|
|
Just _ -> pure . Left $ ChatErrorRemoteHost rhKey RHEBusy
|
|
|
|
Nothing -> do
|
|
|
|
sessionSeq <- stateTVar nextSessionSeq $ \s -> (s, s + 1)
|
|
|
|
Right sessionSeq <$ TM.insert rhKey (sessionSeq, RHSessionStarting) sessions
|
2023-11-08 22:13:52 +02:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
closeRemoteHost :: RHKey -> CM ()
|
2023-11-08 22:13:52 +02:00
|
|
|
closeRemoteHost rhKey = do
|
|
|
|
logNote $ "Closing remote host session for " <> tshow rhKey
|
2023-11-20 12:19:00 +02:00
|
|
|
cancelRemoteHostSession Nothing rhKey
|
2023-11-15 19:31:36 +02:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
cancelRemoteHostSession :: Maybe (SessionSeq, RemoteHostStopReason) -> RHKey -> CM ()
|
2023-11-24 00:00:20 +02:00
|
|
|
cancelRemoteHostSession handlerInfo_ rhKey = do
|
2023-11-15 19:31:36 +02:00
|
|
|
sessions <- asks remoteHostSessions
|
2023-11-20 12:19:00 +02:00
|
|
|
crh <- asks currentRemoteHost
|
2023-11-26 18:16:37 +00:00
|
|
|
deregistered <-
|
|
|
|
atomically $
|
|
|
|
TM.lookup rhKey sessions >>= \case
|
|
|
|
Nothing -> pure Nothing
|
2023-12-09 21:59:40 +00:00
|
|
|
Just (sessSeq, _) | maybe False ((sessSeq /=) . fst) handlerInfo_ -> pure Nothing -- ignore cancel from a ghost session handler
|
2023-11-26 18:16:37 +00:00
|
|
|
Just (_, rhs) -> do
|
|
|
|
TM.delete rhKey sessions
|
|
|
|
modifyTVar' crh $ \cur -> if (RHId <$> cur) == Just rhKey then Nothing else cur -- only wipe the closing RH
|
|
|
|
pure $ Just rhs
|
2023-11-20 12:19:00 +02:00
|
|
|
forM_ deregistered $ \session -> do
|
2023-11-16 16:56:39 +02:00
|
|
|
liftIO $ cancelRemoteHost handlingError session `catchAny` (logError . tshow)
|
2023-11-24 00:00:20 +02:00
|
|
|
forM_ (snd <$> handlerInfo_) $ \rhStopReason ->
|
2025-05-04 22:14:36 +01:00
|
|
|
toView CEvtRemoteHostStopped {remoteHostId_, rhsState = rhsSessionState session, rhStopReason}
|
2023-11-15 19:31:36 +02:00
|
|
|
where
|
2023-11-24 00:00:20 +02:00
|
|
|
handlingError = isJust handlerInfo_
|
|
|
|
remoteHostId_ = case rhKey of
|
2023-11-15 19:31:36 +02:00
|
|
|
RHNew -> Nothing
|
|
|
|
RHId rhId -> Just rhId
|
2023-11-08 22:13:52 +02:00
|
|
|
|
2023-11-15 19:31:36 +02:00
|
|
|
cancelRemoteHost :: Bool -> RemoteHostSession -> IO ()
|
2023-11-16 16:56:39 +02:00
|
|
|
cancelRemoteHost handlingError = \case
|
2023-11-08 22:13:52 +02:00
|
|
|
RHSessionStarting -> pure ()
|
2023-11-13 20:16:34 +00:00
|
|
|
RHSessionConnecting _inv rhs -> cancelPendingSession rhs
|
|
|
|
RHSessionPendingConfirmation _sessCode tls rhs -> do
|
|
|
|
cancelPendingSession rhs
|
|
|
|
closeConnection tls
|
2023-11-10 00:43:44 +02:00
|
|
|
RHSessionConfirmed tls rhs -> do
|
|
|
|
cancelPendingSession rhs
|
|
|
|
closeConnection tls
|
2023-11-13 20:39:41 +02:00
|
|
|
RHSessionConnected {rchClient, tls, rhClient = RemoteHostClient {httpClient}, pollAction} -> do
|
2023-11-08 22:13:52 +02:00
|
|
|
uninterruptibleCancel pollAction
|
2023-11-15 19:31:36 +02:00
|
|
|
cancelHostClient rchClient `catchAny` (logError . tshow)
|
2023-11-16 16:56:39 +02:00
|
|
|
closeConnection tls `catchAny` (logError . tshow)
|
|
|
|
unless handlingError $ closeHTTP2Client httpClient `catchAny` (logError . tshow)
|
2023-11-08 22:13:52 +02:00
|
|
|
where
|
|
|
|
cancelPendingSession RHPendingSession {rchClient, rhsWaitSession} = do
|
2023-11-16 16:56:39 +02:00
|
|
|
unless handlingError $ uninterruptibleCancel rhsWaitSession `catchAny` (logError . tshow)
|
|
|
|
cancelHostClient rchClient `catchAny` (logError . tshow)
|
2023-10-04 18:36:10 +03:00
|
|
|
|
|
|
|
-- | Generate a random 16-char filepath without / in it by using base64url encoding.
|
|
|
|
randomStorePath :: IO FilePath
|
|
|
|
randomStorePath = B.unpack . B64U.encode <$> getRandomBytes 12
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
listRemoteHosts :: CM [RemoteHostInfo]
|
2023-10-04 18:36:10 +03:00
|
|
|
listRemoteHosts = do
|
2023-11-13 20:16:34 +00:00
|
|
|
sessions <- chatReadVar remoteHostSessions
|
|
|
|
map (rhInfo sessions) <$> withStore' getRemoteHosts
|
2023-10-14 13:10:06 +01:00
|
|
|
where
|
2023-11-13 20:16:34 +00:00
|
|
|
rhInfo sessions rh@RemoteHost {remoteHostId} =
|
2023-11-20 12:19:00 +02:00
|
|
|
remoteHostInfo rh $ rhsSessionState . snd <$> M.lookup (RHId remoteHostId) sessions
|
2023-10-15 00:18:04 +01:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
switchRemoteHost :: Maybe RemoteHostId -> CM (Maybe RemoteHostInfo)
|
2023-11-10 19:49:23 +02:00
|
|
|
switchRemoteHost rhId_ = do
|
|
|
|
rhi_ <- forM rhId_ $ \rhId -> do
|
|
|
|
let rhKey = RHId rhId
|
2023-11-13 20:16:34 +00:00
|
|
|
rh <- withStore (`getRemoteHost` rhId)
|
|
|
|
sessions <- chatReadVar remoteHostSessions
|
|
|
|
case M.lookup rhKey sessions of
|
2023-11-20 12:19:00 +02:00
|
|
|
Just (_, RHSessionConnected {tls}) -> pure $ remoteHostInfo rh $ Just RHSConnected {sessionCode = tlsSessionCode tls}
|
2023-11-10 19:49:23 +02:00
|
|
|
_ -> throwError $ ChatErrorRemoteHost rhKey RHEInactive
|
|
|
|
rhi_ <$ chatWriteVar currentRemoteHost rhId_
|
|
|
|
|
2023-11-13 20:16:34 +00:00
|
|
|
remoteHostInfo :: RemoteHost -> Maybe RemoteHostSessionState -> RemoteHostInfo
|
2023-11-28 18:32:33 +02:00
|
|
|
remoteHostInfo RemoteHost {remoteHostId, storePath, hostDeviceName, bindAddress_, bindPort_} sessionState =
|
|
|
|
RemoteHostInfo {remoteHostId, storePath, hostDeviceName, bindAddress_, bindPort_, sessionState}
|
2023-10-04 18:36:10 +03:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
deleteRemoteHost :: RemoteHostId -> CM ()
|
2023-10-15 14:17:36 +01:00
|
|
|
deleteRemoteHost rhId = do
|
|
|
|
RemoteHost {storePath} <- withStore (`getRemoteHost` rhId)
|
2023-11-15 17:57:29 +02:00
|
|
|
chatReadVar remoteHostsFolder >>= \case
|
2023-10-13 20:53:04 +03:00
|
|
|
Just baseDir -> do
|
|
|
|
let hostStore = baseDir </> storePath
|
2023-11-15 15:09:52 +02:00
|
|
|
logInfo $ "removing host store at " <> tshow hostStore
|
2023-11-15 17:57:29 +02:00
|
|
|
whenM (doesDirectoryExist hostStore) $ removeDirectoryRecursive hostStore
|
2023-10-13 20:53:04 +03:00
|
|
|
Nothing -> logWarn "Local file store not available while deleting remote host"
|
2023-10-15 14:17:36 +01:00
|
|
|
withStore' (`deleteRemoteHostRecord` rhId)
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
storeRemoteFile :: RemoteHostId -> Maybe Bool -> FilePath -> CM CryptoFile
|
2023-10-29 19:06:32 +00:00
|
|
|
storeRemoteFile rhId encrypted_ localPath = do
|
2023-11-08 22:13:52 +02:00
|
|
|
c@RemoteHostClient {encryptHostFiles, storePath} <- getRemoteHostClient rhId
|
|
|
|
let encrypt = fromMaybe encryptHostFiles encrypted_
|
|
|
|
cf@CryptoFile {filePath} <- if encrypt then encryptLocalFile else pure $ CF.plain localPath
|
|
|
|
filePath' <- liftRH rhId $ remoteStoreFile c filePath (takeFileName localPath)
|
|
|
|
hf_ <- chatReadVar remoteHostsFolder
|
|
|
|
forM_ hf_ $ \hf -> do
|
2025-01-10 15:27:29 +04:00
|
|
|
let rhf = hf </> storePath </> remoteFilesFolder
|
2023-11-08 22:13:52 +02:00
|
|
|
hPath = rhf </> takeFileName filePath'
|
|
|
|
createDirectoryIfMissing True rhf
|
|
|
|
(if encrypt then renameFile else copyFile) filePath hPath
|
|
|
|
pure (cf :: CryptoFile) {filePath = filePath'}
|
2023-10-29 19:06:32 +00:00
|
|
|
where
|
2024-04-01 13:34:45 +01:00
|
|
|
encryptLocalFile :: CM CryptoFile
|
2023-10-29 19:06:32 +00:00
|
|
|
encryptLocalFile = do
|
2024-04-01 13:34:45 +01:00
|
|
|
tmpDir <- lift getChatTempDirectory
|
2023-10-29 19:06:32 +00:00
|
|
|
createDirectoryIfMissing True tmpDir
|
2024-04-01 13:34:45 +01:00
|
|
|
tmpFile <- liftIO $ tmpDir `uniqueCombine` takeFileName localPath
|
2023-12-21 00:42:40 +00:00
|
|
|
cfArgs <- atomically . CF.randomArgs =<< asks random
|
2023-10-29 19:06:32 +00:00
|
|
|
liftError (ChatError . CEFileWrite tmpFile) $ encryptFile localPath tmpFile cfArgs
|
|
|
|
pure $ CryptoFile tmpFile $ Just cfArgs
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
getRemoteFile :: RemoteHostId -> RemoteFile -> CM ()
|
2023-10-29 19:06:32 +00:00
|
|
|
getRemoteFile rhId rf = do
|
2023-11-08 22:13:52 +02:00
|
|
|
c@RemoteHostClient {storePath} <- getRemoteHostClient rhId
|
2025-01-10 15:27:29 +04:00
|
|
|
dir <- lift $ (</> storePath </> remoteFilesFolder) <$> (maybe getDefaultFilesFolder pure =<< chatReadVar' remoteHostsFolder)
|
2023-11-08 22:13:52 +02:00
|
|
|
createDirectoryIfMissing True dir
|
|
|
|
liftRH rhId $ remoteGetFile c dir rf
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
processRemoteCommand :: RemoteHostId -> RemoteHostClient -> ChatCommand -> ByteString -> CM ChatResponse
|
2023-11-08 22:13:52 +02:00
|
|
|
processRemoteCommand remoteHostId c cmd s = case cmd of
|
2023-10-29 19:06:32 +00:00
|
|
|
SendFile chatName f -> sendFile "/f" chatName f
|
2023-10-30 16:00:54 +02:00
|
|
|
SendImage chatName f -> sendFile "/img" chatName f
|
2023-11-08 22:13:52 +02:00
|
|
|
_ -> liftRH remoteHostId $ remoteSend c s
|
2023-10-29 19:06:32 +00:00
|
|
|
where
|
|
|
|
sendFile cmdName chatName (CryptoFile path cfArgs) = do
|
|
|
|
-- don't encrypt in host if already encrypted locally
|
|
|
|
CryptoFile path' cfArgs' <- storeRemoteFile remoteHostId (cfArgs $> False) path
|
|
|
|
let f = CryptoFile path' (cfArgs <|> cfArgs') -- use local or host encryption
|
2023-11-08 22:13:52 +02:00
|
|
|
liftRH remoteHostId $ remoteSend c $ B.unwords [cmdName, B.pack (chatNameStr chatName), cryptoFileStr f]
|
2023-10-29 19:06:32 +00:00
|
|
|
cryptoFileStr CryptoFile {filePath, cryptoArgs} =
|
|
|
|
maybe "" (\(CFArgs key nonce) -> "key=" <> strEncode key <> " nonce=" <> strEncode nonce <> " ") cryptoArgs
|
|
|
|
<> encodeUtf8 (T.pack filePath)
|
2023-10-07 16:23:24 +03:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
liftRH :: RemoteHostId -> ExceptT RemoteProtocolError IO a -> CM a
|
2023-11-08 22:13:52 +02:00
|
|
|
liftRH rhId = liftError (ChatErrorRemoteHost (RHId rhId) . RHEProtocolError)
|
2023-09-29 14:56:56 +03:00
|
|
|
|
2023-10-22 11:42:19 +03:00
|
|
|
-- * Mobile side
|
2023-09-29 14:56:56 +03:00
|
|
|
|
2023-11-17 20:50:38 +02:00
|
|
|
-- ** QR/link
|
2023-11-08 22:13:52 +02:00
|
|
|
|
|
|
|
-- | Use provided OOB link as an annouce
|
2024-04-01 13:34:45 +01:00
|
|
|
connectRemoteCtrlURI :: RCSignedInvitation -> CM (Maybe RemoteCtrlInfo, CtrlAppInfo)
|
2023-11-20 12:19:00 +02:00
|
|
|
connectRemoteCtrlURI signedInv = do
|
2023-11-17 20:50:38 +02:00
|
|
|
verifiedInv <- maybe (throwError $ ChatErrorRemoteCtrl RCEBadInvitation) pure $ verifySignedInvitation signedInv
|
2023-11-20 12:19:00 +02:00
|
|
|
sseq <- startRemoteCtrlSession
|
|
|
|
connectRemoteCtrl verifiedInv sseq
|
2023-11-17 20:50:38 +02:00
|
|
|
|
|
|
|
-- ** Multicast
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
findKnownRemoteCtrl :: CM ()
|
2023-11-20 12:19:00 +02:00
|
|
|
findKnownRemoteCtrl = do
|
2023-11-17 20:50:38 +02:00
|
|
|
knownCtrls <- withStore' getRemoteCtrls
|
|
|
|
pairings <- case nonEmpty knownCtrls of
|
|
|
|
Nothing -> throwError $ ChatErrorRemoteCtrl RCENoKnownControllers
|
|
|
|
Just ne -> pure $ fmap (\RemoteCtrl {ctrlPairing} -> ctrlPairing) ne
|
2023-11-20 12:19:00 +02:00
|
|
|
sseq <- startRemoteCtrlSession
|
2023-11-17 20:50:38 +02:00
|
|
|
foundCtrl <- newEmptyTMVarIO
|
|
|
|
cmdOk <- newEmptyTMVarIO
|
2023-11-24 00:00:20 +02:00
|
|
|
action <- async $ handleCtrlError sseq RCSRDiscoveryFailed "findKnownRemoteCtrl.discover" $ do
|
2023-11-17 20:50:38 +02:00
|
|
|
atomically $ takeTMVar cmdOk
|
2023-11-23 10:07:26 +00:00
|
|
|
(RCCtrlPairing {ctrlFingerprint}, inv@(RCVerifiedInvitation RCInvitation {app})) <-
|
|
|
|
timeoutThrow (ChatErrorRemoteCtrl RCETimeout) discoveryTimeout . withAgent $ \a -> rcDiscoverCtrl a pairings
|
|
|
|
ctrlAppInfo_ <- (Just <$> parseCtrlAppInfo app) `catchChatError` const (pure Nothing)
|
2023-11-26 18:16:37 +00:00
|
|
|
rc <-
|
|
|
|
withStore' (`getRemoteCtrlByFingerprint` ctrlFingerprint) >>= \case
|
|
|
|
Nothing -> throwChatError $ CEInternalError "connecting with a stored ctrl"
|
|
|
|
Just rc -> pure rc
|
2023-11-17 20:50:38 +02:00
|
|
|
atomically $ putTMVar foundCtrl (rc, inv)
|
2023-11-23 10:07:26 +00:00
|
|
|
let compatible = isJust $ compatibleAppVersion hostAppVersionRange . appVersionRange =<< ctrlAppInfo_
|
2025-05-04 22:14:36 +01:00
|
|
|
toView CEvtRemoteCtrlFound {remoteCtrl = remoteCtrlInfo rc (Just RCSSearching), ctrlAppInfo_, appVersion = currentAppVersion, compatible}
|
2023-11-20 12:19:00 +02:00
|
|
|
updateRemoteCtrlSession sseq $ \case
|
|
|
|
RCSessionStarting -> Right RCSessionSearching {action, foundCtrl}
|
2023-11-17 20:50:38 +02:00
|
|
|
_ -> Left $ ChatErrorRemoteCtrl RCEBadState
|
|
|
|
atomically $ putTMVar cmdOk ()
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
confirmRemoteCtrl :: RemoteCtrlId -> CM (RemoteCtrlInfo, CtrlAppInfo)
|
2023-11-17 20:50:38 +02:00
|
|
|
confirmRemoteCtrl rcId = do
|
2023-11-20 12:19:00 +02:00
|
|
|
session <- asks remoteCtrlSession
|
|
|
|
(sseq, listener, found) <- liftIOEither $ atomically $ do
|
|
|
|
readTVar session >>= \case
|
|
|
|
Just (sseq, RCSessionSearching {action, foundCtrl}) -> do
|
|
|
|
writeTVar session $ Just (sseq, RCSessionStarting) -- drop intermediate "Searching" state so connectRemoteCtrl can proceed
|
|
|
|
pure $ Right (sseq, action, foundCtrl)
|
|
|
|
_ -> pure . Left $ ChatErrorRemoteCtrl RCEBadState
|
2023-11-17 20:50:38 +02:00
|
|
|
uninterruptibleCancel listener
|
2023-11-26 18:16:37 +00:00
|
|
|
(RemoteCtrl {remoteCtrlId = foundRcId}, verifiedInv) <- atomically $ takeTMVar found
|
2023-11-17 20:50:38 +02:00
|
|
|
unless (rcId == foundRcId) $ throwError $ ChatErrorRemoteCtrl RCEBadController
|
2023-11-20 12:19:00 +02:00
|
|
|
connectRemoteCtrl verifiedInv sseq >>= \case
|
2023-11-17 20:50:38 +02:00
|
|
|
(Nothing, _) -> throwChatError $ CEInternalError "connecting with a stored ctrl"
|
|
|
|
(Just rci, appInfo) -> pure (rci, appInfo)
|
|
|
|
|
|
|
|
-- ** Common
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
startRemoteCtrlSession :: CM SessionSeq
|
2023-11-20 12:19:00 +02:00
|
|
|
startRemoteCtrlSession = do
|
|
|
|
session <- asks remoteCtrlSession
|
|
|
|
nextSessionSeq <- asks remoteSessionSeq
|
|
|
|
liftIOEither . atomically $
|
|
|
|
readTVar session >>= \case
|
|
|
|
Just _ -> pure . Left $ ChatErrorRemoteCtrl RCEBusy
|
|
|
|
Nothing -> do
|
|
|
|
sseq <- stateTVar nextSessionSeq $ \s -> (s, s + 1)
|
|
|
|
Right sseq <$ writeTVar session (Just (sseq, RCSessionStarting))
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
connectRemoteCtrl :: RCVerifiedInvitation -> SessionSeq -> CM (Maybe RemoteCtrlInfo, CtrlAppInfo)
|
2023-11-24 00:00:20 +02:00
|
|
|
connectRemoteCtrl verifiedInv@(RCVerifiedInvitation inv@RCInvitation {ca, app}) sseq = handleCtrlError sseq RCSRConnectionFailed "connectRemoteCtrl" $ do
|
2023-11-23 10:07:26 +00:00
|
|
|
ctrlInfo@CtrlAppInfo {deviceName = ctrlDeviceName} <- parseCtrlAppInfo app
|
|
|
|
v <- checkAppVersion ctrlInfo
|
2023-11-08 22:13:52 +02:00
|
|
|
rc_ <- withStore' $ \db -> getRemoteCtrlByFingerprint db ca
|
2023-11-12 14:40:49 +00:00
|
|
|
mapM_ (validateRemoteCtrl inv) rc_
|
2023-11-08 22:13:52 +02:00
|
|
|
hostAppInfo <- getHostAppInfo v
|
2023-11-13 20:39:41 +02:00
|
|
|
(rcsClient, vars) <- timeoutThrow (ChatErrorRemoteCtrl RCETimeout) networkIOTimeout . withAgent $ \a ->
|
2023-11-17 20:50:38 +02:00
|
|
|
rcConnectCtrl a verifiedInv (ctrlPairing <$> rc_) (J.toJSON hostAppInfo)
|
2023-11-09 20:25:05 +02:00
|
|
|
cmdOk <- newEmptyTMVarIO
|
|
|
|
rcsWaitSession <- async $ do
|
|
|
|
atomically $ takeTMVar cmdOk
|
2023-11-24 00:00:20 +02:00
|
|
|
handleCtrlError sseq RCSRConnectionFailed "waitForCtrlSession" $ waitForCtrlSession rc_ ctrlDeviceName rcsClient vars
|
2023-11-20 12:19:00 +02:00
|
|
|
updateRemoteCtrlSession sseq $ \case
|
2023-11-15 13:17:31 +00:00
|
|
|
RCSessionStarting -> Right RCSessionConnecting {remoteCtrlId_ = remoteCtrlId' <$> rc_, rcsClient, rcsWaitSession}
|
2023-11-09 20:25:05 +02:00
|
|
|
_ -> Left $ ChatErrorRemoteCtrl RCEBadState
|
|
|
|
atomically $ putTMVar cmdOk ()
|
2023-11-15 13:17:31 +00:00
|
|
|
pure ((`remoteCtrlInfo` Just RCSConnecting) <$> rc_, ctrlInfo)
|
2023-11-08 22:13:52 +02:00
|
|
|
where
|
2023-11-12 14:40:49 +00:00
|
|
|
validateRemoteCtrl RCInvitation {idkey} RemoteCtrl {ctrlPairing = RCCtrlPairing {idPubKey}} =
|
|
|
|
unless (idkey == idPubKey) $ throwError $ ChatErrorRemoteCtrl $ RCEProtocolError $ PRERemoteControl RCEIdentity
|
2024-04-01 13:34:45 +01:00
|
|
|
waitForCtrlSession :: Maybe RemoteCtrl -> Text -> RCCtrlClient -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> CM ()
|
2023-11-11 16:03:12 +00:00
|
|
|
waitForCtrlSession rc_ ctrlName rcsClient vars = do
|
2023-11-13 20:39:41 +02:00
|
|
|
(uniq, tls, rcsWaitConfirmation) <- timeoutThrow (ChatErrorRemoteCtrl RCETimeout) networkIOTimeout $ takeRCStep vars
|
2023-11-08 22:13:52 +02:00
|
|
|
let sessionCode = verificationCode uniq
|
2023-11-20 12:19:00 +02:00
|
|
|
updateRemoteCtrlSession sseq $ \case
|
2023-11-15 13:17:31 +00:00
|
|
|
RCSessionConnecting {rcsWaitSession} ->
|
|
|
|
let remoteCtrlId_ = remoteCtrlId' <$> rc_
|
|
|
|
in Right RCSessionPendingConfirmation {remoteCtrlId_, ctrlDeviceName = ctrlName, rcsClient, tls, sessionCode, rcsWaitSession, rcsWaitConfirmation}
|
2023-11-09 20:25:05 +02:00
|
|
|
_ -> Left $ ChatErrorRemoteCtrl RCEBadState
|
2025-05-04 22:14:36 +01:00
|
|
|
toView CEvtRemoteCtrlSessionCode {remoteCtrl_ = (`remoteCtrlInfo` Just RCSPendingConfirmation {sessionCode}) <$> rc_, sessionCode}
|
2023-11-23 10:07:26 +00:00
|
|
|
checkAppVersion CtrlAppInfo {appVersionRange} =
|
|
|
|
case compatibleAppVersion hostAppVersionRange appVersionRange of
|
2023-11-08 22:13:52 +02:00
|
|
|
Just (AppCompatible v) -> pure v
|
|
|
|
Nothing -> throwError $ ChatErrorRemoteCtrl $ RCEBadVersion $ maxVersion appVersionRange
|
|
|
|
getHostAppInfo appVersion = do
|
|
|
|
hostDeviceName <- chatReadVar localDeviceName
|
|
|
|
encryptFiles <- chatReadVar encryptLocalFiles
|
|
|
|
pure HostAppInfo {appVersion, deviceName = hostDeviceName, encoding = localEncoding, encryptFiles}
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
parseCtrlAppInfo :: JT.Value -> CM CtrlAppInfo
|
2023-11-23 10:07:26 +00:00
|
|
|
parseCtrlAppInfo ctrlAppInfo = do
|
|
|
|
liftEitherWith (const $ ChatErrorRemoteCtrl RCEBadInvitation) $ JT.parseEither J.parseJSON ctrlAppInfo
|
|
|
|
|
2025-05-04 22:14:36 +01:00
|
|
|
handleRemoteCommand :: (ByteString -> CM' ChatResponse) -> RemoteCrypto -> TBQueue ChatEvent -> HTTP2Request -> CM' ()
|
2023-11-10 16:10:10 +00:00
|
|
|
handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {request, reqBody, sendResponse} = do
|
2023-10-22 11:42:19 +03:00
|
|
|
logDebug "handleRemoteCommand"
|
2024-04-01 13:34:45 +01:00
|
|
|
liftIO (tryRemoteError' parseRequest) >>= \case
|
2024-09-24 09:25:41 +01:00
|
|
|
Right (rfKN, getNext, rc) -> do
|
2024-04-01 13:34:45 +01:00
|
|
|
chatReadVar' currentUser >>= \case
|
2023-10-29 19:06:32 +00:00
|
|
|
Nothing -> replyError $ ChatError CENoActiveUser
|
2024-09-24 09:25:41 +01:00
|
|
|
Just user -> processCommand user rfKN getNext rc `catchChatError'` replyError
|
2023-10-22 11:42:19 +03:00
|
|
|
Left e -> reply $ RRProtocolError e
|
2023-10-15 14:17:36 +01:00
|
|
|
where
|
2024-09-24 09:25:41 +01:00
|
|
|
parseRequest :: ExceptT RemoteProtocolError IO (C.SbKeyNonce, GetChunk, RemoteCommand)
|
2023-10-22 11:42:19 +03:00
|
|
|
parseRequest = do
|
2024-09-24 09:25:41 +01:00
|
|
|
(rfKN, header, getNext) <- parseDecryptHTTP2Body encryption request reqBody
|
|
|
|
(rfKN,getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecode header)
|
2023-10-29 19:06:32 +00:00
|
|
|
replyError = reply . RRChatResponse . CRChatCmdError Nothing
|
2024-09-24 09:25:41 +01:00
|
|
|
processCommand :: User -> C.SbKeyNonce -> GetChunk -> RemoteCommand -> CM ()
|
|
|
|
processCommand user rfKN getNext = \case
|
2024-04-01 13:34:45 +01:00
|
|
|
RCSend {command} -> lift $ handleSend execChatCommand command >>= reply
|
|
|
|
RCRecv {wait = time} -> lift $ liftIO (handleRecv time remoteOutputQ) >>= reply
|
2024-09-24 09:25:41 +01:00
|
|
|
RCStoreFile {fileName, fileSize, fileDigest} -> lift $ handleStoreFile rfKN fileName fileSize fileDigest getNext >>= reply
|
|
|
|
RCGetFile {file} -> handleGetFile user file replyWith
|
2024-04-01 13:34:45 +01:00
|
|
|
reply :: RemoteResponse -> CM' ()
|
2024-09-24 09:25:41 +01:00
|
|
|
reply = (`replyWith` \_ _ -> pure ())
|
2024-04-01 13:34:45 +01:00
|
|
|
replyWith :: Respond
|
2024-09-24 09:25:41 +01:00
|
|
|
replyWith rr attach = do
|
|
|
|
(corrId, cmdKN, sfKN) <- atomically $ getRemoteSndKeys encryption
|
|
|
|
liftIO (tryRemoteError' . encryptEncodeHTTP2Body corrId cmdKN encryption $ J.encode rr) >>= \case
|
2024-04-01 13:34:45 +01:00
|
|
|
Right resp -> liftIO . sendResponse . responseStreaming N.status200 [] $ \send flush -> do
|
|
|
|
send resp
|
2024-09-24 09:25:41 +01:00
|
|
|
attach sfKN send
|
2024-04-01 13:34:45 +01:00
|
|
|
flush
|
2025-05-04 22:14:36 +01:00
|
|
|
Left e -> toView' . CEvtChatError Nothing . ChatErrorRemoteCtrl $ RCEProtocolError e
|
2024-04-01 13:34:45 +01:00
|
|
|
|
|
|
|
takeRCStep :: RCStepTMVar a -> CM a
|
|
|
|
takeRCStep = liftError' (\e -> ChatErrorAgent {agentError = RCP e, connectionEntity_ = Nothing}) . atomically . takeTMVar
|
2023-11-08 22:13:52 +02:00
|
|
|
|
2023-10-22 11:42:19 +03:00
|
|
|
type GetChunk = Int -> IO ByteString
|
|
|
|
|
|
|
|
type SendChunk = Builder -> IO ()
|
|
|
|
|
2024-09-24 09:25:41 +01:00
|
|
|
type Respond = RemoteResponse -> (C.SbKeyNonce -> SendChunk -> IO ()) -> CM' ()
|
2023-10-22 11:42:19 +03:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
liftRC :: ExceptT RemoteProtocolError IO a -> CM a
|
2023-10-22 11:42:19 +03:00
|
|
|
liftRC = liftError (ChatErrorRemoteCtrl . RCEProtocolError)
|
|
|
|
|
|
|
|
tryRemoteError :: ExceptT RemoteProtocolError IO a -> ExceptT RemoteProtocolError IO (Either RemoteProtocolError a)
|
|
|
|
tryRemoteError = tryAllErrors (RPEException . tshow)
|
|
|
|
{-# INLINE tryRemoteError #-}
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
tryRemoteError' :: ExceptT RemoteProtocolError IO a -> IO (Either RemoteProtocolError a)
|
|
|
|
tryRemoteError' = tryAllErrors' (RPEException . tshow)
|
|
|
|
{-# INLINE tryRemoteError' #-}
|
|
|
|
|
|
|
|
handleSend :: (ByteString -> CM' ChatResponse) -> Text -> CM' RemoteResponse
|
2023-10-22 11:42:19 +03:00
|
|
|
handleSend execChatCommand command = do
|
|
|
|
logDebug $ "Send: " <> tshow command
|
|
|
|
-- execChatCommand checks for remote-allowed commands
|
2024-04-01 13:34:45 +01:00
|
|
|
-- convert errors thrown in execChatCommand into error responses to prevent aborting the protocol wrapper
|
|
|
|
RRChatResponse <$> execChatCommand (encodeUtf8 command)
|
2023-10-22 11:42:19 +03:00
|
|
|
|
2025-05-04 22:14:36 +01:00
|
|
|
handleRecv :: Int -> TBQueue ChatEvent -> IO RemoteResponse
|
2023-10-22 11:42:19 +03:00
|
|
|
handleRecv time events = do
|
|
|
|
logDebug $ "Recv: " <> tshow time
|
|
|
|
RRChatEvent <$> (timeout time . atomically $ readTBQueue events)
|
|
|
|
|
2023-10-29 19:06:32 +00:00
|
|
|
-- TODO this command could remember stored files and return IDs to allow removing files that are not needed.
|
|
|
|
-- Also, there should be some process removing unused files uploaded to remote host (possibly, all unused files).
|
2024-09-24 09:25:41 +01:00
|
|
|
handleStoreFile :: C.SbKeyNonce -> FilePath -> Word32 -> FileDigest -> GetChunk -> CM' RemoteResponse
|
|
|
|
handleStoreFile rfKN fileName fileSize fileDigest getChunk =
|
2024-04-01 13:34:45 +01:00
|
|
|
either RRProtocolError RRFileStored <$> (chatReadVar' filesFolder >>= storeFile)
|
2023-10-29 19:06:32 +00:00
|
|
|
where
|
2024-04-01 13:34:45 +01:00
|
|
|
storeFile :: Maybe FilePath -> CM' (Either RemoteProtocolError FilePath)
|
2023-10-29 19:06:32 +00:00
|
|
|
storeFile = \case
|
|
|
|
Just ff -> takeFileName <$$> storeFileTo ff
|
|
|
|
Nothing -> storeFileTo =<< getDefaultFilesFolder
|
2024-04-01 13:34:45 +01:00
|
|
|
storeFileTo :: FilePath -> CM' (Either RemoteProtocolError FilePath)
|
|
|
|
storeFileTo dir = liftIO . tryRemoteError' $ do
|
|
|
|
filePath <- liftIO $ dir `uniqueCombine` fileName
|
2024-09-24 09:25:41 +01:00
|
|
|
receiveEncryptedFile rfKN getChunk fileSize fileDigest filePath
|
2023-10-29 19:06:32 +00:00
|
|
|
pure filePath
|
|
|
|
|
2024-09-24 09:25:41 +01:00
|
|
|
handleGetFile :: User -> RemoteFile -> Respond -> CM ()
|
|
|
|
handleGetFile User {userId} RemoteFile {userId = commandUserId, fileId, sent, fileSource = cf'@CryptoFile {filePath}} reply = do
|
2023-10-29 19:06:32 +00:00
|
|
|
logDebug $ "GetFile: " <> tshow filePath
|
|
|
|
unless (userId == commandUserId) $ throwChatError $ CEDifferentActiveUser {commandUserId, activeUserId = userId}
|
|
|
|
path <- maybe filePath (</> filePath) <$> chatReadVar filesFolder
|
|
|
|
withStore $ \db -> do
|
|
|
|
cf <- getLocalCryptoFile db commandUserId fileId sent
|
|
|
|
unless (cf == cf') $ throwError $ SEFileNotFound fileId
|
|
|
|
liftRC (tryRemoteError $ getFileInfo path) >>= \case
|
2024-09-24 09:25:41 +01:00
|
|
|
Left e -> lift $ reply (RRProtocolError e) $ \_ _ -> pure ()
|
2023-10-29 19:06:32 +00:00
|
|
|
Right (fileSize, fileDigest) ->
|
2024-09-24 09:25:41 +01:00
|
|
|
lift . withFile path ReadMode $ \h -> do
|
|
|
|
reply RRFile {fileSize, fileDigest} $ \sfKN send -> void . runExceptT $ do
|
|
|
|
encFile <- prepareEncryptedFile sfKN (h, fileSize)
|
|
|
|
liftIO $ sendEncryptedFile encFile send
|
2023-10-15 14:17:36 +01:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
listRemoteCtrls :: CM [RemoteCtrlInfo]
|
2023-10-04 18:36:10 +03:00
|
|
|
listRemoteCtrls = do
|
2023-11-20 12:19:00 +02:00
|
|
|
session <- snd <$$> chatReadVar remoteCtrlSession
|
2023-11-15 13:17:31 +00:00
|
|
|
let rcId = sessionRcId =<< session
|
|
|
|
sessState = rcsSessionState <$> session
|
|
|
|
map (rcInfo rcId sessState) <$> withStore' getRemoteCtrls
|
2023-10-14 13:10:06 +01:00
|
|
|
where
|
2023-11-15 13:17:31 +00:00
|
|
|
rcInfo :: Maybe RemoteCtrlId -> Maybe RemoteCtrlSessionState -> RemoteCtrl -> RemoteCtrlInfo
|
|
|
|
rcInfo rcId sessState rc@RemoteCtrl {remoteCtrlId} =
|
|
|
|
remoteCtrlInfo rc $ if rcId == Just remoteCtrlId then sessState else Nothing
|
|
|
|
sessionRcId = \case
|
|
|
|
RCSessionConnecting {remoteCtrlId_} -> remoteCtrlId_
|
|
|
|
RCSessionPendingConfirmation {remoteCtrlId_} -> remoteCtrlId_
|
|
|
|
RCSessionConnected {remoteCtrlId} -> Just remoteCtrlId
|
|
|
|
_ -> Nothing
|
|
|
|
|
|
|
|
remoteCtrlInfo :: RemoteCtrl -> Maybe RemoteCtrlSessionState -> RemoteCtrlInfo
|
|
|
|
remoteCtrlInfo RemoteCtrl {remoteCtrlId, ctrlDeviceName} sessionState =
|
|
|
|
RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName, sessionState}
|
2023-10-04 18:36:10 +03:00
|
|
|
|
2023-11-08 22:13:52 +02:00
|
|
|
-- | Take a look at emoji of tlsunique, commit pairing, and start session server
|
2024-04-01 13:34:45 +01:00
|
|
|
verifyRemoteCtrlSession :: (ByteString -> CM' ChatResponse) -> Text -> CM RemoteCtrlInfo
|
2023-11-20 12:19:00 +02:00
|
|
|
verifyRemoteCtrlSession execChatCommand sessCode' = do
|
|
|
|
(sseq, client, ctrlName, sessionCode, vars) <-
|
|
|
|
chatReadVar remoteCtrlSession >>= \case
|
|
|
|
Nothing -> throwError $ ChatErrorRemoteCtrl RCEInactive
|
|
|
|
Just (sseq, RCSessionPendingConfirmation {rcsClient, ctrlDeviceName = ctrlName, sessionCode, rcsWaitConfirmation}) -> pure (sseq, rcsClient, ctrlName, sessionCode, rcsWaitConfirmation)
|
2023-11-08 22:13:52 +02:00
|
|
|
_ -> throwError $ ChatErrorRemoteCtrl RCEBadState
|
2023-11-24 00:00:20 +02:00
|
|
|
handleCtrlError sseq RCSRSetupFailed "verifyRemoteCtrlSession" $ do
|
2023-11-20 12:19:00 +02:00
|
|
|
let verified = sameVerificationCode sessCode' sessionCode
|
|
|
|
timeoutThrow (ChatErrorRemoteCtrl RCETimeout) networkIOTimeout . liftIO $ confirmCtrlSession client verified -- signal verification result before crashing
|
|
|
|
unless verified $ throwError $ ChatErrorRemoteCtrl $ RCEProtocolError PRESessionCode
|
|
|
|
(rcsSession@RCCtrlSession {tls, sessionKeys}, rcCtrlPairing) <- timeoutThrow (ChatErrorRemoteCtrl RCETimeout) networkIOTimeout $ takeRCStep vars
|
|
|
|
rc@RemoteCtrl {remoteCtrlId} <- upsertRemoteCtrl ctrlName rcCtrlPairing
|
|
|
|
remoteOutputQ <- asks (tbqSize . config) >>= newTBQueueIO
|
|
|
|
encryption <- mkCtrlRemoteCrypto sessionKeys $ tlsUniq tls
|
2024-04-01 13:34:45 +01:00
|
|
|
cc <- ask
|
|
|
|
http2Server <- liftIO . async $ attachHTTP2Server tls $ \req -> handleRemoteCommand execChatCommand encryption remoteOutputQ req `runReaderT` cc
|
2023-11-20 12:19:00 +02:00
|
|
|
void . forkIO $ monitor sseq http2Server
|
|
|
|
updateRemoteCtrlSession sseq $ \case
|
|
|
|
RCSessionPendingConfirmation {} -> Right RCSessionConnected {remoteCtrlId, rcsClient = client, rcsSession, tls, http2Server, remoteOutputQ}
|
|
|
|
_ -> Left $ ChatErrorRemoteCtrl RCEBadState
|
|
|
|
pure $ remoteCtrlInfo rc $ Just RCSConnected {sessionCode = tlsSessionCode tls}
|
2023-11-09 20:25:05 +02:00
|
|
|
where
|
2024-04-01 13:34:45 +01:00
|
|
|
upsertRemoteCtrl :: Text -> RCCtrlPairing -> CM RemoteCtrl
|
2023-11-09 20:25:05 +02:00
|
|
|
upsertRemoteCtrl ctrlName rcCtrlPairing = withStore $ \db -> do
|
|
|
|
rc_ <- liftIO $ getRemoteCtrlByFingerprint db (ctrlFingerprint rcCtrlPairing)
|
|
|
|
case rc_ of
|
|
|
|
Nothing -> insertRemoteCtrl db ctrlName rcCtrlPairing >>= getRemoteCtrl db
|
2023-11-12 12:40:13 +00:00
|
|
|
Just rc@RemoteCtrl {ctrlPairing} -> do
|
|
|
|
let dhPrivKey' = dhPrivKey rcCtrlPairing
|
|
|
|
liftIO $ updateRemoteCtrl db rc ctrlName dhPrivKey'
|
2023-11-12 14:40:49 +00:00
|
|
|
pure rc {ctrlDeviceName = ctrlName, ctrlPairing = ctrlPairing {dhPrivKey = dhPrivKey'}}
|
2024-04-01 13:34:45 +01:00
|
|
|
monitor :: SessionSeq -> Async () -> CM ()
|
2023-11-20 12:19:00 +02:00
|
|
|
monitor sseq server = do
|
2023-11-10 00:43:44 +02:00
|
|
|
res <- waitCatch server
|
|
|
|
logInfo $ "HTTP2 server stopped: " <> tshow res
|
2023-11-24 00:00:20 +02:00
|
|
|
cancelActiveRemoteCtrl $ Just (sseq, RCSRDisconnected)
|
2023-10-15 14:17:36 +01:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
stopRemoteCtrl :: CM ()
|
2023-11-20 12:19:00 +02:00
|
|
|
stopRemoteCtrl = cancelActiveRemoteCtrl Nothing
|
2023-11-08 22:13:52 +02:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
handleCtrlError :: SessionSeq -> (ChatError -> RemoteCtrlStopReason) -> Text -> CM a -> CM a
|
2023-11-24 00:00:20 +02:00
|
|
|
handleCtrlError sseq mkReason name action =
|
2023-11-17 20:50:38 +02:00
|
|
|
action `catchChatError` \e -> do
|
|
|
|
logError $ name <> " remote ctrl error: " <> tshow e
|
2023-11-24 00:00:20 +02:00
|
|
|
cancelActiveRemoteCtrl $ Just (sseq, mkReason e)
|
2023-11-17 20:50:38 +02:00
|
|
|
throwError e
|
2023-11-11 16:03:12 +00:00
|
|
|
|
2023-11-20 12:19:00 +02:00
|
|
|
-- | Stop session controller, unless session update key is present but stale
|
2024-04-01 13:34:45 +01:00
|
|
|
cancelActiveRemoteCtrl :: Maybe (SessionSeq, RemoteCtrlStopReason) -> CM ()
|
2023-11-24 00:00:20 +02:00
|
|
|
cancelActiveRemoteCtrl handlerInfo_ = handleAny (logError . tshow) $ do
|
2023-11-20 12:19:00 +02:00
|
|
|
var <- asks remoteCtrlSession
|
2023-11-26 18:16:37 +00:00
|
|
|
session_ <-
|
|
|
|
atomically $
|
|
|
|
readTVar var >>= \case
|
|
|
|
Nothing -> pure Nothing
|
|
|
|
Just (oldSeq, _) | (maybe False ((oldSeq /=) . fst) handlerInfo_) -> pure Nothing
|
|
|
|
Just (_, s) -> Just s <$ writeTVar var Nothing
|
2023-11-15 19:31:36 +02:00
|
|
|
forM_ session_ $ \session -> do
|
2023-11-16 16:56:39 +02:00
|
|
|
liftIO $ cancelRemoteCtrl handlingError session
|
2023-11-24 00:00:20 +02:00
|
|
|
forM_ (snd <$> handlerInfo_) $ \rcStopReason ->
|
2025-05-04 22:14:36 +01:00
|
|
|
toView CEvtRemoteCtrlStopped {rcsState = rcsSessionState session, rcStopReason}
|
2023-11-20 12:19:00 +02:00
|
|
|
where
|
2023-11-24 00:00:20 +02:00
|
|
|
handlingError = isJust handlerInfo_
|
2023-11-11 16:03:12 +00:00
|
|
|
|
2023-11-16 16:56:39 +02:00
|
|
|
cancelRemoteCtrl :: Bool -> RemoteCtrlSession -> IO ()
|
|
|
|
cancelRemoteCtrl handlingError = \case
|
2023-11-08 22:13:52 +02:00
|
|
|
RCSessionStarting -> pure ()
|
2023-11-23 13:00:57 +02:00
|
|
|
RCSessionSearching {action} ->
|
|
|
|
unless handlingError $ uninterruptibleCancel action
|
2023-11-08 22:13:52 +02:00
|
|
|
RCSessionConnecting {rcsClient, rcsWaitSession} -> do
|
2023-11-16 16:56:39 +02:00
|
|
|
unless handlingError $ uninterruptibleCancel rcsWaitSession
|
2023-11-08 22:13:52 +02:00
|
|
|
cancelCtrlClient rcsClient
|
2023-11-10 00:43:44 +02:00
|
|
|
RCSessionPendingConfirmation {rcsClient, tls, rcsWaitSession} -> do
|
2023-11-16 16:56:39 +02:00
|
|
|
unless handlingError $ uninterruptibleCancel rcsWaitSession
|
2023-11-08 22:13:52 +02:00
|
|
|
cancelCtrlClient rcsClient
|
2023-11-10 00:43:44 +02:00
|
|
|
closeConnection tls
|
|
|
|
RCSessionConnected {rcsClient, tls, http2Server} -> do
|
2023-11-16 16:56:39 +02:00
|
|
|
unless handlingError $ uninterruptibleCancel http2Server
|
2023-11-10 00:43:44 +02:00
|
|
|
cancelCtrlClient rcsClient
|
|
|
|
closeConnection tls
|
2023-10-07 16:23:24 +03:00
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
deleteRemoteCtrl :: RemoteCtrlId -> CM ()
|
2023-10-15 14:17:36 +01:00
|
|
|
deleteRemoteCtrl rcId = do
|
|
|
|
checkNoRemoteCtrlSession
|
|
|
|
-- TODO check it exists
|
|
|
|
withStore' (`deleteRemoteCtrlRecord` rcId)
|
|
|
|
|
2024-04-01 13:34:45 +01:00
|
|
|
checkNoRemoteCtrlSession :: CM ()
|
2023-10-15 14:17:36 +01:00
|
|
|
checkNoRemoteCtrlSession =
|
|
|
|
chatReadVar remoteCtrlSession >>= maybe (pure ()) (\_ -> throwError $ ChatErrorRemoteCtrl RCEBusy)
|
2023-10-11 11:45:05 +03:00
|
|
|
|
2023-11-20 12:19:00 +02:00
|
|
|
-- | Transition controller to a new state, unless session update key is stale
|
2024-04-01 13:34:45 +01:00
|
|
|
updateRemoteCtrlSession :: SessionSeq -> (RemoteCtrlSession -> Either ChatError RemoteCtrlSession) -> CM ()
|
2023-11-20 12:19:00 +02:00
|
|
|
updateRemoteCtrlSession sseq state = do
|
2023-11-08 22:13:52 +02:00
|
|
|
session <- asks remoteCtrlSession
|
2023-11-20 12:19:00 +02:00
|
|
|
r <- atomically $ do
|
|
|
|
readTVar session >>= \case
|
|
|
|
Nothing -> pure . Left $ ChatErrorRemoteCtrl RCEInactive
|
|
|
|
Just (oldSeq, st)
|
|
|
|
| oldSeq /= sseq -> pure . Left $ ChatErrorRemoteCtrl RCEBadState
|
|
|
|
| otherwise -> case state st of
|
|
|
|
Left ce -> pure $ Left ce
|
|
|
|
Right st' -> Right () <$ writeTVar session (Just (sseq, st'))
|
2023-11-08 22:13:52 +02:00
|
|
|
liftEither r
|
|
|
|
|
2023-10-11 11:45:05 +03:00
|
|
|
utf8String :: [Char] -> ByteString
|
|
|
|
utf8String = encodeUtf8 . T.pack
|
|
|
|
{-# INLINE utf8String #-}
|