Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: remote error handling #3347

Merged
merged 2 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Simplex/Chat.hs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@
. map (\ServerCfg {server} -> server)
. filter (\ServerCfg {enabled} -> enabled)

cfgServers :: UserProtocol p => SProtocolType p -> (DefaultAgentServers -> NonEmpty (ProtoServerWithAuth p))

Check warning on line 297 in src/Simplex/Chat.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-20.04

Redundant constraint: UserProtocol p

Check warning on line 297 in src/Simplex/Chat.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-22.04

Redundant constraint: UserProtocol p

Check warning on line 297 in src/Simplex/Chat.hs

View workflow job for this annotation

GitHub Actions / build-macos-latest

Redundant constraint: UserProtocol p
cfgServers p s = case p of
SPSMP -> s.smp
SPXFTP -> s.xftp
Expand Down Expand Up @@ -1955,7 +1955,7 @@
ListRemoteHosts -> withUser_ $ CRRemoteHostList <$> listRemoteHosts
SwitchRemoteHost rh_ -> withUser_ $ CRCurrentRemoteHost <$> switchRemoteHost rh_
StartRemoteHost rh_ -> withUser_ $ do
(remoteHost_, inv) <- startRemoteHost' rh_
(remoteHost_, inv) <- startRemoteHost rh_
pure CRRemoteHostStarted {remoteHost_, invitation = decodeLatin1 $ strEncode inv}
StopRemoteHost rh_ -> withUser_ $ closeRemoteHost rh_ >> ok_
DeleteRemoteHost rh -> withUser_ $ deleteRemoteHost rh >> ok_
Expand Down
61 changes: 29 additions & 32 deletions src/Simplex/Chat/Remote.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@
import qualified Data.ByteString.Char8 as B
import Data.Functor (($>))
import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe, isNothing)

Check warning on line 32 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-20.04

The import of ‘isNothing’ from module ‘Data.Maybe’ is redundant

Check warning on line 32 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-22.04

The import of ‘isNothing’ from module ‘Data.Maybe’ is redundant

Check warning on line 32 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-macos-latest

The import of ‘isNothing’ from module ‘Data.Maybe’ is redundant
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import Data.Word (Word16, Word32)
import qualified Network.HTTP.Types as N
import Network.HTTP2.Client (HTTP2Error (..))

Check warning on line 38 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-20.04

The import of ‘Network.HTTP2.Client’ is redundant

Check warning on line 38 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-22.04

The import of ‘Network.HTTP2.Client’ is redundant

Check warning on line 38 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-macos-latest

The import of ‘Network.HTTP2.Client’ is redundant
import Network.HTTP2.Server (responseStreaming)
import qualified Paths_simplex_chat as SC
import Simplex.Chat.Archive (archiveFilesFolder)
Expand Down Expand Up @@ -122,8 +122,8 @@
Just s -> Right () <$ TM.insert (RHId rhId) s sessions
liftEither r

startRemoteHost' :: ChatMonad m => Maybe (RemoteHostId, Bool) -> m (Maybe RemoteHostInfo, RCSignedInvitation)
startRemoteHost' rh_ = do
startRemoteHost :: ChatMonad m => Maybe (RemoteHostId, Bool) -> m (Maybe RemoteHostInfo, RCSignedInvitation)
startRemoteHost rh_ = do
(rhKey, multicast, remoteHost_, pairing) <- case rh_ of
Just (rhId, multicast) -> do
rh@RemoteHost {hostPairing} <- withStore $ \db -> getRemoteHost db rhId
Expand All @@ -134,8 +134,9 @@
(invitation, rchClient, vars) <- withAgent $ \a -> rcConnectHost a pairing (J.toJSON ctrlAppInfo) multicast
cmdOk <- newEmptyTMVarIO
rhsWaitSession <- async $ do
rhKeyVar <- newTVarIO rhKey
atomically $ takeTMVar cmdOk
cleanupOnError rchClient $ waitForSession remoteHost_ vars
handleHostError rhKeyVar $ waitForHostSession remoteHost_ rhKey rhKeyVar vars
let rhs = RHPendingSession {rhKey, rchClient, rhsWaitSession, remoteHost_}
withRemoteHostSession rhKey $ \case
RHSessionStarting -> Right ((), RHSessionConnecting rhs)
Expand All @@ -152,18 +153,15 @@
unless (isAppCompatible appVersion ctrlAppVersionRange) $ throwError $ RHEBadVersion appVersion
when (encoding == PEKotlin && localEncoding == PESwift) $ throwError $ RHEProtocolError RPEIncompatibleEncoding
pure hostInfo
cleanupOnError :: ChatMonad m => RCHostClient -> (TMVar RHKey -> m ()) -> m ()
cleanupOnError rchClient action = do
currentKey <- newEmptyTMVarIO
action currentKey `catchChatError` \err -> do
logError $ "startRemoteHost'.waitForSession crashed: " <> tshow err
handleHostError :: ChatMonad m => TVar RHKey -> m () -> m ()
handleHostError rhKeyVar action = do
action `catchChatError` \err -> do
logError $ "startRemoteHost.waitForHostSession crashed: " <> tshow err
sessions <- asks remoteHostSessions
atomically $ readTMVar currentKey >>= (`TM.delete` sessions)
liftIO $ cancelHostClient rchClient
waitForSession :: ChatMonad m => Maybe RemoteHostInfo -> RCStepTMVar (ByteString, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> TMVar RHKey -> m ()
waitForSession remoteHost_ vars currentKey = do
let rhKey = maybe RHNew (\RemoteHostInfo {remoteHostId} -> RHId remoteHostId) remoteHost_
atomically $ writeTMVar currentKey rhKey
session_ <- atomically $ readTVar rhKeyVar >>= (`TM.lookupDelete` sessions)
mapM_ (liftIO . cancelRemoteHost) session_
waitForHostSession :: ChatMonad m => Maybe RemoteHostInfo -> RHKey -> TVar RHKey -> RCStepTMVar (ByteString, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> m ()
waitForHostSession remoteHost_ rhKey rhKeyVar vars = do
(sessId, vars') <- takeRCStep vars
toView $ CRRemoteHostSessionCode {remoteHost_, sessionCode = verificationCode sessId} -- display confirmation code, wait for mobile to confirm
(RCHostSession {tls, sessionKeys}, rhHello, pairing') <- takeRCStep vars'
Expand All @@ -175,7 +173,7 @@
-- update remoteHost with updated pairing
rhi@RemoteHostInfo {remoteHostId, storePath} <- upsertRemoteHost pairing' remoteHost_ hostDeviceName
let rhKey' = RHId remoteHostId -- rhKey may be invalid after upserting on RHNew
atomically $ writeTMVar currentKey rhKey'
atomically $ writeTVar rhKeyVar rhKey'
disconnected <- toIO $ onDisconnected remoteHostId
httpClient <- liftEitherError (httpError rhKey') $ attachRevHTTP2Client disconnected tls
rhClient <- mkRemoteHostClient httpClient sessionKeys sessId storePath hostInfo
Expand Down Expand Up @@ -252,7 +250,7 @@
switchRemoteHost rhId_ = do
rhi_ <- forM rhId_ $ \rhId -> do
let rhKey = RHId rhId
rhi <- withError (const $ ChatErrorRemoteHost rhKey RHEMissing) $ (`remoteHostInfo` True) <$> withStore (`getRemoteHost` rhId)
rhi <- (`remoteHostInfo` True) <$> withStore (`getRemoteHost` rhId)
active <- chatReadVar remoteHostSessions
case M.lookup rhKey active of
Just RHSessionConnected {} -> pure rhi
Expand Down Expand Up @@ -324,7 +322,7 @@

-- * Mobile side

findKnownRemoteCtrl :: ChatMonad m => m ()

Check warning on line 325 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-20.04

Redundant constraint: ChatMonad m

Check warning on line 325 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-22.04

Redundant constraint: ChatMonad m

Check warning on line 325 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-macos-latest

Redundant constraint: ChatMonad m
findKnownRemoteCtrl = undefined -- do

-- | Use provided OOB link as an annouce
Expand All @@ -338,19 +336,14 @@
cmdOk <- newEmptyTMVarIO
rcsWaitSession <- async $ do
atomically $ takeTMVar cmdOk
cleanupOnError rcsClient $ waitForSession rc_ ctrlDeviceName rcsClient vars
cleanupOnError rcsClient . updateRemoteCtrlSession $ \case
handleCtrlError "waitForCtrlSession" $ waitForCtrlSession rc_ ctrlDeviceName rcsClient vars
handleCtrlError "connectRemoteCtrl" . updateRemoteCtrlSession $ \case
RCSessionStarting -> Right RCSessionConnecting {rcsClient, rcsWaitSession}
_ -> Left $ ChatErrorRemoteCtrl RCEBadState
atomically $ putTMVar cmdOk ()
where
cleanupOnError :: ChatMonad m => RCCtrlClient -> m () -> m ()
cleanupOnError rcsClient action = action `catchChatError` \e -> do
logError $ "connectRemoteCtrl crashed with: " <> tshow e
chatWriteVar remoteCtrlSession Nothing -- XXX: can only wipe PendingConfirmation or RCSessionConnecting, which only have rcsClient to cancel
liftIO $ cancelCtrlClient rcsClient
waitForSession :: ChatMonad m => Maybe RemoteCtrl -> Text -> RCCtrlClient -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> m ()
waitForSession rc_ ctrlName rcsClient vars = do
waitForCtrlSession :: ChatMonad m => Maybe RemoteCtrl -> Text -> RCCtrlClient -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCCtrlSession, RCCtrlPairing)) -> m ()
waitForCtrlSession rc_ ctrlName rcsClient vars = do
(uniq, tls, rcsWaitConfirmation) <- takeRCStep vars
let sessionCode = verificationCode uniq
toView CRRemoteCtrlSessionCode {remoteCtrl_ = (`remoteCtrlInfo` True) <$> rc_, sessionCode}
Expand Down Expand Up @@ -458,8 +451,8 @@
withFile path ReadMode $ \h ->
reply RRFile {fileSize, fileDigest} $ \send -> hSendFile h send fileSize

discoverRemoteCtrls :: ChatMonad m => TM.TMap C.KeyHash (TransportHost, Word16) -> m ()

Check warning on line 454 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-20.04

Redundant constraint: ChatMonad m

Check warning on line 454 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-22.04

Redundant constraint: ChatMonad m

Check warning on line 454 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-macos-latest

Redundant constraint: ChatMonad m
discoverRemoteCtrls discovered = do

Check warning on line 455 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-20.04

Defined but not used: ‘discovered’

Check warning on line 455 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-22.04

Defined but not used: ‘discovered’

Check warning on line 455 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-macos-latest

Defined but not used: ‘discovered’
error "TODO: discoverRemoteCtrls"

listRemoteCtrls :: ChatMonad m => m [RemoteCtrlInfo]
Expand All @@ -477,7 +470,7 @@
RemoteCtrlInfo {remoteCtrlId, ctrlName, sessionActive}

-- XXX: only used for multicast
confirmRemoteCtrl :: ChatMonad m => RemoteCtrlId -> m ()

Check warning on line 473 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-20.04

Redundant constraint: ChatMonad m

Check warning on line 473 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-ubuntu-22.04

Redundant constraint: ChatMonad m

Check warning on line 473 in src/Simplex/Chat/Remote.hs

View workflow job for this annotation

GitHub Actions / build-macos-latest

Redundant constraint: ChatMonad m
confirmRemoteCtrl _rcId = do
-- TODO check it exists, check the ID is the same as in session
-- RemoteCtrlSession {confirmed} <- getRemoteCtrlSession
Expand All @@ -487,7 +480,7 @@

-- | Take a look at emoji of tlsunique, commit pairing, and start session server
verifyRemoteCtrlSession :: ChatMonad m => (ByteString -> m ChatResponse) -> Text -> m RemoteCtrlInfo
verifyRemoteCtrlSession execChatCommand sessCode' = cleanupOnError $ do
verifyRemoteCtrlSession execChatCommand sessCode' = handleCtrlError "verifyRemoteCtrlSession" $ do
(client, ctrlName, sessionCode, vars) <-
getRemoteCtrlSession >>= \case
RCSessionPendingConfirmation {rcsClient, ctrlName, sessionCode, rcsWaitConfirmation} -> pure (rcsClient, ctrlName, sessionCode, rcsWaitConfirmation)
Expand All @@ -514,23 +507,27 @@
Just rc@RemoteCtrl {remoteCtrlId} -> do
liftIO $ updateCtrlPairingKeys db remoteCtrlId (dhPrivKey rcCtrlPairing)
pure rc
cleanupOnError :: ChatMonad m => m a -> m a
cleanupOnError action = action `catchChatError` \e -> do
logError $ "verifyRemoteCtrlSession crashed with: " <> tshow e
withRemoteCtrlSession_ (\s -> pure (s, Nothing)) >>= mapM_ (liftIO . cancelRemoteCtrl) -- cancel session threads, if any
throwError e
monitor :: ChatMonad m => Async () -> m ()
monitor server = do
res <- waitCatch server
logInfo $ "HTTP2 server stopped: " <> tshow res
withRemoteCtrlSession_ (\s -> pure (s, Nothing)) >>= mapM_ (liftIO . cancelRemoteCtrl) -- cancel session threads, if any
cancelActiveRemoteCtrl
toView CRRemoteCtrlStopped

stopRemoteCtrl :: ChatMonad m => m ()
stopRemoteCtrl =
join . withRemoteCtrlSession_ . maybe (Left $ ChatErrorRemoteCtrl RCEInactive) $
\s -> Right (liftIO $ cancelRemoteCtrl s, Nothing)

handleCtrlError :: ChatMonad m => Text -> m a -> m a
handleCtrlError name action = action `catchChatError` \e -> do
logError $ name <> " remote ctrl error: " <> tshow e
cancelActiveRemoteCtrl
throwError e

cancelActiveRemoteCtrl :: ChatMonad m => m ()
cancelActiveRemoteCtrl = withRemoteCtrlSession_ (\s -> pure (s, Nothing)) >>= mapM_ (liftIO . cancelRemoteCtrl)

cancelRemoteCtrl :: RemoteCtrlSession -> IO ()
cancelRemoteCtrl = \case
RCSessionStarting -> pure ()
Expand Down
2 changes: 2 additions & 0 deletions src/Simplex/Chat/View.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1822,6 +1822,8 @@ viewChatError logLevel = \case
SEChatItemNotFoundByText text -> ["message not found by text: " <> plain text]
SEDuplicateGroupLink g -> ["you already have link for this group, to show: " <> highlight ("/show link #" <> viewGroupName g)]
SEGroupLinkNotFound g -> ["no group link, to create: " <> highlight ("/create link #" <> viewGroupName g)]
SERemoteCtrlNotFound rcId -> ["no remote controller " <> sShow rcId]
SERemoteHostNotFound rhId -> ["no remote host " <> sShow rhId]
e -> ["chat db error: " <> sShow e]
ChatErrorDatabase err -> case err of
DBErrorEncrypted -> ["error: chat database is already encrypted"]
Expand Down
2 changes: 1 addition & 1 deletion tests/RemoteTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ switchRemoteHostTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \
desktop <## "bob (Bob)"

desktop ##> "/switch remote host 123"
desktop <## "remote host 123 error: RHEMissing"
desktop <## "no remote host 123"

stopDesktop mobile desktop
desktop ##> "/contacts"
Expand Down
Loading